General OSS health (#643)

This commit is contained in:
Eric Gustin 2025-10-22 18:26:27 -07:00 committed by GitHub
parent 49e53d2b33
commit 5f55258268
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
75 changed files with 90 additions and 329 deletions

121
README.md
View file

@ -32,16 +32,16 @@
</div>
<p align="center" style="display: flex; justify-content: center; gap: 5px; font-size: 15px;">
<a href="https://docs.arcade.dev/home" target="_blank">Documentation</a>
<a href="https://docs.arcade.dev/tools" target="_blank">Tools</a>
<a href="https://docs.arcade.dev/home/quickstart" target="_blank">Quickstart</a>
<a href="https://docs.arcade.dev/tools" target="_blank">Prebuilt Tools</a>
<a href="https://docs.arcade.dev/home/contact-us" target="_blank">Contact Us</a>
# Arcade MCP Server Framework
**To learn more about Arcade.dev, check out our [documentation](https://docs.arcade.dev/home).**
* **To see example servers built with Arcade MCP Server Framework (this repo), check out our [examples](examples/)**
**To learn more about the Arcade MCP Server Framework, check out our [Arcade MCP documentation](https://python.mcp.arcade.dev/)**
* **To learn more about the Arcade MCP Server Framework (this repo), check out our [Arcade MCP documentation](https://docs.arcade.dev/en/home/build-tools/create-a-mcp-server)**
* **To learn more about other offerings from Arcade.dev, check out our [documentation](https://docs.arcade.dev/home).**
_Pst. hey, you, give us a star if you like it!_
@ -49,13 +49,13 @@ _Pst. hey, you, give us a star if you like it!_
<img src="https://img.shields.io/github/stars/ArcadeAI/arcade-mcp.svg" alt="GitHub stars">
</a>
### Quick Start: Create a New Server
## Quick Start: Create a New Server
The fastest way to get started is with the `arcade new` command, which creates a complete MCP server project:
The fastest way to get started is with the `arcade new` CLI command, which creates a complete MCP server project:
```bash
# Install the CLI
uv pip install arcade-mcp
uv tool install arcade-mcp
# Create a new server project
arcade new my_server
@ -64,7 +64,7 @@ arcade new my_server
cd my_server
```
This generates a complete project with:
This generates a project with:
- **server.py** - Main server file with MCPApp and example tools
@ -72,24 +72,84 @@ This generates a complete project with:
- **.env.example** - Example `.env` file containing a secret required by one of the generated tools in `server.py`
The generated `server.py` includes proper command-line argument handling:
The generated `server.py` includes proper command-line argument handling and three example tools:
```python
#!/usr/bin/env python3
"""simple_server MCP server"""
import sys
from typing import Annotated
from arcade_mcp_server import MCPApp
app = MCPApp(name="my_server", version="1.0.0")
import httpx
from arcade_mcp_server import Context, MCPApp
from arcade_mcp_server.auth import Reddit
app = MCPApp(name="simple_server", version="1.0.0", log_level="DEBUG")
@app.tool
def greet(name: Annotated[str, "Name to greet"]) -> str:
"""Greet someone by name."""
def greet(name: Annotated[str, "The name of the person to greet"]) -> str:
"""Greet a person by name."""
return f"Hello, {name}!"
# To use this tool locally, you need to either set the secret in the .env file or as an environment variable
@app.tool(requires_secrets=["MY_SECRET_KEY"])
def whisper_secret(context: Context) -> Annotated[str, "The last 4 characters of the secret"]:
"""Reveal the last 4 characters of a secret"""
# Secrets are injected into the context at runtime.
# LLMs and MCP clients cannot see or access your secrets
# You can define secrets in a .env file.
try:
secret = context.get_secret("MY_SECRET_KEY")
except Exception as e:
return str(e)
return "The last 4 characters of the secret are: " + secret[-4:]
# To use this tool locally, you need to install the Arcade CLI (uv tool install arcade-mcp)
# and then run 'arcade login' to authenticate.
@app.tool(requires_auth=Reddit(scopes=["read"]))
async def get_posts_in_subreddit(
context: Context, subreddit: Annotated[str, "The name of the subreddit"]
) -> dict:
"""Get posts from a specific subreddit"""
# Normalize the subreddit name
subreddit = subreddit.lower().replace("r/", "").replace(" ", "")
# Prepare the httpx request
# OAuth token is injected into the context at runtime.
# LLMs and MCP clients cannot see or access your OAuth tokens.
oauth_token = context.get_auth_token_or_empty()
headers = {
"Authorization": f"Bearer {oauth_token}",
"User-Agent": "{{ toolkit_name }}-mcp-server",
}
params = {"limit": 5}
url = f"https://oauth.reddit.com/r/{subreddit}/hot"
# Make the request
async with httpx.AsyncClient() as client:
response = await client.get(url, headers=headers, params=params)
response.raise_for_status()
# Return the response
return response.json()
# Run with specific transport
if __name__ == "__main__":
transport = sys.argv[1] if len(sys.argv) > 1 else "http"
# Get transport from command line argument, default to "stdio"
# - "stdio" (default): Standard I/O for Claude Desktop, CLI tools, etc.
# Supports tools that require_auth or require_secrets out-of-the-box
# - "http": HTTPS streaming for Cursor, VS Code, etc.
# Does not support tools that require_auth or require_secrets unless the server is deployed
# using 'arcade deploy' or added in the Arcade Developer Dashboard with 'Arcade' server type
transport = sys.argv[1] if len(sys.argv) > 1 else "stdio"
# Run the server
app.run(transport=transport, host="127.0.0.1", port=8000)
```
This approach gives you:
@ -106,11 +166,11 @@ This approach gives you:
Run your server directly with Python:
```bash
# Run with HTTP transport (default)
# Run with stdio transport (default)
uv run server.py
# Run with stdio transport (for Claude Desktop)
uv run server.py stdio
# Run with http transport via command line argument
uv run server.py http
# Or use python directly
python server.py http
@ -124,26 +184,15 @@ Your server will start and listen for connections. With HTTP transport, you can
Once your server is running, connect it to your favorite AI assistant:
```bash
# Configure Claude Desktop (configures for stdio)
arcade configure claude --from-local
# Configure Cursor (configures for http streamable)
arcade configure cursor --from-local
# Configure VS Code (configures for http streamable)
arcade configure vscode --from-local
arcade configure claude # Configure Claude Desktop to connect to your stdio server in your current directory
arcade configure cursor --transport http --port 8080 # Configure Cursor to connect to your local HTTP server on port 8080
arcade configure vscode --entrypoint my_server.py # Configure VSCode to connect to your stdio server that will run when my_server.py is executed directly
```
## Client Libraries
- **[ArcadeAI/arcade-py](https://github.com/ArcadeAI/arcade-py):**
The Python client for interacting with Arcade.
- **[ArcadeAI/arcade-js](https://github.com/ArcadeAI/arcade-js):**
The JavaScript client for interacting with Arcade.
- **[ArcadeAI/arcade-go](https://github.com/ArcadeAI/arcade-go):**
The Go client for interacting with Arcade.
## Installing this Repo from Source
```bash
git clone https://github.com/ArcadeAI/arcade-mcp.git && cd arcade-mcp && make install
```
## Support and Community

View file

Before

Width:  |  Height:  |  Size: 651 KiB

After

Width:  |  Height:  |  Size: 651 KiB

2
examples/.gitignore vendored
View file

@ -1,2 +0,0 @@
# Un-ignore poetry.lock because the projects in this directory represent runnable sample apps
!poetry.lock

View file

@ -1,40 +0,0 @@
"""
This example demonstrates how to directly call a tool that does not require authorization.
"""
from arcadepy import Arcade # pip install arcade-py
def call_non_auth_tool(client: Arcade, user_id: str) -> None:
"""Directly call a prebuilt tool that does not require authorization.
In this example, we are
1. Preparing the inputs to the Math.Add tool
2. Executing the tool
3. Printing the output of the tool's execution, i.e., the result of adding 9001 and 42
This is a simple example of calling a non-auth tool. Next, try writing your own non-auth tool for your own use case.
"""
# Prepare the inputs to the tool as a dictionary where keys are the names of the parameters expected by the tool and the values are the actual values to pass to the tool
tool_input = {"a": 9001, "b": 42}
# Execute the tool
response = client.tools.execute(
tool_name="Math.Add",
input=tool_input,
user_id=user_id,
)
# Print the output of the tool execution
print(response.output.value)
if __name__ == "__main__":
cloud_host = "https://api.arcade.dev"
client = Arcade(
base_url=cloud_host, # Alternatively, use http://localhost:9099 if you are running Arcade Engine locally, or any base_url if you're hosting elsewhere
)
user_id = "you@example.com"
call_non_auth_tool(client, user_id)

View file

@ -1,51 +0,0 @@
"""
This example demonstrates how to directly call a tool that requires authorization.
"""
from arcadepy import Arcade # pip install arcade-py
def call_auth_tool(client: Arcade, user_id: str) -> None:
"""Directly call a prebuilt tool that requires authorization.
In this example, we are
1. Authorizing Arcade to read emails from the user's Gmail account with the user's permission to do so
2. Reading 5 emails from the user's Gmail account
3. Printing the emails
Try altering this example to call a tool that requires a different authorization.
"""
# Start the authorization process
auth_response = client.tools.authorize(
tool_name="Gmail.ListEmails",
user_id=user_id,
)
# If not already authorized, then wait for the user to authorize the permissions required by the tool
if auth_response.status != "completed":
print(f"Click this link to authorize: {auth_response.url}")
# Wait for the user to complete the auth flow, if necessary
client.auth.wait_for_completion(auth_response)
# Prepare the inputs to the tool as a dictionary where keys are the names of the parameters expected by the tool and the values are the actual values to pass to the tool
tool_input = {"n_emails": 5}
# Execute the tool
response = client.tools.execute(
tool_name="Gmail.ListEmails",
input=tool_input,
user_id=user_id,
)
# Print the output of the tool execution.
print(response)
if __name__ == "__main__":
client = Arcade(
base_url="https://api.arcade.dev", # Alternatively, use http://localhost:9099 if you are running Arcade Engine locally, or any base_url if you're hosting elsewhere
)
user_id = "you@example.com"
call_auth_tool(client, user_id)

View file

@ -1,45 +0,0 @@
"""
This example shows how to call a tool that requires authorization with an LLM using the OpenAI Python client.
"""
import os
from openai import OpenAI
def call_tool_with_openai(client: OpenAI) -> dict:
response = client.chat.completions.create(
messages=[
{"role": "user", "content": "Star the ArcadeAI/arcade-mcp repository."},
],
model="gpt-4o-mini", # TODO: Try "claude-3-5-sonnet-20240620" or other models from our supported model providers. Checkout out our docs for a full list https://docs.arcade.dev
user="you@example.com",
tools=["Github.SetStarred"],
tool_choice="generate", # TODO: Try "execute" and note any differences
)
return response
if __name__ == "__main__":
arcade_api_key = os.environ.get(
"ARCADE_API_KEY"
) # If you forget your Arcade API key, it is stored at ~/.arcade/credentials.yaml on `arcade login`
cloud_host = "https://api.arcade.dev" + "/v1"
openai_client = OpenAI(
api_key=arcade_api_key,
base_url=cloud_host, # Alternatively, use http://localhost:9099/v1 if you are running Arcade Engine locally
)
chat_result = call_tool_with_openai(openai_client)
# If the tool call requires authorization, then wait for the user to authorize and then call the tool again
if (
chat_result.choices[0].tool_authorizations
and chat_result.choices[0].tool_authorizations[0].get("status") == "pending"
):
print(chat_result.choices[0].message.content)
input("After you have authorized, press Enter to continue...")
chat_result = call_tool_with_openai(openai_client)
print(chat_result.choices[0].message.content)

View file

@ -1,58 +0,0 @@
"""
This example demonstrates how to get an authorization token for a user and then use it to make a request to the Google API on behalf of the user.
"""
from arcadepy import Arcade
from google.oauth2.credentials import Credentials # pip install google-auth
from googleapiclient.discovery import build # pip install google-api-python-client
def get_auth_token(client: Arcade, user_id: str) -> str:
"""Get an authorization token for a user.
In this example, we are
1. Starting the authorization process for the Gmail Readonly scope
2. Waiting for the user to authorize the scope
3. Getting the authorization token
4. Using the authorization token to make a request to the Google API on behalf of the user
"""
# Start the authorization process
auth_response = client.auth.start(
user_id, "google", scopes=["https://www.googleapis.com/auth/gmail.readonly"]
)
if auth_response.status != "completed":
print(f"Click this link to authorize: {auth_response.url}")
auth_response = client.auth.wait_for_completion(auth_response)
return auth_response.context.token
def use_auth_token(token: str) -> None:
"""Use an authorization token to make a request to the Google API on behalf of a user.
In this example, we are
1. Using the authorization token that we got from the authorization process to make a request to the Google API
client.auth.wait_for_completion(auth_response)
"""
# Use the token from the authorization response
creds = Credentials(token)
service = build("gmail", "v1", credentials=creds)
# Now you can use the Google API
results = service.users().labels().list(userId="me").execute()
labels = results.get("labels", [])
print("Labels:", labels)
if __name__ == "__main__":
cloud_host = "https://api.arcade.dev"
client = Arcade(
base_url=cloud_host, # Alternatively, use http://localhost:9099 if you are running Arcade locally, or any base_url if you're hosting elsewhere
)
user_id = "you@example.com"
token = get_auth_token(client, user_id)
use_auth_token(token)

View file

@ -1,10 +0,0 @@
ARG VERSION=latest
# Base worker image
FROM ghcr.io/arcadeai/worker-base:${VERSION}
# Copy requirements and constraints
COPY toolkits.txt ./
# Install toolkits from file
RUN pip install -r toolkits.txt

View file

@ -1,30 +0,0 @@
## Custom Worker Image
This example shows how to build a custom worker image with toolkits.
### Requirements
- Docker
### Build
```
docker build -t custom-worker:0.1.0 .
```
### Run
```
docker run -p 8002:8002 custom-worker:0.1.0
```
### Change the Toolkits
To change the toolkits, edit the `toolkits.txt` file.
```
arcade-gmail==0.1.0
arcade-firecrawl==0.1.0
arcade-zoom==0.1.2
...
```

View file

@ -1 +0,0 @@
arcade-gmail

View file

@ -1,17 +0,0 @@
## Deploy a Custom Arcade Worker on Modal
### Requirements
- Python 3.10+
- Modal CLI
### Deploy
```bash
cd examples/serving-tools
modal deploy run-arcade-worker.py
```
### Changing the Toolkits
To change the toolkits, edit the `toolkits` list in the `run-arcade-worker.py` file.

View file

@ -1,34 +0,0 @@
import os
from modal import App, Image, asgi_app
# Define the FastAPI app
app = App("arcade-worker")
toolkits = ["arcade_gmail", "arcade_slack"]
image = (
Image.debian_slim().pip_install("arcade_tdk").pip_install("arcade_serve").pip_install(toolkits)
)
@app.function(image=image)
@asgi_app()
def fastapi_app():
from arcade_serve.fastapi.worker import FastAPIWorker
from arcade_tdk import Toolkit
from fastapi import FastAPI
web_app = FastAPI()
# Initialize app and Arcade FastAPIWorker
worker_secret = os.environ.get("ARCADE_WORKER_SECRET", "dev")
worker = FastAPIWorker(web_app, secret=worker_secret)
# Register toolkits we've installed
installed_toolkits = Toolkit.find_all_arcade_toolkits()
for toolkit in installed_toolkits:
if toolkit.package_name in toolkits:
worker.register_toolkit(toolkit)
return web_app

View file

@ -14,7 +14,7 @@ Arcade CLI provides a comprehensive command-line interface for the Arcade platfo
## Installation
```bash
pip install arcade-mcp
uv tool install arcade-mcp
```
## Usage

View file

@ -531,7 +531,7 @@ def configure(
Examples:
arcade configure claude
arcade configure cursor --transport http --port 8080
arcade configure vscode --host arcade --entrypoint ../../../mcp/server.py --config .vscode/mcp.json
arcade configure vscode --host arcade --entrypoint my_server.py --config .vscode/mcp.json
arcade configure claude --host local --name my_server_name
"""
from arcade_cli.configure import configure_client

View file

@ -17,7 +17,7 @@ def greet(name: Annotated[str, "The name of the person to greet"]) -> str:
return f"Hello, {name}!"
# To use this tool, you need to either set the secret in the .env file or as an environment variable
# To use this tool locally, you need to either set the secret in the .env file or as an environment variable
@app.tool(requires_secrets=["MY_SECRET_KEY"])
def whisper_secret(context: Context) -> Annotated[str, "The last 4 characters of the secret"]:
"""Reveal the last 4 characters of a secret"""
@ -31,8 +31,8 @@ def whisper_secret(context: Context) -> Annotated[str, "The last 4 characters of
return "The last 4 characters of the secret are: " + secret[-4:]
# To use this tool, you need to either set your ARCADE_API_KEY as an environment variable or
# use the Arcade CLI (uv pip install arcade-mcp) and run 'arcade login' to authenticate.
# To use this tool locally, you need to install the Arcade CLI (uv tool install arcade-mcp)
# and then run 'arcade login' to authenticate.
@app.tool(requires_auth=Reddit(scopes=["read"]))
async def get_posts_in_subreddit(
context: Context, subreddit: Annotated[str, "The name of the subreddit"]