Skip to content

MCP Endpoint

TDB exposes a Model Context Protocol endpoint at POST /v1/mcp. MCP uses JSON-RPC 2.0 over HTTP.

This endpoint is how Claude Desktop, Cursor, and other AI tools query your data sources directly — without building a custom integration.

Setting up a client

This page is the protocol reference. For step-by-step config (Claude Desktop, VS Code, Cursor, JetBrains, Windsurf, Cline) and example queries to run once connected, see Connect an IDE / AI Tool →.


Supported methods

JSON-RPC method Auth required Description
initialize No MCP handshake — returns protocol version and capabilities
tools/list Yes Lists available tools
tools/call Yes Executes a tool

Only initialize is unauthenticated. This is intentional — MCP clients must complete the handshake before presenting credentials, per the MCP spec.


Available tools

TDB Enterprise exposes seven tools:

Tool What it does Works with
query_source Run a SQL SELECT against a source All sources, including database-wide
schema_source Column names and types, no SQL required All sources — lists every table for database-wide sources
preview_source First N rows, no SQL required All sources — table argument required for database-wide
filter_source Rows matching one column condition All sources — table argument required for database-wide
aggregate_source COUNT / SUM / AVG / MIN / MAX with optional GROUP BY All sources — table argument required for database-wide
list_views List the available YAML views Requires TDB_VIEWS_DIR
run_view Execute a named view with typed parameters Requires TDB_VIEWS_DIR

Database-wide sources and the no-SQL tools

For a database-wide source (registered without table), the four no-SQL convenience tools (schema_source, preview_source, filter_source, aggregate_source) accept an optional table argument to pick which table to target, validated against the source's actual tables:

  • schema_source — omit table to list every table's columns; pass it to see one table's columns.
  • preview_source, filter_source, aggregate_sourcetable is required. Omitting it returns a tool-level error listing the available tables, so the AI tool can retry with a valid one.

Column/value validation and the generated SQL are scoped to whichever table you name. query_source remains the only tool that can JOIN across tables in a single call.

Tools that return rows pass their output through the prompt-injection filter before the result reaches the MCP client. API keys can be restricted to a subset of tools — see Tool allow-lists per key.


Authentication

All MCP methods except initialize require a Bearer token:

Authorization: Bearer <token>

Any valid TDB credential works: static env key, DB-managed key, or JWT.

If the token is missing or invalid, TDB returns HTTP 401 with a WWW-Authenticate header that MCP-aware clients use to discover the OAuth authorization server:

WWW-Authenticate: Bearer realm="TDB", resource_metadata="/.well-known/oauth-protected-resource"

Claude Desktop and Cursor use this header to trigger the OAuth 2.1 PKCE flow automatically. See OAuth 2.1 →.


Request format

All requests are JSON-RPC 2.0 objects sent to POST /v1/mcp:

{
  "jsonrpc": "2.0",
  "id": <integer or string>,
  "method": "<method>",
  "params": { ... }
}

Method reference

initialize

Completes the MCP handshake. No auth required.

curl -X POST http://localhost:8000/v1/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {"tools": {}},
    "serverInfo": {"name": "tdb-enterprise", "version": "0.1.0"}
  }
}

tools/list

Returns the list of available tools. Requires auth.

curl -X POST http://localhost:8000/v1/mcp \
  -H "Authorization: Bearer <KEY>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'

Response (abbreviated — one entry per tool, seven in total):

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      { "name": "query_source",     "description": "...", "inputSchema": { ... } },
      { "name": "schema_source",    "description": "...", "inputSchema": { ... } },
      { "name": "preview_source",   "description": "...", "inputSchema": { ... } },
      { "name": "filter_source",    "description": "...", "inputSchema": { ... } },
      { "name": "aggregate_source", "description": "...", "inputSchema": { ... } },
      { "name": "list_views",       "description": "...", "inputSchema": { ... } },
      { "name": "run_view",         "description": "...", "inputSchema": { ... } }
    ]
  }
}

tools/list always advertises all seven tools. A key with a tool allow-list still sees the full list but is rejected at call time for tools outside its allow-list.


tools/callquery_source

Executes a SQL query against a registered source. Maximum 1,000 rows returned.

Argument Type Required Default Description
sql string Yes SQL SELECT statement
source_name string No First registered source Registered source name (exact match)

Use the table name that matches the source: CSV sources are queried as data; database sources use real table names. Database-wide sources can JOIN across any tables in the database.

Always pass source_name when more than one source is registered

