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¶
Connecting from C++ (any Qt6 binary)¶
Use mcp::McpHttpClient (src/qt6/mcp-lib/McpHttpClient.h) — the
same client gva-voip-agent uses internally:
#include "McpHttpClient.h"
auto mcp = std::make_unique<mcp::McpHttpClient>();
mcp->setEndpoint(QUrl("http://127.0.0.1:7077/mcp"));
mcp->setClientInfo("my-app", "1.0.0");
QObject::connect(mcp.get(), &mcp::McpHttpClient::connected, [&] {
mcp->listTools([](bool ok, const QJsonArray& tools, const QString& err) {
if (!ok) { qWarning() << "listTools failed:" << err; return; }
qInfo() << "Discovered" << tools.size() << "tools";
});
});
mcp->connectToServer();
Tool calls follow the same pattern:
mcp->callTool("get_weather", QJsonObject{{"location", "London"}},
[](bool ok, const QJsonObject& result, const QString& err) {
if (!ok) { qWarning() << err; return; }
// result follows MCP shape: { content: [{type:"text", text:"…"}], isError: bool }
});
Command line¶
# stdio (default — for Claude Desktop / single client launches)
./build/bin/gva-mcp-server --config etc/gva-mcp-server-ollama.json
# HTTP daemon mode (what gva-voip-agent and the systemd unit use)
./build/bin/gva-mcp-server \
--config etc/gva-mcp-server-ollama.json \
--no-stdio --http --port 7077 --address 127.0.0.1
Both transports can run simultaneously — omit --no-stdio to keep stdio
available while the HTTP port is also listening.
Features¶
Registering tools on the server¶
Tools live in gva-mcp-server (or mcp-lib) and are exposed to every client
automatically. The fastest path is mcp::LambdaTool:
// In gva-mcp-server (e.g. GvaTools.cpp)
server.registerTool(std::make_unique<mcp::LambdaTool>(
"get_vehicle_status",
"Get current vehicle status from DDS",
QJsonObject{
{"type", "object"},
{"properties", QJsonObject{
{"system", QJsonObject{
{"type", "string"},
{"description", "System name (engine, transmission, …)"}
}}
}},
{"required", QJsonArray{"system"}}
},
[](const QJsonObject& args) -> mcp::ToolResult {
const QString system = args.value("system").toString();
// Query DDS for vehicle status…
return mcp::ToolResult::text(QString("%1: operational, 85.5°C").arg(system));
}
));
For reusable generic tools, subclass mcp::McpTool in src/qt6/mcp-lib/
and register it from
main.cpp behind a config flag.
Tool-call timeouts¶
Clients (mcp::McpHttpClient) set timeouts per call — the server itself does
not impose a global tool timeout, but the bash tool reads timeout_ms from
its server-side config.
Client signals¶
mcp::McpHttpClient emits the following signals for monitoring:
connect(mcp.get(), &mcp::McpHttpClient::connected, []() {
qDebug() << "MCP HTTP transport connected";
});
connect(mcp.get(), &mcp::McpHttpClient::disconnected, []() {
qDebug() << "MCP HTTP transport disconnected";
});
connect(mcp.get(), &mcp::McpHttpClient::errorOccurred, [](const QString& err) {
qWarning() << "MCP error:" << err;
});
Tool discovery and dispatch use callbacks on listTools() / callTool()
directly rather than signals.
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