MCP Tool Server

The GVA VoIP Agent includes an MCP (Model Context Protocol) client that connects to tool servers, extending the AI assistant's capabilities. MCP provides a standardised way for LLMs to interact with external tools, databases, and services.

Overview

graph LR subgraph "VoIP Agent" LLM[Ollama LLM] MCP[MCP Client] end subgraph "MCP Servers" WX[Weather Server] FS[Filesystem Server] FT[Fetch Server] MEM[Memory Server] MIL[Military Tools
Native C++] end LLM <--> MCP MCP <-->|stdio| WX MCP <-->|stdio| FS MCP <-->|stdio| FT MCP <-->|stdio| MEM MCP <--> MIL style MCP fill:#9C27B0 style MIL fill:#4CAF50

Architecture

MCP uses JSON-RPC 2.0 over stdio for communication between the client and tool servers. Each server runs as a separate process, providing isolation and allowing servers written in any language.

Protocol Flow

sequenceDiagram participant LLM as Ollama LLM participant MCP as MCP Client participant SRV as Tool Server Note over MCP,SRV: Initialization MCP->>SRV: initialize (protocol version) SRV->>MCP: capabilities MCP->>SRV: notifications/initialized MCP->>SRV: tools/list SRV->>MCP: available tools Note over LLM,SRV: Tool Call LLM->>MCP: call tool "get_weather" MCP->>SRV: tools/call {name, arguments} SRV->>MCP: result MCP->>LLM: tool result

Built-in Native Tools

The MCP client includes native C++ tools that don't require external servers:

Military Coordinate Tools

Tool Description Parameters
mgrs_to_latlon Convert MGRS to lat/lon mgrs: MGRS string
latlon_to_mgrs Convert lat/lon to MGRS latitude, longitude, precision
calculate_bearing Calculate bearing between points from_lat, from_lon, to_lat, to_lon
calculate_distance Calculate distance (Haversine) from_lat, from_lon, to_lat, to_lon
format_dtg Format Date-Time Group timestamp (optional), timezone

Example Usage

// Register military tools automatically
m_mcp->registerMilitaryTools();

// Call a tool
m_mcp->callTool("calculate_bearing", {
    {"from_lat", -37.8136},
    {"from_lon", 144.9631},
    {"to_lat", -33.8688},
    {"to_lon", 151.2093}
}, [](bool success, const QJsonValue& result) {
    // result: {"bearing_degrees": 54.2, "bearing_mils": 964, ...}
});

External MCP Servers

Weather Server

Provides weather forecasts via Open-Meteo API.

# Install
npm install -g @modelcontextprotocol/server-weather

# Or use npx (auto-downloads)
npx -y @modelcontextprotocol/server-weather

Tools provided:

  • get_forecast - Weather forecast for a location

Filesystem Server

Provides read/write access to a specified directory.

npx -y @modelcontextprotocol/server-filesystem /path/to/allowed/directory

Tools provided:

  • read_file - Read file contents
  • write_file - Write to file
  • list_directory - List directory contents
  • search_files - Search for files

Fetch Server

Makes HTTP requests to external URLs.

npx -y @anthropic-ai/fetch-mcp

Tools provided:

  • fetch - Make HTTP GET/POST requests

Memory Server

Provides persistent key-value storage.

npx -y @modelcontextprotocol/server-memory

Tools provided:

  • store - Store a value
  • retrieve - Retrieve a value
  • delete - Delete a value

Configuration

Adding Servers Programmatically

#include "McpToolClient.h"

// Create MCP client
auto mcp = std::make_unique<McpToolClient>();

// Add weather server
mcp->addServer(McpToolClient::weatherServer());

// Add filesystem server with root path
mcp->addServer(McpToolClient::fileSystemServer("/home/data"));

// Add custom server
McpServerConfig customServer;
customServer.name = "my-tools";
customServer.command = "npx";
customServer.args = {"-y", "@myorg/mcp-server"};
customServer.env = {"API_KEY=secret123"};
customServer.maxRestarts = 3;
customServer.restartDelayMs = 1000;
mcp->addServer(customServer);

// Start all servers
mcp->startServers();

Command Line

# Enable weather and fetch servers
./build/bin/gva-voip-agent \
    --standalone \
    --model=gemma2 \
    --mcp-weather \
    --mcp-fetch

Features

Tool Call Timeouts

Prevent hanging on slow or unresponsive tools:

