MCP (Model Context Protocol) Developer Guide

The Model Context Protocol (MCP) enables Ragwalla agents to connect with external tools and services through a standardized interface. This guide covers everything you need to integrate MCP servers with Ragwalla.

Table of Contents

Overview

MCP provides a standard way for AI assistants to interact with external systems. Key benefits:

  • Language Agnostic: Build MCP servers in any language
  • Tool Discovery: Agents automatically discover available tools
  • Standardized Protocol: Consistent interface across all integrations
  • Security: Built-in authentication and transport encryption
  • Flexibility: Support for various transport mechanisms

How MCP Works

┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│   Agent     │────▶│   Ragwalla   │────▶│ MCP Server  │
│             │◀────│   MCP Client │◀────│             │
└─────────────┘     └──────────────┘     └─────────────┘
     1. Tool Request    2. Forward         3. Execute
     6. Use Result      5. Return          4. Result

Quick Start

1. Deploy an MCP Server

Deploy a simple MCP server to Cloudflare Workers:

npm create cloudflare@latest -- my-mcp-server --template=cloudflare/ai/demos/remote-mcp-authless
cd my-mcp-server
npm run deploy

This creates an MCP server at: https://my-mcp-server.<your-account>.workers.dev/mcp

2. Register the MCP Server with Ragwalla

const mcpServer = await fetch('https://example.ai.ragwalla.com/v1/mcp-servers', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'my-tools-server',
    url: 'https://my-mcp-server.example.workers.dev/mcp',
    description: 'Custom tools for my agents'
  })
});

const server = await mcpServer.json();
console.log('Registered MCP server:', server.id);

3. Discover Available Tools

const tools = await fetch(
  `https://example.ai.ragwalla.com/v1/mcp-servers/${server.id}/tools`,
  { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
).then(r => r.json());

console.log('Available tools:', tools);
// Output: [
//   { name: 'get_weather', description: 'Get weather for a location' },
//   { name: 'search_news', description: 'Search recent news articles' }
// ]

4. Add MCP Tools to Your Agent

// Add a specific tool to an agent
await fetch(`https://example.ai.ragwalla.com/v1/agents/${agentId}/tools`, {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    type: 'mcp',
    mcp: {
      serverId: server.id,
      toolName: 'get_weather'
    }
  })
});

5. Use the Agent

const ws = new WebSocket(`wss://example.ai.ragwalla.com/v1/agents/${agentName}/main?token=${token}`);

ws.onopen = () => {
  ws.send(JSON.stringify({
    type: 'message',
    content: "What's the weather in San Francisco?"
  }));
};

// The agent will automatically use the MCP tool to get weather data

Creating MCP Servers

MCP Server Structure

An MCP server must implement these endpoints:

interface MCPServer {
  // List available tools
  '/tools/list': () => Tool[]

  // Execute a tool
  '/tools/call': (request: ToolCallRequest) => ToolCallResponse

  // Optional: List resources
  '/resources/list': () => Resource[]

  // Optional: Read resource contents
  '/resources/read': (uri: string) => ResourceContent
}

Basic MCP Server Example

// Cloudflare Worker MCP Server
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

export class MyMCPServer extends McpAgent {
  get server() {
    return this._mcpServer;
  }

  private _mcpServer = new McpServer({
    name: "My MCP Server",
    version: "1.0.0",
    description: "Custom tools for my application"
  });

  async init() {
    // Define a weather tool
    this._mcpServer.tool(
      "get_weather",
      "Get current weather for a location",
      {
        location: z.string().describe("City name"),
        units: z.enum(["celsius", "fahrenheit"]).default("celsius")
      },
      async ({ location, units }) => {
        // Tool implementation
        const weather = await fetchWeatherData(location, units);
        return {
          content: [{
            type: "text",
            text: JSON.stringify(weather)
          }]
        };
      }
    );

    // Define a search tool
    this._mcpServer.tool(
      "search_database",
      "Search internal database",
      {
        query: z.string().describe("Search query"),
        limit: z.number().default(10)
      },
      async ({ query, limit }) => {
        const results = await searchDatabase(query, limit);
        return {
          content: [{
            type: "text",
            text: JSON.stringify(results)
          }]
        };
      }
    );
  }
}

export default {
  fetch(request, env, ctx) {
    const url = new URL(request.url);

    if (url.pathname === "/mcp") {
      return MyMCPServer.serve("/mcp").fetch(request, env, ctx);
    }

    return new Response("MCP Server", { status: 200 });
  }
};

Registering MCP Servers

Server Configuration

