diff --git a/libs/arcade-mcp-server/arcade_mcp_server/mcp_app.py b/libs/arcade-mcp-server/arcade_mcp_server/mcp_app.py index 081dace1..4d902172 100644 --- a/libs/arcade-mcp-server/arcade_mcp_server/mcp_app.py +++ b/libs/arcade-mcp-server/arcade_mcp_server/mcp_app.py @@ -226,7 +226,6 @@ class MCPApp: asyncio.run( run_stdio_server( catalog=self._catalog, - host=host, port=port, reload=reload, **self.server_kwargs, diff --git a/libs/arcade-mcp-server/docs/advanced/sharing-servers.md b/libs/arcade-mcp-server/docs/advanced/sharing-servers.md new file mode 100644 index 00000000..d5bbb5d2 --- /dev/null +++ b/libs/arcade-mcp-server/docs/advanced/sharing-servers.md @@ -0,0 +1,180 @@ +# 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 diff --git a/libs/arcade-mcp-server/docs/advanced/transports.md b/libs/arcade-mcp-server/docs/advanced/transports.md index 3446db34..f097bcf3 100644 --- a/libs/arcade-mcp-server/docs/advanced/transports.md +++ b/libs/arcade-mcp-server/docs/advanced/transports.md @@ -18,16 +18,16 @@ The stdio (standard input/output) transport is used for direct client connection ```bash # Run with stdio transport -arcade mcp stdio +uv run server.py stdio ``` **Alternative: Direct Python** ```bash -# Using the module directly -python -m arcade_mcp_server stdio +# Run your server directly +uv run server.py stdio -# Or with MCPApp +# Or with python app.run(transport="stdio") ``` @@ -70,19 +70,17 @@ The HTTP transport provides REST/SSE endpoints for web-based clients. ```bash # Run with HTTP transport (default) -arcade mcp - -# With specific host and port -arcade mcp --host 0.0.0.0 --port 8080 +uv run server.py +uv run server.py http ``` **Alternative: Direct Python** ```bash -# Using the module directly -python -m arcade_mcp_server +# Run your server directly +uv run server.py -# Or with MCPApp +# Or with python app.run(transport="http", host="0.0.0.0", port=8080) ``` @@ -99,9 +97,9 @@ When running in HTTP mode, the server provides: **With Arcade CLI:** -```bash +```python # Enable hot reload and debug mode -arcade mcp --reload --debug +app.run(host="127.0.0.1", port=8000, reload=True) # This enables: # - Automatic restart on code changes diff --git a/libs/arcade-mcp-server/docs/api/cli.md b/libs/arcade-mcp-server/docs/api/cli.md deleted file mode 100644 index 35fa8360..00000000 --- a/libs/arcade-mcp-server/docs/api/cli.md +++ /dev/null @@ -1,137 +0,0 @@ -# arcade mcp Command - -The `arcade mcp` command is the recommended way to run MCP servers. It automatically discovers tools in your project, creates a server, and runs it with your chosen transport. - -## Installation - -```bash -uv pip install arcade-mcp -``` - -The `arcade-mcp` package includes the CLI and the `arcade-mcp-server` library. - -## Command Line Options - -``` -usage: arcade mcp [-h] [--host HOST] [--port PORT] - [--tool-package PACKAGE] [--discover-installed] - [--show-packages] [--reload] [--debug] - [--env-file ENV_FILE] [--name NAME] [--version VERSION] - [--cwd CWD] - [transport] - -Run Arcade MCP Server - -positional arguments: - transport Transport type: stdio, http (default: http) - -optional arguments: - -h, --help show this help message and exit - --host HOST Host to bind to (HTTP mode only, default: 127.0.0.1) - --port PORT Port to bind to (HTTP mode only, default: 8000) - --tool-package PACKAGE, --package PACKAGE, -p PACKAGE - Specific tool package to load (e.g., 'github' for arcade-github) - --discover-installed, --all - Discover all installed arcade tool packages - --show-packages Show loaded packages during discovery - --reload Enable auto-reload on code changes (HTTP mode only) - --debug Enable debug mode with verbose logging - --env-file ENV_FILE Path to environment file - --name NAME Server name - --version VERSION Server version - --cwd CWD Working directory to run from -``` - -## Basic Usage - -```bash -# Run HTTP server (default) -arcade mcp - -# Run stdio server (for Claude Desktop, Cursor, etc.) -arcade mcp stdio - -# Run with debug logging -arcade mcp --debug - -# Run with hot reload (development mode) -arcade mcp --reload --debug -``` - -## Tool Discovery - -The CLI discovers tools in three ways: - -### 1. Auto-Discovery (Default) - -Automatically finds Python files with `@tool` decorated functions in: -- Current directory (`*.py`) -- `tools/` subdirectory -- `arcade_tools/` subdirectory - -Example file structure: -``` -my_project/ -├── hello.py # Contains @tool functions -├── tools/ -│ └── math.py # More @tool functions -└── arcade_tools/ - └── utils.py # Even more @tool functions -``` - -### 2. Package Loading - -Load specific arcade packages installed in your environment: - -```bash -# Load arcade-github package -arcade mcp --tool-package github - -# Load custom package (tries arcade_ prefix first) -arcade mcp -p mycompany_tools -``` - -### 3. Discover All Installed - -Find and load all arcade packages in your Python environment: - -```bash -# Load all arcade packages -arcade mcp --discover-installed - -# Show what's being loaded -arcade mcp --discover-installed --show-packages -``` - -### Example Tool File - -Create any Python file with `@tool` decorated functions: - -```python -from arcade_mcp_server import tool - -@tool -def hello(name: str) -> str: - """Say hello to someone.""" - return f"Hello, {name}!" - -@tool -def add(a: int, b: int) -> int: - """Add two numbers.""" - return a + b -``` - -Then run: -```bash -arcade mcp # Auto-discovers and loads these tools -``` - -## Alternative: Direct Python Usage - -While we recommend using `arcade mcp`, you can also run the server module directly: - -```bash -python -m arcade_mcp_server [options] -``` - -This provides the same functionality but without the benefits of the Arcade CLI ecosystem (like `arcade configure` for client setup). diff --git a/libs/arcade-mcp-server/docs/clients/cursor.md b/libs/arcade-mcp-server/docs/clients/cursor.md index a89058ec..f9ada09d 100644 Binary files a/libs/arcade-mcp-server/docs/clients/cursor.md and b/libs/arcade-mcp-server/docs/clients/cursor.md differ diff --git a/libs/arcade-mcp-server/docs/clients/inspector.md b/libs/arcade-mcp-server/docs/clients/inspector.md index d6bbf61b..ce000540 100644 --- a/libs/arcade-mcp-server/docs/clients/inspector.md +++ b/libs/arcade-mcp-server/docs/clients/inspector.md @@ -24,7 +24,7 @@ For MCP servers running over HTTP: ```bash # Start your MCP server -python -m arcade_mcp_server --host 0.0.0.0 --port 8000 +uv run server.py # In another terminal, start the inspector mcp-inspector http://localhost:8000/mcp @@ -36,10 +36,10 @@ For stdio-based servers: ```bash # Start the inspector with your server command -mcp-inspector "python -m arcade_mcp_server stdio" +mcp-inspector "uv run server.py stdio" -# With additional arguments -mcp-inspector "python -m arcade_mcp_server stdio --tool-package github" +# With additional project directory +mcp-inspector --cwd /path/to/project "uv run server.py stdio" ``` ## Inspector Features @@ -95,10 +95,10 @@ Pass environment variables to your server: ```bash # Using env command -env ARCADE_API_KEY=your-key mcp-inspector "python -m arcade_mcp_server stdio" +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 "python -m arcade_mcp_server stdio" +mcp-inspector --env ARCADE_API_KEY=your-key "uv run server.py stdio" ``` ### Working Directory @@ -106,7 +106,7 @@ mcp-inspector --env ARCADE_API_KEY=your-key "python -m arcade_mcp_server stdio" Set the working directory for your server: ```bash -mcp-inspector --cwd /path/to/project "python -m arcade_mcp_server stdio" +mcp-inspector --cwd /path/to/project "uv run server.py stdio" ``` ### Debug Mode @@ -114,20 +114,28 @@ mcp-inspector --cwd /path/to/project "python -m arcade_mcp_server stdio" Enable verbose logging: ```bash -# Debug the MCP server -mcp-inspector "python -m arcade_mcp_server stdio --debug" +# Debug the inspector +mcp-inspector --debug "uv run server.py stdio" -# Debug the inspector itself -mcp-inspector --debug "python -m arcade_mcp_server 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. **Start your server with hot reload**: +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 - python -m arcade_mcp_server --reload --debug + uv run server.py ``` 2. **Connect the inspector**: @@ -213,8 +221,17 @@ def test_tool_via_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 - python -m arcade_mcp_server --reload --debug --env-file .env.dev + uv run server.py ``` 3. **Save Test Cases**: @@ -226,7 +243,7 @@ def test_tool_via_inspector(): 1. **Test Against Production Config**: ```bash - mcp-inspector "python -m arcade_mcp_server stdio --env-file .env.prod" + mcp-inspector "uv run server.py stdio" ``` 2. **Verify Security**: @@ -298,7 +315,7 @@ def add( EOF # 2. Start inspector -mcp-inspector "python -m arcade_mcp_server stdio" +mcp-inspector "uv run server.py stdio" # 3. Test tools in the web interface ``` @@ -338,17 +355,9 @@ export DEBUG=* export MCP_DEBUG=true # 2. Start server with verbose logging -python -m arcade_mcp_server stdio --debug 2>server.log +# (configure log_level="DEBUG" in your server.py) +uv run server.py stdio 2>server.log # 3. Start inspector with debugging -mcp-inspector --debug "tail -f server.log" & -mcp-inspector --debug "python -m arcade_mcp_server stdio --debug" +mcp-inspector --debug "uv run server.py stdio" ``` - -## Tips and Tricks - -1. **Bookmark Tool URLs**: Save frequently tested tools -2. **Export Test Data**: Save successful requests for documentation -3. **Use Browser DevTools**: Inspect network requests -4. **Create Tool Shortcuts**: Bookmark specific tool tests -5. **Monitor Resources**: Keep an eye on server resources during testing diff --git a/libs/arcade-mcp-server/docs/clients/vscode.md b/libs/arcade-mcp-server/docs/clients/vscode.md index ecc814ab..a05ccd7d 100644 --- a/libs/arcade-mcp-server/docs/clients/vscode.md +++ b/libs/arcade-mcp-server/docs/clients/vscode.md @@ -18,7 +18,7 @@ Use VSCode's integrated terminal to run MCP servers: 1. Open integrated terminal (`Ctrl/Cmd + ` `) 2. Start your MCP server: ```bash - python -m arcade_mcp_server --reload --debug + uv run server.py ``` 3. Use split terminals for multiple servers @@ -387,7 +387,7 @@ Run multiple MCP servers for different purposes: { "label": "Start API Tools", "type": "shell", - "command": "python -m arcade_mcp_server --port 8001", + "command": "uv run server.py", "options": { "cwd": "${workspaceFolder}/api_tools" }, @@ -396,7 +396,7 @@ Run multiple MCP servers for different purposes: { "label": "Start Data Tools", "type": "shell", - "command": "python -m arcade_mcp_server --port 8002", + "command": "uv run server.py", "options": { "cwd": "${workspaceFolder}/data_tools" }, @@ -405,7 +405,7 @@ Run multiple MCP servers for different purposes: { "label": "Start Utility Tools", "type": "shell", - "command": "python -m arcade_mcp_server --port 8003", + "command": "uv run server.py", "options": { "cwd": "${workspaceFolder}/util_tools" }, @@ -426,19 +426,19 @@ Handle multiple environments: { "label": "MCP Server (Dev)", "type": "shell", - "command": "python -m arcade_mcp_server --env-file .env.dev", + "command": "uv run --env-file .env.dev server.py", "problemMatcher": [] }, { "label": "MCP Server (Staging)", "type": "shell", - "command": "python -m arcade_mcp_server --env-file .env.staging", + "command": "uv run --env-file .env.staging server.py", "problemMatcher": [] }, { "label": "MCP Server (Prod)", "type": "shell", - "command": "python -m arcade_mcp_server --env-file .env.prod", + "command": "uv run --env-file .env.prod server.py", "problemMatcher": [], "presentation": { "reveal": "always", diff --git a/libs/arcade-mcp-server/docs/examples/00_hello_world.md b/libs/arcade-mcp-server/docs/examples/00_hello_world.md index 931cf91d..e432dc64 100644 --- a/libs/arcade-mcp-server/docs/examples/00_hello_world.md +++ b/libs/arcade-mcp-server/docs/examples/00_hello_world.md @@ -4,8 +4,8 @@ The simplest possible MCP server with a single tool using arcade-mcp-server. ## Running the Example -- **Run (HTTP default)**: `python -m arcade_mcp_server` -- **Run (stdio for Claude Desktop)**: `python -m arcade_mcp_server stdio` +- **Run (HTTP default)**: `uv run 00_hello_world.py` +- **Run (stdio for Claude Desktop)**: `uv run 00_hello_world.py stdio` ## Source Code @@ -15,7 +15,8 @@ The simplest possible MCP server with a single tool using arcade-mcp-server. ## Key Concepts -- **Minimal Setup**: Just import `@tool` decorator and annotate your function -- **Auto-Discovery**: The CLI automatically finds tools in your current directory +- **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 diff --git a/libs/arcade-mcp-server/docs/examples/00_hello_world.py b/libs/arcade-mcp-server/docs/examples/00_hello_world.py index 060f6e79..64b0a59e 100644 --- a/libs/arcade-mcp-server/docs/examples/00_hello_world.py +++ b/libs/arcade-mcp-server/docs/examples/00_hello_world.py @@ -1,37 +1,38 @@ +#!/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 a single tool using arcade-mcp-server with direct Python execution. -To run (auto-discovery): -1. Keep this file in the current directory -2. Run: python -m arcade_mcp_server - -For Claude Desktop (stdio transport): - python -m arcade_mcp_server stdio +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 tool +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" +) -@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." -# That's it! The arcade_mcp_server CLI will handle everything else: -# - Creating the MCP server -# - Setting up the transport (stdio or HTTP) -# - Registering your tool -# - Handling all the protocol communication +if __name__ == "__main__": + # Check if stdio transport was requested + transport = "stdio" if len(sys.argv) > 1 and sys.argv[1] == "stdio" else "http" -# When you run `python -m arcade_mcp_server`, it will: -# 1. Discover this file (if in current directory) -# 2. Find the @tool decorated function -# 3. Create an MCP server with this tool -# 4. Start listening for requests + 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) diff --git a/libs/arcade-mcp-server/docs/examples/01_tools.md b/libs/arcade-mcp-server/docs/examples/01_tools.md index b950f67d..fabf73e6 100644 --- a/libs/arcade-mcp-server/docs/examples/01_tools.md +++ b/libs/arcade-mcp-server/docs/examples/01_tools.md @@ -4,11 +4,8 @@ Learn how to create tools with different parameter types and how arcade_mcp_serv ## Running the Example -- **Run**: `python -m arcade_mcp_server` -- **Run (stdio)**: `python -m arcade_mcp_server stdio` -- **Show loaded packages**: `python -m arcade_mcp_server --show-packages` -- **Load specific package**: `python -m arcade_mcp_server --tool-package github` -- **Discover all installed**: `python -m arcade_mcp_server --discover-installed` +- **Run (HTTP default)**: `uv run 01_tools.py` +- **Run (stdio for Claude Desktop)**: `uv run 01_tools.py stdio` ## Source Code @@ -23,12 +20,12 @@ Learn how to create tools with different parameter types and how arcade_mcp_serv Basic tools with simple parameter types: ```python -@tool +@app.tool def hello(name: Annotated[str, "Name to greet"]) -> str: """Say hello to someone.""" return f"Hello, {name}!" -@tool +@app.tool def add( a: Annotated[float, "First number"], b: Annotated[float, "Second number"] @@ -42,7 +39,7 @@ def add( Working with lists of values: ```python -@tool +@app.tool def calculate_average( numbers: Annotated[list[float], "List of numbers to average"] ) -> Annotated[float, "Average of all numbers"]: @@ -71,40 +68,43 @@ def create_user_profile( # Implementation here ``` -## Tool Discovery +## Managing Tools in MCPApp -The arcade_mcp_server CLI discovers tools in multiple ways: +With the direct Python approach, you have full control over your tools: -### 1. Current Directory -- Scans all `*.py` files in the current directory -- Imports and checks for `@tool` decorated functions - -### 2. Standard Directories -- `tools/` directory - Common convention for organizing tools -- `arcade_tools/` directory - Alternative naming convention -- Both are recursively scanned for Python files - -### 3. Package Loading -```bash -# Load a specific package -python -m arcade_mcp_server --tool-package github - -# Discover all installed arcade packages -python -m arcade_mcp_server --discover-installed +### 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}" ``` -### 4. File Organization +### 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/ -├── hello.py # Contains @tool functions +├── server.py # Main MCPApp ├── tools/ -│ └── math.py # More @tool functions -└── arcade_tools/ - └── utils.py # Even more @tool functions +│ ├── 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 @@ -124,8 +124,8 @@ my_project/ ## Key Concepts -- **Auto-Discovery**: Automatically finds tools without explicit registration +- **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 -- **Flexible Organization**: Structure your tools however makes sense for your project -- **Multiple Sources**: Discover from files, directories, and packages +- **Import Flexibility**: Import tools from your own files and external packages +- **Direct Execution**: Run servers directly with `uv run` for better development experience diff --git a/libs/arcade-mcp-server/docs/examples/01_tools.py b/libs/arcade-mcp-server/docs/examples/01_tools.py index f185cbd6..acafa25d 100644 --- a/libs/arcade-mcp-server/docs/examples/01_tools.py +++ b/libs/arcade-mcp-server/docs/examples/01_tools.py @@ -1,67 +1,40 @@ -#!/usr/bin/env python -from typing import Annotated - -from arcade_mcp_server import tool -from typing_extensions import TypedDict - +#!/usr/bin/env python3 """ -01_tools.py - Tool creation, discovery, and parameter types +01_tools.py - Tool creation and parameter types This example demonstrates: -1. How to create tools with the @tool decorator +1. How to create tools with @app.tool decorator in MCPApp 2. Different parameter types (simple, lists, TypedDict) -3. How arcade_mcp_server discovers tools automatically +3. Direct Python execution for better control To run: - python -m arcade_mcp_server # Auto-discover all tools - python -m arcade_mcp_server --show-packages # Show what's being loaded - python -m arcade_mcp_server stdio # For Claude Desktop + uv run 01_tools.py # HTTP transport (default) + uv run 01_tools.py stdio # stdio transport for Claude Desktop """ -# === DISCOVERY PATTERNS === +import sys +from typing import Annotated -""" -The arcade_mcp_server CLI discovers tools using these patterns: +from arcade_mcp_server import MCPApp +from typing_extensions import TypedDict -1. Current directory: *.py files - - Scans all Python files in the current directory - - Imports and checks for @tool decorated functions - -2. tools/ directory: - - If exists, recursively scans for Python files - - Common convention for organizing tools - -3. arcade_tools/ directory: - - Alternative directory name - - Also recursively scanned - -4. Package loading with --tool-package: - python -m arcade_mcp_server --tool-package github - - Loads arcade-github package - - Can load any installed package in the current python environment - -5. Discover all installed with --discover-installed: - python -m arcade_mcp_server --discover-installed - - Finds all arcade-* packages in the current python environment - - Loads all their tools - -Discovery tips: -- Use __init__.py in directories for proper imports -- Organize related tools in subdirectories -- Use clear, descriptive tool names -- Tools are namespaced by their toolkit name -""" +# Create the MCP application +app = MCPApp( + name="tools_example", + version="1.0.0", + instructions="Example server demonstrating various tool parameter types", +) # === SIMPLE TOOLS === -@tool +@app.tool def hello(name: Annotated[str, "Name to greet"]) -> Annotated[str, "Greeting message"]: """Say hello to someone.""" return f"Hello, {name}!" -@tool +@app.tool def add( a: Annotated[float, "First number"], b: Annotated[float, "Second number"] ) -> Annotated[float, "Sum of the numbers"]: @@ -72,7 +45,7 @@ def add( # === TOOLS WITH LIST PARAMETERS === -@tool +@app.tool def calculate_average( numbers: Annotated[list[float], "List of numbers to average"], ) -> Annotated[float, "Average of all numbers"]: @@ -82,7 +55,7 @@ def calculate_average( return sum(numbers) / len(numbers) -@tool +@app.tool def factorial(n: Annotated[int, "Non-negative integer"]) -> Annotated[int, "Factorial of n"]: """Calculate the factorial of a number.""" if n < 0: @@ -106,7 +79,7 @@ class PersonInfo(TypedDict): is_active: bool -@tool +@app.tool def create_user_profile( person: Annotated[PersonInfo, "Person's information"], ) -> Annotated[str, "Formatted user profile"]: @@ -129,7 +102,7 @@ class CalculationResult(TypedDict): count: int -@tool +@app.tool def analyze_numbers( values: Annotated[list[float], "List of numbers to analyze"], ) -> Annotated[CalculationResult, "Statistical analysis of the numbers"]: @@ -144,3 +117,14 @@ def analyze_numbers( "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) diff --git a/libs/arcade-mcp-server/docs/examples/02_building_apps.md b/libs/arcade-mcp-server/docs/examples/02_building_apps.md index 8d4c0961..e1715767 100644 --- a/libs/arcade-mcp-server/docs/examples/02_building_apps.md +++ b/libs/arcade-mcp-server/docs/examples/02_building_apps.md @@ -31,7 +31,8 @@ app = MCPApp( ### 2. Adding Tools -Use the `@app.tool` decorator to add 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: @@ -39,6 +40,29 @@ def my_tool(param: Annotated[str, "Description"]) -> str: 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 diff --git a/libs/arcade-mcp-server/docs/examples/02_building_apps.py b/libs/arcade-mcp-server/docs/examples/02_building_apps.py index b3446972..aecba549 100644 --- a/libs/arcade-mcp-server/docs/examples/02_building_apps.py +++ b/libs/arcade-mcp-server/docs/examples/02_building_apps.py @@ -63,4 +63,4 @@ if __name__ == "__main__": app.run(transport="stdio") else: # Default to HTTP transport - app.run(host="127.0.0.1", port=8001) + app.run(host="127.0.0.1", port=8000) diff --git a/libs/arcade-mcp-server/docs/examples/03_context.md b/libs/arcade-mcp-server/docs/examples/03_context.md index 69ac4ab3..8a0747f0 100644 --- a/libs/arcade-mcp-server/docs/examples/03_context.md +++ b/libs/arcade-mcp-server/docs/examples/03_context.md @@ -4,8 +4,8 @@ Access runtime features through Context including logging, secrets, and progress ## Running the Example -- **Run**: `python -m arcade_mcp_server` -- **Run (stdio)**: `python -m arcade_mcp_server stdio` +- **Run**: `uv run 03_context.py` +- **Run (stdio)**: `uv run 03_context.py stdio` - **Env**: set `API_KEY`, `DATABASE_URL` ## Source Code diff --git a/libs/arcade-mcp-server/docs/examples/03_context.py b/libs/arcade-mcp-server/docs/examples/03_context.py index ef1d7ee2..ebbae677 100644 --- a/libs/arcade-mcp-server/docs/examples/03_context.py +++ b/libs/arcade-mcp-server/docs/examples/03_context.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ 03_context.py - Using Context with namespaced runtime APIs @@ -6,23 +6,29 @@ 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 (auto-discovery): - python -m arcade_mcp_server - -For Claude Desktop (stdio transport): - python -m arcade_mcp_server stdio +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, tool +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", +) -@tool +@app.tool async def secure_api_call( context: Context, endpoint: Annotated[str, "API endpoint to call"], @@ -44,7 +50,8 @@ async def secure_api_call( return f"Successfully called {endpoint} with API key: {api_key[:4]}..." -@tool(requires_secrets=["DATABASE_URL"]) +# 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"]: @@ -71,7 +78,7 @@ async def database_info( return f"{user_info}\nDatabase: {db_url}" -@tool +@app.tool async def debug_context( context: Context, show_secrets: Annotated[bool, "Whether to show secret keys (not values)"] = False, @@ -92,7 +99,7 @@ async def debug_context( return info -@tool +@app.tool async def process_with_progress( context: Context, items: Annotated[list[str], "Items to process"], @@ -136,3 +143,13 @@ async def process_with_progress( # - context.get_secret(key): Retrieve a secret value (raises if missing) # - context.log.(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) diff --git a/libs/arcade-mcp-server/docs/examples/04_secrets.md b/libs/arcade-mcp-server/docs/examples/04_secrets.md index 63117c6f..32074333 100644 --- a/libs/arcade-mcp-server/docs/examples/04_secrets.md +++ b/libs/arcade-mcp-server/docs/examples/04_secrets.md @@ -4,14 +4,14 @@ Read secrets from environment and `.env` files securely via Context. ## Running the Example -- **Run**: `python -m arcade_mcp_server` -- **Run (stdio)**: `python -m arcade_mcp_server stdio` +- **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_tool_secrets.py" +--8<-- "docs/examples/04_secrets.py" ``` ## Working with Secrets @@ -22,12 +22,11 @@ Secrets can be provided via environment variables: ```bash export API_KEY="your-secret-key" export DATABASE_URL="postgresql://localhost/mydb" -python -m arcade_mcp_server ``` ### 2. Using .env Files -Create a `.env` file in your working directory: +Create a `.env` file in the directoryof your server: ``` API_KEY=supersecret DATABASE_URL=postgresql://user:pass@localhost/db diff --git a/libs/arcade-mcp-server/docs/examples/04_secrets.py b/libs/arcade-mcp-server/docs/examples/04_secrets.py index 8b593eec..17610b5f 100644 --- a/libs/arcade-mcp-server/docs/examples/04_secrets.py +++ b/libs/arcade-mcp-server/docs/examples/04_secrets.py @@ -1,23 +1,28 @@ #!/usr/bin/env python3 """04: Read secrets from .env via Context -Run (auto-discovery): - python -m arcade_mcp_server - -For Claude Desktop (stdio transport): - python -m arcade_mcp_server stdio +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 """ -from arcade_mcp_server import Context, tool +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", +) -@tool( - name="UseSecret", - desc="Echo a masked secret read from the context", +@app.tool( requires_secrets=["API_KEY"], # declare we need API_KEY ) def use_secret(context: Context) -> str: @@ -28,3 +33,14 @@ def use_secret(context: Context) -> str: 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) diff --git a/libs/arcade-mcp-server/docs/examples/05_logging.py b/libs/arcade-mcp-server/docs/examples/05_logging.py index cfe2587e..8893371d 100644 --- a/libs/arcade-mcp-server/docs/examples/05_logging.py +++ b/libs/arcade-mcp-server/docs/examples/05_logging.py @@ -187,4 +187,4 @@ async def batch_processing_logs( if __name__ == "__main__": # Run the server - app.run(host="127.0.0.1", port=8001) + app.run(host="127.0.0.1", port=8000) diff --git a/libs/arcade-mcp-server/docs/examples/06_tool_organization.md b/libs/arcade-mcp-server/docs/examples/06_tool_organization.md new file mode 100644 index 00000000..c212723a --- /dev/null +++ b/libs/arcade-mcp-server/docs/examples/06_tool_organization.md @@ -0,0 +1,191 @@ +# 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. diff --git a/libs/arcade-mcp-server/docs/examples/06_tool_organization.py b/libs/arcade-mcp-server/docs/examples/06_tool_organization.py new file mode 100644 index 00000000..f7a22dce --- /dev/null +++ b/libs/arcade-mcp-server/docs/examples/06_tool_organization.py @@ -0,0 +1,94 @@ +#!/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) diff --git a/libs/arcade-mcp-server/docs/examples/07_auth.md b/libs/arcade-mcp-server/docs/examples/07_auth.md new file mode 100644 index 00000000..36c5696d --- /dev/null +++ b/libs/arcade-mcp-server/docs/examples/07_auth.md @@ -0,0 +1,123 @@ +# 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 diff --git a/libs/arcade-mcp-server/docs/examples/07_auth.py b/libs/arcade-mcp-server/docs/examples/07_auth.py new file mode 100644 index 00000000..c3769744 --- /dev/null +++ b/libs/arcade-mcp-server/docs/examples/07_auth.py @@ -0,0 +1,73 @@ +#!/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) diff --git a/libs/arcade-mcp-server/docs/examples/README.md b/libs/arcade-mcp-server/docs/examples/README.md index 9b9fcea1..a06a1131 100644 --- a/libs/arcade-mcp-server/docs/examples/README.md +++ b/libs/arcade-mcp-server/docs/examples/README.md @@ -4,83 +4,81 @@ This directory contains examples demonstrating how to build MCP servers with you ## Getting Started -The easiest way to get started is with the `arcade new` command: +The easiest way to get started is with `arcade new`: ```bash -# Install the Arcade CLI +# Install the CLI uv pip install arcade-mcp -# Create a new server with example tools +# Create a new server project with example tools arcade new my_server cd my_server -# Run the server -arcade mcp +# 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: `arcade mcp` (or `arcade mcp stdio`) + - 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: `arcade mcp` + - 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: `python 02_building_apps.py` - - Run stdio: `python 02_building_apps.py stdio` + - 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: `arcade mcp` + - Run: `uv run 03_context.py` -5. **[04_tool_secrets.py](04_tool_secrets.py)** – Working with secrets +5. **[04_tool_secrets.py](04_secrets.py)** – Working with secrets - Use `requires_secrets` and access masked values - - Run: `arcade mcp` + - 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: `python 05_logging.py` + - 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: Using the Arcade CLI +### Recommended: Direct Python Execution -Most examples can be run with the `arcade mcp` command: +Most examples can be run directly with Python using `uv`: ```bash -# Auto-discover tools in current directory -arcade mcp +# 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 -arcade mcp stdio # For Claude Desktop -arcade mcp # HTTP by default +uv run server.py stdio # For Claude Desktop +uv run server.py http # HTTP by default -# With debugging -arcade mcp --debug - -# With hot reload (HTTP only) -arcade mcp --reload +# You can also run with python directly +python 00_hello_world.py +python 02_building_apps.py stdio ``` -### Alternative: Direct Python Execution - -For MCPApp examples, you can run the script directly: - -```bash -python 02_building_apps.py -``` - -Or use the server module directly: - -```bash -python -m arcade_mcp_server -``` - -**Note:** We recommend using `arcade mcp` for a better development experience. +All example files include proper command-line argument handling with `if __name__ == "__main__":` blocks. diff --git a/libs/arcade-mcp-server/docs/examples/tools_math.py b/libs/arcade-mcp-server/docs/examples/tools_math.py new file mode 100644 index 00000000..56e03ee7 --- /dev/null +++ b/libs/arcade-mcp-server/docs/examples/tools_math.py @@ -0,0 +1,31 @@ +#!/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 diff --git a/libs/arcade-mcp-server/docs/examples/tools_text.py b/libs/arcade-mcp-server/docs/examples/tools_text.py new file mode 100644 index 00000000..dfd133ea --- /dev/null +++ b/libs/arcade-mcp-server/docs/examples/tools_text.py @@ -0,0 +1,29 @@ +#!/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] diff --git a/libs/arcade-mcp-server/docs/getting-started/quickstart.md b/libs/arcade-mcp-server/docs/getting-started/quickstart.md index d68e1b4c..d80bba58 100644 --- a/libs/arcade-mcp-server/docs/getting-started/quickstart.md +++ b/libs/arcade-mcp-server/docs/getting-started/quickstart.md @@ -1,8 +1,8 @@ # Quick Start -The `arcade_mcp_server` package provides powerful ways to run MCP servers with your Arcade tools. While you can use the server library directly, **we recommend using the Arcade CLI** for a streamlined development experience. +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: Quick Start with Arcade CLI +## Recommended: Create with arcade new ### 1. Install the CLI @@ -10,44 +10,66 @@ The `arcade_mcp_server` package provides powerful ways to run MCP servers with y uv pip install arcade-mcp ``` -The `arcade-mcp` package includes both the CLI tools and the `arcade-mcp-server` library. +The `arcade-mcp` package includes the CLI tools and the `arcade-mcp-server` library. -### 2. Create a New Server - -Start with a pre-configured server that includes example tools: +### 2. Create Your Server ```bash arcade new my_server cd my_server ``` -This creates a starter MCP server with three example tools: -- **Simple tool** - A basic greeting function -- **Secret-based tool** - Demonstrates using environment secrets -- **OAuth tool** - Shows user authentication flow (requires `arcade login`) +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 HTTP server (default, great for development) -arcade mcp +# Run with uv (recommended) +uv run server.py -# Or run stdio server (for Claude Desktop, Cursor, etc.) -arcade mcp stdio +# 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 -DEBUG | 11:43:11 | arcade_mcp_server.mcp_app:169 | Added tool: greet -INFO | 11:43:11 | arcade_mcp_server.mcp_app:211 | Starting server v1.0.0 with 3 tools -INFO: Started server process [89481] -INFO: Waiting for application startup. -INFO | 11:43:12 | arcade_mcp_server.worker:69 | MCP server started and ready for connections -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +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 ``` -View your server's API docs at http://127.0.0.1:8000/docs. +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 @@ -66,39 +88,6 @@ arcade configure vscode --from-local That's it! Your MCP server is running and connected to your AI assistant. -## Alternative: Direct Python Approach - -If you prefer to use the library directly without the CLI, you can install just the server package: - -```bash -uv pip install arcade-mcp-server -``` - -### Write a Tool - -```python -from arcade_mcp_server import tool -from typing import Annotated - -@tool -def greet(name: Annotated[str, "The name to greet"]) -> Annotated[str, "The greeting"]: - return f"Hello, {name}!" -``` - -### Run the Server - -You can run the server directly with Python: - -```bash -# Using the module directly -python -m arcade_mcp_server - -# Or if you have a server.py file with MCPApp -python server.py -``` - -**Note:** While this approach works, we recommend using `arcade mcp` for a better development experience with features like easy client configuration and starter templates. - ## Building MCP Servers @@ -109,7 +98,7 @@ from arcade_mcp_server import MCPApp from typing import Annotated app = MCPApp( - name="my-tools", + name="my_serve_", version="1.0.0", instructions="Custom MCP server with specialized tools" ) @@ -135,72 +124,13 @@ if __name__ == "__main__": app.run(host="0.0.0.0", port=8080, reload=True) ``` -## Using the `arcade mcp` Command -The `arcade mcp` command provides a simple interface for running MCP servers. It automatically discovers tools, creates a server, and runs it with your chosen transport. -### Auto-Discovery Mode +## Secrets -The simplest way to run is to let the server discover tools in your current directory: +Define your tool secrets in an environment file `.env` in the same directory as your `MCPApp`, or export as environment variables ```bash -# Auto-discover @tool decorated functions -arcade mcp - -# With stdio transport for Claude Desktop -arcade mcp stdio -``` - -### Loading Installed Packages - -Load specific arcade packages or discover all installed ones: - -```bash -# Load a specific arcade package -arcade mcp --tool-package github -arcade mcp -p slack - -# Discover all installed arcade packages -arcade mcp --discover-installed - -# Show which packages are being loaded -arcade mcp --discover-installed --show-packages -``` - -### Development Mode - -For active development with hot reload: - -```bash -# Run with hot reload and debug logging -arcade mcp --reload --debug - -# Specify host and port -arcade mcp --host 0.0.0.0 --port 8000 - -# Load environment variables -arcade mcp --env-file .env -``` - - -## Environment Variables - -Configure the server using environment variables: - -```bash -# Server settings -MCP_SERVER_NAME="My MCP Server" -MCP_SERVER_VERSION="1.0.0" - -# Arcade integration -ARCADE_API_KEY="your-api-key" -ARCADE_API_URL="https://api.arcade.dev" -ARCADE_USER_ID="user@example.com" - -# Development settings -ARCADE_AUTH_DISABLED=true -MCP_DEBUG=true - # Tool secrets (available to tools via context) MY_API_KEY="secret-value" DATABASE_URL="postgresql://..." @@ -209,20 +139,22 @@ DATABASE_URL="postgresql://..." ## Development Tips ### Hot Reload -Use `--reload --debug` for development to automatically restart on code changes: +Use the `reload=True` parameter for development to automatically restart on code changes: -```bash -arcade mcp --reload --debug +```python +app.run(host="127.0.0.1", port=8000, reload=True) ``` ### Logging -- Use `--debug` for verbose logging +- Set `log_level="DEBUG"` in MCPApp for verbose logging - In stdio mode, logs go to stderr - In HTTP mode, logs go to stdout -### Testing Tools -With HTTP transport and debug mode, access API documentation at: +### 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 diff --git a/libs/arcade-mcp-server/docs/index.md b/libs/arcade-mcp-server/docs/index.md index 9cbedbb3..cd010671 100644 --- a/libs/arcade-mcp-server/docs/index.md +++ b/libs/arcade-mcp-server/docs/index.md @@ -27,47 +27,50 @@ Arcade MCP (Model Context Protocol) enables AI assistants and development tools ### Installation -We recommend installing the `arcade-mcp` CLI package, which includes `arcade-mcp-server` and provides a streamlined development workflow: - -```bash -uv pip install arcade-mcp -``` - -Or install just the server library if you prefer a direct Python approach: +We recommend installing the `arcade-mcp-server` library for direct Python development: ```bash uv pip install arcade-mcp-server ``` -### Quick Start: Create a New Server (Recommended) - -The fastest way to get started is with the `arcade new` command, which creates a starter MCP server with example tools: +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 - -# Run the server -arcade mcp ``` -The generated server includes three example tools: -- **Simple tool** - A basic function to get you started -- **Secret-based tool** - Shows how to use environment secrets -- **OAuth tool** - Demonstrates how to use a OAuth tool (requires `arcade login`) +This generates a complete project with: -### Manual Setup: Create Your First Tool +- **server.py** - Main server file with MCPApp and example tools -If you prefer to create tools manually, you can use the `MCPApp` interface: +- **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 -from arcade_mcp_server import MCPApp +#!/usr/bin/env python3 +import sys from typing import Annotated +from arcade_mcp_server import MCPApp -app = MCPApp(name="my-tools", version="1.0.0") +app = MCPApp(name="my_server", version="1.0.0") @app.tool def greet(name: Annotated[str, "Name to greet"]) -> str: @@ -75,30 +78,36 @@ def greet(name: Annotated[str, "Name to greet"]) -> str: return f"Hello, {name}!" if __name__ == "__main__": - app.run() + 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 -**Recommended: Use the Arcade CLI** +Run your server directly with Python: ```bash -# Run HTTP server (default) -arcade mcp +# Run with HTTP transport (default) +uv run server.py -# Run stdio server (for Claude Desktop, Cursor, etc.) -arcade mcp stdio +# Run with stdio transport (for Claude Desktop) +uv run server.py stdio -# Run with debug logging and hot reload -arcade mcp --debug --reload +# Or use python directly +python server.py http +python server.py stdio ``` -**Alternative: Direct Python execution** - -```bash -# Run your server.py file directly -python server.py -``` +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 diff --git a/libs/arcade-mcp-server/mkdocs.yml b/libs/arcade-mcp-server/mkdocs.yml index 1a0484e4..4fa38607 100644 --- a/libs/arcade-mcp-server/mkdocs.yml +++ b/libs/arcade-mcp-server/mkdocs.yml @@ -73,6 +73,8 @@ nav: - 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 @@ -80,7 +82,6 @@ nav: - clients/inspector.md - API Reference: - api/mcp_app.md - - api/cli.md - Server: - api/server/server.md - api/server/middleware.md @@ -88,6 +89,7 @@ nav: - api/server/errors.md - api/server/settings.md - Advanced: + - advanced/sharing-servers.md - advanced/transports.md extra: diff --git a/libs/arcade-mcp-server/pyproject.toml b/libs/arcade-mcp-server/pyproject.toml index f43cae10..55d1e0bc 100644 --- a/libs/arcade-mcp-server/pyproject.toml +++ b/libs/arcade-mcp-server/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "arcade-mcp-server" -version = "1.0.0rc2" +version = "1.0.0rc3" description = "Model Context Protocol (MCP) server framework for Arcade.dev" readme = "README.md" authors = [{ name = "Arcade.dev" }] diff --git a/pyproject.toml b/pyproject.toml index 947f2f8f..ed48dda5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ requires-python = ">=3.10" dependencies = [ # CLI dependencies - "arcade-mcp-server>=1.0.0rc2,<3.0.0", + "arcade-mcp-server>=1.0.0rc3,<3.0.0", "arcade-core>=2.5.0rc2,<3.0.0", "typer==0.10.0", "rich==13.9.4", @@ -41,7 +41,7 @@ all = [ "pytz>=2024.1", "python-dateutil>=2.8.2", # mcp - "arcade-mcp-server>=1.0.0rc2,<3.0.0", + "arcade-mcp-server>=1.0.0rc3,<3.0.0", # serve "arcade-serve>=2.2.0rc2,<3.0.0", # tdk