Skip to main content

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-tool keys are name, description, result-mime-type, allowed-roles, response, and rate-limit. The input schema is generated from the endpoint's request fields; 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 as mcp-remote or mcp-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

Need help building AI-powered data tools? Check out our professional services.

🍪 Cookie Settings