diff --git a/docs/docs/architecture/.pages b/docs/docs/architecture/.pages index 1d81ceefa..f2007480d 100644 --- a/docs/docs/architecture/.pages +++ b/docs/docs/architecture/.pages @@ -2,4 +2,5 @@ nav: - Overview: index.md - Roadmap: roadmap.md - Security Features: security-features.md + - Export-Import Architecture: export-import-architecture.md - Decision Records: adr diff --git a/docs/docs/architecture/adr/015-well-known-uri-handler.md b/docs/docs/architecture/adr/015-well-known-uri-handler.md index 812e651d6..2ca58afb0 100644 --- a/docs/docs/architecture/adr/015-well-known-uri-handler.md +++ b/docs/docs/architecture/adr/015-well-known-uri-handler.md @@ -99,13 +99,13 @@ Implemented a registry for known well-known URIs with metadata: WELL_KNOWN_REGISTRY = { "robots.txt": { "content_type": "text/plain", - "description": "Robot exclusion standard", + "description": "Robot exclusion standard", "rfc": "RFC 9309" }, "security.txt": { "content_type": "text/plain", "description": "Security contact information", - "rfc": "RFC 9116" + "rfc": "RFC 9116" }, # ... additional standard URIs } @@ -258,4 +258,4 @@ Potential improvements for future iterations: ## Status -This well-known URI handler implementation is **accepted and implemented** as of version 0.7.0, providing standards-compliant web service discovery while maintaining security-first defaults appropriate for private API gateway deployments. \ No newline at end of file +This well-known URI handler implementation is **accepted and implemented** as of version 0.7.0, providing standards-compliant web service discovery while maintaining security-first defaults appropriate for private API gateway deployments. diff --git a/docs/docs/deployment/.pages b/docs/docs/deployment/.pages index ce47e4d33..f7e568e00 100644 --- a/docs/docs/deployment/.pages +++ b/docs/docs/deployment/.pages @@ -13,3 +13,4 @@ nav: - aws.md - azure.md - fly-io.md + - proxy-auth.md diff --git a/docs/docs/development/.pages b/docs/docs/development/.pages index d111543d8..c1acb9240 100644 --- a/docs/docs/development/.pages +++ b/docs/docs/development/.pages @@ -1,6 +1,7 @@ nav: - index.md - developer-onboarding.md + - mcp-developer-guide-json-rpc.md - github.md - building.md - documentation.md diff --git a/docs/docs/development/mcp-developer-guide-json-rpc.md b/docs/docs/development/mcp-developer-guide-json-rpc.md new file mode 100644 index 000000000..1311f3761 --- /dev/null +++ b/docs/docs/development/mcp-developer-guide-json-rpc.md @@ -0,0 +1,1173 @@ +# MCP JSON RPC Guide + +This comprehensive guide demonstrates how to interact with MCP (Model Context Protocol) servers using raw JSON-RPC commands via `curl` or through STDIO. This is essential for developers who want to understand the MCP protocol at a low level, integrate MCP into custom applications, or debug MCP implementations. + +## Overview + +The Model Context Protocol (MCP) is a standardized protocol for connecting language models to various data sources and tools. MCP Gateway acts as a federation layer that aggregates multiple MCP servers and provides unified access through various transport mechanisms. + +## Prerequisites + +Before starting, ensure you have: + +- MCP Gateway server running (typically on `http://localhost:4444`) +- `curl` command-line tool installed +- `jq` for JSON formatting (optional but recommended) +- Basic understanding of JSON-RPC 2.0 protocol + +## Authentication Setup + +MCP Gateway uses JWT Bearer tokens for authentication. Generate a token before making any requests: + +```bash +# Generate authentication token +export MCPGATEWAY_BEARER_TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token \ + --username admin --exp 10080 --secret my-test-key) + +# Verify the token was generated +echo "Token: ${MCPGATEWAY_BEARER_TOKEN}" + +# Test connectivity +curl -s -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + http://localhost:4444/health | jq +``` + +**Expected health response:** +```json +{ + "status": "healthy", + "timestamp": "2025-01-15T10:30:00Z", + "version": "0.6.0" +} +``` + +## Understanding MCP Protocol Flow + +MCP follows a specific initialization sequence that must be followed for proper communication: + +1. **Initialize** - Establish protocol version and capabilities +2. **Initialized Notification** - Confirm initialization completion +3. **Protocol Operations** - List and call tools, read resources, get prompts + +### Transport Methods + +MCP Gateway supports multiple transport methods: + +- **HTTP JSON-RPC** (`/rpc`) - Standard JSON-RPC 2.0 over HTTP +- **Server-Sent Events** (`/sse`) - Real-time streaming communication +- **Protocol Endpoints** (`/protocol/*`) - Specialized endpoints for specific operations + +## MCP Protocol Implementation + +### 1. Initialize the Connection + +Every MCP session must begin with proper initialization: + +#### Method 1: Using Protocol Endpoint (Recommended) + +```bash +# Initialize using dedicated protocol endpoint +curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "protocol_version": "2025-03-26", + "capabilities": { + "tools": {}, + "resources": {}, + "prompts": {} + }, + "client_info": { + "name": "cli-developer", + "version": "1.0.0" + } + }' \ + http://localhost:4444/protocol/initialize | jq +``` + +#### Method 2: Using JSON-RPC Endpoint + +```bash +# Initialize using JSON-RPC endpoint +curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2025-03-26", + "capabilities": { + "tools": {}, + "resources": {}, + "prompts": {} + }, + "clientInfo": { + "name": "cli-developer", + "version": "1.0.0" + } + } + }' \ + http://localhost:4444/rpc | jq +``` + +**Expected Response:** +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "protocolVersion": "2025-03-26", + "capabilities": { + "experimental": {}, + "prompts": { + "listChanged": false + }, + "resources": { + "subscribe": false, + "listChanged": false + }, + "tools": { + "listChanged": false + } + }, + "serverInfo": { + "name": "mcpgateway", + "version": "0.6.0" + } + } +} +``` + +### 2. Send Initialized Notification + +After successful initialization, send the initialized notification: + +```bash +# Send initialized notification +curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "notifications/initialized", + "params": {} + }' \ + http://localhost:4444/rpc +``` + +## Working with Tools + +### List Available Tools + +```bash +# List all available tools +curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/list", + "params": {} + }' \ + http://localhost:4444/rpc | jq +``` + +**Response with no tools:** +```json +{ + "jsonrpc": "2.0", + "id": 2, + "result": { + "tools": [] + } +} +``` + +**Response with tools available:** +```json +{ + "jsonrpc": "2.0", + "id": 2, + "result": { + "tools": [ + { + "name": "get_system_time", + "description": "Get current system time in specified timezone", + "inputSchema": { + "type": "object", + "properties": { + "timezone": { + "type": "string", + "description": "IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Defaults to UTC if not specified." + } + } + } + }, + { + "name": "convert_time", + "description": "Convert time between different timezones", + "inputSchema": { + "type": "object", + "properties": { + "time": { + "type": "string", + "description": "Time to convert in RFC3339 format" + }, + "source_timezone": { + "type": "string", + "description": "Source IANA timezone name" + }, + "target_timezone": { + "type": "string", + "description": "Target IANA timezone name" + } + }, + "required": ["time", "source_timezone", "target_timezone"] + } + } + ] + } +} +``` + +### Call a Tool + +```bash +# Call a tool with arguments +curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": { + "name": "get_system_time", + "arguments": { + "timezone": "Europe/Dublin" + } + } + }' \ + http://localhost:4444/rpc | jq +``` + +**Expected Response:** +```json +{ + "jsonrpc": "2.0", + "id": 3, + "result": { + "content": [ + { + "type": "text", + "text": "{\n \"timezone\": \"Europe/Dublin\",\n \"datetime\": \"2025-01-15T16:30:45+00:00\",\n \"is_dst\": false\n}" + } + ], + "isError": false + } +} +``` + +### Tool Call Examples + +**Convert Time Between Timezones:** +```bash +curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 4, + "method": "tools/call", + "params": { + "name": "convert_time", + "arguments": { + "time": "2025-01-15T10:00:00Z", + "source_timezone": "UTC", + "target_timezone": "America/New_York" + } + } + }' \ + http://localhost:4444/rpc | jq +``` + +## Working with Resources + +### List Available Resources + +```bash +# List all available resources +curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 5, + "method": "resources/list", + "params": {} + }' \ + http://localhost:4444/rpc | jq +``` + +**Expected Response:** +```json +{ + "jsonrpc": "2.0", + "id": 5, + "result": { + "resources": [ + { + "uri": "timezone://info", + "name": "Timezone Information", + "description": "Comprehensive timezone information including offsets, DST status, and major cities", + "mimeType": "application/json" + }, + { + "uri": "time://current/world", + "name": "World Clock", + "description": "Current time in major cities around the world", + "mimeType": "application/json" + } + ] + } +} +``` + +### Read a Specific Resource + +```bash +# Read a specific resource +curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 6, + "method": "resources/read", + "params": { + "uri": "timezone://info" + } + }' \ + http://localhost:4444/rpc | jq +``` + +## Working with Prompts + +### List Available Prompts + +```bash +# List all available prompts +curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 7, + "method": "prompts/list", + "params": {} + }' \ + http://localhost:4444/rpc | jq +``` + +### Get a Specific Prompt + +```bash +# Get a prompt with arguments +curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 8, + "method": "prompts/get", + "params": { + "name": "compare_timezones", + "arguments": { + "timezones": "UTC,America/New_York,Europe/London", + "reference_time": "2025-01-15T12:00:00Z" + } + } + }' \ + http://localhost:4444/rpc | jq +``` + +## Server-Sent Events (SSE) Transport + +For real-time communication and better handling of long-running operations, use the SSE transport: + +### Establishing SSE Connection + +```bash +# Terminal 1: Start SSE connection (keeps connection open) +curl -N -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + http://localhost:4444/sse +``` + +### Sending Messages via SSE + +In a separate terminal, send JSON-RPC messages: + +```bash +# Initialize via SSE +curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2025-03-26", + "capabilities": {}, + "clientInfo": {"name": "sse-client", "version": "1.0"} + } + }' \ + http://localhost:4444/message + +# List tools via SSE +curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/list" + }' \ + http://localhost:4444/message + +# Call a tool via SSE +curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": { + "name": "get_system_time", + "arguments": {"timezone": "Asia/Tokyo"} + } + }' \ + http://localhost:4444/message +``` + +## Utility Operations + +### Ping the Server + +```bash +# Test server responsiveness +curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 9, + "method": "ping" + }' \ + http://localhost:4444/rpc | jq +``` + +**Expected Response:** +```json +{ + "jsonrpc": "2.0", + "id": 9, + "result": {} +} +``` + +## STDIO Transport + +For command-line integration and desktop client compatibility, use the STDIO wrapper: + +### Setting Up STDIO Environment + +```bash +# Configure environment variables +export MCP_AUTH_TOKEN=${MCPGATEWAY_BEARER_TOKEN} +export MCP_SERVER_CATALOG_URLS="http://localhost:4444/servers/your-server-id" +export MCP_TOOL_CALL_TIMEOUT=120 +export MCP_WRAPPER_LOG_LEVEL=INFO + +# Run the wrapper +python3 -m mcpgateway.wrapper +``` + +### STDIO Communication + +Send JSON-RPC commands directly to stdin: + +```bash +# Send commands to stdin (each on a single line) +echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"stdio-client","version":"1.0"}}}' | python3 -m mcpgateway.wrapper + +echo '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}' | python3 -m mcpgateway.wrapper + +echo '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | python3 -m mcpgateway.wrapper +``` + +## Complete Session Examples + +### HTTP JSON-RPC Complete Session + +```bash +#!/bin/bash +# Complete MCP Gateway session via HTTP JSON-RPC + +# Setup +export MCPGATEWAY_BEARER_TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token \ + --username admin --exp 10080 --secret my-test-key) + +# Function to make authenticated JSON-RPC calls +make_call() { + curl -s -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d "$1" \ + http://localhost:4444/rpc | jq +} + +# Function for protocol-specific calls +make_protocol_call() { + curl -s -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d "$1" \ + http://localhost:4444/protocol/initialize | jq +} + +echo "=== MCP Gateway Complete Session ===" + +# 1. Test connectivity +echo "=== Testing Connectivity ===" +curl -s -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + http://localhost:4444/health | jq + +# 2. Initialize (using protocol endpoint for reliability) +echo "=== Initializing Session ===" +make_protocol_call '{ + "protocol_version": "2025-03-26", + "capabilities": { + "tools": {}, + "resources": {}, + "prompts": {} + }, + "client_info": { + "name": "complete-session-demo", + "version": "1.0" + } +}' + +# 3. Send initialized notification +echo "=== Sending Initialized Notification ===" +make_call '{ + "jsonrpc": "2.0", + "method": "notifications/initialized", + "params": {} +}' + +# 4. List available tools +echo "=== Listing Available Tools ===" +make_call '{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/list", + "params": {} +}' + +# 5. List available resources +echo "=== Listing Available Resources ===" +make_call '{ + "jsonrpc": "2.0", + "id": 3, + "method": "resources/list", + "params": {} +}' + +# 6. List available prompts +echo "=== Listing Available Prompts ===" +make_call '{ + "jsonrpc": "2.0", + "id": 4, + "method": "prompts/list", + "params": {} +}' + +# 7. Call a tool (if available) +echo "=== Calling Tool (get_system_time) ===" +make_call '{ + "jsonrpc": "2.0", + "id": 5, + "method": "tools/call", + "params": { + "name": "get_system_time", + "arguments": { + "timezone": "UTC" + } + } +}' + +# 8. Read a resource (if available) +echo "=== Reading Resource ===" +make_call '{ + "jsonrpc": "2.0", + "id": 6, + "method": "resources/read", + "params": { + "uri": "timezone://info" + } +}' + +# 9. Test ping +echo "=== Testing Ping ===" +make_call '{ + "jsonrpc": "2.0", + "id": 7, + "method": "ping" +}' + +echo "=== Session Complete ===" +``` + +### SSE Complete Session + +```bash +#!/bin/bash +# Complete MCP Gateway session via Server-Sent Events + +# Setup +export MCPGATEWAY_BEARER_TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token \ + --username admin --exp 10080 --secret my-test-key) + +echo "=== Starting SSE Session ===" + +# Start SSE connection in background +curl -N -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + http://localhost:4444/sse & +SSE_PID=$! + +# Give SSE time to connect +sleep 2 + +# Function to send messages via SSE +send_sse_message() { + curl -s -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d "$1" \ + http://localhost:4444/message + sleep 1 # Allow time for response +} + +echo "=== Sending Initialize Message ===" +send_sse_message '{ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2025-03-26", + "capabilities": {}, + "clientInfo": { + "name": "sse-session-demo", + "version": "1.0" + } + } +}' + +echo "=== Sending Initialized Notification ===" +send_sse_message '{ + "jsonrpc": "2.0", + "method": "notifications/initialized", + "params": {} +}' + +echo "=== Listing Tools ===" +send_sse_message '{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/list" +}' + +echo "=== Calling Tool ===" +send_sse_message '{ + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": { + "name": "get_system_time", + "arguments": { + "timezone": "Europe/Dublin" + } + } +}' + +echo "=== Testing Ping ===" +send_sse_message '{ + "jsonrpc": "2.0", + "id": 4, + "method": "ping" +}' + +# Allow time for final responses +sleep 3 + +# Clean up +kill $SSE_PID 2>/dev/null +echo "=== SSE Session Complete ===" +``` + +## Error Handling and Troubleshooting + +### Common Error Responses + +MCP follows JSON-RPC 2.0 error handling standards: + +**Authentication Error:** +```json +{ + "jsonrpc": "2.0", + "id": 1, + "error": { + "code": -32000, + "message": "Authentication required", + "data": "Missing or invalid authorization header" + } +} +``` + +**Invalid Parameters:** +```json +{ + "jsonrpc": "2.0", + "id": 2, + "error": { + "code": -32602, + "message": "Invalid params", + "data": "Missing required parameter: name" + } +} +``` + +**Method Not Found:** +```json +{ + "jsonrpc": "2.0", + "id": 3, + "error": { + "code": -32601, + "message": "Method not found", + "data": "Unknown method: invalid_method" + } +} +``` + +### Standard JSON-RPC Error Codes + +| Code | Meaning | Description | +|------|---------|-------------| +| -32700 | Parse error | Invalid JSON was received | +| -32600 | Invalid Request | The JSON sent is not a valid Request object | +| -32601 | Method not found | The method does not exist / is not available | +| -32602 | Invalid params | Invalid method parameter(s) | +| -32603 | Internal error | Internal JSON-RPC error | +| -32000 to -32099 | Server error | Reserved for implementation-defined server-errors | + +### Troubleshooting Common Issues + +#### 1. Authentication Problems + +**Symptoms:** +- 401 Unauthorized responses +- "Authentication required" errors + +**Solutions:** +```bash +# Verify token generation +export MCPGATEWAY_BEARER_TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token \ + --username admin --exp 10080 --secret my-test-key) + +# Test token validity +curl -s -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + http://localhost:4444/health + +# Check token expiration +echo $MCPGATEWAY_BEARER_TOKEN | cut -d'.' -f2 | base64 -d | jq .exp +``` + +#### 2. Connection Issues + +**Symptoms:** +- Connection refused errors +- Timeout errors + +**Solutions:** +```bash +# Check if MCP Gateway is running +curl -f http://localhost:4444/health || echo "Gateway not running" + +# Check port availability +lsof -i :4444 + +# Verify network connectivity +ping localhost +``` + +#### 3. Initialize Method Errors + +**Known Issue:** `'coroutine' object has no attribute 'model_dump'` + +**Workaround:** +```bash +# Use protocol endpoint instead of /rpc for initialization +curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "protocol_version": "2025-03-26", + "capabilities": {}, + "client_info": {"name": "workaround", "version": "1.0"} + }' \ + http://localhost:4444/protocol/initialize +``` + +#### 4. Empty Tool/Resource Lists + +**Symptoms:** +- `tools/list` returns empty array +- `resources/list` returns empty array + +**Solutions:** +```bash +# Check if MCP servers are registered +curl -s -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + http://localhost:4444/gateways | jq + +# Verify virtual servers are configured +curl -s -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + http://localhost:4444/servers | jq + +# Check individual server status +curl -s -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + http://localhost:4444/servers/{server-id} | jq +``` + +#### 5. Tool Call Failures + +**Symptoms:** +- Tool calls return `isError: true` +- Timeout errors + +**Solutions:** +```bash +# Check tool schema and required parameters +curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \ + http://localhost:4444/rpc | jq '.result.tools[0].inputSchema' + +# Verify argument types match schema +# Increase timeout for long-running tools +export MCP_TOOL_CALL_TIMEOUT=300 +``` + +## Integration Examples + +### Python Client Implementation + +```python +import json +import requests +import subprocess +from typing import Dict, Any, Optional + +class MCPGatewayClient: + def __init__(self, base_url: str = "http://localhost:4444", auth_token: Optional[str] = None): + self.base_url = base_url + self.session = requests.Session() + self.request_id = 0 + + if auth_token: + self.session.headers.update({ + "Authorization": f"Bearer {auth_token}", + "Content-Type": "application/json" + }) + + def _make_request(self, method: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """Make a JSON-RPC request to the gateway.""" + self.request_id += 1 + + payload = { + "jsonrpc": "2.0", + "id": self.request_id, + "method": method + } + + if params is not None: + payload["params"] = params + + response = self.session.post(f"{self.base_url}/rpc", json=payload) + response.raise_for_status() + + return response.json() + + def initialize(self) -> Dict[str, Any]: + """Initialize the MCP session.""" + # Use protocol endpoint for reliable initialization + payload = { + "protocol_version": "2025-03-26", + "capabilities": { + "tools": {}, + "resources": {}, + "prompts": {} + }, + "client_info": { + "name": "python-mcp-client", + "version": "1.0.0" + } + } + + response = self.session.post(f"{self.base_url}/protocol/initialize", json=payload) + response.raise_for_status() + + # Send initialized notification + self._make_request("notifications/initialized", {}) + + return response.json() + + def list_tools(self) -> Dict[str, Any]: + """List available tools.""" + return self._make_request("tools/list") + + def call_tool(self, name: str, arguments: Dict[str, Any]) -> Dict[str, Any]: + """Call a specific tool with arguments.""" + return self._make_request("tools/call", { + "name": name, + "arguments": arguments + }) + + def list_resources(self) -> Dict[str, Any]: + """List available resources.""" + return self._make_request("resources/list") + + def read_resource(self, uri: str) -> Dict[str, Any]: + """Read a specific resource.""" + return self._make_request("resources/read", {"uri": uri}) + + def list_prompts(self) -> Dict[str, Any]: + """List available prompts.""" + return self._make_request("prompts/list") + + def get_prompt(self, name: str, arguments: Dict[str, Any]) -> Dict[str, Any]: + """Get a specific prompt with arguments.""" + return self._make_request("prompts/get", { + "name": name, + "arguments": arguments + }) + + def ping(self) -> Dict[str, Any]: + """Test server connectivity.""" + return self._make_request("ping") + +# Usage example +def main(): + # Generate authentication token + result = subprocess.run([ + "python3", "-m", "mcpgateway.utils.create_jwt_token", + "--username", "admin", "--exp", "10080", "--secret", "my-test-key" + ], capture_output=True, text=True) + + if result.returncode != 0: + raise Exception(f"Failed to generate token: {result.stderr}") + + auth_token = result.stdout.strip() + + # Create client and initialize + client = MCPGatewayClient(auth_token=auth_token) + + try: + # Initialize session + init_result = client.initialize() + print("Initialized:", json.dumps(init_result, indent=2)) + + # List available tools + tools = client.list_tools() + print("Tools:", json.dumps(tools, indent=2)) + + # Call a tool if available + if tools["result"]["tools"]: + tool_name = tools["result"]["tools"][0]["name"] + result = client.call_tool(tool_name, {"timezone": "UTC"}) + print(f"Tool result:", json.dumps(result, indent=2)) + + # Test ping + ping_result = client.ping() + print("Ping:", json.dumps(ping_result, indent=2)) + + except Exception as e: + print(f"Error: {e}") + +if __name__ == "__main__": + main() +``` + +### Node.js Client Implementation + +```javascript +const axios = require('axios'); +const { execSync } = require('child_process'); + +class MCPGatewayClient { + constructor(baseUrl = 'http://localhost:4444', authToken = null) { + this.baseUrl = baseUrl; + this.requestId = 0; + + this.axiosInstance = axios.create({ + baseURL: baseUrl, + headers: { + 'Content-Type': 'application/json', + ...(authToken && { 'Authorization': `Bearer ${authToken}` }) + } + }); + } + + async makeRequest(method, params = null) { + this.requestId++; + + const payload = { + jsonrpc: '2.0', + id: this.requestId, + method + }; + + if (params !== null) { + payload.params = params; + } + + const response = await this.axiosInstance.post('/rpc', payload); + return response.data; + } + + async initialize() { + // Use protocol endpoint for reliable initialization + const payload = { + protocol_version: '2025-03-26', + capabilities: { + tools: {}, + resources: {}, + prompts: {} + }, + client_info: { + name: 'nodejs-mcp-client', + version: '1.0.0' + } + }; + + const response = await this.axiosInstance.post('/protocol/initialize', payload); + + // Send initialized notification + await this.makeRequest('notifications/initialized', {}); + + return response.data; + } + + async listTools() { + return this.makeRequest('tools/list'); + } + + async callTool(name, arguments) { + return this.makeRequest('tools/call', { name, arguments }); + } + + async listResources() { + return this.makeRequest('resources/list'); + } + + async readResource(uri) { + return this.makeRequest('resources/read', { uri }); + } + + async listPrompts() { + return this.makeRequest('prompts/list'); + } + + async getPrompt(name, arguments) { + return this.makeRequest('prompts/get', { name, arguments }); + } + + async ping() { + return this.makeRequest('ping'); + } +} + +// Usage example +async function main() { + try { + // Generate authentication token + const authToken = execSync( + 'python3 -m mcpgateway.utils.create_jwt_token --username admin --exp 10080 --secret my-test-key', + { encoding: 'utf8' } + ).trim(); + + // Create client and initialize + const client = new MCPGatewayClient('http://localhost:4444', authToken); + + // Initialize session + const initResult = await client.initialize(); + console.log('Initialized:', JSON.stringify(initResult, null, 2)); + + // List available tools + const tools = await client.listTools(); + console.log('Tools:', JSON.stringify(tools, null, 2)); + + // Call a tool if available + if (tools.result.tools.length > 0) { + const toolName = tools.result.tools[0].name; + const result = await client.callTool(toolName, { timezone: 'UTC' }); + console.log('Tool result:', JSON.stringify(result, null, 2)); + } + + // Test ping + const pingResult = await client.ping(); + console.log('Ping:', JSON.stringify(pingResult, null, 2)); + + } catch (error) { + console.error('Error:', error.message); + if (error.response) { + console.error('Response:', error.response.data); + } + } +} + +if (require.main === module) { + main(); +} + +module.exports = MCPGatewayClient; +``` + +## Best Practices + +### 1. Session Management + +- Always initialize before making other requests +- Send the initialized notification after successful initialization +- Use unique request IDs for correlation +- Handle errors gracefully and implement retry logic + +### 2. Authentication + +- Store tokens securely and refresh them before expiration +- Use environment variables for token management +- Implement proper token validation and error handling + +### 3. Transport Selection + +- Use HTTP JSON-RPC for simple request-response patterns +- Use SSE for real-time communication and long-running operations +- Use STDIO for desktop client integration + +### 4. Error Handling + +- Check for JSON-RPC error objects in all responses +- Implement appropriate retry strategies for transient errors +- Log errors with sufficient context for debugging + +### 5. Performance Optimization + +- Reuse HTTP connections when possible +- Implement proper timeout configurations +- Cache tool schemas and capabilities to reduce redundant calls + +## Further Reading + +- [MCP Protocol Specification](https://modelcontextprotocol.io/) +- [JSON-RPC 2.0 Specification](https://www.jsonrpc.org/specification) +- [MCP Gateway Documentation](https://ibm.github.io/mcp-context-forge/) +- [MCP Gateway Admin UI Guide](../manage/index.md) +- [MCP Gateway Wrapper Documentation](mcpgateway-wrapper.md) diff --git a/docs/docs/manage/.pages b/docs/docs/manage/.pages index 4f0f1885b..07d89c902 100644 --- a/docs/docs/manage/.pages +++ b/docs/docs/manage/.pages @@ -2,10 +2,16 @@ nav: - index.md - backup.md - bulk-import.md + - export-import.md + - export-import-tutorial.md + - export-import-reference.md - logging.md - logging-examples.md - - upgrade.md - - tuning.md + - observability.md + - observability + - proxy.md - securing.md + - tuning.md - ui-customization.md - - observability + - upgrade.md + - well-known-uris.md diff --git a/docs/docs/manage/well-known-uris.md b/docs/docs/manage/well-known-uris.md index c69508863..715b6e046 100644 --- a/docs/docs/manage/well-known-uris.md +++ b/docs/docs/manage/well-known-uris.md @@ -118,7 +118,7 @@ curl -H "Authorization: Bearer $TOKEN" \ "cache_max_age": 3600 }, { - "path": "/.well-known/security.txt", + "path": "/.well-known/security.txt", "enabled": true, "description": "Security contact information", "cache_max_age": 3600 @@ -135,7 +135,7 @@ curl -H "Authorization: Bearer $TOKEN" \ For private API gateways, the default configuration: - **Blocks all crawlers** via robots.txt -- **Minimizes information disclosure** +- **Minimizes information disclosure** - **No security.txt** (unless explicitly configured) - **Cache headers** for performance but not long-term public caching @@ -168,7 +168,7 @@ services: WELL_KNOWN_ROBOTS_TXT: | User-agent: monitoring-bot Allow: /health - + User-agent: * Disallow: / WELL_KNOWN_SECURITY_TXT: | @@ -191,7 +191,7 @@ data: WELL_KNOWN_ROBOTS_TXT: | User-agent: * Disallow: / - + # Private API - No public crawling WELL_KNOWN_SECURITY_TXT: | Contact: mailto:security@example.com @@ -216,7 +216,7 @@ config: User-agent: internal-monitor Allow: /health Allow: /metrics - + User-agent: * Disallow: / securityTxt: | @@ -314,4 +314,4 @@ grep -E "(/.well-known/|robots|security\.txt)" /var/log/access.log | \ - [Security Guide](securing.md) - General security configuration - [ADR-015](../architecture/adr/015-well-known-uri-handler.md) - Architecture decision record - [Export/Import](export-import.md) - Configuration management -- [Environment Variables](../overview/index.md) - Complete configuration reference \ No newline at end of file +- [Environment Variables](../overview/index.md) - Complete configuration reference diff --git a/docs/docs/overview/.pages b/docs/docs/overview/.pages index b268a6056..31eef9d2f 100644 --- a/docs/docs/overview/.pages +++ b/docs/docs/overview/.pages @@ -4,4 +4,5 @@ nav: - features.md - ui.md - ui-concepts.md + - passthrough.md - tags.md diff --git a/docs/docs/testing/.pages b/docs/docs/testing/.pages index 4e8cba86c..89fcee2e7 100644 --- a/docs/docs/testing/.pages +++ b/docs/docs/testing/.pages @@ -3,3 +3,4 @@ nav: - basic.md - performance.md - acceptance.md + - fuzzing.md diff --git a/docs/docs/using/.pages b/docs/docs/using/.pages index b2e7fc1e9..12c0f2639 100644 --- a/docs/docs/using/.pages +++ b/docs/docs/using/.pages @@ -3,6 +3,7 @@ nav: - mcpgateway-wrapper.md - mcpgateway-translate.md - reverse-proxy.md + - multi-auth-headers.md - Clients: clients - Agents: agents - Servers: servers diff --git a/docs/docs/using/servers/go-fast-time-server.md b/docs/docs/using/servers/go-fast-time-server.md index cc42355d0..b9a709ae2 100644 --- a/docs/docs/using/servers/go-fast-time-server.md +++ b/docs/docs/using/servers/go-fast-time-server.md @@ -428,6 +428,666 @@ curl -H "Authorization: Bearer $TOKEN" \ http://localhost:8080/api/v1/time ``` +## Developer Guide: Raw JSON-RPC Protocol Usage + +This section demonstrates how to interact with the fast-time-server using raw MCP JSON-RPC commands via curl or stdio. This is useful for developers who want to understand the underlying protocol or integrate with the server at a low level. + +### JSON-RPC Over HTTP + +When running in HTTP mode (`-transport=http`), the server accepts MCP JSON-RPC 2.0 messages over HTTP. + +#### Running the Server + +```bash +# Start in HTTP mode +./fast-time-server -transport=http -port=8080 + +# Or in dual mode (both MCP and REST) +./fast-time-server -transport=dual -port=8080 +``` + +#### Complete Session Example + +Here's a complete session showing the full MCP protocol flow: + +```bash +#!/bin/bash +# Complete MCP JSON-RPC session example + +SERVER="http://localhost:8080/http" # Use /http endpoint in dual mode +# SERVER="http://localhost:8080/" # Root endpoint in http-only mode + +echo "=== MCP JSON-RPC Session with fast-time-server ===" + +# Function to make JSON-RPC calls with pretty output +call_mcp() { + echo "Request: $1" + echo "Response:" + curl -s -X POST "$SERVER" \ + -H "Content-Type: application/json" \ + -d "$1" | jq '.' + echo "---" +} + +# 1. Initialize the MCP connection +echo "=== Step 1: Initialize ===" +call_mcp '{ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2025-03-26", + "capabilities": { + "tools": {}, + "resources": {}, + "prompts": {} + }, + "clientInfo": { + "name": "curl-client", + "version": "1.0" + } + } +}' + +# 2. Send initialized notification +echo "=== Step 2: Send Initialized Notification ===" +call_mcp '{ + "jsonrpc": "2.0", + "method": "notifications/initialized", + "params": {} +}' + +# 3. List available tools +echo "=== Step 3: List Tools ===" +call_mcp '{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/list", + "params": {} +}' + +# 4. List available resources +echo "=== Step 4: List Resources ===" +call_mcp '{ + "jsonrpc": "2.0", + "id": 3, + "method": "resources/list", + "params": {} +}' + +# 5. List available prompts +echo "=== Step 5: List Prompts ===" +call_mcp '{ + "jsonrpc": "2.0", + "id": 4, + "method": "prompts/list", + "params": {} +}' + +# 6. Call get_system_time tool (UTC) +echo "=== Step 6: Get System Time (UTC) ===" +call_mcp '{ + "jsonrpc": "2.0", + "id": 5, + "method": "tools/call", + "params": { + "name": "get_system_time", + "arguments": {} + } +}' + +# 7. Call get_system_time tool (specific timezone) +echo "=== Step 7: Get System Time (New York) ===" +call_mcp '{ + "jsonrpc": "2.0", + "id": 6, + "method": "tools/call", + "params": { + "name": "get_system_time", + "arguments": { + "timezone": "America/New_York" + } + } +}' + +# 8. Call convert_time tool +echo "=== Step 8: Convert Time ===" +call_mcp '{ + "jsonrpc": "2.0", + "id": 7, + "method": "tools/call", + "params": { + "name": "convert_time", + "arguments": { + "time": "2025-01-15T14:00:00Z", + "source_timezone": "UTC", + "target_timezone": "Asia/Tokyo" + } + } +}' + +# 9. Read a resource +echo "=== Step 9: Read Resource (timezone info) ===" +call_mcp '{ + "jsonrpc": "2.0", + "id": 8, + "method": "resources/read", + "params": { + "uri": "timezone://info" + } +}' + +# 10. Get a prompt +echo "=== Step 10: Get Prompt ===" +call_mcp '{ + "jsonrpc": "2.0", + "id": 9, + "method": "prompts/get", + "params": { + "name": "compare_timezones", + "arguments": { + "timezones": "UTC,America/New_York,Europe/London" + } + } +}' + +echo "=== Session Complete ===" +``` + +#### Individual Command Examples + +**Initialize Connection:** +```bash +curl -X POST http://localhost:8080/http \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2025-03-26", + "capabilities": { + "tools": {}, + "resources": {}, + "prompts": {} + }, + "clientInfo": { + "name": "curl-client", + "version": "1.0" + } + } + }' +``` + +**List Available Tools:** +```bash +curl -X POST http://localhost:8080/http \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/list", + "params": {} + }' +``` + +**Call Tool - Get Current Time:** +```bash +# UTC time (default) +curl -X POST http://localhost:8080/http \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": { + "name": "get_system_time", + "arguments": {} + } + }' + +# Specific timezone +curl -X POST http://localhost:8080/http \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 4, + "method": "tools/call", + "params": { + "name": "get_system_time", + "arguments": { + "timezone": "Europe/Dublin" + } + } + }' +``` + +**Call Tool - Convert Time:** +```bash +curl -X POST http://localhost:8080/http \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 5, + "method": "tools/call", + "params": { + "name": "convert_time", + "arguments": { + "time": "2025-01-15T10:00:00", + "source_timezone": "Europe/Dublin", + "target_timezone": "America/New_York" + } + } + }' +``` + +**Read Resources:** +```bash +# List all resources +curl -X POST http://localhost:8080/http \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 6, + "method": "resources/list", + "params": {} + }' + +# Read specific resource +curl -X POST http://localhost:8080/http \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 7, + "method": "resources/read", + "params": { + "uri": "time://current/world" + } + }' +``` + +**Work with Prompts:** +```bash +# List all prompts +curl -X POST http://localhost:8080/http \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 8, + "method": "prompts/list", + "params": {} + }' + +# Get a prompt with arguments +curl -X POST http://localhost:8080/http \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 9, + "method": "prompts/get", + "params": { + "name": "schedule_meeting", + "arguments": { + "participants": "New York,London,Tokyo", + "duration": "60", + "preferred_hours": "9 AM - 5 PM" + } + } + }' +``` + +### JSON-RPC Over STDIO + +When running in stdio mode (`-transport=stdio`), the server communicates via standard input/output using newline-delimited JSON. + +#### Testing STDIO Mode + +```bash +# Start the server in stdio mode +./fast-time-server -transport=stdio -log-level=error + +# The server is now waiting for JSON-RPC messages on stdin +# Each message should be on a single line +``` + +#### STDIO Session Example + +```bash +#!/bin/bash +# Test stdio mode with a script + +echo "=== Testing STDIO Mode ===" + +# Start the server in background and capture its PID +./fast-time-server -transport=stdio -log-level=error & +SERVER_PID=$! + +# Function to send JSON-RPC message and read response +send_message() { + echo "$1" | ./fast-time-server -transport=stdio -log-level=error 2>/dev/null +} + +# Initialize +echo "Initializing..." +INIT_RESPONSE=$(send_message '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"script-client","version":"1.0"}}}') +echo "Response: $INIT_RESPONSE" + +# Send initialized notification +echo "Sending initialized..." +send_message '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}' + +# List tools +echo "Listing tools..." +TOOLS_RESPONSE=$(send_message '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}') +echo "Response: $TOOLS_RESPONSE" + +# Get current time +echo "Getting current time..." +TIME_RESPONSE=$(send_message '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_system_time","arguments":{"timezone":"UTC"}}}') +echo "Response: $TIME_RESPONSE" + +# Clean up +kill $SERVER_PID 2>/dev/null +echo "=== STDIO Test Complete ===" +``` + +#### Interactive STDIO Testing + +For interactive testing, you can use a simple script or tools like `nc` (netcat): + +```bash +# Method 1: Direct pipe interaction +echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | ./fast-time-server -transport=stdio -log-level=error + +# Method 2: Using a here document +./fast-time-server -transport=stdio -log-level=error << 'EOF' +{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}} +{"jsonrpc":"2.0","method":"notifications/initialized","params":{}} +{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}} +{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_system_time","arguments":{}}} +EOF +``` + +### Authentication with JSON-RPC + +When the server is running with authentication (`-auth-token=secret`): + +```bash +# Start server with authentication +./fast-time-server -transport=http -port=8080 -auth-token=mysecret + +# Include Bearer token in HTTP headers +curl -X POST http://localhost:8080/http \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer mysecret" \ + -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2025-03-26", + "capabilities": {}, + "clientInfo": {"name": "auth-client", "version": "1.0"} + } + }' +``` + +### Expected Response Formats + +**Successful Initialize Response:** +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "protocolVersion": "2025-03-26", + "capabilities": { + "tools": { + "listChanged": false + }, + "resources": { + "subscribe": false, + "listChanged": false + }, + "prompts": { + "listChanged": false + } + }, + "serverInfo": { + "name": "fast-time-server", + "version": "1.5.0" + } + } +} +``` + +**Tools List Response:** +```json +{ + "jsonrpc": "2.0", + "id": 2, + "result": { + "tools": [ + { + "name": "get_system_time", + "description": "Get current system time in specified timezone", + "inputSchema": { + "type": "object", + "properties": { + "timezone": { + "type": "string", + "description": "IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Defaults to UTC if not specified." + } + } + } + }, + { + "name": "convert_time", + "description": "Convert time between different timezones", + "inputSchema": { + "type": "object", + "properties": { + "time": { + "type": "string", + "description": "Time to convert in RFC3339 format or common formats like '2006-01-02 15:04:05'" + }, + "source_timezone": { + "type": "string", + "description": "Source IANA timezone name" + }, + "target_timezone": { + "type": "string", + "description": "Target IANA timezone name" + } + }, + "required": ["time", "source_timezone", "target_timezone"] + } + } + ] + } +} +``` + +**Tool Call Response:** +```json +{ + "jsonrpc": "2.0", + "id": 3, + "result": { + "content": [ + { + "type": "text", + "text": "2025-01-15T16:30:45Z" + } + ], + "isError": false + } +} +``` + +**Error Response:** +```json +{ + "jsonrpc": "2.0", + "id": 4, + "result": { + "content": [ + { + "type": "text", + "text": "invalid timezone \"Invalid/Zone\": unknown time zone Invalid/Zone" + } + ], + "isError": true + } +} +``` + +### JSON-RPC Troubleshooting + +**Common Issues:** + +1. **Connection Refused** + ```bash + # Check if server is running + curl -f http://localhost:8080/health || echo "Server not running" + + # Check what's listening on the port + lsof -i :8080 + ``` + +2. **Invalid JSON-RPC Format** + ```bash + # Ensure proper JSON-RPC 2.0 format + # Must include: jsonrpc, method, id (for requests) + curl -X POST http://localhost:8080/http \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' + ``` + +3. **Missing Content-Type Header** + ```bash + # Always include Content-Type for POST requests + curl -X POST http://localhost:8080/http \ + -H "Content-Type: application/json" \ + -d '...' + ``` + +4. **Authentication Errors** + ```bash + # Include Bearer token when server uses authentication + curl -X POST http://localhost:8080/http \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your-token" \ + -d '...' + ``` + +5. **STDIO Mode Issues** + - Ensure each JSON message is on a single line + - Use `-log-level=error` or `-log-level=none` to avoid log interference + - Check that the binary has proper permissions + +### Integration Examples + +**Python Integration:** +```python +import json +import requests +import subprocess + +class MCPClient: + def __init__(self, base_url): + self.base_url = base_url + self.session = requests.Session() + self.request_id = 0 + + def call(self, method, params=None): + self.request_id += 1 + payload = { + "jsonrpc": "2.0", + "id": self.request_id, + "method": method, + "params": params or {} + } + + response = self.session.post( + f"{self.base_url}/http", + json=payload, + headers={"Content-Type": "application/json"} + ) + + return response.json() + + def initialize(self): + return self.call("initialize", { + "protocolVersion": "2025-03-26", + "capabilities": {}, + "clientInfo": {"name": "python-client", "version": "1.0"} + }) + + def get_time(self, timezone="UTC"): + return self.call("tools/call", { + "name": "get_system_time", + "arguments": {"timezone": timezone} + }) + +# Usage +client = MCPClient("http://localhost:8080") +client.initialize() +time_result = client.get_time("America/New_York") +print(time_result["result"]["content"][0]["text"]) +``` + +**Node.js Integration:** +```javascript +const axios = require('axios'); + +class MCPClient { + constructor(baseUrl) { + this.baseUrl = baseUrl; + this.requestId = 0; + } + + async call(method, params = {}) { + this.requestId++; + const payload = { + jsonrpc: "2.0", + id: this.requestId, + method, + params + }; + + const response = await axios.post(`${this.baseUrl}/http`, payload, { + headers: { 'Content-Type': 'application/json' } + }); + + return response.data; + } + + async initialize() { + return this.call("initialize", { + protocolVersion: "2025-03-26", + capabilities: {}, + clientInfo: { name: "node-client", version: "1.0" } + }); + } + + async getTime(timezone = "UTC") { + return this.call("tools/call", { + name: "get_system_time", + arguments: { timezone } + }); + } +} + +// Usage +(async () => { + const client = new MCPClient("http://localhost:8080"); + await client.initialize(); + const result = await client.getTime("Asia/Tokyo"); + console.log(result.result.content[0].text); +})(); +``` + ## Claude Desktop Configuration Add to `claude_desktop_config.json`: diff --git a/tests/unit/mcpgateway/test_well_known.py b/tests/unit/mcpgateway/test_well_known.py index 38fd5c095..eb5093b18 100644 --- a/tests/unit/mcpgateway/test_well_known.py +++ b/tests/unit/mcpgateway/test_well_known.py @@ -60,20 +60,20 @@ class TestSecurityTxtValidation: def test_validate_security_txt_empty(self): """Test validation with empty content.""" from mcpgateway.routers.well_known import validate_security_txt - + result = validate_security_txt("") assert result is None - + result = validate_security_txt(None) assert result is None def test_validate_security_txt_adds_expires(self): """Test that validation adds Expires field.""" from mcpgateway.routers.well_known import validate_security_txt - + content = "Contact: security@example.com" result = validate_security_txt(content) - + assert result is not None assert "Contact: security@example.com" in result assert "Expires:" in result @@ -82,10 +82,10 @@ def test_validate_security_txt_adds_expires(self): def test_validate_security_txt_preserves_expires(self): """Test that validation preserves existing Expires field.""" from mcpgateway.routers.well_known import validate_security_txt - + content = "Contact: security@example.com\nExpires: 2025-12-31T23:59:59Z" result = validate_security_txt(content) - + assert result is not None assert "Expires: 2025-12-31T23:59:59Z" in result # Should not add a second Expires field @@ -94,10 +94,10 @@ def test_validate_security_txt_preserves_expires(self): def test_validate_security_txt_preserves_comments(self): """Test that validation preserves existing comments.""" from mcpgateway.routers.well_known import validate_security_txt - + content = "# Custom security information\nContact: security@example.com" result = validate_security_txt(content) - + assert result is not None assert "# Custom security information" in result assert "Contact: security@example.com" in result @@ -110,9 +110,9 @@ class TestWellKnownRegistry: def test_registry_contains_standard_files(self): """Test that registry contains expected standard files.""" from mcpgateway.routers.well_known import WELL_KNOWN_REGISTRY - + expected_files = ["robots.txt", "security.txt", "ai.txt", "dnt-policy.txt", "change-password"] - + for file in expected_files: assert file in WELL_KNOWN_REGISTRY assert "content_type" in WELL_KNOWN_REGISTRY[file] @@ -122,9 +122,9 @@ def test_registry_contains_standard_files(self): def test_registry_content_types(self): """Test that registry has correct content types.""" from mcpgateway.routers.well_known import WELL_KNOWN_REGISTRY - + # Most should be text/plain text_files = ["robots.txt", "security.txt", "ai.txt", "dnt-policy.txt", "change-password"] - + for file in text_files: - assert WELL_KNOWN_REGISTRY[file]["content_type"] == "text/plain" \ No newline at end of file + assert WELL_KNOWN_REGISTRY[file]["content_type"] == "text/plain"