const serverConfig = {
  name: 'enterprise-tools',
  url: 'https://tools.company.com/mcp',
  description: 'Internal company tools and data access',

  // Optional: Authentication
  authRequired: true,
  authConfig: {
    type: 'bearer',
    token: 'YOUR_MCP_SERVER_TOKEN'
  },

  // Optional: Custom headers
  headers: {
    'X-API-Version': '2.0',
    'X-Company-ID': 'acme-corp'
  },

  // Optional: Metadata
  metadata: {
    department: 'engineering',
    owner: 'platform-team'
  }
};

const response = await fetch('https://example.ai.ragwalla.com/v1/mcp-servers', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(serverConfig)
});

Managing MCP Servers

// List all MCP servers
const servers = await fetch('https://example.ai.ragwalla.com/v1/mcp-servers', {
  headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
}).then(r => r.json());

// Update server configuration
await fetch(`https://example.ai.ragwalla.com/v1/mcp-servers/${serverId}`, {
  method: 'PUT',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    description: 'Updated description',
    authConfig: { type: 'bearer', token: 'NEW_TOKEN' }
  })
});

// Test connection
const testResult = await fetch(
  `https://example.ai.ragwalla.com/v1/mcp-servers/${serverId}/test`,
  { 
    method: 'POST',
    headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
  }
).then(r => r.json());

console.log('Connection test:', testResult.success ? 'Passed' : 'Failed');

// Delete server
await fetch(`https://example.ai.ragwalla.com/v1/mcp-servers/${serverId}`, {
  method: 'DELETE',
  headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
});

Using MCP Tools in Agents

Adding MCP Tools to Agents

// Add all tools from an MCP server
const tools = await discoverTools(serverId);
for (const tool of tools) {
  await fetch(`https://example.ai.ragwalla.com/v1/agents/${agentId}/tools`, {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      type: 'mcp',
      mcp: {
        serverId: serverId,
        toolName: tool.name
      }
    })
  });
}

// Or add specific tools with custom descriptions
await addToolToAgent(agentId, {
  type: 'mcp',
  mcp: {
    serverId: serverId,
    toolName: 'complex_calculation'
  },
  // Override the description for this agent
  description: 'Perform advanced mathematical calculations',
  metadata: {
    category: 'math',
    complexity: 'high'
  }
});

MCP Tool Execution Flow

When an agent uses an MCP tool:

  1. Agent receives user message requiring external data/computation
  2. LLM decides to use MCP tool based on available tools
  3. Ragwalla MCP client calls the server with tool name and arguments
  4. MCP server executes the tool and returns results
  5. Agent incorporates results into the response

Example: Weather Assistant with MCP

// Create agent with MCP weather tools
const weatherAgent = await createAgent({
  name: 'weather-assistant',
  description: 'Provides weather information and forecasts',
  instructions: 'You are a helpful weather assistant. Use the available tools to get current weather and forecasts.',
  tools: [
    {
      type: 'mcp',
      mcp: {
        serverId: weatherServerId,
        toolName: 'get_current_weather'
      }
    },
    {
      type: 'mcp',
      mcp: {
        serverId: weatherServerId,
        toolName: 'get_forecast'
      }
    },
    {
      type: 'mcp',
      mcp: {
        serverId: weatherServerId,
        toolName: 'get_alerts'
      }
    }
  ]
});

// Use the agent
const ws = connectToAgent('weather-assistant');
ws.send({
  type: 'message',
  content: 'Is it going to rain in Seattle this week?'
});

// Agent response flow:
// 1. Calls get_forecast tool via MCP
// 2. Analyzes forecast data
// 3. Provides natural language summary

Building Custom MCP Servers

Python MCP Server Example

from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
import mcp.server.stdio
import mcp.types as types

# Create server instance
server = Server("python-tools")

@server.list_tools()
async def list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="analyze_data",
            description="Analyze dataset and return insights",
            inputSchema={
                "type": "object",
                "properties": {
                    "dataset_id": {"type": "string"},
                    "analysis_type": {"type": "string", "enum": ["summary", "correlation", "trends"]}
                },
                "required": ["dataset_id", "analysis_type"]
            }
        ),
        types.Tool(
            name="generate_report",
            description="Generate PDF report from data",
            inputSchema={
                "type": "object",
                "properties": {
                    "data": {"type": "object"},
                    "template": {"type": "string"}
                },
                "required": ["data", "template"]
            }
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "analyze_data":
        result = await analyze_dataset(
            arguments["dataset_id"],
            arguments["analysis_type"]
        )
        return [types.TextContent(type="text", text=json.dumps(result))]

    elif name == "generate_report":
        report_url = await generate_pdf_report(
            arguments["data"],
            arguments["template"]
        )
        return [types.TextContent(type="text", text=f"Report generated: {report_url}")]

    raise ValueError(f"Unknown tool: {name}")

# Run the server
async def main():
    async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            InitializationOptions(
                server_name="python-tools",
                server_version="1.0.0"
            )
        )

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