// Set default timeout (30 seconds)
mcp->setDefaultTimeout(30000);

// Override per-call
mcp->callTool("slow_tool", args, callback, 60000);  // 60 seconds

// Cancel a pending call
mcp->cancelToolCall(requestId);

Auto-Reconnect

Automatically restart crashed servers with exponential backoff:

McpServerConfig config;
config.name = "weather";
config.command = "npx";
config.args = {"-y", "@modelcontextprotocol/server-weather"};
config.maxRestarts = 3;        // Max restart attempts
config.restartDelayMs = 1000;  // Initial delay (doubles each retry)

mcp->setAutoReconnect(true);   // Enable globally

Registering Native Tools

Add custom C++ tools without external processes:

mcp->registerNativeTool(
    "get_vehicle_status",
    "Get current vehicle status from DDS",
    QJsonObject{
        {"type", "object"},
        {"properties", QJsonObject{
            {"system", QJsonObject{
                {"type", "string"},
                {"description", "System name (engine, transmission, etc.)"}
            }}
        }},
        {"required", QJsonArray{"system"}}
    },
    [](const QJsonObject& args) -> QJsonValue {
        QString system = args["system"].toString();
        // Query DDS for vehicle status
        return QJsonObject{
            {"system", system},
            {"status", "operational"},
            {"temperature", 85.5}
        };
    }
);

Signals

The MCP client emits signals for monitoring:

connect(mcp, &McpToolClient::connected, []() {
    qDebug() << "MCP servers connected";
});

connect(mcp, &McpToolClient::toolsDiscovered, [](const QList<McpTool>& tools) {
    qDebug() << "Available tools:" << tools.size();
});

connect(mcp, &McpToolClient::toolCallComplete, 
    [](const QString& name, const QJsonValue& result) {
    qDebug() << "Tool" << name << "returned:" << result;
});

connect(mcp, &McpToolClient::toolCallTimeout, [](const QString& name) {
    qWarning() << "Tool" << name << "timed out";
});

connect(mcp, &McpToolClient::serverRestarting, 
    [](const QString& server, int attempt) {
    qInfo() << "Restarting" << server << "attempt" << attempt;
});

Creating Custom MCP Servers

Node.js Example

// my-mcp-server.js
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');

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

server.setRequestHandler('tools/list', async () => ({
  tools: [{
    name: 'my_tool',
    description: 'Does something useful',
    inputSchema: {
      type: 'object',
      properties: {
        input: { type: 'string', description: 'Input value' }
      },
      required: ['input']
    }
  }]
}));

server.setRequestHandler('tools/call', async (request) => {
  if (request.params.name === 'my_tool') {
    const input = request.params.arguments.input;
    return {
      content: [{ type: 'text', text: `Processed: ${input}` }]
    };
  }
  throw new Error('Unknown tool');
});

const transport = new StdioServerTransport();
server.connect(transport);

Python Example

# my_mcp_server.py
import json
import sys

def handle_request(request):
    method = request.get('method')

    if method == 'initialize':
        return {'protocolVersion': '2024-11-05', 'capabilities': {'tools': {}}}

    elif method == 'tools/list':
        return {'tools': [{
            'name': 'my_python_tool',
            'description': 'A Python tool',
            'inputSchema': {'type': 'object', 'properties': {}}
        }]}

    elif method == 'tools/call':
        name = request['params']['name']
        args = request['params'].get('arguments', {})
        return {'content': [{'type': 'text', 'text': f'Called {name}'}]}

    return {'error': {'code': -32601, 'message': 'Method not found'}}

for line in sys.stdin:
    request = json.loads(line)
    response = {'jsonrpc': '2.0', 'id': request.get('id')}
    response['result'] = handle_request(request)
    print(json.dumps(response), flush=True)

Troubleshooting

Server Won't Start

# Check if npx is available
which npx

# Test server manually
npx -y @modelcontextprotocol/server-weather
# Should wait for JSON-RPC input

Tool Not Found

  1. Verify server started: check logs for "Started server: name"
  2. Wait for tool discovery (500ms after init)
  3. Check availableTools() returns expected tools

Timeout Issues

  1. Increase timeout for slow tools
  2. Check network connectivity for remote APIs
  3. Monitor server stderr for errors

Server Crashes

  1. Check server logs via serverLog signal
  2. Enable auto-reconnect with appropriate maxRestarts
  3. Review environment variables and permissions

See Also