# MCP onboarding — P9b / M17

## Stav a soukromí

Lokální produktový server má připojené důvěryhodné adaptéry credentials, aktuálních oprávnění, tarifní způsobilosti a společného měření. **Nejde o nasazený konektor ani o hotový OAuth authorization server.** Samostatné `createMcpServer()` bez adaptérů stále bezpečně odmítá přístup.

**Dokumenty zpřístupněné přes MCP může klientská aplikace odeslat svému modelu. Záruky soukromí produktu se na tento přenos nevztahují.** Platí to i při selfhost provozu s vypnutou AI. Žádná desktopová klientská aplikace nebyla v P9b instalována ani ovládána.

`actions[].mcpTools` je kontraktové mapování, nikoli přihlašovací údaj ani záruka okamžité dostupnosti. `/agent/state.status.mcp` odděluje `runtimeAvailable`, `enabled` (souhlas organizace), `entitled` (schopnost tarifu) a `callsThisMonth` (počet rezervovaných MCP volání pro člena organizace).

## Vydání lokální organizační credential

1. Přihlásit se běžnou session cookie a ověřit e-mail.
2. Vlastník organizace výslovně zapne `mcpEnabled:true` přes `PATCH /api/organizations/{orgId}` po seznámení s upozorněním na klientský model. Výchozí stav je vypnuto.
3. Akce `issue-mcp-token`: `POST /mcp/token`, JSON `{"orgId":"<organizace>"}`, session cookie, `Content-Type: application/json` a `x-zentro-request: 1`. Bez souhlasu, oprávnění vlastníka nebo aktivního Standard/Pro je vydání odmítnuto. Selfhost (`tiers:false`) nevyžaduje placený tarif; ostatní kontroly platí.
4. Odpověď vrátí **jen jednou** `token`, `tokenType:"Bearer"`, `credentialId`, `orgId` a `expires` (Unix epoch v milisekundách). Hodnotu tokenu neukládat do verzované konfigurace, URL ani logů. V produktové persistenci je pouze hash credential a vazba na uživatele, organizaci a hash vydávající session.
5. Následující RPC na `/mcp` nebo `/mcp/token` používají `Authorization: Bearer <credential>`. Běžná session cookie sama není MCP bearer a credential není session cookie pro `/api`.

Credential nepřežije expiraci vydávající session (nejvýše 24 hodin). Každý požadavek znovu ověří její platnost, existenci ověřeného uživatele, organizaci, členství a nezměněnou roli, souhlas a aktivní tarif. Logout vydávající session, změna hesla/reset/obnova účtu, odebrání členství nebo změna role zneplatní použití. Vypnutí MCP credential odstraní, takže opětovné zapnutí starý token neoživí. Nové vydání nahradí předchozí credential stejné session/organizace. Smazání účtu/organizace odstraní také příslušné credential záznamy. Zrušený/propadlý placený tarif odmítá MCP, nikoli maže dokumenty.

## HTTP JSON-RPC profil

Používá se lokálně implementovaný profil `2026-07-28`, JSON odpovědi a tyto hlavičky:

- `Content-Type: application/json`
- `Accept: application/json, text/event-stream`
- `MCP-Protocol-Version: 2026-07-28`
- `Mcp-Method`: musí odpovídat `method` v JSON
- `Mcp-Name`: při `tools/call` musí odpovídat `params.name`
- `Authorization: Bearer <credential>`

`params._meta` obsahuje `io.modelcontextprotocol/protocolVersion` a objekt `io.modelcontextprotocol/clientCapabilities`. Podporované požadavky: `initialize`, `ping`, `server/discover`, `tools/list`, `tools/call`. Příklad bez tajných hodnot:

```json
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2026-07-28","capabilities":{},"clientInfo":{"name":"local-client","version":"1"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}
```

GET stream, DELETE session, SSE resume ani starší profil nejsou tímto slibovány. Host/Origin, velikost těla a admission serveru platí také pro MCP; nepovoluje se obecné CORS. HTTP 401 obsahuje Bearer challenge. Chybná credential vrací 401 `MCP_REVOKED`, nikoli předchozí chybějící adapter 503. Neznámé backend chyby jsou sanitizované; běžné `SAVE_CONFLICT` a `PLAN_LIMIT` zůstávají strojově rozlišitelné, včetně metriky limitu.

