Skip to content
Menu

AI & MCP

Explore · AI & MCP

Let AI assistants query your data, governed.

Beacon's MCP server gives Claude, Cursor, VS Code and your own agents eight tools to explore a project's catalog and run read-only SQL. Every call is scoped to the caller's projects, checked by the same gate as the rest of Beacon, masked and audited.

  • 8built-in tools
  • Executescope to connect
  • Every callaudited, failures too
ASSISTANT · BEACON MCPproject 4
Which customers placed the most orders last week? Top three.
  1. get_contextShop analytics · 2 data sources
  2. get_query_contextshop-db · PostgreSQL · verified join
  3. dry_runvalid · 1 PII column
  4. query3 rows · 41 ms
customer_idemail maskedorders
58120a***e14
11904m***e11
73455t***e9
Customer 58120 placed the most orders last week (14), followed by 11904 (11) and 73455 (9). Email addresses are masked by Beacon's PII detection.
01The ask pipeline

From a question to masked rows, in eight stages.

The ask tool does not pipe a prompt straight into SQL. Each question passes routing, grounding, generation and validation before anything reaches the database, and the result is capped and masked before it leaves the server.

  1. RouteThe model picks the data source or sources. Skipped for a single-source project.
  2. GroundM-Schema, verified join paths, glossary, verified examples and learned patterns.
  3. GenerateSQL in the data source's own dialect, checked by deterministic semantic lint.
  4. GateRegex guardrail, AST read-only validator, cross-database rule, row cap.
  5. Dry run, repairA failure goes back with the error, redacted by class, and a refreshed schema.
  6. ExecuteRead-only path, 30-second timeout, 100 rows on one data source.
  7. MaskPII values are masked (a***z) before the result leaves the server.
  8. AuditAn audit row and a learning signal, on success and on failure.

Cross-source questions. When a question spans several data sources, Beacon queries each one separately and joins the results in an in-memory SQLite database.

Database backstop. On PostgreSQL and MySQL the statement also runs inside a read-only transaction. On every engine, the read-only login you connect with is the database-level boundary.

Your own SQL instead. Agents that want full control use get_query_context, write the SQL, check it with dry_run and run it with query.

02Tools

Eight built-in tools, and two families you add.

Every tool is published with a title and safety annotations, so clients can reason about it before calling. What a caller may use follows its role in the project: catalog tools need Read, the SQL-carrying tools need Query.

Caller's role in the project
get_contextRead
Project Overview

Data sources, schemas, tables, quality scores and documentation status. The recommended first call.

readOnlyHint trueidempotentHint true
audit
Refused: "you may read project N but not run ad-hoc queries in it."
searchRead
Search Catalog

Tables, columns and documentation by keyword, fused with semantic matching when a local embedder is configured.

readOnlyHint trueidempotentHint true
audit
Refused: "you may read project N but not run ad-hoc queries in it."
get_documentationRead
Get Documentation

Generated documentation for the project, a data source or a single table, concise or detailed.

readOnlyHint trueidempotentHint true
audit
Refused: "you may read project N but not run ad-hoc queries in it."
get_query_contextRead
Get Grounding Context for a Question

The grounding context ask would use, so the agent can write its own SQL against it.

readOnlyHint trueidempotentHint true
audit
Refused: "you may read project N but not run ad-hoc queries in it."
askQuery
Ask a Data Question

Routes, grounds, generates, validates and runs SQL for a natural-language question.

readOnlyHint trueidempotentHint false
audit + signal
Refused: "you may read project N but not run ad-hoc queries in it."
queryQuery
Run Read-Only Query

Runs one read-only statement on one data source; max_rows defaults to 100.

readOnlyHint trueidempotentHint true
audit + signal
Refused: "you may read project N but not run ad-hoc queries in it."
dry_runQuery
Validate SQL Without Executing

Runs every gate (read_only, schema, provider_dry_run) and reports all issues at once.

readOnlyHint trueidempotentHint true
audit + signal
Refused: "you may read project N but not run ad-hoc queries in it."
feedbackQuery
Record Answer Feedback

Records a correct or incorrect verdict on an ask answer. It curates nothing by itself.

readOnlyHint falseidempotentHint true
audit
Refused: "you may read project N but not run ad-hoc queries in it."
q_<name>Run
Saved query tools

An approved saved query that a SecurityAdmin exposed. It runs the approved version's snapshot, with arguments bound as database parameters, and stays available to Viewers. Above 25 visible tools, a caller gets search_saved_queries and run_saved_query instead.

readOnlyHint trueidempotentHint true
audit only
api_<name>Entra callers
Host endpoint tools

Endpoints of the host application marked as tools, dispatched in-process through the endpoint's own pipeline as the caller. Mapped Entra callers only; API keys never see them. Write tools need the host's opt-in and run for user callers only. Above 20: search_api and call_api.

readOnlyHint declared per endpoint
audit (writes as api_<name>:write)

Every built-in tool carries destructiveHint: false and openWorldHint: false. The query and ask responses also include structuredContent with columns, rows, the row count and a truncation flag, so agents never parse tables out of prose.