Without it, the query runs against the first registered source, which may not be the one the AI tool intended.

Single-table source:

curl -X POST http://localhost:8000/v1/mcp \
  -H "Authorization: Bearer <KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "query_source",
      "arguments": {
        "sql": "SELECT country, COUNT(*) AS n FROM customers GROUP BY country ORDER BY n DESC LIMIT 10",
        "source_name": "customers"
      }
    }
  }'

Database-wide source (cross-table JOIN):

curl -X POST http://localhost:8000/v1/mcp \
  -H "Authorization: Bearer <KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 4,
    "method": "tools/call",
    "params": {
      "name": "query_source",
      "arguments": {
        "sql": "SELECT c.name, SUM(o.total) AS spend FROM customers c JOIN orders o ON o.customer_id = c.id GROUP BY c.name ORDER BY spend DESC LIMIT 10",
        "source_name": "production_db"
      }
    }
  }'

Successful response:

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"source\":\"customers\",\"columns\":[\"country\",\"n\"],\"rows\":[{\"country\":\"US\",\"n\":1420},{\"country\":\"GB\",\"n\":380}],\"rows_returned\":2}"
      }
    ]
  }
}

The text field contains a JSON-serialised result object. AI tools receive this and can present it as a table or process it programmatically.

Error response (tool-level error, still HTTP 200):

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [{"type": "text", "text": "SQL validation error: Only SELECT statements are allowed"}],
    "isError": true
  }
}

tools/callschema_source

Returns column names and data types for a source — the tool an AI assistant calls before writing a query. No SQL required.

Argument Type Required Default Description
source_name string No First registered source Registered source name (exact match)
table string No Table to inspect. Ignored for single-table/CSV sources. For database-wide sources: omit to list every table, or name one to see just its columns.

Single-table or CSV source:

curl -X POST http://localhost:8000/v1/mcp \
  -H "Authorization: Bearer <KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 5,
    "method": "tools/call",
    "params": {"name": "schema_source", "arguments": {"source_name": "customers"}}
  }'

Result payload (inside content[0].text):

{
  "source": "customers",
  "columns": [
    {"name": "id", "type": "integer"},
    {"name": "country", "type": "text"}
  ]
}

Database-wide source, no table (lists every table):

{
  "source": "production_db",
  "tables": {
    "customers": [{"name": "id", "type": "integer"}, {"name": "country", "type": "text"}],
    "orders": [{"name": "id", "type": "integer"}, {"name": "customer_id", "type": "integer"}]
  }
}

Database-wide source with table: "orders":

{
  "source": "production_db",
  "table": "orders",
  "columns": [
    {"name": "id", "type": "integer"},
    {"name": "customer_id", "type": "integer"}
  ]
}

An unrecognized table returns a tool error listing the actual table names.

Schema results are served from the schema cache when caching is enabled.


tools/callpreview_source

Returns the first N rows of a source's table. No SQL required.

Argument Type Required Default Description
source_name string No First registered source Registered source name (exact match)
table string No Table to preview. Required for database-wide sources (registered without a fixed table) — ignored for single-table/CSV sources.
limit integer No 10 Rows to return (1–100)
curl -X POST http://localhost:8000/v1/mcp \
  -H "Authorization: Bearer <KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 6,
    "method": "tools/call",
    "params": {"name": "preview_source", "arguments": {"source_name": "customers", "limit": 5}}
  }'

Database-wide source (table required):

curl -X POST http://localhost:8000/v1/mcp \
  -H "Authorization: Bearer <KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 6,
    "method": "tools/call",
    "params": {"name": "preview_source", "arguments": {"source_name": "production_db", "table": "orders", "limit": 5}}
  }'

Result payload: {"source": ..., "columns": [...], "rows": [...], "rows_returned": N}. Omitting table on a database-wide source returns a tool error listing the available tables.


tools/callfilter_source

Returns rows matching a single column condition, without the AI tool writing SQL. The column name is validated against the source schema and the operator against a fixed allow-list, so the model cannot inject arbitrary SQL through this tool.

Argument Type Required Default Description
column string Yes Column to filter on (must exist in the schema)
value string Yes Comparison value (interpreted by column type)
operator string No = One of =, !=, >, <, >=, <=, LIKE
source_name string No First registered source Registered source name (exact match)
table string No Table to filter. Required for database-wide sources — ignored for single-table/CSV sources. column is validated against this table's schema.
limit integer No 100 Max rows to return (1–1,000)
curl -X POST http://localhost:8000/v1/mcp \
  -H "Authorization: Bearer <KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 7,
    "method": "tools/call",
    "params": {
      "name": "filter_source",
      "arguments": {"source_name": "customers", "column": "country", "value": "US", "limit": 50}
    }
  }'

