Skip to content
Menu

Extending Beacon

View .md

An embedded Beacon can know your application, not only your databases. Three host extensions, all opt-in and all scoped to one host project:

ExtensionYou getPage
ExposeDbContext<TContext>Your EF Core model as a read-only data source, with a table allow-list and column maskingHost DbContext
ExposeDocsYour Markdown documentation imported as grounding for ask, search and get_documentationHost docs
AddHostEndpointToolsYour HTTP endpoints as MCP tools api_<name>, dispatched in-process through your own authorizationHost endpoint tools
builder.Services.AddBeaconServices(builder.Configuration, options => { /* … */ })
.ExposeDbContext<ShopDbContext>(x =>
{
x.ProjectName = "Shop"; // the host project; defaults to the data-source name
x.ReadOnlyConnection("ShopReadOnly") // ConnectionStrings:ShopReadOnly, a read-only login
.AllowTables("Orders", "OrderLines", "Customers")
.AllowEntity<Product>()
.ExcludeColumns(y => y.Table == "Customers" && y.Name == "Notes")
.MaskColumns(y => y.Name is "Email" or "Phone");
})
.AddSqlServerConnector()
.UseSqlServer(); // ends the chain: ExposeDbContext comes first
  • Tables are default-deny; unknown names fail startup.
  • Columns whose names look like secrets (password, token, apikey, hash, otp, …) are always excluded unless you lift one explicitly with IncludeSecretLikeColumn("Table.Column").
  • The connection string is never stored. Beacon keeps only its name and reads it from configuration when it connects.
  • SQL Server and PostgreSQL models are supported.
  • Every statement is checked against the exposed slice in the execution gate and again in the provider; masked columns are masked by output position.
.ExposeDocs(x =>
{
x.ProjectName = "Shop";
x.FromDirectory("docs", "**/*.md").Exclude("drafts/**");
// x.FromEmbeddedResources(typeof(Program).Assembly, "Shop.Docs.");
// x.MaxDocumentBytes = 512 * 1024;
})

.md, .markdown and .txt files are imported into the project, chunked and embedded. MCP clients read them through search ([IMPORTED_DOC] results), get_documentation document=… and get_context format=agents_md.

Both extensions need one startup call, after Beacon’s schema is migrated:

var app = builder.Build();
Beacon.Core.ServiceConfiguration.UseBeacon(app.Services, createSchema: true);
await app.Services.SyncBeaconHostAsync(); // data sources first, then docs; idempotent; replica-safe lock

SyncBeaconHostDataSourcesAsync() syncs the data sources only.

builder.Services.AddBeaconMcp()
.AddHostEndpointTools(x =>
{
x.ProjectName = "Shop"; // the host project the tools belong to
// x.AllowWriteTools = true; // off by default
});
app.MapGet("/api/orders/{id:int}", (int id, OrderService orders) => orders.Find(id))
.RequireAuthorization("ViewOrders")
.WithBeaconTool("order_by_id", "One order by id.", readOnly: true);

Or on a controller action: [BeaconTool("customer_orders", Description = "…", ReadOnly = true)].

  • ReadOnly must be declared explicitly; Beacon never infers it from the HTTP verb.
  • The endpoint must carry authorization metadata (or your host has a fallback policy); otherwise startup refuses it.
  • Calls run in-process through your endpoint’s own pipeline, as a host principal built for the MCP caller (IMcpHostPrincipalFactory).
  • Only mapped Entra callers (users or systems) of the host project can call them; API-key callers cannot.
  • Write tools (ReadOnly = false) need AllowWriteTools, are offered to user callers only and are audited as api_<name>:write.
  • Above 20 tools (NamedToolLimit), clients see search_api + call_api instead of one tool per endpoint.

A SecurityAdmin can publish an approved saved query as an MCP tool q_<name> from the query’s page. See Saved query tools.

Beacon registers most services with TryAdd, so your registration wins if you register first (before AddBeaconServices, AddBeaconApiServices or AddHostEndpointTools, depending on who registers it).

InterfaceDefaultReplace it to
IBeaconSchedulerNone (required)Use your job runner.
IEmailAdapternoneSend email (AddEmailAdapter<T>()).
IBeaconAuthenticationProvideruser storeCheck passwords somewhere else.
IBeaconAuthorizationProvideruser store / allow-allCap users’ global read/write from your own model.
IProjectMembershipSourceBeacon’s membership tableTake project membership from your application. Beacon’s membership endpoints then answer 409.
IMcpCallerMapperBeacon:Mcp:CallersMap JWT callers to projects and scopes your own way.
IMcpHostPrincipalFactorycopies name/email/oid; system callers get HostClaimsBuild the principal your endpoints expect for MCP calls.
IHostEndpointDispatchMiddlewarenoneRun your own middleware logic (tenant, culture, audit) around dispatched tool calls.
IApprovalNotifierSignalRNotify approvers elsewhere.
IBeaconUserContextHttpContextUserContextResolve the current user differently. Replace it in both places it is registered.

IProjectAccessEvaluator is replaceable in principle but it is the single decider for project access; replace it only with great care, and keep the project-authorization tests green if you fork it.