added additional pr guideline for toolkits -- separate pr for each
toolkit change
---------
Co-authored-by: Eric Gustin <34000337+EricGustin@users.noreply.github.com>
This PR fixes two bugs:
1. `serve_default_worker` was not registering toolkits to the worker
(non mcp). This caused workers created during `arcade workerup` and
`arcade serve` to have 0 tools.
2. We were logging the number of files in the toolkit as the number of
tools in the toolkit.
MCP stdio Implementation:
The PR adds support for standard input/output (stdio) as a transport
mechanism for the Message Control Protocol. This is a replacement to the
SSE (Server-Sent Events) transport that was worked on in PR #359 but
will not be merged as it's not deprecated.
This will allow developers to use Arcade tools (written by the dev or
Arcade) in Claude, Cursor, windsurf, etc.
The engine Gateway already supports adding HTTPS streamable (replacement
for SSE) MCP servers as tool servers, and will soon support full gateway
capability in the client API as well.
To use any existing Toolkit just
## Examples
### Quickstart setup with existing toolkits
```bash
pip install arcade-ai
pip install <name of toolkit> # ex. arcade-google
arcade serve --mcp
```
### Run with Claude
Just add the following to the Claude config
```json
{
"mcpServers": {
"arcade": {
"command": "bash",
"args": ["-c", "export ARCADE_API_KEY=arc_xxxx && /path/to/python /path/to/arcade serve --mcp"]
}
}
}
```
### Customizing the Tool Server
Developers can customize their served tools and server furthermore by
importing the worker sdk
```python
import arcade_google # pip install arcade_google
import arcade_search # pip install arcade_search
from arcade.core.catalog import ToolCatalog
from arcade.worker.mcp.stdio import StdioServer
# 2. Create and populate the tool catalog
catalog = ToolCatalog()
catalog.add_module(arcade_google) # Registers all tools in the package
catalog.add_module(arcade_search)
# 3. Main entrypoint
async def main():
# Create the worker with the tool catalog
worker = StdioServer(catalog)
# Run the worker
await worker.run()
if __name__ == "__main__":
import asyncio
asyncio.run(main())
```
Then to run with claude, just run this python file instead of the prebuilt server used in ``arcade serve --mcp``
The Asana toolkit is not published yet, so it cannot be added to the
worker's toolkit list. This is causing container build/publish on main
branch to fail.
* `arcade dashboard` opens the Arcade Dashboard in a web browser.
Defaults to `https://api.arcade.dev/dashboard`, but is configurable via
flags.
* `arcade dashboard --local` opens your locally hosted Arcade Dashboard
in a web browser.
* Performs a health check of the engine and will print a warning to the
console if the Engine is not healthy / not running.
----------------------------------------
* Inspiration from https://minikube.sigs.k8s.io/docs/handbook/dashboard/
| Name | Description |
|------------------------------|--------------------------------------------------------------------------------|
| Microsoft.CreateDraftEmail | Compose a new draft email in Outlook |
| Microsoft.UpdateDraftEmail | Update an existing draft email in Outlook
|
| Microsoft.CreateAndSendEmail | Create and immediately send a new email
in Outlook to the specified recipients |
| Microsoft.SendDraftEmail | Send an existing draft email in Outlook |
| Microsoft.ReplyToEmail | Reply only to the sender of an existing email
in Outlook |
| Microsoft.ReplyAllToEmail | Reply to all recipients of an existing
email in Outlook |
| Microsoft.ListEmails | List emails in the user's mailbox across all
folders |
| Microsoft.ListEmailsInFolder | List the user's emails in the specified
folder |
1. Paginate through all current level blocks before recursing into
children (before this PR we would go back and forth between paginate and
recurse)
2. For top-level blocks only, split blocks into 5 lists, and
concurrently get their content
---------
From my local timing benchmarks, this speeds up the tool call by ~60%
(23 seconds to 9.1 seconds) for larger Notion pages
Without optimization: Avg 22995
Attempt 1: 27503.49497795105
Attempt 2: 20863.977909088135
Attempt 3: 20888.309955596924
Attempt 4: 18574.61714744568
Attempt 5: 27147.75586128235
With optimization: Avg 9148.6
Attempt 1: 9941.372871398926
Attempt 2: 10097.685098648071
Attempt 3: 7855.895042419434
Attempt 4: 9078.719854354858
Attempt 5: 8772.69196510315
Reviewer:
1. DM me to get a client ID/secret for the Hubspot app and login
credentials for a sample Hubspot account
2. For now, you'll need to run the engine on the branch
`nate/token-introspection` for the auth flow to work with Hubspot
3. Add the following to `auth.providers` in your engine yaml:
```yaml
- id: arcade-hubspot
description: 'Hubspot provider'
enabled: true
type: oauth2
provider_id: hubspot
client_id: ${env:HUBSPOT_CLIENT_ID}
client_secret: ${env:HUBSPOT_CLIENT_SECRET}
```
Updating the auth provider's `id` to simply `salesforce`. We don't want
it to be confused with a future well-known `provider_id` of
`arcade-salesforce`.
Also, what the toolkit was referring to as `org_domain` is actually the
organization's SUBdomain. We changed all references to "subdomain" to be
more precise. The documentation has been updated accordingly.
## Google File Picker
The Google Picker lets users select or upload Google Drive files. Users
can grant permission to your apps to access their Drive data, providing
a secure and authorized way to interact with their files.
The `generate_google_file_picker_url` returns a URL to a Google File
Picker for the user.
| Name | Description |
|----------------------------------|-------------------------------------------------------------------------|
| Reddit.CheckSubredditAccess | Checks whether the specified subreddit
exists and also if it is accessible |
| Reddit.GetSubredditRules | Gets the rules of the specified subreddit |
| Reddit.GetMyUsername | Get the Reddit username of the authenticated
user |
| Reddit.GetMyPosts | Get posts that were created by the authenticated
user sorted by newest |
Four new tools that I had to create for my demo app.
| Name | Description | Package | Version |
|-----------------------------------|---------------------------------------------------------------------------|---------|---------|
| Stripe.CreateCustomer | This tool will create a customer in Stripe. |
Stripe | 0.0.1 |
| Stripe.ListCustomers | This tool will fetch a list of Customers from
Stripe. | Stripe | 0.0.1 |
| Stripe.CreateProduct | This tool will create a product in Stripe. |
Stripe | 0.0.1 |
| Stripe.ListProducts | This tool will fetch a list of Products from
Stripe. | Stripe | 0.0.1 |
| Stripe.CreatePrice | This tool will create a price in Stripe. If a
product has not already been | Stripe | 0.0.1 |
| Stripe.ListPrices | This tool will fetch a list of Prices from Stripe.
| Stripe | 0.0.1 |
| Stripe.CreatePaymentLink | This tool will create a payment link in
Stripe. | Stripe | 0.0.1 |
| Stripe.ListInvoices | This tool will list invoices in Stripe. | Stripe
| 0.0.1 |
| Stripe.CreateInvoice | This tool will create an invoice in Stripe. |
Stripe | 0.0.1 |
| Stripe.CreateInvoiceItem | This tool will create an invoice item in
Stripe. | Stripe | 0.0.1 |
| Stripe.FinalizeInvoice | This tool will finalize an invoice in Stripe.
| Stripe | 0.0.1 |
| Stripe.RetrieveBalance | This tool will retrieve the balance from
Stripe. It takes no input. | Stripe | 0.0.1 |
| Stripe.CreateRefund | This tool will refund a payment intent in
Stripe. | Stripe | 0.0.1 |
| Stripe.ListPaymentIntents | This tool will list payment intents in
Stripe. | Stripe | 0.0.1 |
| Stripe.CreateBillingPortalSession | This tool will create a billing
portal session. | Stripe | 0.0.1 |
-------------------------
This PR implements the tools in
[stripe-agent-toolkit](https://github.com/stripe/agent-toolkit)
verbatim. This means that the logic needed to implement these tools is
minimal since stripe has already done the heavy lifting.
The tools added in this toolkit are the same tools that are in the
stripe-agent-toolkit, but formatted as Arcade tools. This means that the
names of the tools, the parameter annotations, and tool docstrings are
taken from the stripe-agent-toolkit. I have omitted evals since the tool
definitions are taken from the stripe-agent-toolkit.
The tools in this PR are generated via a script. I have included this
script in the PR (`_generate.py`) so that if the stripe-agent-toolkit
ever adds more tools, then we can simply run that script to generate the
new tools.
This PR adds a new example showcasing how to integrate Arcade tools with
LangGraph.js to create a ReAct agent. The example is based on the
[LangChain React Agent
JS](https://github.com/langchain-ai/react-agent-js/tree/main)
repository.
https://github.com/ArcadeAI/arcade-ai/pull/345 made changes to the Slack
toolkit & made it such that the pipe syntax is used for the return type
for some of its tools. This syntax requires arcade-ai >= 1.2.0
⚠️ Local Worker will not work with Slack toolkit until this is merged
Support [PEP 604](https://peps.python.org/pep-0604/) Union Types for
tool input param types and return types.
https://github.com/ArcadeAI/arcade-ai/pull/345 introduced PEP 604 Union
Types into our toolkits, but the worker did not support this syntax, so
it was failing on start up if a tool used bar syntax in its return type
(Slack).
Additionally, optionals defined with `Union[T, None]` were already
supported, but didn't have any unit tests, so I added them.
Addresses general improvements to all toolkits including changing ruff
from python 3.9 to python 3.10 which is the reason for the removal of
Optional[] among others.
Also, turns out that our `make install` for toolkits wasn't correctly
checking for whether poetry was installed (&> /dev/null syntax isn't
supported by our check-toolkits GitHub action, so we were installing
poetry twice. I replaced with the more portable >/dev/null 2>&1)
Question: Should we also change ruff to py310 for the `arcade/` package
in a later PR?
-------------------
CU-86b4gzyp6
| Name | Description | Package | Version |
|----------------------------|------------------------------------------------------------------|---------|---------|
| Reddit.SubmitTextPost | Submit a text-based post to a subreddit |
Reddit | 0.0.1 |
| Reddit.CommentOnPost | Comment on a Reddit post | Reddit | 0.0.1 |
| Reddit.ReplyToComment | Reply to a Reddit comment | Reddit | 0.0.1 |
| Reddit.GetPostsInSubreddit | Gets posts titles, links, and other
metadata in the specified subreddit | Reddit | 0.0.1 |
| Reddit.GetContentOfPost | Get the content (body) of a Reddit post by
its identifier. | Reddit | 0.0.1 |
| Reddit.GetContentOfMultiplePosts | Get the content (body) of multiple
Reddit posts by their identifiers. | Reddit | 0.0.1 |
| Reddit.GetTopLevelComments | Get the first page of top-level comments
of a Reddit post. | Reddit | 0.0.1 |
### Why not use an SDK?
Reddit API does not have an official SDK, although
[PRAW](https://github.com/praw-dev/praw) has large community support.
I played around with PRAW, but ultimately decided to not use an SDK.
PRAW made it incredibly easy to work with Reddit Objects, but there were
a few drawbacks that ultimately swayed me to not use it:
1. PRAW assumes that it will do the auth for you. A client ID and secret
must be passed to PRAW, but a tool only has the auth token. I was able
to hack around this by manipulating private properties - but it felt too
hacky
2. PRAW does not support Python 3.13
3. PRAW is not async. There is
[AsyncPRAW](https://github.com/praw-dev/asyncpraw), but the community
does not look active there.
It is unnecessary to call `arcadeClient.tools.list` first and then
`arcadeClient.tools.formatted.get` for each tool. We can simply use the
`arcadeClient.tools.formatted.list` function.
The tool used in the mcp demo isn't a valid ToolDefinition.
Unable to list tool 'Demo.lorem': 1 validation error for ToolDefinition
requirements Input should be a valid dictionary or
instance of ToolRequirements For further information visit
https://errors.pydantic.dev/2.10/v/model_type
Some tools have date-related arguments where LLMs fail to provide the
correct values if the user asks in a relative way (e.g. next Monday,
last month, etc). By including today's date and day of the week in the
`arcade chat` system prompt, we avoid that issue.