03Worked example

The transcript above, call by call.

An analyst asks Claude Code which customers ordered the most last week. Their API key has the Execute scope and reaches two projects: Shop analytics (4), where they are an Editor, and the restricted Finance project (7), where they are a Viewer member. Because the key reaches two projects, the assistant passes project_id on every call.

  1. 1

    get_context · find out what exists

    The assistant starts where the server instructions recommend: an overview of project 4, its data sources, schemas, table counts, quality scores and documentation status. It needs Read in the project.

    tools/call · get_context
    { "project_id": 4 }
  2. 2

    get_query_context · pull the grounding

    Instead of delegating generation to ask, the assistant asks for the context Beacon would use itself, ranked against the question: M-Schema renderings, verified join paths, human-verified examples and matching glossary terms. Sections marked authoritative are human-verified. The response is cut at max_chars (12,000 by default) on a section boundary, with an explicit note.

    tools/call · get_query_context
    {
    "question": "Which customers placed the most orders last week?",
    "datasource_name": "shop-db",
    "project_id": 4
    }
    Response (abridged)
    # Query Context: shop-db (PostgreSQL)
    Write PostgreSQL SELECT statements. Validate with dry_run, execute with query. Sections marked (authoritative) are human-verified.
    ## Relevant Tables (full schema)
    shop.orders (order_id PK, customer_id, status, placed_at, total)
    shop.customers (customer_id PK, email, country_code, created_at)
    ## Join Paths (verified)
    shop.orders.customer_id → shop.customers.customer_id
    ## Verified query examples (authoritative)
    Q: Orders per customer last month
    SELECT customer_id, count(*) FROM shop.orders WHERE …
  3. 3

    dry_run · validate without running

    The assistant writes its SQL and validates it through every gate: the shared read-only gate (with PII detection reporting the columns it would mask), the schema-catalog check that catches invented tables and columns, and the database's own EXPLAIN. Issues are collected, not first-failure-wins, so one call reports everything to fix.

    The assistant's SQL
    SELECT c.customer_id, c.email, count(*) AS orders
    FROM shop.orders o
    JOIN shop.customers c ON c.customer_id = o.customer_id
    WHERE o.placed_at >= date_trunc('week', now()) - interval '1 week'
    AND o.placed_at < date_trunc('week', now())
    GROUP BY c.customer_id, c.email
    ORDER BY orders DESC
    LIMIT 3
    structuredContent
    {
    "valid": true,
    "issues": [],
    "executable_sql": "SELECT c.customer_id, c.email, count(*) AS orders … LIMIT 3",
    "pii_columns": ["email"]
    }
  4. 4

    query · run it, read-only

    The statement passes the gate again and runs on the read-only path with the fixed 30-second timeout. max_rows defaults to 100 and is capped at the project's row limit. The email column comes back masked, so the assistant reports customer ids, never addresses.

    RESULT · 3 ROWStruncated: false
    customer_idemailorders
    58120a***e14
    11904m***e11
    73455t***e9
  5. 5

    A refusal · the same key in project 7

    Later the analyst asks about account balances in Finance. There they are a Viewer member: the catalog tools still answer, but query needs Query. The call is refused before any SQL runs, and the refusal is audited and recorded as a signal like any other failed call. Saved-query tools stay available to Viewers.

    tools/call · query
    {
    "datasource_name": "ledger-db",
    "sql": "SELECT account_id, balance FROM finance.accounts",
    "project_id": 7
    }
    isErrorAccess denied: you may read project 7 but not run ad-hoc queries in it.
04Connecting clients

One URL, one bearer token.

Every client talks Streamable HTTP to /beacon/mcp and sends a credential with the Execute scope. A Read-scoped key gets 403 at the door; a browser session reaches no project there and uses the MCP Playground instead.

Terminal
claude mcp add --transport http beacon https://beacon.example.com/beacon/mcp \
--header "Authorization: Bearer $BEACON_MCP_API_KEY"
.mcp.json (shared with the team; Claude Code expands variables)
{
"mcpServers": {
"beacon": {
"type": "http",
"url": "https://beacon.example.com/beacon/mcp",
"headers": { "Authorization": "Bearer ${BEACON_MCP_API_KEY}" }
}
}
}
.cursor/mcp.json
{
"mcpServers": {
"beacon": {
"url": "https://beacon.example.com/beacon/mcp",
"headers": { "Authorization": "Bearer sk-sem_…" }
}
}
}
.vscode/mcp.json
{
"inputs": [
{ "type": "promptString", "id": "beacon-key", "description": "Beacon API key", "password": true }
],
"servers": {
"beacon": {
"type": "http",
"url": "https://beacon.example.com/beacon/mcp",
"headers": { "Authorization": "Bearer ${input:beacon-key}" }
}
}
}

Claude on claude.ai and Claude Desktop connect as a custom connector with the same URL. Connectors sign in with OAuth, so that path needs Entra ID callers.

Option A

API keys