Vydání credential na `/mcp/token` je rozlišeno od RPC absencí RPC hlavičky a bearer autorizace; vždy potřebuje session a CSRF marker. Cookie nenahrazuje bearer v RPC.

## Nástroje a limity

Zdroj katalogu a schémat: [packages/mcp/index.mjs](../packages/mcp/index.mjs). Existuje čtrnáct nástrojů:

| Nástroje | Účel |
| --- | --- |
| `list_projects`, `list_documents` | Dokumenty organizace vázané ke credential; projekt v P5 znamená dokument |
| `read_document`, `read_version`, `list_versions` | Aktuální obsah, konkrétní neměnná verze a historie |
| `read_version_diff` | `documentId`, `fromVersion`, `toVersion`; změny `path/before/after` |
| `read_collaboration` | Vlákna, zprávy a anotace; volitelně `versionId` |
| `add_comment`, `add_annotation` | Zpráva do dostupného vlákna nebo anotace bloku |
| `create_version` | Kanonický Document, `baseVersion`, `confirm:true` |
| `upload_document_version` | `filename`, DOCX `base64`, `baseVersion`, `confirm:true`; stejný dokument |
| `ask_ai` | Modelový dotaz; `documentId`, `question`, volitelně `versionId`; stejná role `ai` a tarifní rezervace jako HTTP |
| `run_recipe` | Sekvenční recept nebo dry run; kanonické parametry a společný runner |
| `search_documents` | Složené lokální doslovné vyhledání, nejvýše 20 výsledků; jediný MCP-only nástroj |

Každý nástroj vyžaduje `orgId` odpovídající credential. Dokumentová oprávnění se ověřují znovu při použití. MCP používá kanonický `roleAllows` z tenancy, nikoli vlastní rozdílný seznam rolí. `confirm:true` je povinný vstup zápisových nástrojů verzí, ne náhrada oprávnění. Samostatný nástroj pro mazání není.

Schopnost `mcp:true` má Standard a Pro přímo v [PLANS](../packages/plans/index.mjs); Free ji nemá. Nevznikla nová cena ani smyšlený placený počet volání. Bez tarifů je selfhost dostupný po výslovném souhlasu. Sdílený produktový měsíční ledger měří celkové `apiCalls` a MCP podmnožinu `mcpCalls`; rezervace probíhá před backendem, a proto počítá také požadavek později odmítnutý backendem. Interní provedení téže operace se podruhé do API ledgeru neúčtuje.

Dokumentové, verzové, storage, importní a egress kvóty produktu platí i přes MCP. Čtení dokumentu/verze/diffu/diskuse účtuje výstup. Import rezervuje `ceil(bytes/3000)` stran před konverzí, ne skutečně vysázené stránky; neúspěšná konverze rezervaci nevrací. Více v [AGENT.md](AGENT.md). `ask_ai` a modelový recept mohou volat nakonfigurovaný produktový model; explicitně mechanické operace nikoli. Počet tokenů cizího klientského modelu se nevymýšlí. `PLAN_LIMIT` má jednotná pole `error`, `metric`, `limit`, `used`, `max`, `unlock`, `next` podle [P12 kontraktu](AGENT.md#p12--jednotné-tarifní-chyby-a-modelový-dotaz). Účetnictví nezahrnuje veškerou TCP/TLS režii ani libovolnou administrativní odpověď.

## Lokální stdio balíček

Balíček [packages/mcp](../packages/mcp) není zveřejněný v npm. Použije se lokální checkout, nikoli stažení domnělé publikované služby:

```sh
npx --offline --package /ABSOLUTE/PATH/TO/repo/packages/mcp zentrodocs-mcp
```

`ZENTRODOCS_MCP_TOKEN_URL` ukazuje na místní `/mcp/token`, `ZENTRODOCS_API_TOKEN` obsahuje vydanou organizační credential z bezpečného prostředí klienta. Most předává token pouze v Authorization hlavičce, odmítá redirecty a běžně vyžaduje HTTPS; explicitní HTTP loopback je výjimka pro testování. Úpravou mostu nelze obejít serverové kontroly. Skutečná konfigurace konkrétní desktopové aplikace nebyla ověřena.

## Co stále není hotové

Hosted OAuth authorization-code/PKCE, vydávání a obnova OAuth access/refresh tokenů, issuer/resource discovery a registrace klientů nejsou implementované. Lokální organizační token není OAuth grant. Samostatné `introspectOAuth` rozhraní knihovny zůstává pro budoucí skutečný serverový adaptér; nenakonfigurovaný adaptér dál vrací bezpečné odmítnutí.

Ověřeno programovým lokálním HTTP klientem a jednotkovými testy, nikoli skutečným Codex/Claude/Cursor desktopovým klientem, skutečným modelem nebo nasazeným endpointem. Žádná skutečná platba, e-mail nebo externí účet. Aktuální výsledky a omezení: [VERIFICATION.md](VERIFICATION.md), [P9/P9b souhrn](../tests/agents/SOUHRN-P9.md).

Use the host's secure environment/secret facility where supported; do not commit credentials. The stdio package forwards to the server, where identity, current membership, tenant scope, roles, active plan, MCP entitlement, kill switch and atomic quota reservation are checked. Editing the local bridge cannot bypass server checks. HTTPS is required except explicit loopback development. Tokens never appear in URL/query or logs; redirects are refused.

## Remote OAuth: feasible, not yet a product login flow

The [Claude connector documentation](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp), [Cursor MCP documentation](https://cursor.com/docs/mcp) and [Codex MCP documentation](https://learn.chatgpt.com/docs/extend/mcp?surface=cli) document remote OAuth support. The research record is `repo/docs/P6-RESEARCH.md` in the checkout. MCP revision **2026-07-28** is implemented here as a restricted request/JSON-response profile. No legacy initialization/session/SSE-resume claim is made. Actual interoperability with each installed client version is **not tested** (desktop operation prohibited in P6); clients limited to older revisions will need a compatibility adapter/pilot.

- Mount `createHttpHandler(server)` at `POST /mcp`; each request carries a bearer token. Mount token bridge separately with `{credentialKind:'api-token'}` at `/mcp/token`.
- Require `Content-Type: application/json`, `Accept: application/json, text/event-stream`, `MCP-Protocol-Version: 2026-07-28`, `Mcp-Method`, and for tools/call `Mcp-Name` matching the JSON body. `params._meta` includes `io.modelcontextprotocol/protocolVersion` and `io.modelcontextprotocol/clientCapabilities`. No GET stream or DELETE session endpoint.
- Validate Origin against the configured allowlist. Host/TLS/body-size controls are also the containing server's responsibility. The module limits request size and does not expose permissive CORS.
- Default missing issuer/introspection adapter returns HTTP 503 `MCP_OAUTH_NOT_CONFIGURED`. It does **not** accept a browser session as an OAuth bearer token, nor fabricate `/authorize`, `/token` or discovery metadata.

**TODO before OAuth is usable:** real authorization-code + PKCE S256 server against product login; registered clients/CIMD or compatible registration; redirect URI validation, explicit organization consent, audience/resource checking; access/refresh issuance and immediate server-side revocation; real issuer metadata and RFC 9728 protected-resource metadata; adapter wiring; deployment/TLS and permitted interoperability pilot. These are **product implementation gaps**, not limitations claiming MCP or those clients cannot do OAuth. Claude remote connectors access a reachable endpoint from Anthropic infrastructure, not a user's arbitrary localhost.

## Tools and paid access

`tools/list` derives its descriptions and JSON Schemas from `TOOLS` in `repo/packages/mcp/index.mjs` in the checkout; no alternate hand-maintained schema. Available descriptors: `list_projects`, `list_documents`, `read_document`, `list_versions`, `read_version`, `search_documents`, `add_comment`, `add_annotation`, `create_version`.

P5 projects are document containers, not another invented project entity. Search is literal current-document search, max 20 matches, not AI or a pricing quota. Comments append to an existing thread; annotations anchor a block. Read-only users cannot write; commenter may comment/annotate but not version; editor/owner may append a version. Document-specific roles are checked in addition to organization role. No destructive delete tool exists. Creating a version requires `confirm:true` and `baseVersion`, does not delete history, and uses P5 optimistic concurrency. Client annotations are hints only, never authorization.

`NAKLADY-BYZNYS.md` §6 in the checkout prohibits Free automation tokens (an internal business document, not a public web route). Paid MCP numeric entitlement/quotas are not specified there: **TODO RB, no invented limits**. Synthetic paid limits in tests are explicitly fixtures, not purchasable plans. Missing entitlement/shared meter denies access. Test adapters do not enable product billing or grant subscription privileges.

Quota exhaustion is a tool error, never empty success:

```json
{"isError":true,"structuredContent":{"error":{"code":"MCP_LIMIT_EXCEEDED","message":"The shared usage quota is exhausted.","action":"Wait for quota reset or contact the organization owner.","resetAt":null}}}
```

Other machine codes include `MCP_TENANT_DENIED`, `MCP_ROLE_DENIED`, `MCP_REVOKED`, `MCP_ORG_DISABLED`, `MCP_PLAN_DENIED`, `MCP_PLAN_INACTIVE`, `MCP_ENTITLEMENT_NOT_CONFIGURED`, `MCP_METER_NOT_CONFIGURED`. Expected tool errors include actionable text. Unexpected backend failures are sanitized, not dumped with document contents or secrets.

## Verification boundaries

`node --test tests/p6-mcp.test.mjs` covers both mock credential kinds, explicit cross-tenant rejection through **every** tool, Free versus synthetic paid quota, concurrent admission, immediate credential revocation, current role/membership/plan changes, organization disable, viewer/commenter write rejection, confirmation, HTTP headers/origin/method checks, local stdio bridge to loopback mock HTTP, and the real P5 product adapter over temporary synthetic accounts/documents/comments/versions. Tests write under `repo/.test-tmp` and remove their fixtures. No real OAuth issuer/login, payment, model, email, user account, deployed connector or desktop app is involved. P5 email is local log only. The gate's tests prove the adapter contract, **not completed product OAuth or shared subscription metering**.

### Protocol checks against primary specification (27 September 2026)

The [stdio binding](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio) also uses per-request metadata, not an initialization handshake. The module implements mandatory [server/discover](https://modelcontextprotocol.io/specification/2026-07-28/server/discover), complete result tags, standard unsupported-version errors, HTTP header mismatch errors, UTF-8 stream framing and no responses to notifications. Stdio cancellation aborts the bridge HTTP request and suppresses its pending response; it cannot undo an already committed product mutation. It does not advertise subscriptions, asynchronous task extensions or legacy protocol support.

HTTP 401 responses include a Bearer challenge; `resource_metadata` is added only when a real metadata URL is explicitly configured. Until the issuer/resource metadata exists this is a disabled OAuth integration, not complete OAuth discovery. No fictitious metadata URL is advertised. Shared web/MCP call/token/egress accounting is still a **production integration gap**: the current P5 API enforces its own document/version/storage limits, while the MCP shared ledger exists only as a required injected contract and synthetic test implementation.

Offline packaging smoke also passed: `npx --offline --yes --package /absolute/local/repo/packages/mcp zentrodocs-mcp` executed the local bin and emitted the expected structured missing-token refusal; it did not contact a real service. No registry package was downloaded or published.

## P11 — `run_recipe`

The tool accepts the HTTP recipe fields `recipe`, `params`, `dryRun`, `resumeToken`, plus the existing required credential-bound `orgId`. It delegates to the same sequential stop-on-first-error runner as `POST /agent/run`. Dry runs return the same prepared plan without product writes. HTTP and MCP can share a continuation only when the trusted resolver binds the exact same product session and matching user/organization; a different session of the same user is rejected. New-account registration is an HTTP bootstrap, not an organization credential creating a different identity.

Committed steps are not rolled back. PLAN_LIMIT returns a failed step and upgrade action; conflicts require review/rebase, and RATE_LIMIT requires waiting for Retry-After. Recipe metadata and execution semantics are documented in [AGENT.md](AGENT.md#recepty-p11).

This is backend/tool parity on synthetic fixtures, not an enabled public MCP runtime. Default OAuth/API-token, paid entitlement and shared-meter gates remain closed. P11 does not issue credentials or introduce a bypass.
