The MCP server
From the same Glotto IR, Glotto generates a multi-mode MCP server your
customers ship, so AI clients (Claude Desktop, Claude Code, Cursor, …) can call your API.
It’s a TypeScript server built on the official MCP TypeScript SDK v2
(@modelcontextprotocol/server), speaking the 2026-07-28 MCP spec — the stateless core
(no protocol-level sessions, serverless/edge-friendly) with server/discover, per-request
version negotiation, and the required standard request headers handled by the SDK.
Three modes, one artifact
Selected at server start (--mode tools|code|dynamic, GLOTTO_MCP_MODE) or per request on the
HTTP transport (?mode=) — no regeneration:
- Tools Mode (the default) — one MCP tool per API operation (
<resource>_<method>). Best for small, focused APIs. - Code Mode — exactly two tools,
execute(runs TypeScript in a sandboxed isolate against your generated SDK, pre-authenticated — see Transports & auth below) +search_docs. Drastically reduces context use and supports chained calls — the competitive wedge for large APIs on code-capable agents. - Dynamic Mode — three meta-tools over the same operations:
list_tools(a compact, schema-free menu, optionally narrowed by resource),describe_tools(full JSON-Schema input schemas on demand — the same fidelity Tools Mode registers), andinvoke_tool(validates the arguments, then executes the identical request and result rendering as the per-operation tool, including itsjq_filterprojection). The honest fallback for large APIs on agents that cannot execute code — schemas arrive on demand instead of up front, so context cost stays near-constant however many operations your API has.
glotto.yml#/mcp/modes restricts which modes the server enables (omit it to enable all
three); a requested-but-disabled mode falls back to the enabled default.
search_docs is always available in every mode, even when other tools are filtered out.
What a search_docs hit tells you
A reference hit describes the operation, not just its name. Alongside the tool name and the
client.<resource>.<method> SDK call, each hit carries:
requestBody— the body’s type, its content type, and whether it is required.response— the type of the first success response.parameters[]— each parameter’s location, whether it is required, its description from your spec, and a fullschemadescribing it.
A schema keeps what a caller needs in order to construct a value: enum members (and any
deprecation notice on a member), string and integer format, array item types, map value types,
object fields and which of them are required, union members and any discriminator, and nullability.
A named model appears as { "kind": "model", "model": "Pet" } rather than being expanded inline.
Those names resolve through the payload’s models map, which carries the models the returned hits
reference — including ones reached only through other models:
{
"results": [
{
"kind": "reference",
"tool": "pets_create",
"sdkCall": "client.pets.create",
"requestBody": { "required": true, "contentType": "application/json",
"schema": { "kind": "model", "model": "Pet" } },
"response": { "kind": "model", "model": "Pet" }
}
],
"models": {
"Pet": { "kind": "object", "required": ["name"],
"fields": { "name": { "kind": "string" },
"status": { "kind": "enum", "values": ["available", "sold"] } } }
}
}
The payload is this { results, models } object rather than a bare array of results. If you consume
search_docs output directly, read .results from it; each entry keeps every field it had before.
Browsing the surface without a query
Searching only helps once you can guess an API’s vocabulary, and Code Mode has no tools/list to
fall back on. So calling search_docs with no query browses the operations instead of searching
the docs: results becomes a compact listing — tool name, one-line title, resource, method, HTTP
method, path — alongside a resources table of contents counting the operations in each. Narrow it
with resource, page it by echoing back the nextCursor a response returns, and search a listed
tool name to get that operation’s full record. Pages are bounded in size, so browsing a large API
never dumps its whole surface into one response.
The listing shows what the server can actually invoke: in Tools and Dynamic Mode that is exactly
what list_tools reports, filters included; in Code Mode — which registers no per-operation tools
for a filter to hide — it is everything glotto.yml#/mcp/permissions allows.
resource and cursor shape a browse, so passing either alongside a query is an error rather
than a silently ignored argument.
A search returns the ten best matches plus total, the number of records that actually matched.
Without that count a query matching your whole API and one matching exactly ten results look
identical, so total is what tells you an answer was truncated and the query is worth narrowing.
execute type-checks before it runs
An agent’s first attempt at an unfamiliar API is often wrong, and the cheapest place to find that
out is not your production upstream. So before execute runs anything, it type-checks the
submitted code against the SDK it is about to bind. Code that calls a method the SDK does not have,
or passes a wrongly-shaped request body, is not run: nothing is sent to your API, and the
diagnostics come back as the tool result — at line and column numbers in the code the agent
submitted, so it can correct the call and resubmit.
That matters most for the mistakes that would otherwise succeed at reaching you. A hallucinated
method is only a wasted round-trip, but a wrong request body is a real, credentialed request
against your API — one that can mutate state on a POST and answers with your validation error
rather than “that field does not exist”.
This is a correctness aid, not an access control. A // @ts-ignore suppresses it, so what a run is
permitted to reach is still governed by the sandbox permissions and by
method permissions. Set GLOTTO_MCP_CODE_MODE_TYPECHECK=off to skip the
check; it is on by default.
Your custom code is not bound in Code Mode
Every generated TypeScript SDK re-exports your never-overwritten lib/ directory as lib, so
sdk.lib.myHelper() is part of your SDK’s public surface. A Code Mode run does not carry it: the
binding hands the isolate the generated SDK, and your own lib/ sources are not part of it.
Rather than let that surface as undefined, an agent that reaches for one of your helpers is stopped
twice — the type-check above refuses the call before the run starts, and if the check is off or
suppressed, the access throws an error that names the boundary instead of
undefined is not a function. Only reaching for a member fails: code that logs, serializes, or
passes lib around keeps working.
Call the generated client from execute and keep helper logic on the agent’s side of the sandbox.
Code Mode’s one prerequisite: Deno
execute runs the agent’s code in a Deno sandbox, so a self-hosted Code
Mode server needs the Deno CLI on its host (GLOTTO_MCP_DENO_PATH selects the binary when it
isn’t on PATH). Two paths need no install at all: the emitted Docker image already includes
Deno, and the hosted Glotto MCP Cloud gateway runs the sandbox on our infrastructure.
Nothing else depends on it: Tools Mode, Dynamic Mode, and search_docs work without Deno. A
server started in Code Mode without it warns at start-up rather than waiting for the first
execute to fail.
Generating the server
The MCP server is not a targets: entry. It is configured by a top-level mcp: block, the
same way the docs site and the mock server are — add one to your glotto.yml beside your spec:
openapi:
source: ./openapi.yaml
targets:
typescript: {} # your SDKs, as usual
mcp: # <- the MCP server, a top-level block
modes: [tools, code, dynamic]
search_docs: true
Glotto publishes the generated server to npm for you, so mcp is a surface for publishing
— but the targets: map is the codegen surface, and putting mcp there fails with
GLOTTO_CONFIG_UNKNOWN_TARGET.
With the block in place there are three ways to get a server, and they differ in what you end up holding:
glotto generate # your SDKs AND the MCP server, into sdks/mcp/
glotto mcp generate --out ./server # just the server
glotto mcp serve # run it now, over stdio — nothing written to disk
glotto generateis the one to use when the server ships beside your SDKs in the same repo — it is the artifact your regeneration and drift checks then cover.glotto mcp generatewrites the server on its own, for a separate repo or image.glotto mcp servestarts a server straight fromglotto.ymlwith no build step. This is the fastest way to point Claude Desktop or Cursor at your API and see the tools appear.
On the published @glotto/cli, generate and mcp generate run server-side and need
glotto login first; mcp serve runs the emitted artifact locally.
The emitted project is an ordinary npm package — npm install && npm run build produces
dist/main.js, which is what you point an MCP client at.
Transports & auth
- stdio — for local clients (an
npxentrypoint). The default. - Streamable HTTP — for remote/hosted deployments, with OAuth 2.1 + PKCE. Selected with
--transport=http(or the shorter--http, orGLOTTO_MCP_TRANSPORT=http); the listen port comes from--port=N, elsePORT, else 3000. A flag always outranks its environment variable, so a container image that baked one in can still be overridden on the command line.
The HTTP transport binds loopback (127.0.0.1) by default, so a server you start on your own
machine is not reachable from the network until you say otherwise. Pass --host=0.0.0.0 (or set
GLOTTO_MCP_HOST) to serve every interface. The emitted Docker image sets 0.0.0.0 for you, and
that is not an exception to the rule: inside a container that address is the container’s own
network namespace, and what makes the server reachable is the port you publish with
-p 3000:3000. Binding loopback inside a container would break publishing rather than secure it.
On the HTTP transport the server also checks the browser Origin header. A request carrying an
Origin you have not allowlisted is refused with 403 before its credentials are checked,
which is what stops a page in someone’s browser from resolving a hostname it controls to
127.0.0.1 and reaching a server bound there. Requests with no Origin — every non-browser
client, so effectively all normal agent traffic — are unaffected, and the allowlist defaults to
your own GLOTTO_MCP_RESOURCE origin, so most deployments need no configuration. Name others
with --allowed-origin a,b (env GLOTTO_MCP_ALLOWED_ORIGINS), or pass * to switch the check
off where a gateway already terminates the browser connection.
How the server validates an inbound bearer depends on what your authorization server mints. For
JWTs, set GLOTTO_MCP_OAUTH_JWKS_URI and the server verifies signatures locally against your
published JWK set — no round trip per request. For opaque tokens, which carry nothing to verify
locally, set GLOTTO_MCP_OAUTH_INTROSPECTION_URL plus the client id and secret your AS issued you
and the server validates each token via RFC 7662 introspection. Set GLOTTO_MCP_OAUTH_ISSUER and
GLOTTO_MCP_OAUTH_AUDIENCE alongside either: RFC 7662 does not require an authorization server to
scope its answer to your server, so those checks are what prove a token was minted for you rather
than for another client of the same AS. Introspection is uncached by default, so revoking a token
takes effect on the very next request; GLOTTO_MCP_OAUTH_INTROSPECTION_CACHE_TTL opts into caching
positive results, and a cached result never outlives the token itself. Configure neither and the
server rejects every token — secure by default; configure both and it refuses to start rather than
silently picking one.
The two directions of auth are distinct: OAuth 2.1 + PKCE above governs how a client
authenticates to the MCP server. To authenticate the server’s outbound calls to your
upstream API, Tools Mode handlers attach the API’s configured credential — matching your
glotto.yml auth scheme (bearer / OAuth2 token, HTTP basic, or API key) — read from the
environment at runtime (the API’s configured env var, else a GLOTTO_API_* default).
Code Mode reaches your API through the same generated SDK. When your glotto.yml builds the
TypeScript SDK alongside the MCP server, execute’s sandbox is handed that SDK as the module
./sdk, so guest code opens with the import it would use anywhere else:
import { Client } from './sdk';
const client = new Client(); // already pointed at your API, already authenticated
const pets = await client.pets.listPets();
new Client() is pre-addressed and pre-authenticated: Glotto resolves the credential
host-side, through the same applyAuth the Tools Mode handlers use — so caller-forwarded
passthrough, per-user outbound OAuth, and the operator’s environment variable all reach guest code
in that same precedence order, per request. No credential belongs in the submitted code.
Binding the SDK grants the sandbox nothing. The credential arrives as a pre-bound request
header rather than as an environment grant, and the isolate keeps its deny-all baseline exactly as
before: no environment access, no filesystem access, and network only to your configured API host.
A guest that tries to read Deno.env still gets a permission denial with the SDK sitting right
there. The one refinement: guest code sees an empty process.env rather than a denial, because
the generated SDK probes it for the telemetry opt-out and for env-var credential fallbacks, and a
denial there would fault new Client() before it could run. Empty is the honest answer — nothing
from your server’s environment is present to read, and the credential arrives as a header regardless.
When your API authenticates per operation (a multi-scheme securitySchemes registry), the
credential is scoped to the request rather than to the client. A client constructed before an
operation is chosen cannot know which scheme’s secret to carry, so the server resolves each
operation’s own requirement up front — through that same applyAuth — and the bound client attaches
only the entry matching the request it is actually making. An endpoint that asks for scheme B
receives B’s credential and nothing else; a scheme’s secret is never sent to an endpoint that did
not ask for it. An API key configured to travel in the query string is applied there rather than as
a header, and an operation you have blocked under mcp.permissions never has its credential placed
in the sandbox at all.
Two cases stay narrower, and the emitted execute description says which one applies rather than
promising more than the server has. Without the typescript target there is no SDK to bind, so
guest code calls the API with fetch. And when there is no credential to attach at all — an
unauthenticated API, or a registry whose every scheme is custom — the SDK is bound but nothing
authenticates it, so an authenticated call passes a credential in through execute’s input.
Acting on behalf of each user (outbound OAuth)
The environment credential above is one credential for the whole deployment — right when you
host a server for your own team, wrong when you host one server for all of your customers. If your
API’s OpenAPI spec declares an OAuth2 authorizationCode flow, the HTTP transport can instead run
that flow as a client to your API, so each caller reaches it as themselves:
mcp:
upstream_oauth:
enabled: true
The authorization and token endpoints come from your spec’s flows.authorizationCode — there
is nothing to re-declare here, and nothing to keep in sync by hand. Register your server with your
provider and give it GLOTTO_MCP_UPSTREAM_CLIENT_ID / GLOTTO_MCP_UPSTREAM_CLIENT_SECRET; secrets
never belong in glotto.yml. On a multi-scheme API, name the scheme the flow satisfies
(upstream_oauth.scheme) — the server will not guess which credential a user’s token stands in for.
Every authorization and token request also names your API as its RFC 8707 resource, so a provider
that supports resource indicators can issue each user a token restricted to your API rather than one
valid everywhere that provider is trusted. It is derived from the environment URL already in your
glotto.yml — nothing to configure — and providers that do not implement the parameter are required
to ignore it, so sending it is safe either way.
Consent is three steps, and the third one is the point. The agent calls
/oauth/upstream/authorize with its own access token and gets a redirect to your provider’s consent
screen. Your provider redirects back to /oauth/upstream/callback, which exchanges the code and —
storing nothing yet — shows the user a single-use claim code. The agent then posts that code to
/oauth/upstream/confirm, again with its own access token, and only that call writes a token to the
vault:
curl -X POST https://your-server.example.com/oauth/upstream/confirm \
-H "Authorization: Bearer $AGENT_ACCESS_TOKEN" \
-H 'content-type: application/json' \
-d '{"claim_code":"<the code from the consent screen>"}'
The extra step exists because starting an authorization is not the same as granting one. Without it,
any authenticated caller could hold a live state, get one of your users to complete the real
consent screen, and receive that user’s credential. The claim code only ever reaches the browser
that saw the consent screen, so holding it is the proof. If the confirming account is not the one
that started the flow, the grant is discarded with a 403 rather than re-homed — finishing an
authorization on behalf of someone who never started it is the outcome this prevents.
Each user’s tokens are keyed on the verified subject of their inbound access token — never on a value the caller supplies — and access tokens refresh automatically, including against providers that rotate the refresh token on every exchange.
Tokens live behind a TokenVault interface. The shipped default holds them in memory, so every
user re-consents after a restart; implement the interface to back it with Redis or a database.
This is opt-in and HTTP-only: with the flag off, or on stdio, the environment credential above is unchanged.
If your API has no OAuth at all, the per-caller passthrough below is the other way to lift the same single-tenant assumption. They are alternatives, not layers: arm both and the server says so at start-up, passthrough wins, and the per-user tokens it vaults are never sent upstream.
Per-caller credentials (remote deployments)
Sourcing the outbound credential from the environment welds one deployment to one credential —
right for a team hosting a server for itself, wrong for an API company hosting one server for
all of its customers. Setting GLOTTO_MCP_CREDENTIAL_PASSTHROUGH=1 on an HTTP deployment forwards
the caller’s credential upstream instead, so a single deployment serves many users with no
OAuth machinery:
{
"mcpServers": {
"my-org-mcp": {
"url": "https://mcp.example.com/mcp",
"headers": { "Authorization": "the-caller's-credential" }
}
}
}
It is off by default because it changes the trust model, and three properties are worth knowing before you arm it:
- It replaces the inbound OAuth bearer gate. The inbound credential is forwarded upstream rather than verified as an MCP access token — your API becomes the authority. The server is not thereby unauthenticated: armed, it holds no credential of its own to spend.
- It fails closed. A request that forwards no credential is refused with
401. The environment credential is never used to serve a caller who supplied none, so an anonymous caller can never spend yours. - Multi-scheme APIs name the scheme. With one scheme the credential rides
Authorization, used verbatim. With several, each scheme has its ownx-glotto-upstream-<scheme>header —Authorizationcannot say which scheme it satisfies, so it is not consulted. The emitted README lists the exact headers your API accepts.
HTTP only, on the standalone transport and the embedded mounts alike; stdio is a local single-user process where the environment credential is already correct.
Capping which schemes may be caller-sourced
The env var is an operator’s switch, and on a multi-scheme API it arms every scheme at once —
including one whose credential is the deployment’s rather than the caller’s, like a partner key or
a signing secret. mcp.credential_passthrough is the author’s half: a ceiling on which schemes
may ever be caller-sourced, which the operator’s switch can only fill, never widen.
mcp:
credential_passthrough:
schemes: [userAuth] # only this scheme may be sourced from the caller
Config declares, env enables. Three properties follow, and the third is the one to read closely:
- It only ever narrows. The env var stays the sole arming switch; the ceiling never turns
passthrough on, and with no
credential_passthroughblock nothing changes. - It cannot be widened at runtime. The cap is baked in at generation time — the emitted server contains no code path to a forwarded credential for a scheme you did not name, so there is nothing an environment variable or flag could switch back on.
- A capped scheme keeps spending your credential. This is the deliberate exception to the fail-closed rule above: while passthrough is armed, a scheme outside the ceiling still sources from its environment variable, and a forwarded header for it is ignored. That is the point — you declared that credential yours to spend — but it does mean any caller the 401 gate admits reaches your API as your deployment for those schemes. Cap a scheme because it should be yours to spend, not as a way to keep callers away from an operation.
The block is an allow-list, so it resolves fail-closed: writing credential_passthrough at all
declares a ceiling, and a name that matches no scheme in your spec simply admits nothing rather
than falling back to admitting everything. Run the server with --debug to see what it resolved:
passthrough userAuth (mcp.credential_passthrough ceiling — 1 of 3)
If your API does declare an OAuth authorizationCode flow, prefer the outbound flow above:
it binds each user’s credential to an identity your server cryptographically verified, where
passthrough trusts whatever the caller sends. Reach for passthrough when there is no OAuth to run.
Tool filtering at start (--resource, --operation read|write, --tag) lets a client
subset a large API; a filtered-out operation is invisible in Tools Mode and to all three
Dynamic Mode meta-tools alike.
--operation read|write splits on HTTP method safety (RFC 9110), the same partition the
tool annotations below publish — so GET, HEAD, OPTIONS and TRACE
are all reads, and only the verbs that can actually change something are writes. Stand up a
read-only deployment with --operation read and a HEAD operation comes with it.
Debugging what the server actually resolved
Four glotto.yml keys can quietly beat a runtime flag — mcp.modes clamps --mode to an
enabled mode, mcp.filters’ locked set overrides --resource/--operation/--tag,
mcp.permissions removes operations before the filters ever see them, and
mcp.credential_passthrough caps what GLOTTO_MCP_CREDENTIAL_PASSTHROUGH can reach — and from
outside the process none of it is visible. --debug (or GLOTTO_MCP_DEBUG=1) prints what the server
resolved, naming the key that won, then starts normally:
glotto mcp debug — petstore-mcp
transport stdio (default)
mode tools (requested "code" is not enabled by mcp.modes)
modes enabled tools, dynamic
filters operation=read (mcp.filters resolved over the runtime flags "operation=write")
tools 2 of 3 registered (mcp.permissions denies 1 of 3)
search_docs registered
sandbox binary deno (default)
sandbox allow-net api.petstore.example
The report goes to stderr, never stdout — on the stdio transport stdout carries the MCP protocol itself, so it stays clean whether or not you pass the flag.
Tool annotations
Every operation tool is annotated with the standard MCP hints, derived from the operation’s HTTP method. You configure nothing — they come from the same spec the tools do:
| HTTP method | readOnlyHint |
idempotentHint |
destructiveHint |
|---|---|---|---|
GET, HEAD, OPTIONS, TRACE |
true |
true |
false |
POST |
false |
false |
false |
PUT |
false |
true |
true |
PATCH |
false |
false |
true |
DELETE |
false |
true |
true |
POST is the one write verb marked non-destructive: it is the verb that creates, where
PUT replaces, PATCH modifies, and DELETE removes state that already exists.
MCP clients increasingly use these hints to decide what an agent may run unattended and what
needs a human to confirm — auto-running a list call while pausing on a delete. Tools Mode
publishes them on tools/list; in Dynamic Mode, describe_tools reports the same values.
Two deliberate choices worth knowing:
openWorldHintis never set. Whether an operation reaches the wider internet is a property of your API’s implementation, and an OpenAPI document does not describe it. Rather than guess, the server leaves the hint unset and your client’s own default applies.- An unrecognized HTTP method is annotated at maximum restriction — not read-only, not idempotent, destructive — rather than left bare, so an unusual verb is never mistaken for a safe one.
Annotations are advice to a client, not enforcement. A client is free to ignore them, and nothing about them prevents a call. To constrain what the server can reach at all, use method permissions below; to enforce access per request, use a scoped API token or the Glotto MCP Cloud gateway.
Method permissions
Filters are an operator convenience an operator can also widen back. When you want the
server to be born unable to reach certain operations — a self-hosted deployment that
should only read, say — declare a permission set in glotto.yml:
mcp:
permissions:
allow_http_gets: true # every operation mapped to HTTP GET
allowed_methods: # regexes over the qualified method name
- pets\.photos\..*
blocked_methods: # applied last — beats both allow keys
- pets\.delete
The name a pattern matches is the operation’s fully-qualified method name —
<resource path>.<method>, dotted through subresources: pets.list, pets.photos.add.
Patterns are fully anchored, so pets\.get matches pets.get and not pets.getAll. An
invalid regex fails glotto generate rather than the running server.
Resolution mirrors the shape you may know from Stainless:
- The allow set is constrained only if
allowed_methodshas a pattern orallow_http_getsistrue; a method is in it if it matches a pattern or is a GET underallow_http_gets. blocked_methodsis subtracted after — a method both allowed and blocked is denied.- With neither allow key set, everything not blocked is permitted.
Enforcement differs by mode, and the difference matters:
- Tools Mode and Dynamic Mode — a denied operation is simply never registered.
list_toolsomits it,describe_toolscalls it unknown,invoke_toolrefuses it. Nothing at runtime widens the set: no--resourceflag, env var, ormcp.filtersvalue can add back a method yourglotto.ymldenies. - Code Mode —
executestatically scans the submitted TypeScript before running it and refuses, without reaching the sandbox, when the code references a denied operation by qualified name or by request path.
Method permissions are a convenience layer, not a security boundary. In Tools and Dynamic Mode the gate is structural — an unregistered tool cannot be called. In Code Mode it is static analysis of guest code, and static analysis can be circumvented: dynamically constructed URLs, indirection, and deliberate obfuscation all defeat it. Use permissions to keep a well-behaved agent inside its lane. To actually protect sensitive API operations, use API-layer authentication with a scoped API token or restricted API key, or put the server behind the Glotto MCP Cloud gateway, where access control is enforced per request.
Experimental: async tasks
The glotto.yml#/mcp/experimental/async_tasks flag opts the operations your spec marks
long-running (a 202-accepted response or an explicit x-polling extension) into async
task-capable tools. The flag is experimental, default off, and revision-tracked —
and that revision tracking just fired exactly as designed: the final 2026-07-28 MCP spec
redesigned async tasks as the official io.modelcontextprotocol/tasks extension and retired
the experimental 2025-11-25 (SEP-1686) shape the flag previously emitted.
The flagged emission is being rebuilt for the extension shape. Until that lands, enabling the
flag for an API with long-running operations fails generation with an actionable error
rather than emitting a server on the retired shape; with the flag absent or false (the
default — and the only state to ship to consumers) the emitted server carries no task surface
and is unaffected.
Install & distribution
The emitted server is a publishable npm package (no private flag, a runnable bin, a
dist-only files list) and ships with every mainstream install affordance:
- README install blocks — a
claude mcp addcommand, a generic.mcp.jsonsnippet, a Cursor install deep link, and annpxquickstart naming the exact env vars the server reads. - npm publish — the release flow publishes it to npm exactly as it does your SDKs
(
--allincludes it automatically whenever yourglotto.ymlhas anmcpblock). - Docker — an emitted
Dockerfileruns the server over stdio (docker run -i) or Streamable HTTP (GLOTTO_MCP_TRANSPORT=http). - Claude Desktop one-click install — an emitted MCPB
manifest.json;npx -y @anthropic-ai/mcpb packproduces the.mcpbbundle, and the installer prompts for the API credential instead of baking it in. - Official MCP registry — set
mcp.registry_name(your reverse-DNS registry namespace) and Glotto emits aserver.jsonfor registry.modelcontextprotocol.io.
Your generated docs site closes the loop with a /connect-mcp/
page — the same install blocks, linked from the site nav, so API consumers can connect an AI
client without leaving your docs.