Skip to content
Menu

Connect AI clients

View .md

Beacon’s MCP server lets AI assistants explore and query your data through eight tools, always project-scoped, read-only, masked and audited. This page wires it into your host and connects clients. The tools themselves are described on MCP server.

builder.Services.AddBeaconApiAuthorization(); // registers the Execute-scope policy MCP needs
builder.Services.AddBeaconAI(builder.Configuration);
builder.Services.AddBeaconMcp();
// …
app.UseMcpResourceMetadataChallenge(); // before UseBeaconAuthentication
app.UseBeaconAuthentication(); // API keys, JWT callers (when enabled), the cookie
app.UseAuthorization();
app.MapBeaconMcp(); // /beacon/mcp, Execute scope required
app.MapMcpDiscovery(); // anonymous discovery documents
ItemValue
Endpoint/beacon/mcp
TransportStreamable HTTP
AuthenticationAuthorization: Bearer sk-sem_… (API key) or Authorization: Bearer <Entra access token>
Required scopeExecute (a Read-scoped key gets 403)
Built-in toolsAll eight by default; AddBeaconMcp(x => …) chooses (BuiltInTools), turns saved-query tools off (SavedQueryTools = false) and replaces the server instructions (ServerInstructions) and the server card’s description (ServerDescription). A tool left out is neither listed nor callable, and the server card lists only the ones served.
SessionsClients that start with initialize (most clients today) get an Mcp-Session-Id (idle timeout 30 minutes) and need session affinity when you run several instances. Clients speaking the stateless protocol revision 2026-07-28 need no session. A request that is neither (no session and no 2026-07-28 metadata) is refused with 400.

MapBeaconMcp() maps the route with the Execute-scope policy, and two filters inside the MCP layer refuse any caller without Execute even if the route were mapped with a weaker policy. Do not map it yourself with MapMcp(...) and a bare RequireAuthorization().

Clients that connect by URL (claude.ai, Claude Desktop connectors, VS Code) discover how to authenticate from three anonymous documents:

PathContent
/.well-known/oauth-protected-resourceRFC 9728 protected-resource metadata: resource ({base}/beacon/mcp), scopes_supported, authorization_servers, bearer in header only
/.well-known/oauth-protected-resource/beacon/mcpThe same, at the path-inserted location
/.well-known/mcp/server-card.jsonServer card: name, transport, endpoint, authentication, the eight built-in tools

Every 401 from /beacon/mcp carries WWW-Authenticate: Bearer resource_metadata="{base}/.well-known/oauth-protected-resource".

Set Beacon:PublicBaseUrl (for example https://beacon.example.com) in every proxied or public deployment. Without it, the documents and challenges are built from the request’s Host header, which the client controls; Beacon warns at startup.

To point OAuth-capable clients at your identity provider:

{
"Beacon": {
"PublicBaseUrl": "https://beacon.example.com",
"Mcp": {
"ProtectedResource": {
"AuthorizationServers": [ "https://login.microsoftonline.com/<tenant-id>/v2.0" ],
"ScopesSupported": [ "api://<beacon-app-id>/mcp.access" ]
}
}
}
}
KeyDefaultWhat it does
AuthorizationServersNoneIssuers published as authorization_servers (each must equal the server metadata’s issuer). Takes precedence.
DeriveAuthorizationServerFromJwtIssuerfalsePublish the bearer-JWT issuer instead (prefers an Entra /v2.0 issuer).
ScopesSupported["Execute"]Scopes published to clients. Set your identity provider’s scope when you publish an external authorization server, and add offline_access so clients get a refresh token instead of asking people to sign in every hour (Beacon warns at startup when an api:// scope is published without it). A configured list replaces the default whole.
ResourceNameBeacon MCPThe resource_name an MCP client shows for the server, also the server card’s name and the server name sent at initialize (Beacon when empty). A host serving Beacon as its own MCP server names its application.
ResourceDocumentationBeacon’s MCP docsThe resource_documentation URL, also the server card’s documentation. Anything but an absolute https URL logs a startup warning.

Code can complete these settings too: services.PostConfigure<McpProtectedResourceOptions>(...) runs after the configuration is bound, on every request.

With none of these set and OIDC enabled, Beacon publishes its OIDC authority.

The simplest path: each person creates a key for themselves.

  1. In API keys (/beacon/api-keys), create a key with the Execute scope, limited to the projects the assistant should use, with a short expiry. (Scripts can do the same through POST /beacon/api/api-keys; see headless first run.)
  2. Copy it. It is shown once.
  3. Configure the client with Authorization: Bearer sk-sem_… (below).

The key acts as its owner, only in the key’s projects, capped by the owner’s current role: reading tools and saved-query tools work for any owner, while query and ask need an owner who can query (Editor or above). When a key reaches several projects, the assistant passes project_id on each call.

For organisation-wide access without personal keys, Beacon accepts Entra access tokens on /beacon/mcp and maps each token to an MCP caller with projects and a scope. Two kinds:

  • Users: delegated (on-behalf-of) tokens from a client app your users sign in to; optionally auto-provisioned as Beacon users.
  • Systems: app-only tokens of a known application or service principal.
builder.Services.AddBeaconJwtAuthentication(jwt =>
{
jwt.EnableBearerAuthentication = true;
jwt.Validation.JwksEndpoint = "https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys";
jwt.Validation.ValidIssuers = [ "https://login.microsoftonline.com/<tenant-id>/v2.0" ];
jwt.Validation.ValidAudiences = [ "api://<beacon-app-id>" ];
});
{
"Beacon": {
"Mcp": {
"Callers": {
"AllowedTenants": [ "<tenant-id>" ],
"Users": {
"Enabled": true,
"RequiredGroups": [ "<group-object-id>" ],
"ProjectIds": [ 4 ],
"GroupProjects": { "<finance-group-id>": [ 7 ] },
"DefaultRoleName": "Viewer"
},
"Systems": [
{ "Name": "reporting-bot", "ClientId": "<app-client-id>", "ProjectIds": [ 4 ], "Scope": "Execute" }
]
}
}
}
}
KeyDefaultWhat it does
AllowedTenantsRequiredTenants whose tokens may map to a caller. Required once any caller is configured.
Users:EnabledfalseAccept delegated user tokens.
Users:RequiredRoles / RequiredGroupsNoneThe user needs one of them; with neither set, AllowAnyTenantUser must be true.
Users:ProjectIds, Users:GroupProjectsNoneProjects every accepted user gets, plus extra projects per group or app role.
Users:ScopeExecuteRead or Execute.
Users:AutoProvision / DefaultRoleNametrue / ViewerCreate a Beacon user on first call, with this role (never Admin or a functional role).
Systems[]NoneName, exactly one of ClientId (app-only token) or ObjectId (service principal), ProjectIds, Scope, HostClaims.

An absent Callers section admits no JWT caller on MCP; it fails closed. Startup validates the section, including that bearer JWT validation is on with explicit issuers and audiences. Audit rows identify Entra callers by a keyed hash (Beacon:Mcp:CallerHashKey, else derived from the encryption key). Full reference: Entra ID callers.

Terminal window
claude mcp add --transport http beacon https://beacon.example.com/beacon/mcp \
--header "Authorization: Bearer $BEACON_MCP_API_KEY"

Or share it with your team in the repository’s .mcp.json (Claude Code expands environment variables):

{
"mcpServers": {
"beacon": {
"type": "http",
"url": "https://beacon.example.com/beacon/mcp",
"headers": { "Authorization": "Bearer ${BEACON_MCP_API_KEY}" }
}
}
}

~/.cursor/mcp.json or .cursor/mcp.json in the project:

{
"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}" }
}
}
}