Node.js MCP Server Example

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

const server = new Server(
  {
    name: 'nodejs-tools',
    version: '1.0.0',
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// Define tools
server.setRequestHandler('tools/list', async () => ({
  tools: [
    {
      name: 'fetch_api_data',
      description: 'Fetch data from external API',
      inputSchema: {
        type: 'object',
        properties: {
          endpoint: { type: 'string' },
          method: { type: 'string', enum: ['GET', 'POST'] },
          headers: { type: 'object' }
        },
        required: ['endpoint', 'method']
      }
    }
  ]
}));

// Handle tool calls
server.setRequestHandler('tools/call', async (request) => {
  const { name, arguments: args } = request.params;

  if (name === 'fetch_api_data') {
    const response = await fetch(args.endpoint, {
      method: args.method,
      headers: args.headers || {}
    });
    const data = await response.json();

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(data, null, 2)
        }
      ]
    };
  }

  throw new Error(`Unknown tool: ${name}`);
});

// Start server
const transport = new StdioServerTransport();
await server.connect(transport);

Authentication

Bearer Token Authentication

// Register server with bearer token auth
const server = await registerMCPServer({
  name: 'secure-tools',
  url: 'https://api.company.com/mcp',
  authRequired: true,
  authConfig: {
    type: 'bearer',
    token: process.env.MCP_SERVER_TOKEN
  }
});

Custom Headers Authentication

// Use custom headers for authentication
const server = await registerMCPServer({
  name: 'custom-auth-tools',
  url: 'https://api.company.com/mcp',
  headers: {
    'X-API-Key': process.env.API_KEY,
    'X-Client-ID': process.env.CLIENT_ID
  }
});

OAuth Authentication (Coming Soon)

// Future support for OAuth
const server = await registerMCPServer({
  name: 'oauth-tools',
  url: 'https://api.company.com/mcp',
  authRequired: true,
  authConfig: {
    type: 'oauth2',
    clientId: process.env.OAUTH_CLIENT_ID,
    clientSecret: process.env.OAUTH_CLIENT_SECRET,
    tokenUrl: 'https://auth.company.com/token'
  }
});

Transport Options

HTTP Transport (Default)

// Standard HTTP transport
const server = await registerMCPServer({
  name: 'http-tools',
  url: 'https://tools.example.com/mcp',
  transport: 'http'  // Optional, this is the default
});

WebSocket Transport (Coming Soon)

// For real-time, bidirectional communication
const server = await registerMCPServer({
  name: 'websocket-tools',
  url: 'wss://tools.example.com/mcp',
  transport: 'websocket'
});

Server-Sent Events (SSE)

// For servers that push updates
const server = await registerMCPServer({
  name: 'sse-tools',
  url: 'https://tools.example.com/sse',
  transport: 'sse',
  // SSE is used for initialization, HTTP for tool calls
  skipSSE: false  // Set to true to use HTTP only
});

Best Practices

1. Tool Design

  • Single Purpose: Each tool should do one thing well
  • Clear Naming: Use descriptive, action-oriented names
  • Rich Descriptions: Help the LLM understand when to use each tool
  • Input Validation: Use schemas to validate inputs
// Good tool design
this._mcpServer.tool(
  "search_customer_orders",
  "Search customer orders by various criteria. Returns order details including status, items, and shipping information.",
  {
    customerId: z.string().optional().describe("Customer ID to filter by"),
    status: z.enum(["pending", "shipped", "delivered", "cancelled"]).optional(),
    dateFrom: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("Start date (YYYY-MM-DD)"),
    dateTo: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("End date (YYYY-MM-DD)"),
    limit: z.number().min(1).max(100).default(20)
  },
  async (params) => {
    // Implementation
  }
);

2. Error Handling

this._mcpServer.tool(
  "process_data",
  "Process data with comprehensive error handling",
  { data: z.any() },
  async ({ data }) => {
    try {
      const result = await processData(data);
      return {
        content: [{
          type: "text",
          text: JSON.stringify({ success: true, result })
        }]
      };
    } catch (error) {
      // Return error in a format the LLM can understand
      return {
        content: [{
          type: "text",
          text: JSON.stringify({
            success: false,
            error: error.message,
            errorType: error.constructor.name,
            suggestion: "Please check the input data format"
          })
        }]
      };
    }
  }
);

3. Performance Optimization

// Implement caching for expensive operations
const cache = new Map();

