This document describes the Model Context Protocol (MCP) server implementation in flAPI.
flAPI implements MCP to expose endpoints as AI-consumable tools:
- JSON-RPC 2.0 transport over HTTP
- Tools, Resources, and Prompts primitives
- Session management for stateful interactions
- Authentication via Bearer tokens or OIDC
graph TB
subgraph "MCP Clients"
AI[AI/LLM Client]
Claude[Claude Desktop]
end
subgraph "Transport Layer"
HTTP[HTTP POST /mcp]
Session[Session Manager]
end
subgraph "Protocol Layer"
MRH[MCPRouteHandlers]
Auth[MCPAuthHandler]
Caps[ClientCapabilitiesDetector]
end
subgraph "Execution Layer"
MTH[MCPToolHandler]
CM[ConfigManager]
QE[QueryExecutor]
end
AI --> HTTP
Claude --> HTTP
HTTP --> MRH
MRH --> Auth
MRH --> Session
MRH --> Caps
MRH --> MTH
MTH --> CM
MTH --> QE
MCPRouteHandlers (src/mcp_route_handlers.cpp) handles all MCP JSON-RPC requests.
void registerRoutes(crow::App<...>& app, int port) {
// Main JSON-RPC endpoint
CROW_ROUTE(app, "/mcp").methods("POST"_method)
([this](const crow::request& req) {
return handleMCPRequest(req);
});
// Health check
CROW_ROUTE(app, "/mcp/health").methods("GET"_method)
([this](const crow::request& req) {
return handleHealth(req);
});
}crow::response handleMCPRequest(const crow::request& req) {
// 1. Parse JSON-RPC request
auto mcp_request = parseMCPRequest(req);
if (!mcp_request) {
return createJsonRpcErrorResponse("-32700", "Parse error");
}
// 2. Validate authentication
if (!auth_handler_->validateAuth(req)) {
return createJsonRpcErrorResponse("-32001", "Authentication failed");
}
// 3. Get or create session
auto session_id = extractSessionIdFromRequest(req);
if (!session_id) {
session_id = session_manager_->createSession();
}
// 4. Dispatch to handler
auto response = dispatchMCPRequest(*mcp_request, req);
// 5. Return JSON-RPC response
return createJsonRpcResponse(*mcp_request, response, session_id);
}MCPResponse dispatchMCPRequest(const MCPRequest& request, const crow::request& http_req) {
const auto& method = request.method;
if (method == "initialize") return handleInitializeRequest(request, http_req);
if (method == "tools/list") return handleToolsListRequest(request, http_req);
if (method == "tools/call") return handleToolsCallRequest(request, http_req);
if (method == "resources/list") return handleResourcesListRequest(request, http_req);
if (method == "resources/read") return handleResourcesReadRequest(request, http_req);
if (method == "prompts/list") return handlePromptsListRequest(request, http_req);
if (method == "prompts/get") return handlePromptsGetRequest(request, http_req);
if (method == "ping") return handlePingRequest(request, http_req);
// Method not found
MCPResponse response;
response.error = formatJsonRpcError(-32601, "Method not found");
return response;
}Protocol handshake and capability negotiation:
// Request
{
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": {},
"resources": {}
},
"clientInfo": {
"name": "claude-desktop",
"version": "1.0.0"
}
},
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": { "listChanged": true },
"resources": { "listChanged": true },
"prompts": { "listChanged": true }
},
"serverInfo": {
"name": "flapi-mcp-server",
"version": "0.1.0"
}
},
"id": 1
}Lists available tools derived from endpoint configurations:
// Response
{
"jsonrpc": "2.0",
"result": {
"tools": [
{
"name": "get_customers",
"description": "Retrieve customer data",
"inputSchema": {
"type": "object",
"properties": {
"id": { "type": "integer", "description": "Customer ID" },
"limit": { "type": "integer", "description": "Max results" }
}
}
}
]
},
"id": 2
}Executes a tool (endpoint):
// Request
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get_customers",
"arguments": {
"id": 123,
"limit": 10
}
},
"id": 3
}
// Response
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "[{\"id\": 123, \"name\": \"Alice\"}]"
}
]
},
"id": 3
}MCPToolHandler (src/mcp_tool_handler.cpp) executes tool calls.
sequenceDiagram
participant MRH as MCPRouteHandlers
participant MTH as MCPToolHandler
participant CM as ConfigManager
participant STP as SQLTemplateProcessor
participant QE as QueryExecutor
MRH->>MTH: handleToolCall("get_customers", {id: 123})
MTH->>CM: getEndpointByMCPToolName("get_customers")
CM-->>MTH: EndpointConfig
MTH->>MTH: mapArgumentsToParams(arguments)
Note over MTH: {id: 123} → params map
MTH->>STP: processTemplate(endpoint, params)
STP-->>MTH: rendered SQL
MTH->>QE: executeQuery(sql)
QE-->>MTH: QueryResult
MTH->>MTH: formatMCPContent(result)
MTH-->>MRH: MCPResponse
std::map<std::string, std::string> mapArgumentsToParams(
const EndpointConfig& endpoint,
const crow::json::wvalue& arguments) {
std::map<std::string, std::string> params;
for (const auto& field : endpoint.request_fields) {
if (arguments.has(field.fieldName)) {
params[field.fieldName] = arguments[field.fieldName].dump();
} else if (!field.defaultValue.empty()) {
params[field.fieldName] = field.defaultValue;
}
}
return params;
}Endpoints can serve both REST and MCP:
# sqls/customers.yaml
url-path: /customers
method: GET
request:
- field-name: id
field-in: query
required: false
template-source: customers.sql
connection: [my-data]
# MCP tool definition (optional)
mcp-tool:
name: get_customers
description: Retrieve customer information by IDEntities can be MCP-only (no REST endpoint):
# sqls/customer_lookup.yaml (MCP tool only)
mcp-tool:
name: customer_lookup
description: Look up customer by various criteria
request:
- field-name: query
description: Search query
template-source: customer_lookup.sql
connection: [my-data]MCPSessionManager (src/mcp_session_manager.cpp) tracks client sessions.
Mcp-Session-Id: abc123
struct MCPSession {
std::string session_id;
std::chrono::time_point<std::chrono::steady_clock> created_at;
std::chrono::time_point<std::chrono::steady_clock> last_accessed;
crow::json::wvalue client_capabilities;
crow::json::wvalue client_info;
};MCPAuthHandler (src/mcp_auth_handler.cpp) validates MCP requests.
mcp:
enabled: true
port: 8081
auth:
enabled: true
type: bearer # or "basic", "oidc"
jwt_secret: ${JWT_SECRET}
jwt_issuer: flapi
methods:
tools/call:
required: true
resources/read:
required: false # Read-only, no authbool validateAuth(const crow::request& req, const std::string& method) {
auto& method_config = auth_config_.methods[method];
if (!method_config.required) {
return true; // Auth not required for this method
}
return validateToken(req);
}MCPClientCapabilitiesDetector (src/mcp_client_capabilities.cpp) adapts responses based on client.
struct ClientCapabilities {
bool supports_streaming = false;
bool supports_progress = false;
bool supports_cancellation = false;
std::string client_name;
std::string client_version;
};
ClientCapabilities detectCapabilities(const crow::json::wvalue& init_params) {
ClientCapabilities caps;
if (init_params.has("capabilities")) {
auto& client_caps = init_params["capabilities"];
// Parse client capabilities
}
return caps;
}# flapi.yaml
mcp:
enabled: true
port: 8081
instructions: |
This MCP server provides access to customer data.
Use the get_customers tool to query the database.
instructions_file: ./mcp_instructions.md # Alternative
auth:
enabled: true
type: bearer| Code | Meaning | When Used |
|---|---|---|
| -32700 | Parse error | Invalid JSON |
| -32600 | Invalid request | Missing required fields |
| -32601 | Method not found | Unknown method |
| -32602 | Invalid params | Missing/invalid arguments |
| -32603 | Internal error | Server error |
| -32001 | Authentication error | Auth failed |
| -32002 | Tool not found | Unknown tool name |
| File | Purpose |
|---|---|
src/mcp_route_handlers.cpp |
Main request handling |
src/mcp_tool_handler.cpp |
Tool execution |
src/mcp_session_manager.cpp |
Session state |
src/mcp_auth_handler.cpp |
Authentication |
src/mcp_client_capabilities.cpp |
Client detection |
src/include/mcp_types.hpp |
Type definitions |
src/include/mcp_constants.hpp |
Protocol constants |
- DESIGN_DECISIONS.md - Why JSON-RPC
- DESIGN_DECISIONS.md - Unified config
- ../../MCP_REFERENCE.md - MCP API reference