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
|
||||
test: ## Run tests
|
||||
uv run pytest --cov=arcade_mcp_server --cov-report=term-missing ../tests/arcade_mcp_server
|
||||
|
||||
.PHONY: docs
|
||||
docs: ## Build docs
|
||||
uv run mkdocs build
|
||||
uv run mkdocs serve
|
||||
|
||||
.PHONY: docs-serve
|
||||
docs-serve: ## Serve docs locally
|
||||
uv run mkdocs serve
|
||||
|
||||
.PHONY: docs-build
|
||||
docs-build: ## Build docs
|
||||
uv run mkdocs build
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
"mypy>=1.0.0",
|
||||
"ruff>=0.1.0",
|
||||
"mkdocs>=1.6.0",
|
||||
"mkdocs-material>=9.6.0",
|
||||
"mkdocstrings[python]>=0.28.0",
|
||||
]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
|
|
|
|||
Loading…
Reference in a new issue