Add Beacon as a custom connector with the URL https://beacon.example.com/beacon/mcp. Connectors authenticate with OAuth, so this path needs Option B: publish your Entra authorization server and scope through Beacon:Mcp:ProtectedResource, register the client’s redirect URI in your Entra app, and map users with Beacon:Mcp:Callers:Users. The step-by-step Entra setup is on Entra ID callers.

Client configuration formats change; if a snippet does not work, check the client’s own documentation. The endpoint, header and transport above are what Beacon needs.

Terminal window
BEACON=https://beacon.example.com
curl -s $BEACON/.well-known/mcp/server-card.json | jq '.tools[].name'
H=(-H "Authorization: Bearer $BEACON_MCP_API_KEY" -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream")
# 1. initialize: the response header Mcp-Session-Id names the session
SESSION=$(curl -s -D - -o /dev/null "${H[@]}" $BEACON/beacon/mcp \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}' \
| awk -F': ' 'tolower($1)=="mcp-session-id"{print $2}' | tr -d '\r')
# 2. confirm initialisation
curl -s "${H[@]}" -H "Mcp-Session-Id: $SESSION" -H "MCP-Protocol-Version: 2025-06-18" $BEACON/beacon/mcp \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
# 3. list the tools
curl -s "${H[@]}" -H "Mcp-Session-Id: $SESSION" -H "MCP-Protocol-Version: 2025-06-18" $BEACON/beacon/mcp \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

A client on the stateless revision skips the session: it sends MCP-Protocol-Version: 2026-07-28 and Mcp-Method: tools/list headers (plus Mcp-Name: <tool> on tools/call) and puts {"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}} in params._meta.

Then ask the assistant something like “Use Beacon’s get_context to describe my project.”

SymptomCause
400 “A new session can only be created by an initialize request”The client sent a request without a session: start with initialize (or use the stateless 2026-07-28 revision).
401 with resource_metadataNo or invalid credential.
403 at the doorThe key has the Read scope, the JWT caller is not mapped, or a browser session is calling.
”Permission denied: this credential does not have the Execute scope”Same, reported as a tool error.
”No projects are available to you.”The key or caller reaches no project.
”Access denied: you may read project N but not run ad-hoc queries in it.”Viewer-level access: query/ask need Query (Editor).

Per-project MCP settings (row limits, read-only, PII detection, content retention, learning) live on MCP settings (/beacon/mcp-settings), editable by a SecurityAdmin and readable by an Auditor. Deployment locks (Beacon:Mcp:ForceReadOnly, ForcePiiDetection, ForceNoContentRetention, ForbidRowDataToLlm) and ceilings (Beacon:Mcp:Ceilings) bind them from configuration. Every call is audited; see MCP observability & audit.