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¶
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¶
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.
Tools provided:
read_file- Read file contentswrite_file- Write to filelist_directory- List directory contentssearch_files- Search for files
Fetch Server¶
Makes HTTP requests to external URLs.
Tools provided:
fetch- Make HTTP GET/POST requests
Memory Server¶
Provides persistent key-value storage.
Tools provided:
store- Store a valueretrieve- Retrieve a valuedelete- 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¶
- Verify server started: check logs for "Started server: name"
- Wait for tool discovery (500ms after init)
- Check
availableTools()returns expected tools
Timeout Issues¶
- Increase timeout for slow tools
- Check network connectivity for remote APIs
- Monitor server stderr for errors
Server Crashes¶
- Check server logs via
serverLogsignal - Enable auto-reconnect with appropriate
maxRestarts - Review environment variables and permissions