Model Context Protocol (MCP) Integration
flAPI has built-in support for the Model Context Protocol (MCP)—a JSON-RPC 2.0 based standard that lets AI agents like Claude discover and invoke structured tools. With MCP, the same YAML configuration that exposes a REST endpoint can also surface a tool to an LLM, with no duplicate work.
What is MCP?
The Model Context Protocol is a standardized interface that allows AI agents to:
- Discover available tools and resources
- Understand each tool's input schema
- Call tools with structured arguments
- Receive structured responses
Think of MCP as OpenAPI/Swagger, but designed specifically for AI agents instead of human developers.
Why MCP + flAPI?
Traditional approaches require building separate integrations:
- REST API for applications
- Custom tool definitions for AI agents
- Duplicate validation logic
- Separate documentation
With flAPI's MCP support:
- One YAML file can produce both a REST endpoint and an MCP tool
- Same validation, same security, same data
- AI agents get structured access to your enterprise data
- Endpoint cache, authentication, and rate limits apply consistently
Architecture in One Picture
MCP Client (Claude, Cursor, custom)
│ JSON-RPC 2.0 over HTTP
▼
POST /mcp/jsonrpc ◄── single transport endpoint
│
flAPI MCP Route Handlers
│
├──► Tools (mcp-tool YAML blocks)
├──► Resources (mcp-resource YAML blocks)
├──► Prompts (mcp-prompt YAML blocks)
└──► Config tools (flapi_* — only with --config-service)
│
▼
DuckDB / Connected sources
The MCP transport is exactly one route: POST /mcp/jsonrpc. All discovery and invocation flows through that route using JSON-RPC 2.0. A separate GET /mcp/health route is available for liveness probes.
Quick Example
Define Once, Use Twice
# sqls/campaign-stats.yaml
url-path: /campaign-stats
# This same file also defines an MCP tool
mcp-tool:
name: get_campaign_performance
description: |
Retrieves marketing campaign performance metrics by country.
Returns click counts, revenue, and conversion rates.
result-mime-type: application/json
request:
- field-name: country
field-in: query
description: Two-letter country code (e.g., US, DE, FR)
required: false
validators:
- type: string
regex: "^[A-Z]{2}$"
template-source: campaign-stats.sql
connection:
- bigquery-marketing
The supported mcp-tool keys are name, description, and result-mime-type. The input schema is generated automatically from request fields.
The SQL Template
-- sqls/campaign-stats.sql
SELECT
campaign_type,
country,
SUM(clicks) AS total_clicks,
SUM(revenue) AS total_revenue,
SUM(conversions) AS total_conversions,
ROUND(SUM(revenue) / SUM(clicks), 2) AS revenue_per_click
FROM marketing_campaigns
WHERE active = true
{{#params.country}}
AND country = '{{{params.country}}}'
{{/params.country}}
GROUP BY 1, 2
ORDER BY total_revenue DESC;
Usage: REST API
curl http://localhost:8080/campaign-stats?country=US
{
"data": [
{
"campaign_type": "social",
"country": "US",
"total_clicks": 150000,
"total_revenue": 45000.00,
"total_conversions": 1200,
"revenue_per_click": 0.30
}
]
}
Usage: AI Agent (MCP)
The agent discovers the tool via tools/list:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}
It receives a JSON Schema definition derived from the YAML:
{
"name": "get_campaign_performance",
"description": "Retrieves marketing campaign performance metrics by country...",
"inputSchema": {
"type": "object",
"properties": {
"country": {
"type": "string",
"description": "Two-letter country code (e.g., US, DE, FR)"
}
}
}
}
The agent invokes the tool with tools/call (note: the parameter key is arguments, not parameters):
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_campaign_performance",
"arguments": { "country": "US" }
}
}
The response wraps the SQL result as MCP content blocks:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "[{\"campaign_type\":\"social\",\"country\":\"US\",\"total_clicks\":150000, ...}]"
}
]
}
}
The AI agent can now reason about campaign performance and make data-driven recommendations.
MCP Configuration Options
Basic Tool Definition
mcp-tool:
name: tool_name
description: What this tool does
Result MIME Type (Optional)
mcp-tool:
name: get_revenue_report
description: Generate a revenue report
result-mime-type: application/json # default
Per-tool security knobs
mcp-tool:
name: customer_lookup
description: Look up a customer by id.
# Required when mcp.auth.enabled: true — flAPI denies every call to a tool
# without an allowed-roles list. The JWT/OIDC principal's `roles` claim
# must intersect this list.
allowed-roles: [analyst, admin]
# Response shaping: applied before the result reaches the agent.
response:
max-rows: 1000 # hard cap on returned rows
redact-columns: [ssn, salary] # replace these columns with the redaction sentinel
# sample: true # return summary only (row_count, columns, sampled: true)
# Per-tool rate limit, keyed on the authenticated principal (with an
# anonymous fallback bucket per tool).
rate-limit:
enabled: true
max: 30
interval: 60
Shadow / dry-run mode
Append "_dryRun": true to the arguments object of any tools/call:
{ "jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{"name":"customer_lookup","arguments":{"id":42,"_dryRun":true}} }
flAPI runs validators + template expansion + EXPLAIN and returns the rendered SQL + plan as a JSON payload without executing the query. RBAC, rate-limit, and validator checks all still apply to dry-runs. Use this for shadow-mode audits before promoting an endpoint to production.
Tool-description hygiene scanner
When mcp.strict-descriptions: true is set in the server config, flAPI refuses to start if any tool's description contains:
- Control characters or JSON-breakout patterns,
- Known role-override phrases ("ignore previous instructions", "you are now…", etc.).
This is the in-product defense against prompt-injection via untrusted tool catalogues.
Note: The supported
mcp-toolkeys arename,description,result-mime-type,allowed-roles,response, andrate-limit. The input schema is generated from the endpoint'srequestfields; the description is the primary surface for guiding the LLM.
MCP Resources
For semi-static data (schemas, reference tables, configuration), use mcp-resource:
# sqls/customer-schema.yaml
mcp-resource:
name: customer_schema
description: Customer database schema definition
mime-type: application/json
template-source: customer-schema.sql
connection:
- customer-database
Resources are addressed by URI (flapi://customer_schema) and read via the resources/read method.
MCP Prompts
For reusable prompt templates with Mustache-style placeholders:
# sqls/analyze-customer.yaml
mcp-prompt:
name: analyze_customer
description: Generate a customer analysis prompt
template: |
Analyze customer {{customer_id}} and produce a churn risk report.
arguments:
- customer_id
Clients retrieve a fully substituted prompt via prompts/get.
Real-World Use Cases
1. Customer Support Agent
Scenario: AI support agent needs to look up customer information.
# Customer lookup tool
url-path: /customers/lookup
mcp-tool:
name: lookup_customer
description: Find customer details by email, phone, or ID
request:
- field-name: email
field-in: query
description: Customer email address
- field-name: phone
field-in: query
description: Customer phone number
- field-name: customer_id
field-in: query
description: Customer ID
Agent Conversation:
User: "I need help with my order, my email is john@example.com"
Agent: [calls lookup_customer with email=john@example.com]
Agent: "Hi John! I found your account. I see you have 3 orders..."
2. Sales Intelligence Agent
Scenario: Sales AI that analyzes opportunities.
# Opportunity analysis
url-path: /sales/opportunities
mcp-tool:
name: analyze_sales_pipeline
description: Get sales opportunities with revenue forecasts
request:
- field-name: stage
description: Pipeline stage (prospecting, negotiation, closing)
- field-name: rep
description: Sales rep name
- field-name: min_value
description: Minimum deal value
Agent Usage:
Sales Manager: "What deals over $100k are in negotiation?"
Agent: [calls analyze_sales_pipeline with stage=negotiation, min_value=100000]
Agent: "There are 12 deals over $100k in negotiation, totaling $2.4M..."
3. Data Analysis Agent
Scenario: Claude analyzing business metrics.
# Revenue analytics
url-path: /analytics/revenue
mcp-tool:
name: get_revenue_breakdown
description: Revenue analysis by product, region, and time period
request:
- field-name: product
description: Product name or ID
- field-name: region
description: Geographic region
- field-name: period
description: Time period (week, month, quarter, year)
Analyst Workflow:
Analyst (to Claude): "Compare Q4 revenue by region vs last year"
Claude: [calls get_revenue_breakdown multiple times with different params]
Claude: "Here's the comparison: North America is up 23%, Europe is..."
Connecting AI Agents
Claude Desktop (via stdio bridge)
Claude Desktop's mcpServers config currently launches stdio subprocesses, but flAPI's MCP server speaks HTTP (POST /mcp/jsonrpc). To connect Claude Desktop today, run a stdio→HTTP bridge such as mcp-remote:
// ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
// %APPDATA%\Claude\claude_desktop_config.json (Windows)
// ~/.config/Claude/claude_desktop_config.json (Linux)
{
"mcpServers": {
"flapi": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"http://localhost:8080/mcp/jsonrpc"
]
}
}
}
If your flAPI server requires a Bearer token, pass it via mcp-remote's auth header argument or wrap the bridge in a small launcher script. See the Claude Integration guide for a complete walkthrough.
Note: Earlier docs suggested
"command": "curl". That does not work—Claude Desktop expects a long-lived stdio MCP process, not a one-shot HTTP request. A stdio adapter (such asmcp-remoteormcp-proxy) is required. Native HTTP-transport support in Claude Desktop is on the MCP roadmap.
Custom Integration
A direct JSON-RPC client in Python:
import requests
BASE = "http://localhost:8080/mcp/jsonrpc"
# 1. initialize
init = requests.post(BASE, json={
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"clientInfo": {"name": "py-client", "version": "1.0.0"}
}
}).json()
session_id = init.get("Mcp-Session-Id") # also in response headers
# 2. list tools
tools = requests.post(BASE, json={
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {}
}).json()
# 3. call a tool
result = requests.post(BASE, json={
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "get_campaign_performance",
"arguments": {"country": "US"}
}
}).json()
For the full message catalog, see the MCP Protocol Reference.
Security Considerations
Authentication
MCP requests use the same authentication mechanisms as REST endpoints. flAPI supports Basic, Bearer/JWT, and OIDC authentication on the MCP transport:
Authorization: Bearer <jwt-token>
Per-method authentication policies allow, for example, an open tools/list with an authenticated tools/call.
Row-Level Security
Use the same SQL templates with the authenticated user's auth.* context (auth.username, auth.roles, auth.email, auth.type, auth.authenticated). auth.roles is a comma-joined string, so match it with LIKE:
SELECT * FROM customer_data
WHERE 1=1
AND (
'{{auth.roles}}' LIKE '%admin%'
OR created_by = '{{{auth.username}}}'
)
The AI agent inherits the permissions of the authenticated user.
Per-tool RBAC (deny-by-default)
When mcp.auth.enabled: true, each tool's endpoint YAML MUST declare mcp-tool.allowed-roles. A tool without an allowed-roles list refuses every call — preventing a freshly-added tool from being callable by any authenticated user.
mcp-tool:
name: admin_dashboard_metrics
description: KPI rollup for the admin dashboard.
allowed-roles: [admin] # only callers with `admin` in their JWT roles claim
Endpoints without mcp.auth.enabled keep working role-free for flapii project init demos.
Rate Limiting
# Global rate limit (applies to REST + MCP traffic)
rate_limit:
enabled: true
max: 100
interval: 60
key: user-or-ip # share-NAT-friendly: per-authenticated-user bucket
# Per-tool rate limit (declared in the endpoint's mcp-tool block)
mcp-tool:
rate-limit:
enabled: true
max: 30
interval: 60
Request audit log
Every REST and MCP call can be recorded to a JSONL file with operator-configurable redaction:
audit:
enabled: true
sink: file
path: ./logs/audit.jsonl
redact: [password, api_key]
Each event records {ts, principal, method, target, params, status, row_count, latency_ms}.
Startup security auditor
At boot, flAPI scans the loaded config and warns about plaintext passwords, MD5 hashes, MCP exposed without auth on a non-loopback bind, and CORS wildcard combined with auth.enabled: true. Combine with mcp.strict-descriptions: true for hard-fail behaviour on tool-description prompt-injection patterns.
Best Practices
1. Write Clear Descriptions
# Bad
mcp-tool:
name: get_data
description: Gets data
# Good
mcp-tool:
name: get_customer_orders
description: |
Retrieves all orders for a specific customer, including order status,
items, and total value. Optionally filter by date range.
The description is the primary signal an LLM uses when choosing which tool to call—invest in it.
2. Use Meaningful Names
# Follow naming conventions
mcp-tool:
name: get_customer_by_id # clear action + resource
name: search_products # clear verb
name: calculate_revenue # descriptive
3. Document Each Parameter
request:
- field-name: customer_id
description: |
Unique customer identifier. Can be found in the customer
profile or order confirmation email.
required: true
The description on each request field becomes the JSON Schema property description in the tool's inputSchema.
4. Handle Edge Cases
SELECT
COALESCE(SUM(revenue), 0) AS total_revenue,
COALESCE(COUNT(*), 0) AS order_count
FROM orders
WHERE customer_id = '{{{params.customer_id}}}'
AND date >= CURRENT_DATE - INTERVAL '{{params.days|30}} days';
Return meaningful results even when no data exists—LLMs handle empty result sets better than null pointer surprises.
Debugging MCP Tools
Health Check
curl http://localhost:8080/mcp/health
{
"status": "healthy",
"server": "flapi-mcp-server",
"version": "0.3.0",
"protocol_version": "2025-11-25",
"mcp_available": true,
"tools_available": true,
"resources_available": true,
"tools_count": 5,
"resources_count": 2
}
List Tools via JSON-RPC
curl -X POST http://localhost:8080/mcp/jsonrpc \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | jq
Call a Tool Directly
curl -X POST http://localhost:8080/mcp/jsonrpc \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_campaign_performance",
"arguments": {"country": "US"}
}
}' | jq
For the full set of methods, the session-header model, and error codes, see the MCP Protocol Reference.
Next Steps
- MCP Protocol Reference: Full JSON-RPC method catalog
- MCP Config Tools (
flapi_*): Manage flAPI itself from an AI agent - Claude Integration Guide: Step-by-step Claude Desktop setup
- Endpoints Overview: Configure API endpoints
- YAML Syntax: Define MCP tool descriptions
- SQL Templating: Create dynamic data access
- Authentication: Secure your AI tools
- Validation: Validate AI agent inputs
- BigQuery Example: See MCP tools in action
Need help building AI-powered data tools? Check out our professional services.