Each person creates a key in API keys with the Execute scope, the projects the assistant should use and a short expiry. It is shown once; Beacon stores only its SHA-256 hash.

  • Acts as its owner, only in the key's projects, capped by the owner's current role
  • Catalog and saved-query tools work for any owner; query and ask need Editor or above
  • Audit rows carry the ApiKeyId
Option B

Entra ID callers

For organisation-wide access without personal keys, Beacon maps Entra access tokens to MCP callers with projects and a scope. An absent configuration admits no JWT caller.

  • Users: delegated tokens, optionally auto-provisioned with the Viewer role
  • Systems: app-only tokens of a known application or service principal
  • Required for OAuth connectors and for host endpoint tools; audited by a keyed hash
05What reaches the LLM provider

Schema goes in. Row data is opt-in or screened, and logged.

Beacon sends prompts to the one provider configured in Admin Settings: OpenAI, Anthropic, Azure OpenAI or AWS Bedrock. What a prompt may carry is fixed, and every request that carries row data is logged.

Always

In the prompt

  • The questionAs the caller asked it
  • SchemaTable and column names, types, descriptions, join paths
  • Curated knowledgeGlossary, documentation, verified examples, learned patterns

Read through the caller's authorized project only: another project's material never enters the prompt, even on a shared data source.

Opt-in or screened

Row data

  • sample-valuesOnly with sample-value collection on, off by default
  • value-matchesOnly with value grounding on, off by default; live probes only for callers who may query
  • column-valuesDocumentation enum discovery, with sample-value collection on
  • result-rowsEval judge only, off by default; PII masked, at most 20 rows
  • database-errorsLesson extraction, always redacted by class; repair prompts carry only redacted errors
  • stored-documentationSections that can list discovered values, for knowledge answers
event 7123 · one record per request carrying row data
category  Beacon.AI.Services.LlmProviders.LlmRowDataEgress
event     7123 RowDataSentToLlm · Information
fields    Categories    sample-values, value-matches
          DataSourceIds 12
          ProjectId     4
          CallerId      31
never     a value, prompt text or a table row

One lock keeps all row data out

The deployment lock Beacon:Mcp:ForbidRowDataToLlm pins sample values, value grounding and the eval judge off for every project. Repair prompts then carry only an error's class and code, and nobody can change that at runtime, an Admin included.

Returned to the client, logged the same way

When get_query_context returns sample values or value matches to an MCP client, that response is recorded under event 7123 too. Local embeddings never use the provider: they run in-process.

06Audit

Every tool call leaves a row.

McpAuditService records every invocation: the built-in tools, saved-query tools and host endpoint tools, successes, validation errors, project-access refusals and calls refused for a missing Execute scope. It is never short-circuited.

McpAuditLog · userId 31paged JSON export
Time (UTC)ToolProjectRowsmsError class
09:41:02get_context438none
09:41:05get_query_context4212none
09:41:09dry_run496none
09:41:10query4341none
09:44:27query712permission
Beacon.Audit · the refused call
category  Beacon.Audit
event     9100 McpToolAudited · Information
fields    Tool        query
          Persisted   true
          ProjectId   7
          UserId      31
          ApiKeyId    57
          DurationMs  12
          ErrorClass  permission
          TraceId     5f2c9a17e04b4c1d8b3e6a90c2d71f44

Rows shown as a table for reading. Auditors and Admins read them through GET /beacon/api/mcp/audit, a paged JSON export; each read is recorded first in the security audit trail, and fails when that record cannot be written (event 9103 follows on the log).

Correlated

Each row keeps the trace and span id of the call, the Mcp-Session-Id, an upstream request id and the caller: an API key's id, or a mapped Entra caller's kind and keyed hash.

Every statement, content by retention

Each row lists every statement the call ran on a data source: an ask's first run, repairs, candidates, probes and dry runs. With content retention on, they keep their SQL and the row its question; with it off, keyed hashes and structure only. The row itself is always written.

Fail closed when required

With Beacon:Mcp:Audit:Required set, a result whose audit row could not be written is withheld from the caller. No unaudited data leaves Beacon.

07Guarantees

What every MCP call can rely on.

Read-only, locked on

Only read-only statements run. Beacon:Mcp:ForceReadOnly pins it on by default, binding on Admins too; only the host's configuration lifts it.

PII masked, locked on

Emails, phone numbers, card numbers, tokens and your own patterns are masked before results leave the server. ForcePiiDetection keeps detection on by default.

Project access on every call

The caller's projects are decided per call, with no session cache. A revoked membership or a disabled user takes effect on the next call, in the same session.

Its own database only

SQL may name only the data source's configured database. Functions that reach other servers, files or storage are refused, also with read-only enforcement off.

Bounded results

A row limit (1,000 at most by default) and a fixed 30-second timeout for query and ask. Truncated results say so and tell the agent how to get more.

Strictest settings on shared sources

A data source several projects link runs with the strictest guardrails among them: the lowest row limit, PII and read-only on if any project turns them on.

The MCP Playground in the dark theme
The MCP Playground runs the same tools, guardrails and audit as a real MCP session, as the signed-in user.