Result payload: {"source": ..., "columns": [...], "rows": [...], "rows_returned": N}. An unknown column returns a tool error listing the valid column names.


tools/callaggregate_source

Runs a single aggregate over a column, optionally grouped. Column names are validated against the source schema; the function is restricted to the five listed below.

Argument Type Required Default Description
function string Yes One of COUNT, SUM, AVG, MIN, MAX
column string Yes Column to aggregate; * is allowed for COUNT(*)
group_by string No Column to group results by
source_name string No First registered source Registered source name (exact match)
table string No Table to aggregate. Required for database-wide sources — ignored for single-table/CSV sources. column/group_by are validated against this table's schema.
limit integer No 100 Max groups to return (1–1,000)
curl -X POST http://localhost:8000/v1/mcp \
  -H "Authorization: Bearer <KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 8,
    "method": "tools/call",
    "params": {
      "name": "aggregate_source",
      "arguments": {"source_name": "customers", "function": "COUNT", "column": "*", "group_by": "country"}
    }
  }'

Result payload: {"source": ..., "function": "COUNT", "column": "*", "group_by": "country", "columns": [...], "rows": [...], "rows_returned": N}.


tools/calllist_views and run_view

Expose YAML named views to AI tools. Views are administrator-approved, pre-defined queries — the safest way to give an AI assistant multi-table access, because the SQL is fixed and only typed parameters vary. Both tools require views to be configured (TDB_VIEWS_DIR); with no views loaded, list_views returns an empty list.

list_views takes no arguments and returns every view with its description, source, and parameter definitions.

run_view:

Argument Type Required Default Description
view_name string Yes Name of the view to execute
parameters object No {} Parameter values required by the view
limit integer No 1,000 Max rows to return (1–1,000)
curl -X POST http://localhost:8000/v1/mcp \
  -H "Authorization: Bearer <KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 9,
    "method": "tools/call",
    "params": {
      "name": "run_view",
      "arguments": {"view_name": "daily_signups", "parameters": {"country": "US"}}
    }
  }'

Result payload: {"view": ..., "source": ..., "columns": [...], "rows": [...], "rows_returned": N}.


Tool allow-lists per API key

DB-managed API keys can carry an allowed_tools list that restricts which MCP tools the key may call — for example, a reporting agent's key limited to ["schema_source", "run_view", "list_views"]. Keys without an allow-list can call every tool.

A disallowed call returns a tool-level error (HTTP 200, isError: true) so MCP clients handle it gracefully:

{
  "content": [{"type": "text", "text": "Tool 'query_source' is not permitted for this API key."}],
  "isError": true
}

See API Keys → tool allow-lists for how to set allowed_tools when creating or updating a key.


Prompt-injection filtering

Two filters protect the MCP path:

  • Input: the sql argument of query_source is screened before validation. A flagged input is rejected with a tool error (Input rejected: potential prompt injection detected.) and logged.
  • Output: rows returned by any tool are screened before the response is serialised. Cells that contain injection patterns (e.g. instructions embedded in data that try to steer the AI model) are redacted, and the redaction count is written to the server log.

The filters run server-side on every call — there is nothing to configure on the MCP client.


Error codes

HTTP status JSON-RPC error code Meaning
200 Success (check isError for tool-level errors)
200 -32700 Parse error — invalid JSON
200 -32600 Invalid JSON-RPC version
200 -32601 Method not found / unknown tool
401 -32001 Unauthorized (missing/invalid token)
429 -32000 Rate limit exceeded

Protocol-level errors (parse, version, unknown method) follow JSON-RPC-over-HTTP convention and are returned with HTTP 200 and an error object; only auth and rate-limit failures use HTTP status codes, so MCP clients can react to them at the transport layer.


Rate limiting on MCP

DB-managed API keys are rate-limited on the MCP path in the same way as the REST API. The rate limit check runs after authentication and before the tool call. HTTP 429 is returned with X-RateLimit-* headers when the limit is exceeded.


Audit log

Every successful tools/call that touches data (query_source, preview_source, filter_source, aggregate_source, run_view) writes a line to tdb_audit.jsonl, same format as the REST query endpoint — including the SQL that the no-SQL tools generated on the caller's behalf (run_view entries record <view:name>). Failed calls (auth failures, SQL validation errors) are logged as warnings only.