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:
parent
64fb783cdd
commit
bee349287f
37 changed files with 0 additions and 3714 deletions
22
.github/workflows/test-docs.yml
vendored
22
.github/workflows/test-docs.yml
vendored
|
|
@ -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
|
|
||||||
|
|
@ -25,16 +25,3 @@ mypy: ## Run mypy
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: ## Run tests
|
test: ## Run tests
|
||||||
uv run pytest --cov=arcade_mcp_server --cov-report=term-missing ../tests/arcade_mcp_server
|
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
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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...")
|
|
||||||
```
|
|
||||||
|
|
@ -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
|
|
||||||
```
|
|
||||||
|
|
@ -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}")
|
|
||||||
```
|
|
||||||
|
|
@ -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])
|
|
||||||
```
|
|
||||||
|
|
@ -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())
|
|
||||||
```
|
|
||||||
|
|
@ -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()
|
|
||||||
```
|
|
||||||
|
|
@ -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
|
|
||||||
)
|
|
||||||
```
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -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"
|
|
||||||
```
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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]
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -41,9 +41,6 @@ dev = [
|
||||||
"pytest-asyncio>=0.23.0",
|
"pytest-asyncio>=0.23.0",
|
||||||
"mypy>=1.0.0",
|
"mypy>=1.0.0",
|
||||||
"ruff>=0.1.0",
|
"ruff>=0.1.0",
|
||||||
"mkdocs>=1.6.0",
|
|
||||||
"mkdocs-material>=9.6.0",
|
|
||||||
"mkdocstrings[python]>=0.28.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.hatch.build.targets.wheel]
|
[tool.hatch.build.targets.wheel]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue