Skip to content Documentation index for agents (llms.txt)
Glotto Beta
Get started

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), and invoke_tool (validates the arguments, then executes the identical request and result rendering as the per-operation tool, including its jq_filter projection). 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 full schema describing 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 generate is 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 generate writes the server on its own, for a separate repo or image.
  • glotto mcp serve starts a server straight from glotto.yml with 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 npx entrypoint). The default.
  • Streamable HTTP — for remote/hosted deployments, with OAuth 2.1 + PKCE. Selected with --transport=http (or the shorter --http, or GLOTTO_MCP_TRANSPORT=http); the listen port comes from --port=N, else PORT, 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 own x-glotto-upstream-<scheme> header — Authorization cannot 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_passthrough block 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.filterslocked 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:

  • openWorldHint is 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:

  1. The allow set is constrained only if allowed_methods has a pattern or allow_http_gets is true; a method is in it if it matches a pattern or is a GET under allow_http_gets.
  2. blocked_methods is subtracted after — a method both allowed and blocked is denied.
  3. 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_tools omits it, describe_tools calls it unknown, invoke_tool refuses it. Nothing at runtime widens the set: no --resource flag, env var, or mcp.filters value can add back a method your glotto.yml denies.
  • Code Modeexecute statically 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 add command, a generic .mcp.json snippet, a Cursor install deep link, and an npx quickstart naming the exact env vars the server reads.
  • npm publish — the release flow publishes it to npm exactly as it does your SDKs (--all includes it automatically whenever your glotto.yml has an mcp block).
  • Docker — an emitted Dockerfile runs 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 pack produces the .mcpb bundle, 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 a server.json for 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.