this._mcpServer.tool(
  "expensive_calculation",
  "Perform complex calculation with caching",
  { input: z.any() },
  async ({ input }) => {
    const cacheKey = JSON.stringify(input);

    if (cache.has(cacheKey)) {
      return {
        content: [{
          type: "text",
          text: cache.get(cacheKey)
        }]
      };
    }

    const result = await performExpensiveCalculation(input);
    cache.set(cacheKey, result);

    // Implement cache expiration
    setTimeout(() => cache.delete(cacheKey), 3600000); // 1 hour

    return {
      content: [{
        type: "text",
        text: result
      }]
    };
  }
);

4. Security

  • Input Sanitization: Always validate and sanitize inputs
  • Rate Limiting: Implement rate limits for resource-intensive tools
  • Access Control: Verify permissions before executing sensitive operations
  • Audit Logging: Log all tool invocations for security analysis
this._mcpServer.tool(
  "delete_record",
  "Delete a record with security checks",
  {
    recordId: z.string().uuid(),
    confirmationToken: z.string()
  },
  async ({ recordId, confirmationToken }, context) => {
    // Verify permissions
    if (!context.user?.permissions?.includes('delete')) {
      throw new Error('Insufficient permissions');
    }

    // Validate confirmation token
    if (!isValidToken(confirmationToken)) {
      throw new Error('Invalid confirmation token');
    }

    // Log the action
    await auditLog({
      action: 'delete_record',
      recordId,
      user: context.user.id,
      timestamp: new Date()
    });

    // Perform deletion
    await deleteRecord(recordId);

    return {
      content: [{
        type: "text",
        text: `Record ${recordId} deleted successfully`
      }]
    };
  }
);

5. Documentation

Document your MCP tools thoroughly:

this._mcpServer.tool(
  "analyze_sentiment",
  `Analyze sentiment of text using advanced NLP.

   Returns sentiment scores:
   - positive: 0.0 to 1.0
   - negative: 0.0 to 1.0  
   - neutral: 0.0 to 1.0
   - compound: -1.0 to 1.0 (overall sentiment)

   Also returns detected emotions: joy, anger, fear, sadness, surprise`,
  {
    text: z.string().min(1).max(5000).describe("Text to analyze (max 5000 chars)"),
    language: z.string().default("en").describe("ISO 639-1 language code"),
    options: z.object({
      includeEmotions: z.boolean().default(true),
      includeKeyPhrases: z.boolean().default(false)
    }).optional()
  },
  async ({ text, language, options }) => {
    // Implementation
  }
);

Troubleshooting

Common Issues

1. Connection Timeouts

// Increase timeout for slow servers
const server = await registerMCPServer({
  name: 'slow-tools',
  url: 'https://tools.example.com/mcp',
  timeout: 60000,  // 60 seconds
  skipSSE: true    // Use HTTP only to avoid SSE timeouts
});

2. Tool Discovery Fails

// Debug tool discovery
try {
  const response = await fetch(
    `https://example.ai.ragwalla.com/v1/mcp-servers/${serverId}/tools`,
    { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
  );

  if (!response.ok) {
    const error = await response.json();
    console.error('Discovery failed:', error);

    // Common fixes:
    // 1. Check server URL is correct
    // 2. Verify authentication credentials
    // 3. Ensure server implements /tools/list endpoint
    // 4. Check network connectivity
  }
} catch (error) {
  console.error('Network error:', error);
}

3. Tool Execution Errors

// Handle tool execution errors gracefully
ws.onmessage = (event) => {
  const data = JSON.parse(event.data);

  if (data.type === 'tool_result' && data.error) {
    console.error('Tool execution failed:', {
      tool: data.tool,
      error: data.error,
      // Common causes:
      // - Invalid input parameters
      // - Server-side errors
      // - Network issues
      // - Authentication failures
    });
  }
};

4. Authentication Issues

// Test authentication separately
const testAuth = async (server) => {
  const response = await fetch(server.url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${server.authConfig.token}`,
      ...server.headers
    },
    body: JSON.stringify({
      jsonrpc: '2.0',
      method: 'tools/list',
      params: {},
      id: 1
    })
  });

  if (response.status === 401) {
    console.error('Authentication failed. Check your credentials.');
  }

  return response.ok;
};

Debug Mode

Enable detailed logging for troubleshooting:

// Set environment variables
process.env.LOG_LEVEL = 'DEBUG';
process.env.LOG_MCP_REQUESTS = 'true';
process.env.LOG_MCP_RESPONSES = 'true';

// Or configure per-server debugging
const server = await registerMCPServer({
  name: 'debug-tools',
  url: 'https://tools.example.com/mcp',
  metadata: {
    debug: true,
    logRequests: true,
    logResponses: true
  }
});

Next Steps

For Ragwalla-specific support, visit docs.ragwalla.com or contact support@ragwalla.com.