remove mkdocs for arcade-mcp (#617)

Compainion to https://github.com/ArcadeAI/docs/pull/488 - This PR turns
off MKDocs. We have the docs in docs.arcade.dev now.
This commit is contained in:
Evan Tahler 2025-10-13 10:47:41 -07:00 committed by GitHub
parent 64fb783cdd
commit bee349287f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 0 additions and 3714 deletions

View file

@ -1,22 +0,0 @@
name: Test Docs
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
jobs:
test-docs:
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Test docs
working-directory: libs/arcade-mcp-server
run: make sync && make docs-build

View file

@ -25,16 +25,3 @@ mypy: ## Run mypy
.PHONY: test
test: ## Run tests
uv run pytest --cov=arcade_mcp_server --cov-report=term-missing ../tests/arcade_mcp_server
.PHONY: docs
docs: ## Build docs
uv run mkdocs build
uv run mkdocs serve
.PHONY: docs-serve
docs-serve: ## Serve docs locally
uv run mkdocs serve
.PHONY: docs-build
docs-build: ## Build docs
uv run mkdocs build

View file

@ -1,180 +0,0 @@
# Sharing Your MCP Server
Make your MCP server accessible to others by exposing it through a secure tunnel and registering it with Arcade. This allows remote users and services to interact with your tools without deploying to a cloud platform.
## Overview
By default, your MCP server runs locally on `localhost:8000`. To share it:
1. Run your server with HTTP transport
2. Create a secure tunnel to expose it publicly
3. Register your server in Arcade
4. Share the tools with others
## Step 1: Run Your Server
First, start your MCP server with HTTP transport:
```bash
# Navigate to your server directory
cd my_server
# Run with HTTP transport (default)
uv run server.py
uv run server.py http
```
Your server will start on `http://localhost:8000`. Keep this terminal running.
## Step 2: Create a Secure Tunnel
Open a **separate terminal** and create a tunnel using one of these options:
### Option A: ngrok (Recommended for Getting Started)
[ngrok](https://ngrok.com) is easy to set up and works across all platforms.
1. **Install ngrok:**
```bash
# macOS
brew install ngrok
# Or download from https://ngrok.com/download
```
2. **Create a tunnel:**
```bash
ngrok http 8000
```
3. **Copy your URL:**
Look for the "Forwarding" line in the ngrok output:
```
Forwarding https://abc123.ngrok-free.app -> http://localhost:8000
```
Copy the `https://abc123.ngrok-free.app` URL - this is your public URL.
**Pros:**
- Quick setup, no account required for basic use
- Automatic HTTPS
- Web dashboard to inspect requests
**Cons:**
- Free tier URLs change on each restart
- May show interstitial page for free tier
### Option B: Cloudflare Tunnel
[Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/) provides persistent URLs and advanced features.
1. **Install cloudflared:**
```bash
# macOS
brew install cloudflare/cloudflare/cloudflared
# Or download from https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/
```
2. **Create a tunnel:**
```bash
cloudflared tunnel --url http://localhost:8000
```
3. **Copy your URL:**
Look for the "Your quick Tunnel has been created" message with your URL.
**Pros:**
- Free tier includes persistent URLs (with setup)
- Built-in DDoS protection
- Access control features
**Cons:**
- Requires Cloudflare account for persistent URLs
- More complex setup for advanced features
### Option C: Tailscale Funnel
[Tailscale Funnel](https://tailscale.com/kb/1223/tailscale-funnel) is ideal for sharing within a team or organization.
1. **Install Tailscale:**
```bash
# macOS
brew install tailscale
# Or download from https://tailscale.com/download
```
2. **Authenticate:**
```bash
tailscale up
```
3. **Create a funnel:**
```bash
tailscale funnel 8000
```
4. **Get your URL:**
Tailscale will display your funnel URL (e.g., `https://my-machine.tail-scale.ts.net`)
**Pros:**
- Persistent URLs tied to your machine
- Private by default (only shared with specified users)
- No bandwidth limits
**Cons:**
- Requires Tailscale account
- Best for team/organization use cases
## Step 3: Register Your MCP Server in Arcade
Once you have a public URL, register your MCP server in the Arcade dashboard to make it accessible through the Arcade API.
### Register Your Server
1. **Navigate to the MCP Servers page** in your [Arcade dashboard](https://api.arcade.dev/dashboard/servers)
2. **Click "Add Server"**
3. **Fill in the registration form:**
- **ID**: Choose a unique identifier (e.g., `my-mcp-server`)
- **Server Type**: Select "HTTP/SSE"
- **URL**: Enter your public tunnel URL from Step 2 with `/mcp` appended
- Example: `https://abc123.ngrok-free.app/mcp`
- Example: `https://my-tunnel.trycloudflare.com/mcp`
- Example: `https://my-machine.tail-scale.ts.net/mcp`
- **Secret**: Enter a secret for your server (or use `dev` for testing)
- **Timeout**: Configure request timeout (default: 30s)
- **Retry**: Configure retry attempts (default: 3)
4. **Click "Create"**
### Configuration Example
```yaml
ID: my-mcp-server
Server Type: HTTP/SSE
URL: https://abc123.ngrok-free.app
Secret: my-secure-secret-123
Timeout: 30s
Retry: 3
```
## Step 4: Test Your MCP Server
Verify that your server is accessible and working correctly.
### Using the Arcade Playground
1. **Go to the [Arcade Playground](https://api.arcade.dev/dashboard/playground/chat)**
2. **Select your MCP server** from the dropdown
3. **Choose a tool** from your server
4. **Execute the tool** with test parameters
5. **Verify the response:**
- Check that the response is correct
- View request logs in your local server terminal
- Inspect the tunnel dashboard for request details

View file

@ -1,213 +0,0 @@
# Transport Modes
MCP servers can communicate with clients through different transport mechanisms. Each transport is optimized for specific use cases and client types.
## stdio Transport
The stdio (standard input/output) transport is used for direct client connections.
### Characteristics
- Communicates via standard input/output streams
- Logs go to stderr to avoid interfering with protocol messages
- Ideal for desktop applications and command-line tools
- Used by Claude Desktop and similar clients
### Usage
**Recommended: Using Arcade CLI**
```bash
# Run with stdio transport
uv run server.py stdio
```
**Alternative: Direct Python**
```bash
# Run your server directly
uv run server.py stdio
# Or with python
app.run(transport="stdio")
```
### Client Configuration
For Claude Desktop, use the `arcade configure` command:
```bash
arcade configure claude --from-local
```
Or manually edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"my-tools": {
"command": "arcade",
"args": ["mcp", "stdio"],
"cwd": "/path/to/your/tools"
}
}
}
```
## HTTP Transport
The HTTP transport provides REST/SSE endpoints for web-based clients.
### Characteristics
- RESTful API with Server-Sent Events (SSE) for streaming
- Supports hot reload for development
- Includes health checks and API documentation
- Can be deployed behind reverse proxies
- Suitable for web applications and services
### Usage
**Recommended: Using Arcade CLI**
```bash
# Run with HTTP transport (default)
uv run server.py
uv run server.py http
```
**Alternative: Direct Python**
```bash
# Run your server directly
uv run server.py
# Or with python
app.run(transport="http", host="0.0.0.0", port=8080)
```
### Endpoints
When running in HTTP mode, the server provides:
- `GET /health` - Health check endpoint
- `GET /mcp` - SSE endpoint for MCP protocol
- `GET /docs` - Swagger UI documentation (debug mode)
- `GET /redoc` - ReDoc documentation (debug mode)
### Development Features
**With Arcade CLI:**
```python
# Enable hot reload and debug mode
app.run(host="127.0.0.1", port=8000, reload=True)
# This enables:
# - Automatic restart on code changes
# - Detailed error messages
# - API documentation endpoints
# - Verbose logging
```
## Choosing a Transport
### Use stdio when:
- Integrating with desktop applications (Claude Desktop, VS Code)
- Building command-line tools
- You need simple, direct communication
- Running in environments without network access
### Use HTTP when:
- Building web applications
- Deploying to cloud environments
- You need to support multiple concurrent clients
- Integrating with existing web services
- You want API documentation and testing tools
## Transport Configuration
### Environment Variables
Both transports respect common environment variables:
```bash
# Server identification
MCP_SERVER_NAME="My MCP Server"
MCP_SERVER_VERSION="1.0.0"
# Logging
MCP_DEBUG=true
MCP_LOG_LEVEL=DEBUG
# HTTP-specific
MCP_HTTP_HOST=0.0.0.0
MCP_HTTP_PORT=8080
```
### Programmatic Configuration
When using MCPApp:
```python
from arcade_mcp_server import MCPApp
app = MCPApp(
name="my-server",
version="1.0.0",
log_level="DEBUG"
)
# Run with specific transport
if __name__ == "__main__":
import sys
if len(sys.argv) > 1 and sys.argv[1] == "stdio":
app.run(transport="stdio")
else:
app.run(transport="http", host="0.0.0.0", port=8080)
```
## Security Considerations
### stdio Transport
- Inherits security context of the parent process
- No network exposure
- Suitable for trusted environments
### HTTP Transport
- Exposes network endpoints
- Should use authentication in production
- Consider using HTTPS with reverse proxy
- Implement rate limiting for public deployments
## Advanced Transport Features
### Custom Middleware (HTTP)
Add custom middleware to HTTP transports:
```python
from arcade_mcp_server import MCPApp
app = MCPApp(name="my-server")
# Add custom middleware
@app.middleware("http")
async def add_custom_headers(request, call_next):
response = await call_next(request)
response.headers["X-Custom-Header"] = "value"
return response
```
### Transport Events
Listen to transport lifecycle events:
```python
@app.on_event("startup")
async def startup_handler():
print("Server starting up...")
@app.on_event("shutdown")
async def shutdown_handler():
print("Server shutting down...")
```

View file

@ -1,47 +0,0 @@
### MCPApp
A FastAPI-like interface for building MCP servers with lazy initialization.
MCPApp provides a clean, minimal API for building MCP servers programmatically. It handles tool collection, server configuration, and transport setup with a developer-friendly interface.
#### Basic Usage
```python
from arcade_mcp_server import MCPApp
app = MCPApp(name="my_server", version="1.0.0")
@app.tool
def greet(name: str) -> str:
return f"Hello, {name}!"
app.run(host="127.0.0.1", port=8000)
```
#### Class Reference
::: arcade_mcp_server.mcp_app.MCPApp
#### Examples
```python
# --- server.py ---
# Programmatic server creation with a simple tool and HTTP transport
from arcade_mcp_server import MCPApp
app = MCPApp(name="example_server", version="1.0.0")
@app.tool
def echo(text: str) -> str:
return f"Echo: {text}"
if __name__ == "__main__":
# Start an HTTP server (good for local development/testing)
app.run(host="0.0.0.0", port=8000, reload=False, debug=True)
```
```bash
# then run the server
python server.py
```

View file

@ -1,36 +0,0 @@
### Exceptions
Domain-specific error types raised by the MCP server and components.
::: arcade_mcp_server.exceptions
#### Examples
```python
from arcade_mcp_server.exceptions import (
MCPError,
NotFoundError,
DuplicateError,
ValidationError,
ToolError,
)
# Raising a not-found when a resource is missing
async def read_resource_or_fail(uri: str) -> str:
if not await exists(uri):
raise NotFoundError(f"Resource not found: {uri}")
return await read(uri)
# Validating input
def validate_age(age: int) -> None:
if age < 0:
raise ValidationError("age must be non-negative")
# Handling tool execution errors in middleware or handlers
async def call_tool_safely(call):
try:
return await call()
except ToolError as e:
# Convert to an error result or re-raise
raise MCPError(f"Tool failed: {e}")
```

View file

@ -1,50 +0,0 @@
### Middleware
Base interfaces and built-in middleware.
::: arcade_mcp_server.middleware.base.Middleware
::: arcade_mcp_server.middleware.base.MiddlewareContext
::: arcade_mcp_server.middleware.base.compose_middleware
#### Built-ins
::: arcade_mcp_server.middleware.logging.LoggingMiddleware
::: arcade_mcp_server.middleware.error_handling.ErrorHandlingMiddleware
#### Examples
```python
# Implement a custom middleware
from arcade_mcp_server.middleware.base import Middleware, MiddlewareContext
class TimingMiddleware(Middleware):
async def __call__(self, context: MiddlewareContext, call_next):
import time
start = time.perf_counter()
try:
return await call_next(context)
finally:
elapsed_ms = (time.perf_counter() - start) * 1000
# Attach timing info to context metadata
context.metadata["elapsed_ms"] = round(elapsed_ms, 2)
```
```python
# Compose middleware and create a server
from arcade_mcp_server.middleware.base import compose_middleware
from arcade_mcp_server.middleware.logging import LoggingMiddleware
from arcade_mcp_server.middleware.error_handling import ErrorHandlingMiddleware
from arcade_mcp_server.server import MCPServer
from arcade_core.catalog import ToolCatalog
middleware = compose_middleware([
ErrorHandlingMiddleware(mask_error_details=False),
LoggingMiddleware(log_level="INFO"),
TimingMiddleware(),
])
server = MCPServer(catalog=ToolCatalog(), middleware=[middleware])
```

View file

@ -1,53 +0,0 @@
# Server
### Low-level Server
Low-level server for hosting Arcade tools over MCP.
::: arcade_mcp_server.server.MCPServer
#### Examples
```python
# Basic server with tool catalog and stdio transport
import asyncio
from arcade_mcp_server.server import MCPServer
from arcade_core.catalog import ToolCatalog
from arcade_mcp_server.transports.stdio import StdioTransport
async def main():
catalog = ToolCatalog()
server = MCPServer(catalog=catalog, name="example", version="1.0.0")
await server._start()
try:
# Run stdio transport loop
transport = StdioTransport()
await transport.run(server)
finally:
await server._stop()
if __name__ == "__main__":
asyncio.run(main())
```
```python
# Handling a single HTTP streamable connection
import asyncio
from arcade_mcp_server.server import MCPServer
from arcade_core.catalog import ToolCatalog
from arcade_mcp_server.transports.http_streamable import HTTPStreamableTransport
async def run_http():
catalog = ToolCatalog()
server = MCPServer(catalog=catalog)
await server._start()
try:
transport = HTTPStreamableTransport(host="0.0.0.0", port=8000)
await transport.run(server)
finally:
await server._stop()
asyncio.run(run_http())
```

View file

@ -1,49 +0,0 @@
### Settings
Global configuration and environment-driven settings.
::: arcade_mcp_server.settings.MCPSettings
#### Sub-settings
::: arcade_mcp_server.settings.ServerSettings
::: arcade_mcp_server.settings.MiddlewareSettings
::: arcade_mcp_server.settings.NotificationSettings
::: arcade_mcp_server.settings.TransportSettings
::: arcade_mcp_server.settings.ArcadeSettings
::: arcade_mcp_server.settings.ToolEnvironmentSettings
#### Examples
```python
from arcade_mcp_server.settings import MCPSettings
settings = MCPSettings(
debug=True,
middleware=MCPSettings.middleware.__class__(
enable_logging=True,
mask_error_details=False,
),
server=MCPSettings.server.__class__(
title="My MCP Server",
instructions="Use responsibly",
),
transport=MCPSettings.transport.__class__(
http_host="0.0.0.0",
http_port=8000,
),
)
```
```python
# Loading from environment
from arcade_mcp_server.settings import MCPSettings
# Values like ARCADE_MCP_DEBUG, ARCADE_MCP_HTTP_PORT, etc. are parsed
settings = MCPSettings()
```

View file

@ -1,37 +0,0 @@
### Types
Core Pydantic models and enums for the MCP protocol shapes.
::: arcade_mcp_server.types
#### Examples
```python
# Constructing a JSON-RPC request and response model
from arcade_mcp_server.types import JSONRPCRequest, JSONRPCResponse
req = JSONRPCRequest(id=1, method="ping", params={})
res = JSONRPCResponse(id=req.id, result={})
print(req.model_dump_json())
print(res.model_dump_json())
```
```python
# Building a tools/call request and examining result shape
from arcade_mcp_server.types import CallToolRequest, CallToolResult, TextContent
call = CallToolRequest(
id=2,
method="tools/call",
params={
"name": "Toolkit.tool",
"arguments": {"text": "hello"},
},
)
# Result would typically be produced by the server:
result = CallToolResult(
content=[TextContent(type="text", text="Echo: hello")],
structuredContent={"result": "Echo: hello"},
isError=False
)
```

View file

@ -1,363 +0,0 @@
# MCP Inspector
The MCP Inspector is a powerful debugging and testing tool for MCP servers. It provides a web-based interface to interact with your Arcade MCP server, test tools, and monitor protocol messages.
## Installation
Install the MCP Inspector globally:
```bash
npm install -g @modelcontextprotocol/inspector
```
Or use npx to run without installing:
```bash
npx @modelcontextprotocol/inspector
```
## Basic Usage
### Connecting to HTTP Servers
For MCP servers running over HTTP:
```bash
# Start your MCP server
uv run server.py
# In another terminal, start the inspector
mcp-inspector http://localhost:8000/mcp
```
### Connecting to stdio Servers
For stdio-based servers:
```bash
# Start the inspector with your server command
mcp-inspector "uv run server.py stdio"
# With additional project directory
mcp-inspector --cwd /path/to/project "uv run server.py stdio"
```
## Inspector Features
### Tool Explorer
The Tool Explorer shows all available tools with:
- Tool names and descriptions
- Parameter schemas
- Return type information
- Example invocations
### Interactive Testing
Test tools directly from the interface:
1. Select a tool from the explorer
2. Fill in parameter values
3. Click "Execute" to run the tool
4. View results and execution time
### Protocol Monitor
Monitor all MCP protocol messages:
- Request/response pairs
- Message timing
- Protocol errors
- Raw JSON data
### Resource Browser
If your server provides resources:
- Browse available resources
- View resource contents
- Test resource operations
### Prompt Templates
Test prompt templates if supported:
- View available prompts
- Fill template parameters
- Preview rendered prompts
## Advanced Usage
### Custom Environment
Pass environment variables to your server:
```bash
# Using env command
env ARCADE_API_KEY=your-key mcp-inspector "uv run server.py stdio"
# Using inspector's env option
mcp-inspector --env ARCADE_API_KEY=your-key "uv run server.py stdio"
```
### Working Directory
Set the working directory for your server:
```bash
mcp-inspector --cwd /path/to/project "uv run server.py stdio"
```
### Debug Mode
Enable verbose logging:
```bash
# Debug the inspector
mcp-inspector --debug "uv run server.py stdio"
# Server debug logging is configured in your server.py
# app = MCPApp(name="my_server", version="1.0.0", log_level="DEBUG")
```
## Testing Workflows
### Tool Development
1. **Configure your server with hot reload**:
```python
# In your server.py
if __name__ == "__main__":
transport = sys.argv[1] if len(sys.argv) > 1 else "http"
app.run(transport=transport, host="127.0.0.1", port=8000, reload=True)
```
Then run:
```bash
uv run server.py
```
2. **Connect the inspector**:
```bash
mcp-inspector http://localhost:8000/mcp
```
3. **Develop and test**:
- Modify your tool code
- Server auto-reloads
- Test immediately in inspector
### Performance Testing
Use the inspector to measure tool performance:
1. Enable timing in the Protocol Monitor
2. Execute tools multiple times
3. Analyze response times
4. Identify bottlenecks
### Error Debugging
Debug tool errors effectively:
1. Enable debug mode on your server
2. Execute the failing tool
3. Check Protocol Monitor for error details
4. View server logs in terminal
## Integration Testing
### Test Suites
Create test suites using the inspector:
```javascript
// test-tools.js
const tests = [
{
tool: "greet",
params: { name: "World" },
expected: "Hello, World!"
},
{
tool: "calculate",
params: { expression: "2 + 2" },
expected: 4
}
];
// Run tests via inspector API
```
### Automated Testing
Combine with testing frameworks:
```python
# test_mcp_tools.py
import subprocess
import json
import pytest
def test_tool_via_inspector():
# Start server
server = subprocess.Popen(
["python", "-m", "arcade_mcp_server"],
stdout=subprocess.PIPE
)
# Use inspector's API to test tools
# ...
```
## Best Practices
### Development Setup
1. **Use Split Terminal**:
- Terminal 1: MCP server with reload
- Terminal 2: Inspector
- Terminal 3: Code editor
2. **Enable All Debugging**:
```python
# In server.py
app = MCPApp(name="my_server", version="1.0.0", log_level="DEBUG")
# Run with reload
app.run(transport="http", host="127.0.0.1", port=8000, reload=True)
```
Then run with environment file:
```bash
uv run server.py
```
3. **Save Test Cases**:
- Export successful tool calls
- Build regression test suite
- Document edge cases
### Production Testing
1. **Test Against Production Config**:
```bash
mcp-inspector "uv run server.py stdio"
```
2. **Verify Security**:
- Test with limited permissions
- Verify API key handling
- Check error messages don't leak secrets
3. **Load Testing**:
- Execute tools rapidly
- Monitor memory usage
- Check for resource leaks
## Troubleshooting
### Connection Issues
#### "Failed to connect"
1. Verify server is running
2. Check correct URL/command
3. Ensure ports aren't blocked
4. Try with `--debug` flag
#### "Protocol error"
1. Ensure server implements MCP correctly
2. Check for version compatibility
3. Review server logs
4. Verify transport type
### Tool Issues
#### "Tool not found"
1. Verify tool is decorated with `@tool`
2. Check tool discovery in server
3. Ensure no import errors
4. Restart server and inspector
#### "Parameter validation failed"
1. Check parameter types match schema
2. Verify required parameters
3. Test with simpler values
4. Review tool documentation
## Examples
### Quick Test Session
```bash
# 1. Start a simple MCP server
cat > test_tools.py << 'EOF'
from arcade_mcp_server import tool
from typing import Annotated
@tool
def echo(message: Annotated[str, "Message to echo"]) -> str:
"""Echo the message back."""
return message
@tool
def add(
a: Annotated[int, "First number"],
b: Annotated[int, "Second number"]
) -> Annotated[int, "Sum"]:
"""Add two numbers."""
return a + b
EOF
# 2. Start inspector
mcp-inspector "uv run server.py stdio"
# 3. Test tools in the web interface
```
### HTTP Server Testing
```bash
# 1. Create an MCPApp server
cat > app.py << 'EOF'
from arcade_mcp_server import MCPApp
from typing import Annotated
app = MCPApp(name="test-server", version="1.0.0")
@app.tool
def get_time() -> Annotated[str, "Current time"]:
"""Get the current time."""
from datetime import datetime
return datetime.now().isoformat()
if __name__ == "__main__":
app.run(port=9000, reload=True)
EOF
# 2. Run the server
python app.py
# 3. Connect inspector
mcp-inspector http://localhost:9000/mcp
```
### Debugging Session
```bash
# 1. Enable all debugging
export DEBUG=*
export MCP_DEBUG=true
# 2. Start server with verbose logging
# (configure log_level="DEBUG" in your server.py)
uv run server.py stdio 2>server.log
# 3. Start inspector with debugging
mcp-inspector --debug "uv run server.py stdio"
```

View file

@ -1,485 +0,0 @@
# Visual Studio Code
While VSCode doesn't have native MCP support yet, you can integrate Arcade MCP servers with VSCode through extensions and custom configurations. This guide shows various integration approaches.
## Prerequisites
- Visual Studio Code installed
- Python 3.10+ installed
- `arcade-mcp` package installed (`pip install arcade-mcp`)
- Python extension for VSCode
## Integration Methods
### Method 1: Terminal Integration
Use VSCode's integrated terminal to run MCP servers:
1. Open integrated terminal (`Ctrl/Cmd + ` `)
2. Start your MCP server:
```bash
uv run server.py
```
3. Use split terminals for multiple servers
### Method 2: Task Runner
Create tasks to manage MCP servers:
#### Create `.vscode/tasks.json`:
```json
{
"version": "2.0.0",
"tasks": [
{
"label": "Start MCP Server",
"type": "shell",
"command": "python",
"args": ["-m", "arcade_mcp_server", "--reload", "--debug"],
"isBackground": true,
"problemMatcher": {
"pattern": {
"regexp": "^(ERROR|WARNING):\\s+(.+)$",
"severity": 1,
"message": 2
},
"background": {
"activeOnStart": true,
"beginsPattern": "^Starting.*",
"endsPattern": "^.*Server ready.*"
}
},
"presentation": {
"reveal": "always",
"panel": "dedicated"
}
},
{
"label": "Start MCP (HTTP)",
"type": "shell",
"command": "python",
"args": [
"-m", "arcade_mcp_server",
"--host", "0.0.0.0",
"--port", "8000",
"--reload"
],
"isBackground": true,
"problemMatcher": []
},
{
"label": "Test Tools",
"type": "shell",
"command": "python",
"args": ["${workspaceFolder}/test_tools.py"],
"problemMatcher": "$python"
}
]
}
```
Run tasks via:
- Command Palette: `Tasks: Run Task`
- Terminal menu: `Terminal > Run Task`
### Method 3: Launch Configurations
Debug your MCP tools with VSCode's debugger:
#### Create `.vscode/launch.json`:
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug MCP Server",
"type": "python",
"request": "launch",
"module": "arcade_mcp_server",
"args": ["--debug", "--reload"],
"cwd": "${workspaceFolder}",
"env": {
"PYTHONPATH": "${workspaceFolder}",
"ARCADE_API_KEY": "${env:ARCADE_API_KEY}"
},
"console": "integratedTerminal"
},
{
"name": "Debug Specific Tool",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/tools/my_tool.py",
"args": ["--test"],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal"
},
{
"name": "Debug with Package",
"type": "python",
"request": "launch",
"module": "arcade_mcp_server",
"args": [
"--tool-package", "github",
"--debug"
],
"env": {
"GITHUB_TOKEN": "${input:githubToken}"
}
}
],
"inputs": [
{
"id": "githubToken",
"type": "promptString",
"description": "Enter your GitHub token",
"password": true
}
]
}
```
## Development Workflow
### Project Setup
Recommended project structure:
```
my-mcp-project/
├── .vscode/
│ ├── launch.json # Debug configurations
│ ├── tasks.json # Task definitions
│ ├── settings.json # Workspace settings
│ └── extensions.json # Recommended extensions
├── .env # Environment variables
├── .env.example
├── tools/
│ ├── __init__.py
│ └── my_tools.py
├── tests/
│ └── test_tools.py
├── requirements.txt
└── pyproject.toml
```
### Workspace Settings
Configure `.vscode/settings.json`:
```json
{
"python.defaultInterpreterPath": "${workspaceFolder}/venv/bin/python",
"python.terminal.activateEnvironment": true,
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.formatting.provider": "black",
"python.testing.pytestEnabled": true,
"python.testing.pytestArgs": ["tests"],
"files.exclude": {
"**/__pycache__": true,
"**/*.pyc": true
},
"terminal.integrated.env.linux": {
"PYTHONPATH": "${workspaceFolder}"
},
"terminal.integrated.env.osx": {
"PYTHONPATH": "${workspaceFolder}"
},
"terminal.integrated.env.windows": {
"PYTHONPATH": "${workspaceFolder}"
}
}
```
### Recommended Extensions
Create `.vscode/extensions.json`:
```json
{
"recommendations": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-vscode.live-server",
"humao.rest-client",
"redhat.vscode-yaml",
"ms-azuretools.vscode-docker"
]
}
```
## Testing Tools
### REST Client Extension
Test HTTP MCP servers using REST Client:
Create `test-mcp.http`:
```http
### Get Server Info
GET http://localhost:8000/health
### List Tools
POST http://localhost:8000/catalog
Content-Type: application/json
Authorization: Bearer {{$env ARCADE_API_KEY}}
{}
### Call Tool
POST http://localhost:8000/call_tool
Content-Type: application/json
Authorization: Bearer {{$env ARCADE_API_KEY}}
{
"tool_name": "greet",
"tool_arguments": {
"name": "World"
}
}
```
### Python Test Scripts
Create test scripts for your tools:
```python
# test_tools.py
import asyncio
from arcade_core.catalog import ToolCatalog
async def test_tools():
# Import your tools
from tools import my_tools
# Create catalog
catalog = ToolCatalog()
catalog.add_tool(my_tools.greet, "test")
# Test tool
result = await catalog.call_tool(
"test.greet",
{"name": "Test"}
)
print(f"Result: {result}")
if __name__ == "__main__":
asyncio.run(test_tools())
```
## Debugging Tips
### Breakpoint Debugging
1. Set breakpoints in your tool code
2. Launch debugger with "Debug MCP Server"
3. Trigger tool execution
4. Step through code execution
### Logging Configuration
Enhanced logging for debugging:
```python
# tools/__init__.py
import logging
from loguru import logger
# Configure loguru
logger.add(
"debug.log",
rotation="10 MB",
level="DEBUG",
format="{time} {level} {message}"
)
# Intercept standard logging
class InterceptHandler(logging.Handler):
def emit(self, record):
logger_opt = logger.opt(depth=6, exception=record.exc_info)
logger_opt.log(record.levelname, record.getMessage())
logging.basicConfig(handlers=[InterceptHandler()], level=0)
```
### Performance Profiling
Profile your tools:
```json
{
"name": "Profile MCP Server",
"type": "python",
"request": "launch",
"module": "cProfile",
"args": [
"-o", "profile.stats",
"-m", "arcade_mcp_server",
"--debug"
],
"cwd": "${workspaceFolder}"
}
```
## Snippets
Create useful code snippets in `.vscode/python.code-snippets`:
```json
{
"Arcade Tool": {
"prefix": "atool",
"body": [
"from arcade_tdk import tool",
"from typing import Annotated",
"",
"@tool",
"def ${1:tool_name}(",
" ${2:param}: Annotated[${3:str}, \"${4:Parameter description}\"]",
") -> Annotated[${5:str}, \"${6:Return description}\"]:",
" \"\"\"${7:Tool description}.\"\"\"",
" ${8:# Implementation}",
" return ${9:result}"
],
"description": "Create an Arcade tool"
},
"Async Tool": {
"prefix": "atoolasync",
"body": [
"from arcade_tdk import tool",
"from typing import Annotated",
"",
"@tool",
"async def ${1:tool_name}(",
" ${2:param}: Annotated[${3:str}, \"${4:Parameter description}\"]",
") -> Annotated[${5:str}, \"${6:Return description}\"]:",
" \"\"\"${7:Tool description}.\"\"\"",
" ${8:# Async implementation}",
" return ${9:result}"
],
"description": "Create an async Arcade tool"
}
}
```
## Integration Examples
### Multi-Server Setup
Run multiple MCP servers for different purposes:
```json
{
"version": "2.0.0",
"tasks": [
{
"label": "Start All Servers",
"dependsOn": [
"Start API Tools",
"Start Data Tools",
"Start Utility Tools"
],
"problemMatcher": []
},
{
"label": "Start API Tools",
"type": "shell",
"command": "uv run server.py",
"options": {
"cwd": "${workspaceFolder}/api_tools"
},
"isBackground": true
},
{
"label": "Start Data Tools",
"type": "shell",
"command": "uv run server.py",
"options": {
"cwd": "${workspaceFolder}/data_tools"
},
"isBackground": true
},
{
"label": "Start Utility Tools",
"type": "shell",
"command": "uv run server.py",
"options": {
"cwd": "${workspaceFolder}/util_tools"
},
"isBackground": true
}
]
}
```
### Environment Management
Handle multiple environments:
```json
{
"version": "2.0.0",
"tasks": [
{
"label": "MCP Server (Dev)",
"type": "shell",
"command": "uv run --env-file .env.dev server.py",
"problemMatcher": []
},
{
"label": "MCP Server (Staging)",
"type": "shell",
"command": "uv run --env-file .env.staging server.py",
"problemMatcher": []
},
{
"label": "MCP Server (Prod)",
"type": "shell",
"command": "uv run --env-file .env.prod server.py",
"problemMatcher": [],
"presentation": {
"reveal": "always",
"panel": "dedicated",
"showReuseMessage": true,
"clear": true
}
}
]
}
```
## Best Practices
1. **Use Virtual Environments**: Always work in isolated environments
2. **Version Control Settings**: Include `.vscode` in your repository
3. **Environment Files**: Use `.env` files for secrets
4. **Consistent Formatting**: Configure formatters and linters
5. **Test Automation**: Set up test tasks and debug configs
6. **Documentation**: Keep README and docstrings updated
7. **Git Hooks**: Use pre-commit for code quality
## Troubleshooting
### Common Issues
1. **Python interpreter not found**:
- Select interpreter: `Cmd/Ctrl + Shift + P` > "Python: Select Interpreter"
- Ensure virtual environment is activated
2. **Module import errors**:
- Check PYTHONPATH in settings
- Verify package installation
- Restart VSCode
3. **Debug breakpoints not working**:
- Ensure you're using the debug configuration
- Check that debugpy is installed
- Verify source maps are correct
4. **Task execution fails**:
- Check task definition syntax
- Verify working directory
- Review terminal output for errors

View file

@ -1,22 +0,0 @@
# 00 - Hello World
The simplest possible MCP server with a single tool using arcade-mcp-server.
## Running the Example
- **Run (HTTP default)**: `uv run 00_hello_world.py`
- **Run (stdio for Claude Desktop)**: `uv run 00_hello_world.py stdio`
## Source Code
```python
--8<-- "docs/examples/00_hello_world.py"
```
## Key Concepts
- **Minimal Setup**: Create `MCPApp`, define tools with `@app.tool`, and run with `app.run()`
- **Direct Execution**: Run your server file directly with `uv run` or `python`
- **Transport Flexibility**: Works with both stdio (for Claude Desktop) and HTTP
- **Type Annotations**: Use `Annotated` to provide descriptions for parameters and return values
- **Command Line Args**: Pass transport type as command line argument

View file

@ -1,38 +0,0 @@
#!/usr/bin/env python3
"""
00_hello_world.py - The simplest possible MCP server
This example shows the absolute minimum code needed to create an MCP server
with a single tool using arcade-mcp-server with direct Python execution.
To run:
uv run 00_hello_world.py # HTTP transport (default)
uv run 00_hello_world.py stdio # stdio transport for Claude Desktop
"""
import sys
from typing import Annotated
from arcade_mcp_server import MCPApp
# Create the MCP application
app = MCPApp(
name="hello_world", version="1.0.0", instructions="A simple MCP server with a greeting tool"
)
@app.tool
def greet(name: Annotated[str, "Name of the person to greet"]) -> Annotated[str, "Welcome message"]:
"""Greet a person by name with a welcome message."""
return f"Hello, {name}! Welcome to Arcade MCP."
if __name__ == "__main__":
# Check if stdio transport was requested
transport = "stdio" if len(sys.argv) > 1 and sys.argv[1] == "stdio" else "http"
print(f"Starting {app.name} v{app.version}")
print(f"Transport: {transport}")
# Run the server
app.run(transport=transport, host="127.0.0.1", port=8000)

View file

@ -1,131 +0,0 @@
# 01 - Tools
Learn how to create tools with different parameter types and how arcade_mcp_server discovers them automatically.
## Running the Example
- **Run (HTTP default)**: `uv run 01_tools.py`
- **Run (stdio for Claude Desktop)**: `uv run 01_tools.py stdio`
## Source Code
```python
--8<-- "docs/examples/01_tools.py"
```
## Creating Tools
### 1. Simple Tools
Basic tools with simple parameter types:
```python
@app.tool
def hello(name: Annotated[str, "Name to greet"]) -> str:
"""Say hello to someone."""
return f"Hello, {name}!"
@app.tool
def add(
a: Annotated[float, "First number"],
b: Annotated[float, "Second number"]
) -> Annotated[float, "Sum of the numbers"]:
"""Add two numbers together."""
return a + b
```
### 2. List Parameters
Working with lists of values:
```python
@app.tool
def calculate_average(
numbers: Annotated[list[float], "List of numbers to average"]
) -> Annotated[float, "Average of all numbers"]:
"""Calculate the average of a list of numbers."""
if not numbers:
return 0.0
return sum(numbers) / len(numbers)
```
### 3. Complex Types with TypedDict
Using TypedDict for structured input and output:
```python
class PersonInfo(TypedDict):
name: str
age: int
email: str
is_active: bool
@tool
def create_user_profile(
person: Annotated[PersonInfo, "Person's information"]
) -> Annotated[str, "Formatted user profile"]:
"""Create a formatted user profile from person information."""
# Implementation here
```
## Managing Tools in MCPApp
With the direct Python approach, you have full control over your tools:
### 1. Defining Tools Directily
Use `@app.tool` to define tools directly on your MCPApp instance:
```python
@app.tool
def my_tool(param: str) -> str:
"""Tool description."""
return f"Processed: {param}"
```
### 2. Importing Tools from Files
You can import tools from other files and add them explicitly:
```python
from my_tools import calculate, process_data
# Add imported tools to the app
app.add_tool(calculate)
app.add_tool(process_data)
```
### 3. Project Organization
Example project structure:
```
my_project/
├── server.py # Main MCPApp
├── tools/
│ ├── math.py # Tools using @tool decorator
│ └── utils.py # More tools
└── pyproject.toml # Dependencies
```
This approach gives you explicit control over which tools are loaded and how they're organized.
## Best Practices
### Parameter Annotations
- **Always use `Annotated`**: Provide descriptions for all parameters
- **Clear descriptions**: Help the AI understand what each parameter does
- **Type hints**: Use proper Python type hints for validation
### Tool Design
- **Single purpose**: Each tool should do one thing well
- **Error handling**: Add validation and helpful error messages
- **Return types**: Always annotate return types with descriptions
### Organization
- **Group related tools**: Use directories to organize by functionality
- **Naming conventions**: Use clear, descriptive names
- **Documentation**: Write clear docstrings for each tool
## Key Concepts
- **Explicit Control**: Use `@app.tool` decorators and `app.add_tool()` for precise tool management
- **Type Safety**: Full type annotation support with runtime validation
- **TypedDict Support**: Use TypedDict for complex structured data
- **Import Flexibility**: Import tools from your own files and external packages
- **Direct Execution**: Run servers directly with `uv run` for better development experience

View file

@ -1,130 +0,0 @@
#!/usr/bin/env python3
"""
01_tools.py - Tool creation and parameter types
This example demonstrates:
1. How to create tools with @app.tool decorator in MCPApp
2. Different parameter types (simple, lists, TypedDict)
3. Direct Python execution for better control
To run:
uv run 01_tools.py # HTTP transport (default)
uv run 01_tools.py stdio # stdio transport for Claude Desktop
"""
import sys
from typing import Annotated
from arcade_mcp_server import MCPApp
from typing_extensions import TypedDict
# Create the MCP application
app = MCPApp(
name="tools_example",
version="1.0.0",
instructions="Example server demonstrating various tool parameter types",
)
# === SIMPLE TOOLS ===
@app.tool
def hello(name: Annotated[str, "Name to greet"]) -> Annotated[str, "Greeting message"]:
"""Say hello to someone."""
return f"Hello, {name}!"
@app.tool
def add(
a: Annotated[float, "First number"], b: Annotated[float, "Second number"]
) -> Annotated[float, "Sum of the numbers"]:
"""Add two numbers together."""
return a + b
# === TOOLS WITH LIST PARAMETERS ===
@app.tool
def calculate_average(
numbers: Annotated[list[float], "List of numbers to average"],
) -> Annotated[float, "Average of all numbers"]:
"""Calculate the average of a list of numbers."""
if not numbers:
return 0.0
return sum(numbers) / len(numbers)
@app.tool
def factorial(n: Annotated[int, "Non-negative integer"]) -> Annotated[int, "Factorial of n"]:
"""Calculate the factorial of a number."""
if n < 0:
raise ValueError("Factorial not defined for negative numbers")
if n == 0:
return 1
result = 1
for i in range(1, n + 1):
result *= i
return result
# === TOOLS WITH COMPLEX TYPES (TypedDict) ===
class PersonInfo(TypedDict):
name: str
age: int
email: str
is_active: bool
@app.tool
def create_user_profile(
person: Annotated[PersonInfo, "Person's information"],
) -> Annotated[str, "Formatted user profile"]:
"""Create a formatted user profile from person information."""
status = "Active" if person["is_active"] else "Inactive"
return f"""
User Profile:
- Name: {person["name"]}
- Age: {person["age"]}
- Email: {person["email"]}
- Status: {status}
""".strip()
class CalculationResult(TypedDict):
sum: float
average: float
min: float
max: float
count: int
@app.tool
def analyze_numbers(
values: Annotated[list[float], "List of numbers to analyze"],
) -> Annotated[CalculationResult, "Statistical analysis of the numbers"]:
"""Analyze a list of numbers and return statistics."""
if not values:
return {"sum": 0.0, "average": 0.0, "min": 0.0, "max": 0.0, "count": 0}
return {
"sum": sum(values),
"average": sum(values) / len(values),
"min": min(values),
"max": max(values),
"count": len(values),
}
if __name__ == "__main__":
# Check if stdio transport was requested
transport = "stdio" if len(sys.argv) > 1 and sys.argv[1] == "stdio" else "http"
print(f"Starting {app.name} v{app.version}")
print(f"Transport: {transport}")
# Run the server
app.run(transport=transport, host="127.0.0.1", port=8000)

View file

@ -1,116 +0,0 @@
# 02 - Building Apps
Build and run an MCP server programmatically using the FastAPI-like `MCPApp` interface.
## Running the Example
- **Run HTTP**: `python examples/02_building_apps.py`
- **Run stdio**: `python examples/02_building_apps.py stdio`
## Source Code
```python
--8<-- "docs/examples/02_building_apps.py"
```
## MCPApp Features
### 1. Creating an App
```python
from arcade_mcp_server import MCPApp
app = MCPApp(
name="my_server",
version="1.0.0",
title="My MCP Server",
instructions="This server provides utility tools",
log_level="INFO"
)
```
### 2. Adding Tools
#### Method 1: Direct Tool Definition
Use the `@app.tool` decorator to define tools directly:
```python
@app.tool
def my_tool(param: Annotated[str, "Description"]) -> str:
"""Tool description."""
return f"Result: {param}"
```
#### Method 2: Importing Tools from Files
Import tools from other files and add them explicitly:
```python
from my_tools import calculate, process_data
# Add imported tools to the app
app.add_tool(calculate)
app.add_tool(process_data)
```
#### Method 3: Importing from Packages
Import tools from Arcade packages:
```python
from arcade_gmail.tools import list_emails
# Add package tools to the app
app.add_tool(list_emails)
```
This approach gives you explicit control over which tools are loaded and allows for modular organization.
**For a comprehensive example of tool organization, see [06_tool_organization.md](06_tool_organization.md).**
### 3. Running the Server
```python
# Default HTTP transport
app.run()
# Specify options
app.run(
host="0.0.0.0",
port=8080,
reload=True, # Auto-reload on code changes
transport="http"
)
# For stdio transport (Claude Desktop)
app.run(transport="stdio")
```
### 4. Using Context
Tools can access runtime context:
```python
@app.tool
async def context_aware(context: Context, value: str) -> dict:
"""Tool that uses context features."""
# Access user info
user_id = context.user_id
# Use MCP features if available
if context:
await context.log.info(f"Processing for user: {user_id}")
# Access secrets
secret_keys = list(context.secrets.keys())
return {
"user": user_id,
"value": value,
"available_secrets": secret_keys
}
```
## Key Concepts
- **FastAPI-like Interface**: Familiar decorator-based API design
- **Programmatic Control**: Build servers without CLI dependency
- **Transport Flexibility**: Support for both HTTP and stdio transports
- **Context Integration**: Access to user info, logging, and secrets
- **Development Features**: Hot reload, debug logging, and more

View file

@ -1,66 +0,0 @@
#!/usr/bin/env python
"""
02_building_apps.py - Build an MCP server using MCPApp
This example shows how to build and run an MCP server programmatically
using `MCPApp` instead of relying on the arcade_mcp_server CLI.
To run (HTTP transport by default):
python 02_building_apps.py
To run with stdio transport (for Claude Desktop):
python 02_building_apps.py stdio
"""
import sys
from typing import Annotated
from arcade_mcp_server import Context, MCPApp
# Create the MCP application
app = MCPApp(
name="my_mcp_server", version="0.1.0", instructions="Example MCP server built with MCPApp"
)
@app.tool
def greet(
name: Annotated[str, "Name of the person to greet"],
) -> Annotated[str, "Greeting message"]:
"""Return a friendly greeting.
Parameters:
name: Person's name
Returns:
Greeting message.
"""
return f"Hello, {name}!"
@app.tool
async def whoami(context: Context) -> Annotated[dict, "Basic server and user information"]:
"""Return basic information from the tool context.
Returns:
Dictionary with `user_id` and whether MCP features are available.
"""
user_id = context.user_id or "anonymous"
if context:
await context.log.info(f"whoami called by: {user_id}")
secret_keys = [secret.key for secret in context.secrets] if context.secrets else []
return {
"user_id": user_id,
"secret_keys": secret_keys,
}
if __name__ == "__main__":
# Check if stdio transport was requested
if len(sys.argv) > 1 and sys.argv[1] == "stdio":
app.run(transport="stdio")
else:
# Default to HTTP transport
app.run(host="127.0.0.1", port=8000)

View file

@ -1,64 +0,0 @@
# 03 - Tool Context
Access runtime features through Context including logging, secrets, and progress reporting.
## Running the Example
- **Run**: `uv run 03_context.py`
- **Run (stdio)**: `uv run 03_context.py stdio`
- **Env**: set `API_KEY`, `DATABASE_URL`
## Source Code
```python
--8<-- "docs/examples/03_context.py"
```
## Context Features
The Context provides access to runtime features:
### 1. Logging
Send log messages at different levels:
```python
await context.log.debug("Debug message")
await context.log.info("Information message")
await context.log.warning("Warning message")
await context.log.error("Error message")
```
### 2. Secrets Management
Access environment variables securely:
```python
try:
api_key = context.get_secret("API_KEY")
except ValueError:
# Handle missing secret
```
### 3. User Context
Access information about the current user:
```python
user_id = context.user_id or "anonymous"
```
### 4. Progress Reporting
Report progress for long-running operations:
```python
await context.progress.report(current, total, "Processing...")
```
### 5. Tool Decorator Options
Specify required secrets:
```python
@tool(requires_secrets=["DATABASE_URL", "API_KEY"])
async def my_tool(context: Context, ...):
```
## Key Concepts
- **Context Parameter**: Tools receive a `Context` as their first parameter
- **Async Functions**: Use `async def` for tools that use context features
- **Secure Secrets**: Secrets are accessed through context, not hardcoded
- **Structured Logging**: Log at appropriate levels for debugging
- **Progress Updates**: Keep users informed during long operations

View file

@ -1,155 +0,0 @@
#!/usr/bin/env python3
"""
03_context.py - Using Context with namespaced runtime APIs
This example shows how tools can access runtime features through
Context (provided at runtime by the TDK wrapper), including logging,
secrets, and progress reporting.
To run:
uv run 03_context.py # HTTP transport (default)
uv run 03_context.py stdio # stdio transport for Claude Desktop
Set environment variables for secrets:
export API_KEY="your-secret-key"
export DATABASE_URL="postgresql://localhost/mydb"
"""
import sys
from typing import Annotated, Any
from arcade_mcp_server import Context, MCPApp
# Create the MCP application
app = MCPApp(
name="context_example",
version="1.0.0",
instructions="Example server demonstrating Context usage",
)
@app.tool
async def secure_api_call(
context: Context,
endpoint: Annotated[str, "API endpoint to call"],
method: Annotated[str, "HTTP method (GET, POST, etc.)"] = "GET",
) -> Annotated[str, "API response or error message"]:
"""Make a secure API call using secrets from context."""
# Access secrets from environment via Context helper
try:
api_key = context.get_secret("API_KEY")
except ValueError:
await context.log.error("API_KEY not found in environment")
return "Error: API_KEY not configured"
# Log the API call
await context.log.info(f"Making {method} request to {endpoint}")
# Simulate API call (in real code, use httpx or aiohttp)
return f"Successfully called {endpoint} with API key: {api_key[:4]}..."
# Don't forget to add the secret to the .env file or export it as an environment variable
@app.tool(requires_secrets=["DATABASE_URL"])
async def database_info(
context: Context, table_name: Annotated[str | None, "Specific table to check"] = None
) -> Annotated[str, "Database connection info"]:
"""Get database connection information from context."""
# Get database URL from secrets
try:
db_url = context.get_secret("DATABASE_URL")
except ValueError:
db_url = "Not configured"
# Log at different levels
if db_url == "Not configured":
await context.log.warning("DATABASE_URL not set")
else:
await context.log.debug(f"Checking database: {db_url.split('@')[-1]}")
# Get user info
user_info = f"User: {context.user_id or 'anonymous'}"
if table_name:
return f"{user_info}\nDatabase: {db_url}\nChecking table: {table_name}"
else:
return f"{user_info}\nDatabase: {db_url}"
@app.tool
async def debug_context(
context: Context,
show_secrets: Annotated[bool, "Whether to show secret keys (not values)"] = False,
) -> Annotated[dict, "Current context information"]:
"""Debug tool to inspect the current context."""
info: dict[str, Any] = {
"user_id": context.user_id,
}
if show_secrets:
# Only show keys, not values for security
info["secret_keys"] = [s.key for s in (context.secrets or [])]
# Log that debug info was accessed
await context.log.info(f"Debug context accessed by {context.user_id or 'unknown'}")
return info
@app.tool
async def process_with_progress(
context: Context,
items: Annotated[list[str], "Items to process"],
delay_seconds: Annotated[float, "Delay between items"] = 0.1,
) -> Annotated[dict, "Processing results"]:
"""Process items with progress notifications."""
results: dict[str, list] = {"processed": [], "errors": []}
# Log start
await context.log.info(f"Starting to process {len(items)} items")
for i, item in enumerate(items):
try:
# Simulate processing
import asyncio
await asyncio.sleep(delay_seconds)
# Report progress (current, total, message)
await context.progress.report(i + 1, len(items), f"Processing: {item}")
await context.log.debug(f"Processing item {i + 1}/{len(items)}: {item}")
results["processed"].append(item.upper())
except Exception as e:
await context.log.error(f"Failed to process {item}: {e}")
results["errors"].append({"item": item, "error": str(e)})
# Log completion
await context.log.info(
f"Processing complete: {len(results['processed'])} succeeded, "
f"{len(results['errors'])} failed"
)
return results
# The Context provides at runtime (via TDK wrapper):
# - context.user_id: ID of the user making the request
# - context.get_secret(key): Retrieve a secret value (raises if missing)
# - context.log.<level>(msg): Send log messages to the client (debug/info/warning/error)
# - context.progress.report(progress, total=None, message=None): Progress updates
if __name__ == "__main__":
# Check if stdio transport was requested
transport = "stdio" if len(sys.argv) > 1 and sys.argv[1] == "stdio" else "http"
print(f"Starting {app.name} v{app.version}")
print(f"Transport: {transport}")
# Run the server
app.run(transport=transport, host="127.0.0.1", port=8000)

View file

@ -1,59 +0,0 @@
# 04 - Tool Secrets
Read secrets from environment and `.env` files securely via Context.
## Running the Example
- **Run**: `uv run 04_secrets.py`
- **Run (stdio)**: `uv run 04_secrets.py stdio`
- **Create `.env`**: Add `API_KEY=supersecret` to a `.env` file
## Source Code
```python
--8<-- "docs/examples/04_secrets.py"
```
## Working with Secrets
### 1. Environment Variables
Secrets can be provided via environment variables:
```bash
export API_KEY="your-secret-key"
export DATABASE_URL="postgresql://localhost/mydb"
```
### 2. Using .env Files
Create a `.env` file in the directoryof your server:
```
API_KEY=supersecret
DATABASE_URL=postgresql://user:pass@localhost/db
GITHUB_TOKEN=ghp_xxxxxxxxxxxx
```
### 3. Declaring Required Secrets
Use the `requires_secrets` parameter to declare which secrets your tool needs:
```python
@tool(requires_secrets=["API_KEY", "DATABASE_URL"])
def my_secure_tool(context: Context) -> str:
api_key = context.get_secret("API_KEY")
db_url = context.get_secret("DATABASE_URL")
```
### 4. Security Best Practices
- **Never log secret values**: Always mask or truncate when displaying
- **Declare requirements**: Use `requires_secrets` to document dependencies
- **Handle missing secrets**: Use try/except when accessing secrets
- **Use descriptive names**: Make it clear what each secret is for
## Key Concepts
- **Secure Access**: Secrets are accessed through context, not imported directly
- **Environment Integration**: Works with both environment variables and .env files
- **Error Handling**: Always handle the case where a secret might be missing
- **Masking**: Never expose full secret values in logs or return values
- **Declaration**: Use `requires_secrets` to make dependencies explicit

View file

@ -1,46 +0,0 @@
#!/usr/bin/env python3
"""04: Read secrets from .env via Context
Run:
uv run 04_secrets.py # HTTP transport (default)
uv run 04_secrets.py stdio # stdio transport for Claude Desktop
Environment:
# Create a .env in the working directory with:
# API_KEY=supersecret
"""
import sys
from arcade_mcp_server import Context, MCPApp
# Create the MCP application
app = MCPApp(
name="secrets_example",
version="1.0.0",
instructions="Example server demonstrating secrets usage",
)
@app.tool(
requires_secrets=["API_KEY"], # declare we need API_KEY
)
def use_secret(context: Context) -> str:
"""Read API_KEY from context and return a masked confirmation string."""
try:
value = context.get_secret("API_KEY")
masked = value[:2] + "***" if len(value) >= 2 else "***"
return f"Got API_KEY of length {len(value)} -> {masked}"
except Exception as e:
return f"Error getting secret: {e}"
if __name__ == "__main__":
# Check if stdio transport was requested
transport = "stdio" if len(sys.argv) > 1 and sys.argv[1] == "stdio" else "http"
print(f"Starting {app.name} v{app.version}")
print(f"Transport: {transport}")
# Run the server
app.run(transport=transport, host="127.0.0.1", port=8000)

View file

@ -1,101 +0,0 @@
# 05 - Logging
Demonstrates MCP logging capabilities with various levels and patterns for debugging and monitoring.
## Running the Example
- **Run**: `python examples/05_logging.py`
- Set `log_level="DEBUG"` in `MCPApp` to see debug logs
## Source Code
```python
--8<-- "docs/examples/05_logging.py"
```
## Logging Features
### 1. Log Levels
MCP supports standard log levels:
```python
await context.log.debug("Detailed debugging information")
await context.log.info("General information")
await context.log.warning("Warning messages")
await context.log.error("Error messages")
```
### 2. Structured Logging
Log with context and metadata:
```python
# Include user context
await context.log.info(
f"Action performed by user: {context.user_id}"
)
# Add operation details
await context.log.debug(
f"Processing {item_count} items with options: {options}"
)
```
### 3. Error Logging
Proper error handling and logging:
```python
try:
# Operation that might fail
result = risky_operation()
except Exception as e:
# Log error with type and message
await context.log.error(
f"Operation failed: {type(e).__name__}: {str(e)}"
)
# Log traceback at debug level
await context.log.debug(
f"Traceback:\n{traceback.format_exc()}"
)
```
### 4. Progress Logging
Track long-running operations:
```python
for i, item in enumerate(items):
# Log progress
await context.log.debug(
f"Progress: {i+1}/{len(items)} ({(i+1)/len(items)*100:.0f}%)"
)
# Process item
process(item)
```
### 5. Batch Processing
Log batch operations effectively:
```python
# Log batch start
await context.log.info(f"Starting batch of {count} items")
# Log individual items at debug level
for item in items:
await context.log.debug(f"Processing: {item}")
# Log summary
await context.log.info(
f"Batch complete: {success_count} successful, {fail_count} failed"
)
```
## Best Practices
1. **Use Appropriate Levels**: Debug for details, info for general flow, warning for issues, error for failures
2. **Include Context**: Always include relevant context like user ID, operation names, counts
3. **Structure Messages**: Use consistent message formats for easier parsing
4. **Handle Errors Gracefully**: Log errors with enough detail to debug but not expose sensitive data
5. **Progress Updates**: For long operations, provide regular progress updates
6. **Batch Summaries**: For batch operations, log both individual items (debug) and summaries (info)
7. **Performance Considerations**: Be mindful of log volume in production environments

View file

@ -1,190 +0,0 @@
#!/usr/bin/env python
"""
05_logging.py - MCP logging capabilities
This example demonstrates the various logging levels and patterns
available through the MCP protocol for debugging and monitoring.
To run:
python 05_logging.py
To see debug logs:
Set log_level="DEBUG" when creating MCPApp
"""
import asyncio
import time
import traceback
from typing import Annotated, Optional
from arcade_mcp_server import Context, MCPApp
# Create the app with debug logging
app = MCPApp(name="logging_examples", version="0.1.0", log_level="DEBUG")
@app.tool
async def demonstrate_log_levels(
context: Context, message: Annotated[str, "Base message to log at different levels"]
) -> Annotated[dict, "Summary of logged messages"]:
"""Demonstrate all MCP logging levels."""
# Log at each level
levels = ["debug", "info", "warning", "error"]
logged = {}
for level in levels:
log_message = f"[{level.upper()}] {message}"
await context.log(level, log_message)
logged[level] = log_message
return {"logged_messages": logged, "note": "Check your MCP client to see these messages"}
@app.tool
async def timed_operation(
context: Context,
operation_name: Annotated[str, "Name of the operation"],
duration_seconds: Annotated[float, "How long the operation takes"] = 2.0,
) -> Annotated[dict, "Operation timing details"]:
"""Perform a timed operation with detailed logging."""
start_time = time.time()
# Log operation start
await context.log.info(
f"Starting operation: {operation_name} (expected duration: {duration_seconds}s)"
)
# Simulate work with progress logging
steps = 5
for i in range(steps):
await context.log.debug(f"Progress: step {i + 1}/{steps} ({(i + 1) / steps * 100:.0f}%)")
await asyncio.sleep(duration_seconds / steps)
# Calculate results
end_time = time.time()
actual_duration = end_time - start_time
# Log completion
await context.log.info(f"Completed operation: {operation_name} in {actual_duration:.2f}s")
return {
"operation": operation_name,
"expected_duration": duration_seconds,
"actual_duration": round(actual_duration, 2),
"start_time": start_time,
"end_time": end_time,
}
@app.tool
async def error_handling_example(
context: Context,
should_fail: Annotated[bool, "Whether to simulate an error"],
error_type: Annotated[str, "Type of error to simulate"] = "ValueError",
) -> Annotated[dict, "Result or error details"]:
"""Demonstrate error logging and handling."""
try:
await context.log.debug(f"Error handling test: should_fail={should_fail}")
if should_fail:
if error_type == "ValueError":
raise ValueError("This is a simulated value error") # noqa: TRY301
elif error_type == "KeyError":
raise KeyError("missing_key") # noqa: TRY301
elif error_type == "ZeroDivisionError":
result = 1 / 0
return {"result": result}
else:
raise Exception(f"Generic error of type: {error_type}") # noqa: TRY002, TRY301
# Success case
await context.log.info("Operation completed successfully")
except Exception as e:
# Log the error with details
await context.log.error(f"Operation failed with {type(e).__name__}: {e!s}")
# Log traceback separately at debug level
await context.log.debug(f"Traceback:\n{traceback.format_exc()}")
return {
"status": "error",
"error_type": type(e).__name__,
"error_message": str(e),
"handled": True,
}
else:
return {"status": "success", "message": "No errors occurred"}
@app.tool
async def structured_logging(
context: Context,
user_action: Annotated[str, "Action the user is performing"],
metadata: Annotated[dict | None, "Additional metadata to log"] = None,
) -> Annotated[str, "Confirmation message"]:
"""Demonstrate structured logging patterns."""
# Log main action
await context.log.info(
f"User action: {user_action} (user_id: {context.user_id or 'anonymous'})"
)
# Log additional details at debug level
await context.log.debug(
f"Context details: {len(context.secrets) if context.secrets else 0} secrets available"
)
# Log metadata if provided
if metadata:
await context.log.debug(f"Custom metadata: {metadata}")
return f"Logged user action: {user_action}"
@app.tool
async def batch_processing_logs(
context: Context,
items: Annotated[list[str], "Items to process"],
fail_on_item: Annotated[Optional[str], "Item that should fail"] = None,
) -> Annotated[dict, "Processing results with detailed logs"]:
"""Process items with detailed logging for each step."""
results: dict[str, list] = {"successful": [], "failed": []}
await context.log.info(f"Starting batch processing of {len(items)} items")
for i, item in enumerate(items):
try:
# Log item start
await context.log.debug(f"Processing item {i + 1}/{len(items)}: {item}")
# Simulate failure if requested
if item == fail_on_item:
raise ValueError(f"Simulated failure for item: {item}") # noqa: TRY301
# Simulate processing
await asyncio.sleep(0.1)
results["successful"].append(item)
except Exception as e:
await context.log.warning(f"Failed to process '{item}': {e!s}")
results["failed"].append({"item": item, "error": str(e)})
# Log summary
await context.log.info(
f"Batch processing complete: {len(results['successful'])} successful, "
f"{len(results['failed'])} failed",
)
return results
if __name__ == "__main__":
# Run the server
app.run(host="127.0.0.1", port=8000)

View file

@ -1,191 +0,0 @@
# 06 - Tool Organization
This example demonstrates the power of direct Python server execution by showing how to organize tools across multiple files and packages.
## Running the Example
- **Run HTTP**: `uv run 06_tool_organization.py`
- **Run stdio**: `uv run 06_tool_organization.py stdio`
## Project Structure
The example demonstrates this recommended project structure:
```
my_server/
├── .env
├── server.py # Main MCPApp
├── tools/
│ ├── __init__.py
│ ├── math_tools.py # @tool decorated functions
│ └── text_tools.py # @tool decorated functions
├── pyproject.toml
└── README.md
```
## Source Code
```python
--8<-- "docs/examples/06_tool_organization.py"
```
## Key Concepts
### 1. Modular Tool Organization
Define tools in separate files using the `@tool` decorator:
```python
# tools/math_tools.py
from arcade_mcp_server import tool
from typing import Annotated
@tool
def add(a: Annotated[int, "First number"], b: Annotated[int, "Second number"]) -> int:
"""Add two numbers together."""
return a + b
```
### 2. Importing Tools from Files
Import tools from your local files and add them explicitly:
```python
# server.py
from tools_math import add, multiply
from tools_text import capitalize_string, word_count
app.add_tool(add)
app.add_tool(multiply)
app.add_tool(capitalize_string)
app.add_tool(word_count)
```
### 3. Importing Tools from Packages
You can also import tools from Arcade packages:
```python
# Import tools from other Arcade packages
from arcade_gmail.tools import list_emails
from arcade_google.tools import search_web
app.add_tool(list_emails)
app.add_tool(search_web)
```
### 4. Mixed Approaches
Combine imported tools with direct tool definitions:
```python
# Import tools from files
from tools_math import add
app.add_tool(add)
# Define tools directly
@app.tool
def server_info() -> dict:
"""Return information about this server."""
return {"name": "My Server", "version": "1.0.0"}
```
## Benefits of This Approach
### Explicit Control
- Choose exactly which tools to include
- No auto-discovery surprises
- Clear dependency management
### Standard Python Patterns
- Use normal Python imports
- Follow Python packaging conventions
- Leverage existing Python tools (uv, poetry, etc.)
### Flexible Organization
- Tools can be in separate files
- Tools can be in separate packages
- Easy to test individual tools
### Development Workflow
- Use `uv run server.py` for fast iteration
- Standard Python debugging tools work
- Easy to add CLI arguments for configuration
## Running Your Own Organized Server
### 1. Create Your Project Structure
```
my_server/
├── .env
├── server.py
├── tools/
│ ├── __init__.py
│ ├── email_tools.py
│ ├── file_tools.py
│ └── api_tools.py
└── pyproject.toml
```
### 2. Create Tool Files
```python
# tools/email_tools.py
from arcade_mcp_server import tool
@tool
def send_email(to: str, subject: str, body: str) -> dict:
"""Send an email."""
# Implementation here
return {"status": "sent", "to": to}
```
### 3. Build Your Server
```python
# server.py
import sys
from arcade_mcp_server import MCPApp
from tools.email_tools import send_email
from tools.file_tools import read_file, write_file
app = MCPApp(name="my_server", version="1.0.0")
# Add imported tools
app.add_tool(send_email)
app.add_tool(read_file)
app.add_tool(write_file)
# Add direct tools
@app.tool
def server_status() -> str:
return "Server is running"
if __name__ == "__main__":
transport = sys.argv[1] if len(sys.argv) > 1 else "http"
app.run(transport=transport)
```
### 4. Run Your Server
```bash
# Run with uv
uv run server.py
# Run with stdio for Claude Desktop
uv run server.py stdio
```
## Comparison with CLI Approach
| Feature | Direct Python | CLI Auto-discovery |
|---------|---------------|-------------------|
| Tool Selection | Explicit with `app.add_tool()` | Automatic discovery |
| File Organization | Your choice | Directory-based |
| Import Control | Full control | Limited |
| Deployment | Standard Python | Custom CLI needed |
| Testing | Standard Python tools | Mix Python + CLI |
| Debugging | Python debuggers work | Limited |
The direct Python approach gives you full control and follows standard Python patterns, making it ideal for production servers and complex tool organization.

View file

@ -1,94 +0,0 @@
#!/usr/bin/env python3
"""
06_tool_organization.py - Demonstrating modular tool organization
This example showcases the power of the direct Python approach by demonstrating:
- Tools defined in separate files and imported
- Tools imported from other Arcade packages
- Mixed approaches: @app.tool decorators + imported tools
- Explicit control over which tools are added to the server
Project Structure (recommended):
my_server/
.env
server.py # Main MCPApp
tools/
__init__.py
math_tools.py # @tool decorated functions
text_tools.py # @tool decorated functions
pyproject.toml
README.md
To run (HTTP transport by default):
uv run 06_tool_organization.py
To run with stdio transport (for Claude Desktop):
uv run 06_tool_organization.py stdio
"""
import sys
from typing import Annotated
from arcade_mcp_server import MCPApp
# Import tools from our 'mock' other_files module
# In a real project, these could come from actual separate files
from tools_math import add, multiply
from tools_text import capitalize_string, word_count
# In a real project, you could also import from Arcade PyPI packages:
# from arcade_gmail.tools import list_emails
# Create the MCP application
app = MCPApp(
name="organized_server",
version="1.0.0",
instructions="Example server demonstrating modular tool organization",
)
# Method 1: Add imported tools explicitly
app.add_tool(add)
app.add_tool(multiply)
app.add_tool(capitalize_string)
app.add_tool(word_count)
# Method 2: Define tools directly on the app
@app.tool
def server_info() -> Annotated[dict, "Information about this server"]:
"""Return information about this MCP server."""
return {
"name": "Organized Server",
"version": "1.0.0",
"description": "Demonstrates modular tool organization",
"total_tools": 6, # 4 imported + 2 defined here
}
@app.tool
def combine_results(
text: Annotated[str, "Text to process"],
add_num: Annotated[int, "Number to add"],
multiply_num: Annotated[int, "Number to multiply"],
) -> Annotated[dict, "Combined results from multiple tools"]:
"""Demonstrate using multiple tools together."""
return {
"original_text": text,
"capitalized": capitalize_string(text),
"word_count": word_count(text),
"math_result": multiply(add(5, add_num), multiply_num),
}
if __name__ == "__main__":
# Check if stdio transport was requested
transport = "stdio" if len(sys.argv) > 1 and sys.argv[1] == "stdio" else "http"
print(f"Starting {app.name} v{app.version}")
print(f"Transport: {transport}")
print("Setting up database...")
# simulate a database setup
print("Database setup complete")
# Run the server
app.run(transport=transport, host="127.0.0.1", port=8000)

View file

@ -1,123 +0,0 @@
# 07 - OAuth Tools
Learn how to create tools that require OAuth
## Prerequisites
Before running this example, you need to authenticate with Arcade:
```bash
# Install the Arcade CLI (if not already installed)
uv pip install arcade-mcp
# Login to Arcade
arcade login
```
## Running the Example
- **Run HTTP**: `uv run examples/07_auth.py`
- **Run stdio**: `uv run examples/07_auth.py stdio`
## Source Code
```python
--8<-- "docs/examples/07_auth.py"
```
## OAuth Authentication Features
### 1. Requiring Authentication
Use the `requires_auth` parameter with an auth provider to require OAuth:
```python
from arcade_mcp_server.auth import Reddit
@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."""
# OAuth token is automatically injected into context
oauth_token = context.get_auth_token_or_empty()
# Use the token to make authenticated API requests
```
### 2. Specifying OAuth Scopes
Different tools may require different scopes:
```python
# Read-only access
@app.tool(requires_auth=Reddit(scopes=["read"]))
async def read_only_tool(context: Context) -> dict:
"""Tool that only reads data."""
pass
# Multiple scopes for more permissions
@app.tool(requires_auth=Reddit(scopes=["read", "identity"]))
async def identity_tool(context: Context) -> dict:
"""Tool that accesses user identity."""
pass
```
### 3. Accessing OAuth Tokens
OAuth tokens are securely injected into the context at runtime:
```python
# Get the token (returns empty string if not authenticated)
oauth_token = context.get_auth_token_or_empty()
# Use token in API requests
headers = {
"Authorization": f"Bearer {oauth_token}",
"User-Agent": "my-app",
}
```
### 4. Making Authenticated API Requests
Use the OAuth token with httpx or other HTTP clients:
```python
import httpx
async with httpx.AsyncClient() as client:
response = await client.get(
"https://oauth.reddit.com/api/endpoint",
headers={"Authorization": f"Bearer {oauth_token}"}
)
response.raise_for_status()
return response.json()
```
## Available Auth Providers
The `arcade_mcp_server.auth` module provides several OAuth providers:
## Authentication Flow
1. **User runs `arcade login`**: Authenticates with Arcade and stores credentials
2. **Tool is called**: MCP client calls a tool that requires authentication
3. **Authorization Required**: If the user has not authorized the required scopes, then they are prompted to go through an OAuth flow
3. **Token injection**: Arcade injects the OAuth token into the tool's context
4. **API request**: Tool uses the token to make authenticated API requests
5. **Response**: Tool returns data to the MCP client
The LLM and MCP clients never see the OAuth tokens - they are securely injected server-side.
## Security Best Practices
1. **Never log tokens**: OAuth tokens should never be logged or exposed
2. **Use appropriate scopes**: Request only the scopes your tool actually needs
## Key Concepts
- **OAuth Integration**: Arcade handles OAuth flows and token management
- **Secure Token Injection**: Tokens are injected into context at runtime
- **Scope Management**: Specify exactly which permissions your tool needs
- **Provider Support**: Multiple OAuth providers available out of the box
- **User Privacy**: LLMs and MCP clients never see OAuth tokens

View file

@ -1,73 +0,0 @@
#!/usr/bin/env python3
"""
07_auth.py - Using tools with OAuth
This example demonstrates how to create and use tools that require OAuth.
Tools that require auth will automatically prompt users to authorize the action when called.
Prerequisites:
1. Install arcade-mcp: uv pip install arcade-mcp
2. Login to Arcade: arcade login
3. Run this server: uv run 07_auth.py
To run:
uv run examples/07_auth.py
uv run examples/07_auth.py stdio # For Claude Desktop
"""
import sys
from typing import Annotated
import httpx
from arcade_mcp_server import Context, MCPApp
from arcade_mcp_server.auth import Reddit
# Create the app
app = MCPApp(name="auth_example", version="1.0.0", log_level="DEBUG")
# To use this tool, you need to use the Arcade CLI (uv pip install arcade-mcp)
# and 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": "mcp_server-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__":
# Get transport from command line argument, default to "http"
transport = sys.argv[1] if len(sys.argv) > 1 else "http"
print(f"Starting auth example server with {transport} transport")
print("Prerequisites:")
print(" 1. Install: uv pip install arcade-mcp")
print(" 2. Login: arcade login")
print("")
# Run the server
# - "http" (default): HTTP streaming for Cursor, VS Code, etc.
# - "stdio": Standard I/O for Claude Desktop, CLI tools, etc.
app.run(transport=transport, host="127.0.0.1", port=8000)

View file

@ -1,84 +0,0 @@
# Arcade MCP Examples
This directory contains examples demonstrating how to build MCP servers with your Arcade tools.
## Getting Started
The easiest way to get started is with `arcade new`:
```bash
# Install the CLI
uv pip install arcade-mcp
# Create a new server project with example tools
arcade new my_server
cd my_server
# Run your server
uv run server.py
```
This creates a complete project with `server.py`, `pyproject.toml`, and example tools showing best practices.
## Examples Overview
### Basic Examples
1. **[00_hello_world.py](00_hello_world.py)** Minimal tool example
- Single `@tool` function showing the basics
- Run: `uv run 00_hello_world.py` (or `uv run 00_hello_world.py stdio`)
2. **[01_tools.py](01_tools.py)** Creating tools and discovery
- Simple parameters, lists, and `TypedDict`
- How the server discovers tools automatically
- Run: `uv run 01_tools.py`
3. **[02_building_apps.py](02_building_apps.py)** Building apps with MCPApp
- Create an `MCPApp`, register tools with `@app.tool`
- Run HTTP: `uv run 02_building_apps.py`
- Run stdio: `uv run 02_building_apps.py stdio`
4. **[03_context.py](03_context.py)** Using `Context`
- Access secrets, logging, and user context
- Run: `uv run 03_context.py`
5. **[04_tool_secrets.py](04_secrets.py)** Working with secrets
- Use `requires_secrets` and access masked values
- Run: `uv run 04_secrets.py`
6. **[05_logging.py](05_logging.py)** Logging with MCP
- Demonstrates debug/info/warning/error levels and structured logs
- Run: `uv run 05_logging.py`
7. **[06_tool_organization.py](06_tool_organization.py)** Tool organization and imports
- Demonstrate modular tool organization, importing from files and packages
- Run: `uv run 06_tool_organization.py`
8. **[07_auth.py](07_auth.py)** Tools that require auth
- Create tools that require OAuth scopes
- Use Reddit OAuth to fetch posts
- Prerequisites: Run `arcade login` to authenticate with Arcade
- Run: `uv run 07_auth.py`
## Running Examples
### Recommended: Direct Python Execution
Most examples can be run directly with Python using `uv`:
```bash
# Run any example file directly
uv run 00_hello_world.py
uv run 02_building_apps.py
uv run 06_tool_organization.py
# With specific transport
uv run server.py stdio # For Claude Desktop
uv run server.py http # HTTP by default
# You can also run with python directly
python 00_hello_world.py
python 02_building_apps.py stdio
```
All example files include proper command-line argument handling with `if __name__ == "__main__":` blocks.

View file

@ -1,31 +0,0 @@
#!/usr/bin/env python3
"""
tools_math.py - Mathematical tools
This file demonstrates how to organize tools in separate files.
All functions decorated with @tool will be discoverable and can be imported.
"""
from typing import Annotated
from arcade_mcp_server import tool
@tool
def add(a: Annotated[int, "First number"], b: Annotated[int, "Second number"]) -> int:
"""Add two numbers together."""
return a + b
@tool
def multiply(a: Annotated[int, "First number"], b: Annotated[int, "Second number"]) -> int:
"""Multiply two numbers together."""
return a * b
@tool
def divide(a: Annotated[float, "Dividend"], b: Annotated[float, "Divisor"]) -> float:
"""Divide two numbers."""
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b

View file

@ -1,29 +0,0 @@
#!/usr/bin/env python3
"""
tools_text.py - Text processing tools
This file demonstrates how to organize text processing tools in separate files.
All functions decorated with @tool will be discoverable and can be imported.
"""
from typing import Annotated
from arcade_mcp_server import tool
@tool
def capitalize_string(text: Annotated[str, "Text to capitalize"]) -> str:
"""Capitalize the first letter of a string."""
return text.capitalize()
@tool
def word_count(text: Annotated[str, "Text to count words in"]) -> int:
"""Count the number of words in a string."""
return len(text.split())
@tool
def reverse_string(text: Annotated[str, "Text to reverse"]) -> str:
"""Reverse a string."""
return text[::-1]

View file

@ -1,165 +0,0 @@
# Quick Start
The `arcade_mcp_server` package provides powerful ways to run MCP servers with your Arcade tools. **We recommend using `arcade new`** from the `arcade-mcp` CLI to create your server project with all necessary files and dependencies.
## Recommended: Create with arcade new
### 1. Install the CLI
```bash
uv pip install arcade-mcp
```
The `arcade-mcp` package includes the CLI tools and the `arcade-mcp-server` library.
### 2. Create Your Server
```bash
arcade new my_server
cd my_server
```
This generates a complete project with:
- **server.py** - Main server file with MCPApp and example tools
- **pyproject.toml** - Dependencies and project configuration
- **.env.example** - Example `.env` file containing a secret required by one of the generated tools in `server.py`
The generated `server.py` includes proper structure with command-line argument handling:
```python
#!/usr/bin/env python3
import sys
from typing import Annotated
from arcade_mcp_server import MCPApp
app = MCPApp(name="my_server", version="1.0.0")
@app.tool
def greet(name: Annotated[str, "Name to greet"]) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
if __name__ == "__main__":
transport = sys.argv[1] if len(sys.argv) > 1 else "http"
app.run(transport=transport, host="127.0.0.1", port=8000)
```
### 3. Run Your Server
```bash
# Run with uv (recommended)
uv run server.py
# Run with HTTP transport (default)
uv run server.py http
# Run with stdio transport (for Claude Desktop)
uv run server.py stdio
```
You should see output like:
```text
INFO | Starting server v1.0.0 (my_server)
INFO | Added tool: greet
INFO | Added tool: add_numbers
INFO | Starting MCP server on http://127.0.0.1:8000
```
For HTTP transport, view your server's API docs at [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs).
### 4. Configure MCP Clients
Connect your server to AI assistants:
```bash
# Configure Claude Desktop
arcade configure claude --from-local
# Configure Cursor IDE
arcade configure cursor --from-local
# Configure VS Code
arcade configure vscode --from-local
```
That's it! Your MCP server is running and connected to your AI assistant.
## Building MCP Servers
The simplest way to create an MCP server programmatically is using `MCPApp`, which provides a FastAPI-like interface:
```python
from arcade_mcp_server import MCPApp
from typing import Annotated
app = MCPApp(
name="my_serve_",
version="1.0.0",
instructions="Custom MCP server with specialized tools"
)
@app.tool
def calculate(
expression: Annotated[str, "Mathematical expression to evaluate"]
) -> Annotated[float, "The result of the calculation"]:
"""Safely evaluate a mathematical expression."""
# Safe evaluation logic here
return eval(expression, {"__builtins__": {}}, {})
@app.tool
def fetch_data(
url: Annotated[str, "URL to fetch data from"]
) -> Annotated[dict, "The fetched data"]:
"""Fetch data from an API endpoint."""
import requests
return requests.get(url).json()
# Run the server
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080, reload=True)
```
## Secrets
Define your tool secrets in an environment file `.env` in the same directory as your `MCPApp`, or export as environment variables
```bash
# Tool secrets (available to tools via context)
MY_API_KEY="secret-value"
DATABASE_URL="postgresql://..."
```
## Development Tips
### Hot Reload
Use the `reload=True` parameter for development to automatically restart on code changes:
```python
app.run(host="127.0.0.1", port=8000, reload=True)
```
### Logging
- Set `log_level="DEBUG"` in MCPApp for verbose logging
- In stdio mode, logs go to stderr
- In HTTP mode, logs go to stdout
### Docs for your MCP Server
With HTTP transport, access API documentation at:
- http://localhost:8000/docs (Swagger UI)
- http://localhost:8000/redoc (ReDoc)
## Next Steps
- Check out the [Examples](../examples/README.md) for detailed tutorials
- Learn about [Client Integration](../clients/claude.md) with Claude Desktop, Cursor, and VS Code
- Explore the [MCPApp API](../api/mcp_app.md) for advanced server customization
- Read about [Transport Modes](../advanced/transports.md) (stdio vs HTTP)

View file

@ -1,152 +0,0 @@
# Arcade MCP
<p align="center">
<img src="https://docs.arcade.dev/images/logo/arcade-logo.png" alt="Arcade Logo" width="200"/>
</p>
Arcade MCP (Model Context Protocol) enables AI assistants and development tools to interact with your Arcade tools through a standardized protocol. Build, deploy, and integrate your MCP servers seamlessly across different AI platforms.
## Quick Links
- **[Quickstart Guide](getting-started/quickstart.md)** - Get up and running in minutes
- **[Walkthrough](examples/README.md)** - Learn by example
- **[API Reference](api/mcp_app.md)** - MCPApp API documentation
## Features
- 🚀 **FastAPI-like Interface** - Simple, intuitive API with `MCPApp`
- 🔧 **Tool Discovery** - Automatic discovery of tools in your project
- 🔌 **Multiple Transports** - Support for stdio and HTTP/SSE
- 🤖 **Multi-Client Support** - Works with Claude, Cursor, VS Code, and more
- 📦 **Package Integration** - Load installed Arcade packages
- 🔐 **Built-in Security** - Environment-based configuration and secrets
- 🔄 **Hot Reload** - Development mode with automatic reloading
- 📊 **Production Ready** - Deploy with Docker, systemd, PM2, or cloud platforms
## Getting Started
### Installation
We recommend installing the `arcade-mcp-server` library for direct Python development:
```bash
uv pip install arcade-mcp-server
```
Or install the `arcade-mcp` CLI package for additional tooling and streamlined development workflow:
```bash
uv pip install arcade-mcp
```
### Quick Start: Create a New Server (Recommended)
The fastest way to get started is with the `arcade new` command, which creates a complete MCP server project:
```bash
# Install the CLI
uv pip install arcade-mcp
# Create a new server project
arcade new my_server
# Navigate to the project
cd my_server
```
This generates a complete project with:
- **server.py** - Main server file with MCPApp and example tools
- **pyproject.toml** - Dependencies and project configuration
- **.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:
```python
#!/usr/bin/env python3
import sys
from typing import Annotated
from arcade_mcp_server import MCPApp
app = MCPApp(name="my_server", version="1.0.0")
@app.tool
def greet(name: Annotated[str, "Name to greet"]) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
if __name__ == "__main__":
transport = sys.argv[1] if len(sys.argv) > 1 else "http"
app.run(transport=transport, host="127.0.0.1", port=8000)
```
This approach gives you:
- **Complete Project Setup** - Everything you need in one command
- **Best Practices** - Proper dependency management with pyproject.toml
- **Example Code** - Learn from working examples of common patterns
- **Production Ready** - Structured for growth and deployment
### Running Your Server
Run your server directly with Python:
```bash
# Run with HTTP transport (default)
uv run server.py
# Run with stdio transport (for Claude Desktop)
uv run server.py stdio
# Or use python directly
python server.py http
python server.py stdio
```
Your server will start and listen for connections. With HTTP transport, you can access the API docs at http://127.0.0.1:8000/docs.
### Configure MCP Clients
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
```
## Client Integration
Connect your MCP server with AI assistants and development tools:
- **[Claude Desktop](clients/claude.md)** - Native MCP support in Claude
- **[Cursor IDE](clients/cursor.md)** - Enhanced AI coding with MCP tools
- **[VS Code](clients/vscode.md)** - Integrate with Visual Studio Code
- **[MCP Inspector](clients/inspector.md)** - Debug and test your tools
## Learn More
- **[Walkthrough](examples/README.md)** - Comprehensive examples and tutorials
- **[API Reference](api/mcp_app.md)** - Detailed API documentation
- **[Transport Modes](advanced/transports.md)** - stdio and HTTP transport details
## Community
- [GitHub Repository](https://github.com/ArcadeAI/arcade-mcp)
- [Discord Community](https://discord.com/invite/GUZEMpEZ9p)
- [Documentation](https://docs.arcade.dev)
## License
Arcade MCP server is open source software licensed under the MIT license.

View file

@ -1,103 +0,0 @@
site_name: Arcade MCP
site_description: MCP (Model Context Protocol) server framework from Arcade.
site_url: https://python.mcp.arcade.dev/
repo_url: https://github.com/ArcadeAI/arcade-mcp
repo_name: ArcadeAI/arcade-mcp
theme:
palette:
- media: "(prefers-color-scheme)"
toggle:
icon: material/brightness-auto
name: Switch to light mode
- media: "(prefers-color-scheme: light)"
scheme: default
toggle:
icon: material/brightness-7
name: Switch to dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
toggle:
icon: material/brightness-4
name: Switch to system preference
name: material
logo: https://docs.arcade.dev/images/logo/arcade-logo.png
favicon: https://docs.arcade.dev/apple-touch-icon.png
features:
- navigation.instant
- navigation.tracking
- navigation.expand
- navigation.indexes
- content.code.copy
- content.code.annotate
markdown_extensions:
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.superfences
- admonition
- pymdownx.details
- pymdownx.tabbed:
alternate_style: true
- tables
- footnotes
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
plugins:
- search
- mkdocstrings:
handlers:
python:
paths: [../arcade_mcp_server]
options:
show_source: false
show_root_heading: true
heading_level: 3
exclude_docs: |
/README.md
nav:
- Home: index.md
- Getting Started:
- getting-started/quickstart.md
- Walkthrough:
- examples/README.md
- examples/00_hello_world.md
- examples/01_tools.md
- examples/02_building_apps.md
- examples/03_context.md
- examples/04_secrets.md
- examples/05_logging.md
- examples/06_tool_organization.md
- examples/07_auth.md
- Clients:
- clients/claude.md
- clients/cursor.md
- clients/vscode.md
- clients/inspector.md
- API Reference:
- api/mcp_app.md
- Server:
- api/server/server.md
- api/server/middleware.md
- api/server/types.md
- api/server/errors.md
- api/server/settings.md
- Advanced:
- advanced/sharing-servers.md
- advanced/transports.md
extra:
social:
- icon: fontawesome/brands/github
link: https://github.com/ArcadeAI/arcade-mcp
- icon: fontawesome/brands/python
link: https://pypi.org/project/arcade-mcp/
analytics:
provider: google
property: G-MG2E60KKVX

View file

@ -41,9 +41,6 @@ dev = [
"pytest-asyncio>=0.23.0",
"mypy>=1.0.0",
"ruff>=0.1.0",
"mkdocs>=1.6.0",
"mkdocs-material>=9.6.0",
"mkdocstrings[python]>=0.28.0",
]
[tool.hatch.build.targets.wheel]