documentation /concept

MCP Server

Concept

A group of tools a Model Context Protocol server offers. External MCP clients (Claude, Cursor, …) authenticate with an access token from the app’s identity provider and call the configured tools directly. Not a page: it renders nothing and is not referenced from any tab.

Placement

Headless placement: a module with this concept renders nothing, so it is declared under items: and served by a handler that names it with module: rather than being placed in a tab segment or a handler’s chat: slot. That handler’s authentication: decides whose access tokens the endpoint honors and, through its rule, which of those callers it admits: roles: for the holders of any of the roles the authenticator declares, from its users-table columns or its membership table, and no key for anyone who signs in. An app may declare at most one such handler.

Outline Display: Expose {{tools}} to external MCP clients

Details

A module with this concept is declared under items: and served by a handler of type: mcp, which names it with module:. Referencing it from a tab segment or a chat: slot is a type error, and so is a page handler naming it.

Tool names are what a client calls, and they have to stay distinct within the module: a name contributed twice – easy to do when ingredientRef: splices shared tool lists into tools: – would appear twice in tools/list while every call to it reached whichever comes first, so the spec checker rejects it. By default a tool is named after what it acts on (lookup_Orders), so two lookups on the same table collide however they are otherwise configured; give an entry name: (a sibling of kind:, 1-64 characters of letters, digits, underscore, or hyphen) to set the advertised name yourself and tell them apart. Beside the name, tools/list carries each tool’s title, the human-readable name a client shows: the tool’s toolTitle setting, or derived from the wire name when unset (search_open_orders is “Search Open Orders”). Titles must be distinct within the module too, derived ones included, since a person tells tools apart by them. An entry may also carry category:, a free-text label that groups tools in Nectry’s own spec views; it is internal only and never appears in tools/list. The name the endpoint reports to clients is the handler’s title:, not a module setting.

That handler’s authentication: decides whose access tokens the endpoint honors, exactly as it decides who may load a page, and its optional query: decides which of those callers are admitted at all, with the same [Username] semantics a page query has. It must name an OIDC-backed authenticator whose API is configured with issuer, introspection_url, and audience; password, SecretCode, and Dummy authenticators have no external issuer and are rejected.

Set audience to this app’s own MCP endpoint URL – https://<the app's host and path>/mcp. The endpoint derives the absolute URLs in its discovery documents from that setting, so an opaque identifier there compiles but leaves the endpoint advertising a metadata URL that resolves nowhere.

The handler serves two HTTP endpoints: /mcp, which speaks JSON-RPC over POST, and /.well-known/oauth-protected-resource, which tells callers which identity provider issues tokens for this app. A 401 from /mcp carries the absolute URL of the second in its WWW-Authenticate challenge, which is how MCP requires clients to discover it. A 401 means the identity provider judged the token and refused it, or the handler’s query: did not admit the resolved caller; where the provider could not be reached at all, the endpoint answers 503 with a Retry-After instead, so a client keeps a token that was never in question. The app is an OAuth resource server only: it issues no tokens and runs no consent screen. Every request must carry Authorization: Bearer <token>, and a token minted for a different audience is rejected. Because those paths are fixed, an app may declare at most one mcp handler.

The tools: list draws on the same ingredient family Chat uses: table lookups, inserts, updates and deletes, and Tool pipelines built from row-action steps. A tool that must do several things – look something up and then act on it, call an API and record the result, check a rule before writing – is one Tool: its parameters declare what the caller supplies, its rowAction wires the steps, and its outputMapping picks what returns to the model. That ingredient’s steps decide its approval gate where the spec leaves requiresApproval unset, but an MCP surface applies no such gate, so put the policy in the pipeline instead: a Require placed before the step it protects refuses the call with its message, and If condition branches when both outcomes are normal.

Tools run unattended – there is no user in the loop to approve a call – so requiresApproval gates nothing here; it warns rather than failing, so one tool list can be shared with a Chat concept, which does gate it. The handler’s roles: decides who may call at all; scope what an admitted caller can reach with rowFilters (which see the token’s subject as currentUser) and with column selection, exactly as you would for Chat. A caller resolves to the same app user their browser session would, so per-user row filters and audit attribution behave identically on both paths.

Between the handler’s admission and those row filters sits an entry’s roles:, a sibling of name: and category: naming roles the handler’s authenticator declares: a caller must hold any one of them to see and call that tool. A caller holding none finds the tool absent from tools/list, and calling it anyway answers exactly as for a name that never existed. Absent means every admitted caller; [] means nobody.

Settings

Optional