@12-apps/mcp 3.5.1 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ADOPTING.md CHANGED
@@ -13,7 +13,7 @@ library updates, every host updates with **no app changes**. Same contract
13
13
  | **Core** | `@12-apps/mcp` | `generateTools(openapi)`, `createToolRegistry`, `dispatchTool` (bearer passthrough), `buildManifest`, the surface lock, and both discovery-document builders. |
14
14
  | **OAuth AS** | `@12-apps/mcp/oauth` | `createApiMcpOauth({ stores, resolveSession })` → `routes` + named `handlers` + `verifyBearer`. register / authorize / token, the JWKS, and both `.well-known` documents, with PKCE, stateless codes, hashed rotating refresh tokens and replay revocation inside. |
15
15
  | **Hono** | `@12-apps/mcp/hono` | `const oauth = mcpOauthRouter({ … }); app.route('/', oauth.router)` — at the ORIGIN ROOT (see rule 2). `hono` is an OPTIONAL peer. |
16
- | **React** | `@12-apps/mcp/react` | The AI-connect onboarding UI and status board (pt-BR, overridable). |
16
+ | **React** | `@12-apps/mcp/react` | The AI-connect onboarding UI and status board. Every word is REQUIRED host config — see below. |
17
17
  | **`mcp:generate` / `mcp:check`** | `@12-apps/mcp/generate` | Your script becomes `mcpGenerateCli({ document, version, source, versionLocation, outputs, check })`. `12-apps/ci`'s `mcp-contract.yml` shells out to your `mcp:check` package script and keeps working unchanged. |
18
18
  | **`mcp:coverage`** | `@12-apps/mcp/coverage` | `mcpCoverageCli({ appDir, endpoints, exclusionsPath, actionMapPath })` — the route/action completeness gate. |
19
19
  | **Prisma** | `prisma/mcp.prisma` + `prisma/migrations/*` | `pnpm --filter @12-apps/mcp prisma:sync -- <host schema dir>`: the partial is **COPIED** into the host's multi-file schema folder — never symlinked (a symlinked migration is silently skipped by Prisma; a symlinked partial dangles under `turbo prune`). |
@@ -320,3 +320,71 @@ Deliberate deltas to reconcile:
320
320
  (12-23's scope).
321
321
  - **Authorization codes as rows.** They are stateless signed blobs, so there is no
322
322
  table and nothing to sweep — only the replay store (rule 8).
323
+
324
+ ## The AI screens take their words (FUT-760)
325
+
326
+ `@12-apps/mcp/react` used to ship this surface's pt-BR: the walkthrough for each
327
+ assistant, the capability cards, the permission reassurance, every step label
328
+ and every button. `hosts`, `capabilities` and `permissionModel` were OPTIONAL
329
+ props that fell back to it, and `guide.ts` described them as "the shared
330
+ defaults" — so a host that configured nothing published one product's
331
+ Portuguese, and had no field to decline it.
332
+
333
+ They are required now, and the retired wording ships as NAMED packs:
334
+
335
+ ```ts
336
+ import {
337
+ PT_BR_AI_CAPABILITIES,
338
+ PT_BR_AI_CONNECT_PROMPT,
339
+ PT_BR_AI_HOST_GUIDES,
340
+ PT_BR_AI_PERMISSION_MODEL,
341
+ } from "@12-apps/mcp";
342
+ import { PT_BR_MCP_AI_COPY } from "@12-apps/mcp/react";
343
+
344
+ <AiIntegrationOnboarding
345
+ hosts={PT_BR_AI_HOST_GUIDES(platformName)}
346
+ capabilities={PT_BR_AI_CAPABILITIES}
347
+ permissionModel={PT_BR_AI_PERMISSION_MODEL}
348
+ connectPrompt={PT_BR_AI_CONNECT_PROMPT({ announceTool, probeTool, probeSubject, identifierName })}
349
+ copy={PT_BR_MCP_AI_COPY}
350
+
351
+ />
352
+ ```
353
+
354
+ Nothing changes on screen if you adopt them — they are the same sentences,
355
+ now chosen in a diff.
356
+
357
+ ### What moved
358
+
359
+ - **`aiHostGuides(platformName)` → `PT_BR_AI_HOST_GUIDES(platformName)`**, in
360
+ `@12-apps/mcp` (from `./guide`, which now carries types only). The guides are
361
+ data as much as copy — which assistants are offered, their stage ids and
362
+ brands — and both halves travel together, because a step label and the stage
363
+ it labels are useless apart.
364
+ - **`AI_CAPABILITIES` → `PT_BR_AI_CAPABILITIES`**, same move.
365
+ - **`AI_PERMISSION_MODEL` → `PT_BR_AI_PERMISSION_MODEL`**. The export left
366
+ behind is now the TYPE `AiPermissionModel`.
367
+ - **`aiConnectPrompt(spec)` → `aiConnectPrompt(spec, copy)`**, where `copy` is a
368
+ template. It already took the host's tool NAMES; the words around them are the
369
+ host's too now. `PT_BR_AI_CONNECT_PROMPT` is that template.
370
+ - **`platformName` is gone from `<AiIntegrationOnboarding>`.** It existed only
371
+ to build the default guides. The host builds its own now and interpolates its
372
+ own name, so a required prop that did nothing has been removed rather than
373
+ left to mislead.
374
+
375
+ ### The screens each take their own slice
376
+
377
+ `AiLanding`, `AiCapabilities`, `AiStatusBoard` and `HostSelectStep` are exported
378
+ standalone, so each takes the slice it renders (`copy.landing`,
379
+ `copy.capabilities`, `copy.statusBoard`, `copy.hostSelect`) rather than the whole
380
+ object — a host mounting only the status board should not have to supply words
381
+ for a wizard it never renders. `<AiIntegrationOnboarding>` takes the whole
382
+ `McpAiCopy` and passes the slices down.
383
+
384
+ ### The icons stayed
385
+
386
+ The landing's reassurance strip is keyed by the pack's own ids (`login`,
387
+ `install`, `permissions`, `surface`) against an icon map inside the package. An
388
+ icon is not copy, and a pack that had to ship React elements could not be a
389
+ plain data file. An id the map does not know renders without an icon rather
390
+ than throwing, so a host adding a fifth point still gets its words on screen.
@@ -0,0 +1,22 @@
1
+ import {
2
+ __name
3
+ } from "./chunk-7QVYU63E.js";
4
+
5
+ // src/guide.ts
6
+ function providerForHostId(hostId) {
7
+ if (hostId === "claude" || hostId === "claude-desktop") return "claude";
8
+ if (hostId === "chatgpt") return "chatgpt";
9
+ if (hostId === "codex") return "codex";
10
+ return null;
11
+ }
12
+ __name(providerForHostId, "providerForHostId");
13
+ function aiConnectPrompt(spec, copy) {
14
+ return copy(spec);
15
+ }
16
+ __name(aiConnectPrompt, "aiConnectPrompt");
17
+
18
+ export {
19
+ providerForHostId,
20
+ aiConnectPrompt
21
+ };
22
+ //# sourceMappingURL=chunk-FBUSSQSK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/guide.ts"],"sourcesContent":["/**\n * AI-integration guide content — the reusable copy for a store's \"Integração com\n * IA\" onboarding. Pure, public, non-sensitive text (no React) so the on-page\n * walkthrough stays a portable, testable content module. Host brand marks/icons\n * and colours live in the components. Apps may pass their own overrides to\n * `<AiIntegrationOnboarding>`; these are the shared defaults.\n *\n * The MCP endpoint URL is NOT hardcoded here — the app derives it per request\n * from the deployment origin and passes it in.\n */\n\n/** Which brand a host belongs to — drives the icon/accent in the UI. */\nexport type AiHostBrand = \"claude\" | \"openai\";\n\nexport interface AiHostLink {\n /** Deep-link straight to the host's connector settings, when one exists. */\n url: string;\n label: string;\n}\n\n/**\n * One stage of the \"configure the connector\" part of the flow. A host can split\n * configuration across several stages, each with its OWN deep link, step label\n * and instructions — e.g. ChatGPT needs \"enable developer mode\" (Security\n * settings) and then \"configurar\" (create the connector). When a host declares\n * `configureStages`, the wizard renders one wizard step per stage (between\n * \"Copiar URL\" and \"Confirmar\") instead of the default Configurar/Conectar\n * split, and the sign-in happens inside a stage — no prompt to paste.\n */\nexport interface AiHostConfigureStage {\n /** Stable id — also the wizard step id + test id suffix (`ai-stage-${id}`). */\n id: string;\n /** Step label shown in the stepper. */\n label: string;\n /** Optional deep link opened on this stage (unlocks \"Próximo\" once opened). */\n link?: AiHostLink;\n /** The instructions shown for this stage. */\n steps: readonly string[];\n}\n\n/**\n * The AI providers a connection can be attributed to — derived server-side from\n * the OAuth client's redirect URIs (and confirmed by the `announceAiConnection`\n * tool). Drives which host card lights up green on the status board.\n */\nexport type AiProvider = \"claude\" | \"chatgpt\" | \"codex\";\n\n/** The provider a host id belongs to (claude-desktop shares Claude's provider). */\nexport function providerForHostId(hostId: string): AiProvider | null {\n if (hostId === \"claude\" || hostId === \"claude-desktop\") return \"claude\";\n if (hostId === \"chatgpt\") return \"chatgpt\";\n if (hostId === \"codex\") return \"codex\";\n return null;\n}\n\nexport interface AiHostGuide {\n /** Stable id — also the panel test id suffix (`ai-setup-${id}`). */\n id: string;\n /** Tab label shown to the owner. */\n label: string;\n brand: AiHostBrand;\n /** One-line hint of what this host is (web app, desktop app, CLI…). */\n kind: string;\n /** Optional direct link button (desktop apps have no deep link). */\n link?: AiHostLink;\n /**\n * Optional link to the host's OFFICIAL connector docs — a stable vendor URL\n * shown as a \"Para mais informações\" reference (never a deep link with a\n * volatile connector id / UI params).\n */\n docs?: AiHostLink;\n steps: readonly string[];\n /**\n * Optional per-stage configuration (each with its own deep link + label). When\n * present, the wizard renders one step per stage in place of the default\n * Configurar → Conectar split, and `steps` mirrors the flattened stage\n * instructions so the MCP guide stays a single source of truth.\n */\n configureStages?: readonly AiHostConfigureStage[];\n /**\n * Optional deep link to a PUBLISHED one-click plugin/connector for this host\n * (set per app via env, e.g. `CHATGPT_PLUGIN_URL` / `CLAUDE_PLUGIN_URL`). When\n * present, the wizard swaps the manual \"copy URL + configure connector\" path\n * for a simplified \"open → Install → authorize\" flow: the owner never copies\n * the MCP URL. Absent → the full manual flow.\n */\n pluginUrl?: string;\n}\n\nexport interface AiCapability {\n /** Stable id — maps to an icon in the component. */\n id: string;\n /** Card headline. */\n title: string;\n /** Short supporting line, usually an example prompt in quotes. */\n detail: string;\n}\n\n\n/**\n * The permission model in one line, shown prominently: the assistant acts AS\n * the signed-in owner (auth-passthrough) — it can do exactly what the owner\n * can, nothing more, and no extra credential/API key is ever created.\n *\n * A TYPE now, not a constant. The sentence is copy, and a package that shipped\n * it made one product's Portuguese every adopter's silent default.\n */\nexport type AiPermissionModel = string;\n\n/** The two tools the paste-in prompt drives, and what to call the store id. */\nexport interface AiConnectPromptSpec {\n /**\n * The tool that REGISTERS the connection server-side, so the store learns\n * which assistant connected.\n */\n announceTool: string;\n /** A real READ tool, called straight after, to prove the access works. */\n probeTool: string;\n /** What that read returns, in the owner's own words (\"o estoque da loja\"). */\n probeSubject: string;\n /** What the assistant should ask for if it needs to identify the store. */\n identifierName: string;\n}\n\n/**\n * The message the owner pastes into the assistant's chat right after\n * connecting: announce the connection, then read something real to prove it\n * works. The owner types nothing.\n *\n * BUILT from the host's tool names rather than shipped with them. This was a\n * constant naming two tools — `announceAiConnection` and `listInventory` — that\n * THIS PACKAGE does not define or serve; they belong to one adopter's surface.\n * Any other host handed its owner a prompt instructing the assistant to call\n * two tools that do not exist, and because nothing registered the connection,\n * the wizard's confirm step then waited forever for a state that could never\n * arrive.\n\n/**\n * The sentences the paste-in prompt is written in.\n *\n * A template rather than a string, because the prompt INTERPOLATES the host's\n * own tool names and its word order is language-specific. The package supplies\n * the spec; the host supplies the way it is said.\n */\nexport interface AiConnectPromptCopy {\n (spec: AiConnectPromptSpec): string;\n}\n\n/**\n * The message the owner pastes into the assistant's chat right after\n * connecting: announce the connection, then read something real to prove it\n * works. The owner types nothing.\n *\n * BUILT from the host's tool names rather than shipped with them. This was a\n * constant naming two tools — `announceAiConnection` and `listInventory` — that\n * THIS PACKAGE does not define or serve; they belong to one adopter's surface.\n * Any other host handed its owner a prompt instructing the assistant to call\n * two tools that do not exist, and because nothing registered the connection,\n * the wizard's confirm step then waited forever for a state that could never\n * arrive.\n *\n * The WORDS around those names are the host's too, since FUT-760: this is a\n * thin seam that hands the spec to the host's own template.\n */\nexport function aiConnectPrompt(spec: AiConnectPromptSpec, copy: AiConnectPromptCopy): string {\n return copy(spec);\n}\n"],"mappings":";;;;;AAgDO,SAAS,kBAAkB,QAAmC;AACnE,MAAI,WAAW,YAAY,WAAW,iBAAkB,QAAO;AAC/D,MAAI,WAAW,UAAW,QAAO;AACjC,MAAI,WAAW,QAAS,QAAO;AAC/B,SAAO;AACT;AALgB;AAoHT,SAAS,gBAAgB,MAA2B,MAAmC;AAC5F,SAAO,KAAK,IAAI;AAClB;AAFgB;","names":[]}
@@ -2,14 +2,7 @@ import {
2
2
  __name
3
3
  } from "./chunk-7QVYU63E.js";
4
4
 
5
- // src/guide.ts
6
- function providerForHostId(hostId) {
7
- if (hostId === "claude" || hostId === "claude-desktop") return "claude";
8
- if (hostId === "chatgpt") return "chatgpt";
9
- if (hostId === "codex") return "codex";
10
- return null;
11
- }
12
- __name(providerForHostId, "providerForHostId");
5
+ // src/pt-BR.ts
13
6
  var CONNECTOR_TAIL = [
14
7
  "Deixe OAuth Client ID e Client Secret em branco \u2014 n\xE3o \xE9 preciso gerar credenciais: a loja registra o conector automaticamente no primeiro acesso.",
15
8
  "Confirme e clique em Connect: abre a tela de login da loja \u2014 entre com a SUA conta de lojista e autorize o acesso.",
@@ -44,7 +37,7 @@ function chatgptConfigureStages(platformName) {
44
37
  ];
45
38
  }
46
39
  __name(chatgptConfigureStages, "chatgptConfigureStages");
47
- function aiHostGuides(platformName) {
40
+ function PT_BR_AI_HOST_GUIDES(platformName) {
48
41
  const chatgptStages = chatgptConfigureStages(platformName);
49
42
  return [
50
43
  {
@@ -120,8 +113,8 @@ function aiHostGuides(platformName) {
120
113
  }
121
114
  ];
122
115
  }
123
- __name(aiHostGuides, "aiHostGuides");
124
- var AI_CAPABILITIES = [
116
+ __name(PT_BR_AI_HOST_GUIDES, "PT_BR_AI_HOST_GUIDES");
117
+ var PT_BR_AI_CAPABILITIES = [
125
118
  {
126
119
  id: "orders",
127
120
  title: "Acompanhe seus pedidos",
@@ -143,20 +136,19 @@ var AI_CAPABILITIES = [
143
136
  detail: '"Qual foi o faturamento da semana?"'
144
137
  }
145
138
  ];
146
- var AI_PERMISSION_MODEL = "O assistente age em seu nome, com exatamente as suas permiss\xF5es: ele pode fazer o que voc\xEA pode fazer na sua loja \u2014 nada al\xE9m disso. N\xE3o \xE9 preciso criar nenhuma chave ou credencial extra; a autoriza\xE7\xE3o usa o seu pr\xF3prio login.";
147
- function aiConnectPrompt(spec) {
139
+ var PT_BR_AI_PERMISSION_MODEL = "O assistente age em seu nome, com exatamente as suas permiss\xF5es: ele pode fazer o que voc\xEA pode fazer na sua loja \u2014 nada al\xE9m disso. N\xE3o \xE9 preciso criar nenhuma chave ou credencial extra; a autoriza\xE7\xE3o usa o seu pr\xF3prio login.";
140
+ function PT_BR_AI_CONNECT_PROMPT(spec) {
148
141
  return `Voc\xEA agora tem acesso ao conector MCP da minha loja. Fa\xE7a, nesta ordem:
149
142
  1) Execute a ferramenta ${spec.announceTool} informando qual assistente voc\xEA \xE9 (host: "chatgpt", "claude" ou "codex") para registrar a conex\xE3o com a minha loja.
150
143
  2) Execute a ferramenta ${spec.probeTool} para confirmar o acesso a ${spec.probeSubject}.
151
144
  Se precisar do identificador da loja, me pergunte o ${spec.identifierName}.`;
152
145
  }
153
- __name(aiConnectPrompt, "aiConnectPrompt");
146
+ __name(PT_BR_AI_CONNECT_PROMPT, "PT_BR_AI_CONNECT_PROMPT");
154
147
 
155
148
  export {
156
- providerForHostId,
157
- aiHostGuides,
158
- AI_CAPABILITIES,
159
- AI_PERMISSION_MODEL,
160
- aiConnectPrompt
149
+ PT_BR_AI_HOST_GUIDES,
150
+ PT_BR_AI_CAPABILITIES,
151
+ PT_BR_AI_PERMISSION_MODEL,
152
+ PT_BR_AI_CONNECT_PROMPT
161
153
  };
162
- //# sourceMappingURL=chunk-FYEVBTDU.js.map
154
+ //# sourceMappingURL=chunk-HRAQKMDC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/pt-BR.ts"],"sourcesContent":["import type {\n AiCapability,\n AiConnectPromptSpec,\n AiHostConfigureStage,\n AiHostGuide,\n AiPermissionModel,\n} from \"./guide\";\n\n/**\n * The pt-BR pack for the AI-integration surface — NAMED constants a host passes\n * by hand, never defaults (FUT-760).\n *\n * The filename is what exempts this file from the copy-portability gate:\n * Portuguese may ship, it may not be silent. Every sentence here is VERBATIM\n * what `guide.ts` used to compile in, so a host adopting it sees no change on\n * screen — what changes is that the walkthrough is chosen in a diff.\n *\n * The guides are DATA as much as copy: which assistants are offered, their\n * stage ids and brands. Both halves travel together because a step label and\n * the stage it labels are useless apart.\n */\n\nconst CONNECTOR_TAIL: readonly string[] = [\n \"Deixe OAuth Client ID e Client Secret em branco — não é preciso gerar credenciais: a loja registra o conector automaticamente no primeiro acesso.\",\n \"Confirme e clique em Connect: abre a tela de login da loja — entre com a SUA conta de lojista e autorize o acesso.\",\n \"Pronto: ative o conector na conversa para o assistente consultar e operar a sua loja.\",\n];\n\n/**\n * ChatGPT's two-stage configuration (Developer mode is now required before a\n * connector can be created). Stage 1 enables Developer mode in Security & login;\n * stage 2 creates the connector and signs in — which registers the connection on\n * the store side, so no prompt needs to be pasted afterwards.\n */\nfunction chatgptConfigureStages(\n platformName: string,\n): readonly AiHostConfigureStage[] {\n return [\n {\n id: \"enable-dev-mode\",\n label: \"enable developer mode\",\n link: {\n url: \"https://chatgpt.com/plugins#settings/Security\",\n label: \"Abrir Segurança e login\",\n },\n steps: [\n \"Ative o Modo desenvolvedor em Settings › Security and login (Segurança e login).\",\n ],\n },\n {\n id: \"configurar\",\n label: \"configurar\",\n link: {\n url: \"https://chatgpt.com/plugins#settings/Connectors?create-connector=true&redirectAfter=%2Fplugins\",\n label: \"Criar o conector\",\n },\n steps: [\n \"Isso vai abrir um popup para você criar um plugin novo. Coloque como nome o nome da sua loja e, no campo MCP, o link copiado no passo anterior.\",\n 'Marque a caixa \"I understand and want to continue\" — a OpenAI não revisou este servidor MCP; ela avisa que sites podem tentar roubar seus dados ou induzir o modelo a ações indevidas, incluindo destruir dados.',\n `Clique em \"Sign in with ${platformName}\" e entre com a sua conta de lojista para autorizar o acesso. Pronto: a conexão é registrada automaticamente.`,\n ],\n },\n ];\n}\n\n/**\n * The AI hosts a store owner can connect, in recommended order. Same OAuth flow\n * everywhere (the host drives it) — only the menu path differs per app.\n *\n * A FUNCTION of the platform's name, because one step is not generic: the\n * ChatGPT connector's consent screen shows an OAuth button labelled with\n * whoever operates the server, and the owner is told which button to click. It\n * used to name one particular STORE on one particular deployment — not even the\n * product, a tenant of it — so every other adopter instructed its owners to\n * click a button that does not exist.\n */\nexport function PT_BR_AI_HOST_GUIDES(platformName: string): readonly AiHostGuide[] {\n const chatgptStages = chatgptConfigureStages(platformName);\n return [\n {\n id: \"claude\",\n label: \"Claude.ai\",\n brand: \"claude\",\n kind: \"No navegador\",\n link: {\n url: \"https://claude.ai/new?modal=add-custom-connector#settings/customize-connectors\",\n label: \"Abrir os conectores do Claude\",\n },\n docs: {\n url: \"https://support.anthropic.com/en/articles/11175166-how-do-i-connect-mcp-servers-to-claude-ai\",\n label: \"documentação oficial da Anthropic — conectores personalizados\",\n },\n steps: [\n \"Clique no botão acima (ou vá em Settings › Customize › Connectors) e escolha Add custom connector.\",\n \"Dê um nome ao conector (ex.: o nome da sua loja) e cole a URL do servidor MCP da sua loja (copie acima) no campo de URL.\",\n ...CONNECTOR_TAIL,\n ],\n },\n {\n id: \"claude-desktop\",\n label: \"Claude Desktop\",\n brand: \"claude\",\n kind: \"Aplicativo (Windows/Mac)\",\n docs: {\n url: \"https://support.anthropic.com/en/articles/11175166-how-do-i-connect-mcp-servers-to-claude-ai\",\n label: \"documentação oficial da Anthropic — conectores personalizados\",\n },\n steps: [\n \"Abra o Claude Desktop e vá em Settings (⚙️) › Connectors.\",\n \"Clique em Add custom connector e cole a URL do servidor MCP da sua loja (copie acima).\",\n ...CONNECTOR_TAIL,\n ],\n },\n {\n id: \"chatgpt\",\n label: \"ChatGPT\",\n brand: \"openai\",\n kind: \"No navegador\",\n link: {\n url: \"https://chatgpt.com/plugins\",\n label: \"Abrir os plugins do ChatGPT\",\n },\n docs: {\n url: \"https://developers.openai.com/apps-sdk/deploy/connect-chatgpt\",\n label:\n \"documentação oficial da OpenAI — conectar um servidor MCP ao ChatGPT\",\n },\n configureStages: chatgptStages,\n // Mirrors the flattened stage instructions so the MCP connect guide\n // (`connectToChatGpt`) can never drift from what owners see in the wizard.\n steps: chatgptStages.flatMap((stage) => stage.steps),\n },\n {\n id: \"codex\",\n label: \"Codex\",\n brand: \"openai\",\n kind: \"App / CLI de desenvolvedor\",\n link: {\n url: \"https://developers.openai.com/codex\",\n label: \"Documentação do Codex\",\n },\n docs: {\n url: \"https://developers.openai.com/apps-sdk/deploy/connect-chatgpt\",\n label: \"documentação oficial da OpenAI — conectar um servidor MCP\",\n },\n steps: [\n \"No Codex, abra as configurações de MCP (Settings › MCP no app, ou o arquivo de configuração na CLI).\",\n \"Adicione um servidor MCP e cole a URL do servidor MCP da sua loja (copie acima) como um conector remoto (HTTP).\",\n ...CONNECTOR_TAIL,\n ],\n },\n ];\n}\n\nexport const PT_BR_AI_CAPABILITIES: readonly AiCapability[] = [\n {\n id: \"orders\",\n title: \"Acompanhe seus pedidos\",\n detail: '\"Quais pedidos entraram hoje?\"',\n },\n {\n id: \"inventory\",\n title: \"Controle o estoque\",\n detail:\n '\"Quanto ainda tenho do produto X? Registre a entrada de 20 unidades.\"',\n },\n {\n id: \"catalog\",\n title: \"Gerencie o catálogo\",\n detail: \"Crie e edite produtos e categorias conversando.\",\n },\n {\n id: \"sales\",\n title: \"Entenda suas vendas\",\n detail: '\"Qual foi o faturamento da semana?\"',\n },\n];\n\n/**\n * The permission model in one line, shown prominently: the assistant acts AS\n * the signed-in owner (auth-passthrough) — it can do exactly what the owner\n * can, nothing more, and no extra credential/API key is ever created.\n */\nexport const PT_BR_AI_PERMISSION_MODEL: AiPermissionModel =\n \"O assistente age em seu nome, com exatamente as suas permissões: ele pode fazer o que você pode fazer na sua loja — nada além disso. Não é preciso criar nenhuma chave ou credencial extra; a autorização usa o seu próprio login.\";\n\n/** The pt-BR paste-in prompt, built from the host's own tool names. */\nexport function PT_BR_AI_CONNECT_PROMPT(spec: AiConnectPromptSpec): string {\n return (\n \"Você agora tem acesso ao conector MCP da minha loja. Faça, nesta ordem:\\n\" +\n `1) Execute a ferramenta ${spec.announceTool} informando qual assistente você é (host: \"chatgpt\", \"claude\" ou \"codex\") para registrar a conexão com a minha loja.\\n` +\n `2) Execute a ferramenta ${spec.probeTool} para confirmar o acesso a ${spec.probeSubject}.\\n` +\n `Se precisar do identificador da loja, me pergunte o ${spec.identifierName}.`\n );\n}\n"],"mappings":";;;;;AAsBA,IAAM,iBAAoC;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AACF;AAQA,SAAS,uBACP,cACiC;AACjC,SAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OAAO;AAAA,MACT;AAAA,MACA,OAAO;AAAA,QACL;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OAAO;AAAA,MACT;AAAA,MACA,OAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,2BAA2B,YAAY;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AACF;AA7BS;AA0CF,SAAS,qBAAqB,cAA8C;AACjF,QAAM,gBAAgB,uBAAuB,YAAY;AACzD,SAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,OAAO;AAAA,MACP,MAAM;AAAA,MACN,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OAAO;AAAA,MACT;AAAA,MACA,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OAAO;AAAA,MACT;AAAA,MACA,OAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,GAAG;AAAA,MACL;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,OAAO;AAAA,MACP,MAAM;AAAA,MACN,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OAAO;AAAA,MACT;AAAA,MACA,OAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,GAAG;AAAA,MACL;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,OAAO;AAAA,MACP,MAAM;AAAA,MACN,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OAAO;AAAA,MACT;AAAA,MACA,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OACE;AAAA,MACJ;AAAA,MACA,iBAAiB;AAAA;AAAA;AAAA,MAGjB,OAAO,cAAc,QAAQ,CAAC,UAAU,MAAM,KAAK;AAAA,IACrD;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,OAAO;AAAA,MACP,MAAM;AAAA,MACN,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OAAO;AAAA,MACT;AAAA,MACA,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,OAAO;AAAA,MACT;AAAA,MACA,OAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,GAAG;AAAA,MACL;AAAA,IACF;AAAA,EACF;AACF;AA5EgB;AA8ET,IAAM,wBAAiD;AAAA,EAC5D;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QACE;AAAA,EACJ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AACF;AAOO,IAAM,4BACX;AAGK,SAAS,wBAAwB,MAAmC;AACzE,SACE;AAAA,0BAC2B,KAAK,YAAY;AAAA,0BACjB,KAAK,SAAS,8BAA8B,KAAK,YAAY;AAAA,sDACjC,KAAK,cAAc;AAE9E;AAPgB;","names":[]}
@@ -75,18 +75,6 @@ interface AiHostGuide {
75
75
  */
76
76
  pluginUrl?: string;
77
77
  }
78
- /**
79
- * The AI hosts a store owner can connect, in recommended order. Same OAuth flow
80
- * everywhere (the host drives it) — only the menu path differs per app.
81
- *
82
- * A FUNCTION of the platform's name, because one step is not generic: the
83
- * ChatGPT connector's consent screen shows an OAuth button labelled with
84
- * whoever operates the server, and the owner is told which button to click. It
85
- * used to name one particular STORE on one particular deployment — not even the
86
- * product, a tenant of it — so every other adopter instructed its owners to
87
- * click a button that does not exist.
88
- */
89
- declare function aiHostGuides(platformName: string): readonly AiHostGuide[];
90
78
  interface AiCapability {
91
79
  /** Stable id — maps to an icon in the component. */
92
80
  id: string;
@@ -95,14 +83,15 @@ interface AiCapability {
95
83
  /** Short supporting line, usually an example prompt in quotes. */
96
84
  detail: string;
97
85
  }
98
- /** What a connected assistant does for the owner — one marketing card each. */
99
- declare const AI_CAPABILITIES: readonly AiCapability[];
100
86
  /**
101
87
  * The permission model in one line, shown prominently: the assistant acts AS
102
88
  * the signed-in owner (auth-passthrough) — it can do exactly what the owner
103
89
  * can, nothing more, and no extra credential/API key is ever created.
90
+ *
91
+ * A TYPE now, not a constant. The sentence is copy, and a package that shipped
92
+ * it made one product's Portuguese every adopter's silent default.
104
93
  */
105
- declare const AI_PERMISSION_MODEL = "O assistente age em seu nome, com exatamente as suas permiss\u00F5es: ele pode fazer o que voc\u00EA pode fazer na sua loja \u2014 nada al\u00E9m disso. N\u00E3o \u00E9 preciso criar nenhuma chave ou credencial extra; a autoriza\u00E7\u00E3o usa o seu pr\u00F3prio login.";
94
+ type AiPermissionModel = string;
106
95
  /** The two tools the paste-in prompt drives, and what to call the store id. */
107
96
  interface AiConnectPromptSpec {
108
97
  /**
@@ -129,7 +118,33 @@ interface AiConnectPromptSpec {
129
118
  * two tools that do not exist, and because nothing registered the connection,
130
119
  * the wizard's confirm step then waited forever for a state that could never
131
120
  * arrive.
121
+
122
+ /**
123
+ * The sentences the paste-in prompt is written in.
124
+ *
125
+ * A template rather than a string, because the prompt INTERPOLATES the host's
126
+ * own tool names and its word order is language-specific. The package supplies
127
+ * the spec; the host supplies the way it is said.
128
+ */
129
+ interface AiConnectPromptCopy {
130
+ (spec: AiConnectPromptSpec): string;
131
+ }
132
+ /**
133
+ * The message the owner pastes into the assistant's chat right after
134
+ * connecting: announce the connection, then read something real to prove it
135
+ * works. The owner types nothing.
136
+ *
137
+ * BUILT from the host's tool names rather than shipped with them. This was a
138
+ * constant naming two tools — `announceAiConnection` and `listInventory` — that
139
+ * THIS PACKAGE does not define or serve; they belong to one adopter's surface.
140
+ * Any other host handed its owner a prompt instructing the assistant to call
141
+ * two tools that do not exist, and because nothing registered the connection,
142
+ * the wizard's confirm step then waited forever for a state that could never
143
+ * arrive.
144
+ *
145
+ * The WORDS around those names are the host's too, since FUT-760: this is a
146
+ * thin seam that hands the spec to the host's own template.
132
147
  */
133
- declare function aiConnectPrompt(spec: AiConnectPromptSpec): string;
148
+ declare function aiConnectPrompt(spec: AiConnectPromptSpec, copy: AiConnectPromptCopy): string;
134
149
 
135
- export { AI_CAPABILITIES as A, AI_PERMISSION_MODEL as a, type AiCapability as b, type AiConnectPromptSpec as c, type AiHostBrand as d, type AiHostConfigureStage as e, type AiHostGuide as f, type AiHostLink as g, type AiProvider as h, aiConnectPrompt as i, aiHostGuides as j, providerForHostId as p };
150
+ export { type AiCapability as A, type AiConnectPromptCopy as a, type AiConnectPromptSpec as b, type AiHostBrand as c, type AiHostConfigureStage as d, type AiHostGuide as e, type AiHostLink as f, type AiProvider as g, aiConnectPrompt as h, type AiPermissionModel as i, providerForHostId as p };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { J as JsonSchema, G as GeneratedTool, D as DispatchConfig, a as DispatchResult, b as ToolAnnotations, R as RequestAuth, T as ToolManifest } from './generate-Dx3cK8th.js';
2
2
  export { A as AuthResolver, c as GenerateOptions, O as OpenApiDocument, d as OpenApiOperation, e as OpenApiParameter, f as OpenApiRequestBody, g as OpenApiResponse, P as ParameterLocation, h as ToolParameter, i as generateTools } from './generate-Dx3cK8th.js';
3
- export { A as AI_CAPABILITIES, a as AI_PERMISSION_MODEL, b as AiCapability, c as AiConnectPromptSpec, d as AiHostBrand, e as AiHostConfigureStage, f as AiHostGuide, g as AiHostLink, h as AiProvider, i as aiConnectPrompt, j as aiHostGuides, p as providerForHostId } from './guide-DV5MQbCg.js';
3
+ export { A as AiCapability, a as AiConnectPromptCopy, b as AiConnectPromptSpec, c as AiHostBrand, d as AiHostConfigureStage, e as AiHostGuide, f as AiHostLink, g as AiProvider, h as aiConnectPrompt, p as providerForHostId } from './guide-KQNcXlMG.js';
4
4
  import { z } from 'zod';
5
+ export { P as PT_BR_AI_CAPABILITIES, a as PT_BR_AI_CONNECT_PROMPT, b as PT_BR_AI_HOST_GUIDES, c as PT_BR_AI_PERMISSION_MODEL } from './pt-BR-e1RnV2v8.js';
5
6
 
6
7
  /**
7
8
  * Raised when a JSON Schema cannot be turned into a flat, self-contained tool
package/dist/index.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import {
2
- AI_CAPABILITIES,
3
- AI_PERMISSION_MODEL,
2
+ PT_BR_AI_CAPABILITIES,
3
+ PT_BR_AI_CONNECT_PROMPT,
4
+ PT_BR_AI_HOST_GUIDES,
5
+ PT_BR_AI_PERMISSION_MODEL
6
+ } from "./chunk-HRAQKMDC.js";
7
+ import {
4
8
  aiConnectPrompt,
5
- aiHostGuides,
6
9
  providerForHostId
7
- } from "./chunk-FYEVBTDU.js";
10
+ } from "./chunk-FBUSSQSK.js";
8
11
  import {
9
12
  PROTECTED_RESOURCE_METADATA_PATH,
10
13
  bearerChallenge,
@@ -353,16 +356,17 @@ async function handleMcpJsonRpc(request, registry, auth, options) {
353
356
  }
354
357
  __name(handleMcpJsonRpc, "handleMcpJsonRpc");
355
358
  export {
356
- AI_CAPABILITIES,
357
- AI_PERMISSION_MODEL,
358
359
  DispatchInputError,
359
360
  HTTP_STATUS_META_KEY,
360
361
  MCP_PROTOCOL_VERSION,
361
362
  PROTECTED_RESOURCE_METADATA_PATH,
363
+ PT_BR_AI_CAPABILITIES,
364
+ PT_BR_AI_CONNECT_PROMPT,
365
+ PT_BR_AI_HOST_GUIDES,
366
+ PT_BR_AI_PERMISSION_MODEL,
362
367
  UNAUTHORIZED_CODE,
363
368
  UnsupportedSchemaError,
364
369
  aiConnectPrompt,
365
- aiHostGuides,
366
370
  bearerChallenge,
367
371
  buildAuthorizationServerMetadata,
368
372
  buildManifest,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/openapi/refs.ts","../src/dispatch/proxy.ts","../src/server/redact.ts","../src/server/registry.ts","../src/server/jsonrpc.ts"],"sourcesContent":["import type { JsonSchema } from \"../types\";\n\n/**\n * Raised when a JSON Schema cannot be turned into a flat, self-contained tool\n * input — an unresolvable `$ref`, an unsupported pointer, or a recursive schema.\n * The MCP tool surface is deliberately finite and flat (it is handed to an LLM\n * and committed to the drift manifest), so recursion is rejected rather than\n * silently truncated.\n */\nexport class UnsupportedSchemaError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"UnsupportedSchemaError\";\n }\n}\n\n/** Local-definition containers a `$ref` may point into, in resolution order. */\nconst REF_PREFIXES = [\"#/$defs/\", \"#/definitions/\", \"#/components/schemas/\"] as const;\nconst DEF_CONTAINERS = [\"$defs\", \"definitions\"] as const;\n\n/** The definition name a supported local pointer targets, or `null` if unsupported. */\nfunction refName(ref: string): string | null {\n const prefix = REF_PREFIXES.find((candidate) => ref.startsWith(candidate));\n return prefix ? decodeURIComponent(ref.slice(prefix.length)) : null;\n}\n\n/** Collect the `$defs`/`definitions` maps hoisted onto a schema root into one lookup. */\nfunction collectDefs(root: JsonSchema): Record<string, JsonSchema> {\n const defs: Record<string, JsonSchema> = {};\n DEF_CONTAINERS.forEach((key) => {\n const container = root[key];\n if (container && typeof container === \"object\") {\n Object.assign(defs, container as Record<string, JsonSchema>);\n }\n });\n return defs;\n}\n\nfunction inlineRef(\n ref: string,\n defs: Record<string, JsonSchema>,\n active: Set<string>,\n): unknown {\n const name = refName(ref);\n if (name === null) throw new UnsupportedSchemaError(`Unsupported $ref pointer: ${ref}`);\n const target = defs[name];\n if (!target) throw new UnsupportedSchemaError(`Unresolved $ref: ${ref}`);\n if (active.has(name)) throw new UnsupportedSchemaError(`Recursive schema not supported: ${name}`);\n active.add(name);\n const resolved = walk(target, defs, active);\n active.delete(name);\n return resolved;\n}\n\n/** Deep-copy `node`, inlining every `$ref` and stripping definition containers. */\nfunction walk(node: unknown, defs: Record<string, JsonSchema>, active: Set<string>): unknown {\n if (Array.isArray(node)) return node.map((item) => walk(item, defs, active));\n if (!node || typeof node !== \"object\") return node;\n\n const obj = node as Record<string, unknown>;\n if (typeof obj.$ref === \"string\") return inlineRef(obj.$ref, defs, active);\n\n const out: Record<string, unknown> = {};\n Object.entries(obj).forEach(([key, value]) => {\n if (!DEF_CONTAINERS.includes(key as (typeof DEF_CONTAINERS)[number])) {\n out[key] = walk(value, defs, active);\n }\n });\n return out;\n}\n\n/**\n * Inline every local `$ref` in a JSON Schema and drop the now-empty `$defs`/\n * `definitions` containers, yielding a flat, self-contained schema. Diamond reuse\n * (the same definition referenced by sibling branches) is fine; only a true cycle\n * — a definition that references itself up the resolution stack — is rejected.\n *\n * A schema with no `$ref`/definitions is returned structurally unchanged, so\n * inlining an already-flat spec is a no-op (the drift gate stays a stable diff).\n */\nexport function inlineSchemaRefs(schema: JsonSchema): JsonSchema {\n const defs = collectDefs(schema);\n return walk(schema, defs, new Set<string>()) as JsonSchema;\n}\n","import type { DispatchConfig, DispatchResult, GeneratedTool } from \"../types\";\n\n/** Raised when tool arguments cannot be routed onto the HTTP request. */\nexport class DispatchInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"DispatchInputError\";\n }\n}\n\n/** Expand a path template (`/products/{id}`) using the path args, URL-encoding each. */\nfunction expandPath(\n tool: GeneratedTool,\n args: Record<string, unknown>,\n): string {\n return tool.path.replace(/\\{([^}]+)\\}/g, (_match, key: string) => {\n const value = args[key];\n if (value === undefined || value === null) {\n throw new DispatchInputError(`Missing required path parameter: ${key}`);\n }\n return encodeURIComponent(String(value));\n });\n}\n\n/**\n * Route the non-path parameters (query + header) from the flat args. A missing\n * required parameter is a hard error; a missing optional one is simply omitted.\n */\nfunction routeParams(\n tool: GeneratedTool,\n args: Record<string, unknown>,\n): { query: URLSearchParams; headers: Record<string, string> } {\n const query = new URLSearchParams();\n const headers: Record<string, string> = {};\n\n tool.parameters\n .filter((param) => param.in !== \"path\")\n .forEach((param) => {\n const value = args[param.name];\n if (value === undefined || value === null) {\n if (param.required) {\n throw new DispatchInputError(`Missing required ${param.in} parameter: ${param.name}`);\n }\n return;\n }\n if (param.in === \"query\") query.set(param.name, String(value));\n else headers[param.name] = String(value);\n });\n\n return { query, headers };\n}\n\n/**\n * Reconstruct the request body from the flat args using the routing metadata.\n * Anything not claimed by a known body property is dropped — the input schema is\n * `additionalProperties: false`, so a validated call never carries extras and an\n * unvalidated one cannot smuggle fields upstream.\n */\nfunction routeBody(tool: GeneratedTool, args: Record<string, unknown>): unknown {\n if (tool.bodyIsWhole) return args.body;\n if (!tool.bodyProps.length) return undefined;\n const payload: Record<string, unknown> = {};\n tool.bodyProps.forEach((key) => {\n if (args[key] !== undefined) payload[key] = args[key];\n });\n return Object.keys(payload).length ? payload : undefined;\n}\n\n/**\n * Execute one generated tool by proxying to its HTTP endpoint, forwarding the\n * caller's bearer verbatim. This function performs NO authorization — the\n * endpoint does, exactly as it would for a first-party request. That is the whole\n * point of the passthrough: the agent can do precisely what the user can.\n */\nexport async function dispatchTool(\n tool: GeneratedTool,\n args: Record<string, unknown>,\n config: DispatchConfig,\n): Promise<DispatchResult> {\n const doFetch = config.fetchImpl ?? fetch;\n const pathname = expandPath(tool, args);\n const { query, headers } = routeParams(tool, args);\n const body = routeBody(tool, args);\n\n const url = new URL(pathname, config.baseUrl);\n for (const [key, value] of query) url.searchParams.set(key, value);\n\n // Carry the proxy origin as the standard reverse-proxy forwarded headers. The\n // wrapped endpoint's auth guard re-derives the request origin (to check the\n // access token's `aud`) WITHOUT a request object, so it can only see the origin\n // through these headers. `baseUrl` is exactly the origin the token was minted\n // and transport-verified against, so forwarding its scheme+host makes the\n // wrapped guard reconstruct the SAME origin — the `aud` survives the replay in\n // both dev (http) and prod (any proxied https origin). Omitting them let the\n // guard default the scheme to https and 401 a valid http-minted bearer.\n const base = new URL(config.baseUrl);\n\n const init: RequestInit = {\n method: tool.method,\n headers: {\n accept: \"application/json\",\n authorization: `Bearer ${config.bearer}`,\n \"x-forwarded-proto\": base.protocol.replace(/:$/, \"\"),\n \"x-forwarded-host\": base.host,\n ...headers,\n },\n };\n if (body !== undefined) {\n (init.headers as Record<string, string>)[\"content-type\"] = \"application/json\";\n init.body = JSON.stringify(body);\n }\n\n const response = await doFetch(url.toString(), init);\n const text = await response.text();\n let parsed: unknown = text;\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"application/json\") && text) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = text;\n }\n }\n\n return { status: response.status, ok: response.ok, body: parsed };\n}\n","import type { JsonSchema } from \"../types\";\n\n/**\n * Both halves of the redaction contract: the SCHEMA a tool advertises, and the\n * BODY it returns.\n *\n * They only work as a pair, and the failure mode when they disagree is not a\n * cosmetic one. The dispatcher forwards the wrapped endpoint's response body\n * verbatim, so a narrowed `outputSchema` alone would only change what the\n * manifest CLAIMS is returned — the value still reaches the agent. Strip the\n * body but leave the schema advertising the field, and every successful call\n * fails validation against the very schema the manifest published, worst of all\n * when the field was `required`.\n *\n * So `redactResponseSchema` removes the paths at generate time and\n * `redactResponseBody` removes the same paths at dispatch, from one list. A host\n * that takes one and hand-rolls the other is back to the disagreement this pair\n * exists to prevent.\n */\n\n/** Structural keywords that wrap a value without naming a field. */\nconst SCHEMA_WRAPPERS = [\"items\", \"anyOf\", \"oneOf\", \"allOf\"] as const;\n\nfunction isSchema(value: unknown): value is JsonSchema {\n return Boolean(value) && typeof value === \"object\";\n}\n\n/** Drop `field` from an object schema's properties AND its `required` list. */\nfunction deleteProperty(schema: JsonSchema, field: string): true {\n delete (schema.properties as Record<string, JsonSchema>)[field];\n if (Array.isArray(schema.required)) {\n const kept = (schema.required as string[]).filter((name) => name !== field);\n if (kept.length) schema.required = kept;\n else delete schema.required;\n }\n return true;\n}\n\n/**\n * Descend into wrappers without consuming a segment, so `data.taxId` addresses\n * an array of rows and a `.nullable()` union branch alike.\n */\nfunction omitInWrappers(schema: JsonSchema, segments: readonly string[]): boolean {\n return SCHEMA_WRAPPERS.reduce((removed, keyword) => {\n const child = schema[keyword];\n const branches = Array.isArray(child) ? child : [child];\n return branches.reduce<boolean>(\n (acc, branch) => (isSchema(branch) && omitSchemaPath(branch, segments)) || acc,\n removed,\n );\n }, false);\n}\n\nfunction omitSchemaPath(schema: JsonSchema, segments: readonly string[]): boolean {\n const inWrappers = omitInWrappers(schema, segments);\n\n const properties = schema.properties as Record<string, JsonSchema> | undefined;\n const [head, ...rest] = segments;\n if (!properties || !head || !(head in properties)) return inWrappers;\n\n if (rest.length === 0) return deleteProperty(schema, head);\n\n const child = properties[head];\n return (isSchema(child) && omitSchemaPath(child, rest)) || inWrappers;\n}\n\n/**\n * Return `schema` without the listed dotted paths — the advertised half.\n *\n * THROWS when a path names nothing, rather than returning quietly: a typo'd or\n * stale redaction would otherwise protect nothing at all, and it would do so\n * invisibly, which is the one outcome a redaction list must never have. Failing\n * here turns it into a generator error naming the offending path.\n *\n * The input is cloned, not narrowed in place: a caller may hold the converted\n * schema for other uses (a shared `$defs` component, a schema reused across two\n * operations), and mutating it would redact those too.\n *\n * `operationId` only shapes the error message — pass it so the failure names\n * which tool declared the bad path.\n */\nexport function redactResponseSchema(\n schema: JsonSchema,\n paths: readonly string[],\n operationId?: string,\n): JsonSchema {\n if (!paths.length) return schema;\n\n const clone = structuredClone(schema);\n paths.forEach((path) => {\n if (!omitSchemaPath(clone, path.split(\".\"))) {\n const subject = operationId ? `MCP endpoint \"${operationId}\"` : \"This response schema\";\n throw new Error(\n `${subject} declares a response redaction for \"${path}\", which is not a field of its response schema`,\n );\n }\n });\n return clone;\n}\n\n/** Walk one dotted path, mapping over arrays, and delete the leaf. */\nfunction stripPath(value: unknown, segments: readonly string[]): void {\n if (value === null || typeof value !== \"object\") return;\n\n if (Array.isArray(value)) {\n value.forEach((entry) => stripPath(entry, segments));\n return;\n }\n\n const [head, ...rest] = segments;\n if (!head) return;\n\n const record = value as Record<string, unknown>;\n if (rest.length === 0) {\n delete record[head];\n return;\n }\n if (head in record) stripPath(record[head], rest);\n}\n\n/**\n * Return `body` without the listed dotted paths.\n *\n * The input is deep-cloned first: dispatch results are handed straight to the\n * JSON-RPC encoder AND reused as `structuredContent`, so mutating in place\n * could leak a half-redacted object into one of the two surfaces.\n */\nexport function redactResponseBody(\n body: unknown,\n paths: readonly string[] | undefined,\n): unknown {\n if (!paths?.length || body === null || typeof body !== \"object\") return body;\n\n const clone = structuredClone(body);\n paths.forEach((path) => stripPath(clone, path.split(\".\")));\n return clone;\n}\n","import { dispatchTool } from \"../dispatch/proxy\";\nimport type {\n GeneratedTool,\n JsonSchema,\n RequestAuth,\n ToolAnnotations,\n} from \"../types\";\nimport { redactResponseBody } from \"./redact\";\n\n/**\n * The registry is the transport-agnostic seam between the generated tools and the\n * MCP SDK. The consuming app owns the HTTP/JSON-RPC transport (mounting it at\n * `/api/mcp`) and, per request, resolves {@link RequestAuth} and calls\n * {@link ToolRegistry.listTools} / {@link ToolRegistry.callTool}. Keeping the\n * SDK out of this package means the core stays testable and portable.\n */\n\n/** An MCP tool descriptor as advertised to clients (subset of the MCP schema). */\nexport interface McpToolDescriptor {\n name: string;\n description: string;\n inputSchema: JsonSchema;\n outputSchema?: JsonSchema;\n annotations: ToolAnnotations;\n}\n\n/**\n * `_meta` key carrying the upstream HTTP status of a dispatched call.\n *\n * `isError` is one bit, and it collapses answers that mean opposite things: a\n * 404 for a record that does not exist, a 403 a guard correctly refused, a\n * domain refusal (\"this store does not use comandas\"), and a 500 where the route\n * threw all arrive identical. Callers that need to tell \"correctly refused\" from\n * \"actually broken\" — `mcp:smoke` above all — cannot, because the status is\n * known at dispatch and then dropped. Publishing it under a namespaced `_meta`\n * key (permitted by the MCP result schema) keeps `isError` as the agent-facing\n * signal while making the distinction recoverable.\n */\nexport const HTTP_STATUS_META_KEY = \"dispatch/httpStatus\";\n\n/** An MCP tool-call result (subset of the MCP schema). */\nexport interface McpToolResult {\n content: Array<{ type: \"text\"; text: string }>;\n isError: boolean;\n /** Machine-readable output matching the advertised outputSchema. */\n structuredContent?: Record<string, unknown>;\n /** Out-of-band metadata; carries {@link HTTP_STATUS_META_KEY} when dispatched. */\n _meta?: Record<string, unknown>;\n}\n\nexport interface ToolRegistry {\n listTools(auth?: RequestAuth): McpToolDescriptor[];\n callTool(\n name: string,\n args: Record<string, unknown>,\n auth: RequestAuth,\n ): Promise<McpToolResult>;\n}\n\nexport interface RegistryOptions {\n tools: GeneratedTool[];\n /** Origin the tools proxy to (usually the app's own public URL). */\n baseUrl: string;\n fetchImpl?: typeof fetch;\n /**\n * Optional visibility filter — e.g. hide mutating tools, or tools whose\n * required scope the caller lacks. Authorization is still enforced upstream;\n * this only shapes what the agent is shown.\n */\n isVisible?: (tool: GeneratedTool, auth?: RequestAuth) => boolean;\n}\n\nfunction textResult(\n value: unknown,\n isError: boolean,\n httpStatus?: number,\n): McpToolResult {\n const text =\n typeof value === \"string\" ? value : JSON.stringify(value, null, 2);\n const result: McpToolResult = { content: [{ type: \"text\", text }], isError };\n if (httpStatus !== undefined) {\n result._meta = { [HTTP_STATUS_META_KEY]: httpStatus };\n }\n return result;\n}\n\nfunction successfulResult(tool: GeneratedTool, value: unknown): McpToolResult {\n // Redact BEFORE rendering the text block: the agent reads `content` even when\n // it ignores `structuredContent`, so stripping only the latter would still\n // hand over the field.\n const safe = redactResponseBody(value, tool.redactResponse);\n const result = textResult(safe, false);\n if (\n tool.outputSchema &&\n safe !== null &&\n typeof safe === \"object\" &&\n !Array.isArray(safe)\n ) {\n result.structuredContent = safe as Record<string, unknown>;\n }\n return result;\n}\n\nexport function createToolRegistry(options: RegistryOptions): ToolRegistry {\n const byName = new Map(options.tools.map((tool) => [tool.name, tool]));\n\n return {\n listTools(auth) {\n return options.tools\n .filter((tool) =>\n options.isVisible ? options.isVisible(tool, auth) : true,\n )\n .map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n ...(tool.outputSchema ? { outputSchema: tool.outputSchema } : {}),\n annotations: tool.annotations,\n }));\n },\n\n async callTool(name, args, auth) {\n const tool = byName.get(name);\n if (!tool) return textResult(`Unknown tool: ${name}`, true);\n\n try {\n const result = await dispatchTool(tool, args, {\n baseUrl: options.baseUrl,\n bearer: auth.bearer,\n fetchImpl: options.fetchImpl,\n });\n // A non-2xx from the endpoint (e.g. 403 tenant-forbidden) is surfaced to\n // the agent as an error result, NOT thrown — the permission decision was\n // made upstream and its message is the useful signal. The status rides\n // along in `_meta` so a caller can tell a correct refusal from a break.\n return result.ok\n ? successfulResult(tool, result.body)\n : textResult(result.body, true, result.status);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return textResult(`Tool dispatch failed: ${message}`, true);\n }\n },\n };\n}\n","import type { RequestAuth } from \"../types\";\n\nimport type { ToolRegistry } from \"./registry\";\n\n/**\n * The MCP JSON-RPC 2.0 request half of the Streamable HTTP transport.\n *\n * Implemented directly rather than via the MCP SDK because the SDK's transport is\n * Node-`http` oriented, and a host serving Web `Request`/`Response` (a Next route\n * handler, a Hono route) has no `http.IncomingMessage` to hand it. It covers the\n * methods a client needs to discover and call tools: `initialize`, `tools/list`,\n * `tools/call`, plus `ping`.\n *\n * WHAT IS MECHANISM AND LIVES HERE: the envelope, the method table, the error\n * codes, the well-formedness rule, and the notification convention. None of it\n * varies per host — it is JSON-RPC 2.0 and the MCP specification.\n *\n * WHAT IS VOCABULARY AND STAYS WITH THE HOST: the server's NAME, its version, and\n * the `instructions` string an agent reads on connect. Those describe one\n * particular product's tool surface, so they arrive as {@link McpJsonRpcOptions}\n * rather than being written here.\n */\n\n/** The MCP protocol revision this transport implements. */\nexport const MCP_PROTOCOL_VERSION = \"2025-06-18\";\n\n/**\n * JSON-RPC error code for \"authentication required\", returned by `tools/call`\n * when the request carried no valid bearer. Outside the reserved -32768..-32000\n * band's *defined* codes on purpose: it is an implementation-defined server\n * error, and a host maps it to HTTP 401.\n */\nexport const UNAUTHORIZED_CODE = -32001;\n\n/** JSON-RPC \"Invalid Request\" — a payload that isn't a well-formed request object. */\nconst INVALID_REQUEST_CODE = -32600;\n\n/** JSON-RPC \"Method not found\". */\nconst METHOD_NOT_FOUND_CODE = -32601;\n\n/** JSON-RPC \"Invalid params\". */\nconst INVALID_PARAMS_CODE = -32602;\n\nexport interface JsonRpcRequest {\n jsonrpc: \"2.0\";\n id?: string | number | null;\n method: string;\n params?: unknown;\n}\n\nexport interface JsonRpcResponse {\n jsonrpc: \"2.0\";\n id: string | number | null;\n result?: unknown;\n error?: { code: number; message: string };\n}\n\n/** What a client is told it connected to, in `initialize`'s `serverInfo`. */\nexport interface McpServerInfo {\n /** The server's name, as a connected host displays it. */\n name: string;\n /**\n * The advertised surface version.\n *\n * This is the ONLY signal a client gets that the tool surface changed: the\n * transport is request/response only, so `notifications/tools/list_changed`\n * can never be sent, and a host that cached `tools/list` at the handshake has\n * no other reason to ask again. See `server/surface-lock.ts` for the guard\n * that makes forgetting to move it a build error instead of a comment.\n */\n version: string;\n}\n\nexport interface McpJsonRpcOptions {\n /** The host's identity, returned verbatim in `initialize`. */\n serverInfo: McpServerInfo;\n /**\n * Server-level guidance surfaced to the model on `initialize` (the MCP spec's\n * optional `instructions` field). Omitted from the result when absent, rather\n * than sent empty — a blank string is a claim that there is guidance.\n */\n instructions?: string;\n /**\n * Override the advertised protocol revision. Defaults to\n * {@link MCP_PROTOCOL_VERSION}; a host should not normally set it.\n */\n protocolVersion?: string;\n}\n\nfunction ok(id: JsonRpcRequest[\"id\"], result: unknown): JsonRpcResponse {\n return { jsonrpc: \"2.0\", id: id ?? null, result };\n}\n\nfunction fail(id: JsonRpcRequest[\"id\"], code: number, message: string): JsonRpcResponse {\n return { jsonrpc: \"2.0\", id: id ?? null, error: { code, message } };\n}\n\n/** A parsed body is a usable request only if it's an object carrying a string `method`. */\nfunction isWellFormed(request: JsonRpcRequest): boolean {\n return request != null && typeof request === \"object\" && typeof request.method === \"string\";\n}\n\nasync function handleToolsCall(\n request: JsonRpcRequest,\n registry: ToolRegistry,\n auth: RequestAuth | null,\n): Promise<JsonRpcResponse> {\n if (!auth) return fail(request.id, UNAUTHORIZED_CODE, \"Authentication required\");\n const params = (request.params ?? {}) as { name?: string; arguments?: Record<string, unknown> };\n if (!params.name) return fail(request.id, INVALID_PARAMS_CODE, \"Missing tool name\");\n const result = await registry.callTool(params.name, params.arguments ?? {}, auth);\n return ok(request.id, result);\n}\n\nfunction handleInitialize(\n request: JsonRpcRequest,\n options: McpJsonRpcOptions,\n): JsonRpcResponse {\n return ok(request.id, {\n protocolVersion: options.protocolVersion ?? MCP_PROTOCOL_VERSION,\n // Deliberately does NOT claim `listChanged`: this transport has no\n // server→client stream, so the notification could never be sent, and\n // advertising it would stop a host from ever re-reading `tools/list`.\n capabilities: { tools: {} },\n serverInfo: options.serverInfo,\n ...(options.instructions ? { instructions: options.instructions } : {}),\n });\n}\n\n/**\n * Handle one MCP JSON-RPC request.\n *\n * Returns `null` for notifications (no id, no reply expected). `auth` is the\n * verified caller identity, or `null` when the request carried no valid bearer —\n * `tools/call` then returns {@link UNAUTHORIZED_CODE}, which the host surfaces as\n * HTTP 401. Discovery (`initialize`, `ping`, `tools/list`) stays open, so a client\n * can read the surface before it has a token.\n */\nexport async function handleMcpJsonRpc(\n request: JsonRpcRequest,\n registry: ToolRegistry,\n auth: RequestAuth | null,\n options: McpJsonRpcOptions,\n): Promise<JsonRpcResponse | null> {\n // A host casts the parsed body to JsonRpcRequest without validating it, so a\n // malformed payload can arrive here: a `null` body/batch element, a non-object,\n // or an object with no `method`. Reject any of these as Invalid Request rather\n // than dereferencing `request`/`request.method` and throwing a 500 below.\n if (!isWellFormed(request)) {\n return fail(request?.id ?? null, INVALID_REQUEST_CODE, \"Invalid Request\");\n }\n switch (request.method) {\n case \"initialize\":\n return handleInitialize(request, options);\n case \"ping\":\n return ok(request.id, {});\n case \"tools/list\":\n return ok(request.id, { tools: registry.listTools(auth ?? undefined) });\n case \"tools/call\":\n return handleToolsCall(request, registry, auth);\n default:\n // JSON-RPC notifications (`notifications/*`) expect no reply — silently\n // ignore any we don't explicitly handle, rather than returning an error.\n if (request.method.startsWith(\"notifications/\")) return null;\n return fail(request.id, METHOD_NOT_FOUND_CODE, `Method not found: ${request.method}`);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AASO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EATlD,OASkD;AAAA;AAAA;AAAA,EAChD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAGA,IAAM,eAAe,CAAC,YAAY,kBAAkB,uBAAuB;AAC3E,IAAM,iBAAiB,CAAC,SAAS,aAAa;AAG9C,SAAS,QAAQ,KAA4B;AAC3C,QAAM,SAAS,aAAa,KAAK,CAAC,cAAc,IAAI,WAAW,SAAS,CAAC;AACzE,SAAO,SAAS,mBAAmB,IAAI,MAAM,OAAO,MAAM,CAAC,IAAI;AACjE;AAHS;AAMT,SAAS,YAAY,MAA8C;AACjE,QAAM,OAAmC,CAAC;AAC1C,iBAAe,QAAQ,CAAC,QAAQ;AAC9B,UAAM,YAAY,KAAK,GAAG;AAC1B,QAAI,aAAa,OAAO,cAAc,UAAU;AAC9C,aAAO,OAAO,MAAM,SAAuC;AAAA,IAC7D;AAAA,EACF,CAAC;AACD,SAAO;AACT;AATS;AAWT,SAAS,UACP,KACA,MACA,QACS;AACT,QAAM,OAAO,QAAQ,GAAG;AACxB,MAAI,SAAS,KAAM,OAAM,IAAI,uBAAuB,6BAA6B,GAAG,EAAE;AACtF,QAAM,SAAS,KAAK,IAAI;AACxB,MAAI,CAAC,OAAQ,OAAM,IAAI,uBAAuB,oBAAoB,GAAG,EAAE;AACvE,MAAI,OAAO,IAAI,IAAI,EAAG,OAAM,IAAI,uBAAuB,mCAAmC,IAAI,EAAE;AAChG,SAAO,IAAI,IAAI;AACf,QAAM,WAAW,KAAK,QAAQ,MAAM,MAAM;AAC1C,SAAO,OAAO,IAAI;AAClB,SAAO;AACT;AAdS;AAiBT,SAAS,KAAK,MAAe,MAAkC,QAA8B;AAC3F,MAAI,MAAM,QAAQ,IAAI,EAAG,QAAO,KAAK,IAAI,CAAC,SAAS,KAAK,MAAM,MAAM,MAAM,CAAC;AAC3E,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAE9C,QAAM,MAAM;AACZ,MAAI,OAAO,IAAI,SAAS,SAAU,QAAO,UAAU,IAAI,MAAM,MAAM,MAAM;AAEzE,QAAM,MAA+B,CAAC;AACtC,SAAO,QAAQ,GAAG,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AAC5C,QAAI,CAAC,eAAe,SAAS,GAAsC,GAAG;AACpE,UAAI,GAAG,IAAI,KAAK,OAAO,MAAM,MAAM;AAAA,IACrC;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAdS;AAyBF,SAAS,iBAAiB,QAAgC;AAC/D,QAAM,OAAO,YAAY,MAAM;AAC/B,SAAO,KAAK,QAAQ,MAAM,oBAAI,IAAY,CAAC;AAC7C;AAHgB;;;AC7ET,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAH9C,OAG8C;AAAA;AAAA;AAAA,EAC5C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAGA,SAAS,WACP,MACA,MACQ;AACR,SAAO,KAAK,KAAK,QAAQ,gBAAgB,CAAC,QAAQ,QAAgB;AAChE,UAAM,QAAQ,KAAK,GAAG;AACtB,QAAI,UAAU,UAAa,UAAU,MAAM;AACzC,YAAM,IAAI,mBAAmB,oCAAoC,GAAG,EAAE;AAAA,IACxE;AACA,WAAO,mBAAmB,OAAO,KAAK,CAAC;AAAA,EACzC,CAAC;AACH;AAXS;AAiBT,SAAS,YACP,MACA,MAC6D;AAC7D,QAAM,QAAQ,IAAI,gBAAgB;AAClC,QAAM,UAAkC,CAAC;AAEzC,OAAK,WACF,OAAO,CAAC,UAAU,MAAM,OAAO,MAAM,EACrC,QAAQ,CAAC,UAAU;AAClB,UAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,QAAI,UAAU,UAAa,UAAU,MAAM;AACzC,UAAI,MAAM,UAAU;AAClB,cAAM,IAAI,mBAAmB,oBAAoB,MAAM,EAAE,eAAe,MAAM,IAAI,EAAE;AAAA,MACtF;AACA;AAAA,IACF;AACA,QAAI,MAAM,OAAO,QAAS,OAAM,IAAI,MAAM,MAAM,OAAO,KAAK,CAAC;AAAA,QACxD,SAAQ,MAAM,IAAI,IAAI,OAAO,KAAK;AAAA,EACzC,CAAC;AAEH,SAAO,EAAE,OAAO,QAAQ;AAC1B;AAtBS;AA8BT,SAAS,UAAU,MAAqB,MAAwC;AAC9E,MAAI,KAAK,YAAa,QAAO,KAAK;AAClC,MAAI,CAAC,KAAK,UAAU,OAAQ,QAAO;AACnC,QAAM,UAAmC,CAAC;AAC1C,OAAK,UAAU,QAAQ,CAAC,QAAQ;AAC9B,QAAI,KAAK,GAAG,MAAM,OAAW,SAAQ,GAAG,IAAI,KAAK,GAAG;AAAA,EACtD,CAAC;AACD,SAAO,OAAO,KAAK,OAAO,EAAE,SAAS,UAAU;AACjD;AARS;AAgBT,eAAsB,aACpB,MACA,MACA,QACyB;AACzB,QAAM,UAAU,OAAO,aAAa;AACpC,QAAM,WAAW,WAAW,MAAM,IAAI;AACtC,QAAM,EAAE,OAAO,QAAQ,IAAI,YAAY,MAAM,IAAI;AACjD,QAAM,OAAO,UAAU,MAAM,IAAI;AAEjC,QAAM,MAAM,IAAI,IAAI,UAAU,OAAO,OAAO;AAC5C,aAAW,CAAC,KAAK,KAAK,KAAK,MAAO,KAAI,aAAa,IAAI,KAAK,KAAK;AAUjE,QAAM,OAAO,IAAI,IAAI,OAAO,OAAO;AAEnC,QAAM,OAAoB;AAAA,IACxB,QAAQ,KAAK;AAAA,IACb,SAAS;AAAA,MACP,QAAQ;AAAA,MACR,eAAe,UAAU,OAAO,MAAM;AAAA,MACtC,qBAAqB,KAAK,SAAS,QAAQ,MAAM,EAAE;AAAA,MACnD,oBAAoB,KAAK;AAAA,MACzB,GAAG;AAAA,IACL;AAAA,EACF;AACA,MAAI,SAAS,QAAW;AACtB,IAAC,KAAK,QAAmC,cAAc,IAAI;AAC3D,SAAK,OAAO,KAAK,UAAU,IAAI;AAAA,EACjC;AAEA,QAAM,WAAW,MAAM,QAAQ,IAAI,SAAS,GAAG,IAAI;AACnD,QAAM,OAAO,MAAM,SAAS,KAAK;AACjC,MAAI,SAAkB;AACtB,QAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;AAC5D,MAAI,YAAY,SAAS,kBAAkB,KAAK,MAAM;AACpD,QAAI;AACF,eAAS,KAAK,MAAM,IAAI;AAAA,IAC1B,QAAQ;AACN,eAAS;AAAA,IACX;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ,SAAS,QAAQ,IAAI,SAAS,IAAI,MAAM,OAAO;AAClE;AAnDsB;;;ACrDtB,IAAM,kBAAkB,CAAC,SAAS,SAAS,SAAS,OAAO;AAE3D,SAAS,SAAS,OAAqC;AACrD,SAAO,QAAQ,KAAK,KAAK,OAAO,UAAU;AAC5C;AAFS;AAKT,SAAS,eAAe,QAAoB,OAAqB;AAC/D,SAAQ,OAAO,WAA0C,KAAK;AAC9D,MAAI,MAAM,QAAQ,OAAO,QAAQ,GAAG;AAClC,UAAM,OAAQ,OAAO,SAAsB,OAAO,CAAC,SAAS,SAAS,KAAK;AAC1E,QAAI,KAAK,OAAQ,QAAO,WAAW;AAAA,QAC9B,QAAO,OAAO;AAAA,EACrB;AACA,SAAO;AACT;AARS;AAcT,SAAS,eAAe,QAAoB,UAAsC;AAChF,SAAO,gBAAgB,OAAO,CAAC,SAAS,YAAY;AAClD,UAAM,QAAQ,OAAO,OAAO;AAC5B,UAAM,WAAW,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AACtD,WAAO,SAAS;AAAA,MACd,CAAC,KAAK,WAAY,SAAS,MAAM,KAAK,eAAe,QAAQ,QAAQ,KAAM;AAAA,MAC3E;AAAA,IACF;AAAA,EACF,GAAG,KAAK;AACV;AATS;AAWT,SAAS,eAAe,QAAoB,UAAsC;AAChF,QAAM,aAAa,eAAe,QAAQ,QAAQ;AAElD,QAAM,aAAa,OAAO;AAC1B,QAAM,CAAC,MAAM,GAAG,IAAI,IAAI;AACxB,MAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,QAAQ,YAAa,QAAO;AAE1D,MAAI,KAAK,WAAW,EAAG,QAAO,eAAe,QAAQ,IAAI;AAEzD,QAAM,QAAQ,WAAW,IAAI;AAC7B,SAAQ,SAAS,KAAK,KAAK,eAAe,OAAO,IAAI,KAAM;AAC7D;AAXS;AA4BF,SAAS,qBACd,QACA,OACA,aACY;AACZ,MAAI,CAAC,MAAM,OAAQ,QAAO;AAE1B,QAAM,QAAQ,gBAAgB,MAAM;AACpC,QAAM,QAAQ,CAAC,SAAS;AACtB,QAAI,CAAC,eAAe,OAAO,KAAK,MAAM,GAAG,CAAC,GAAG;AAC3C,YAAM,UAAU,cAAc,iBAAiB,WAAW,MAAM;AAChE,YAAM,IAAI;AAAA,QACR,GAAG,OAAO,uCAAuC,IAAI;AAAA,MACvD;AAAA,IACF;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAjBgB;AAoBhB,SAAS,UAAU,OAAgB,UAAmC;AACpE,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU;AAEjD,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,UAAM,QAAQ,CAAC,UAAU,UAAU,OAAO,QAAQ,CAAC;AACnD;AAAA,EACF;AAEA,QAAM,CAAC,MAAM,GAAG,IAAI,IAAI;AACxB,MAAI,CAAC,KAAM;AAEX,QAAM,SAAS;AACf,MAAI,KAAK,WAAW,GAAG;AACrB,WAAO,OAAO,IAAI;AAClB;AAAA,EACF;AACA,MAAI,QAAQ,OAAQ,WAAU,OAAO,IAAI,GAAG,IAAI;AAClD;AAjBS;AA0BF,SAAS,mBACd,MACA,OACS;AACT,MAAI,CAAC,OAAO,UAAU,SAAS,QAAQ,OAAO,SAAS,SAAU,QAAO;AAExE,QAAM,QAAQ,gBAAgB,IAAI;AAClC,QAAM,QAAQ,CAAC,SAAS,UAAU,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC;AACzD,SAAO;AACT;AATgB;;;ACzFT,IAAM,uBAAuB;AAkCpC,SAAS,WACP,OACA,SACA,YACe;AACf,QAAM,OACJ,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,OAAO,MAAM,CAAC;AACnE,QAAM,SAAwB,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,KAAK,CAAC,GAAG,QAAQ;AAC3E,MAAI,eAAe,QAAW;AAC5B,WAAO,QAAQ,EAAE,CAAC,oBAAoB,GAAG,WAAW;AAAA,EACtD;AACA,SAAO;AACT;AAZS;AAcT,SAAS,iBAAiB,MAAqB,OAA+B;AAI5E,QAAM,OAAO,mBAAmB,OAAO,KAAK,cAAc;AAC1D,QAAM,SAAS,WAAW,MAAM,KAAK;AACrC,MACE,KAAK,gBACL,SAAS,QACT,OAAO,SAAS,YAChB,CAAC,MAAM,QAAQ,IAAI,GACnB;AACA,WAAO,oBAAoB;AAAA,EAC7B;AACA,SAAO;AACT;AAfS;AAiBF,SAAS,mBAAmB,SAAwC;AACzE,QAAM,SAAS,IAAI,IAAI,QAAQ,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC;AAErE,SAAO;AAAA,IACL,UAAU,MAAM;AACd,aAAO,QAAQ,MACZ;AAAA,QAAO,CAAC,SACP,QAAQ,YAAY,QAAQ,UAAU,MAAM,IAAI,IAAI;AAAA,MACtD,EACC,IAAI,CAAC,UAAU;AAAA,QACd,MAAM,KAAK;AAAA,QACX,aAAa,KAAK;AAAA,QAClB,aAAa,KAAK;AAAA,QAClB,GAAI,KAAK,eAAe,EAAE,cAAc,KAAK,aAAa,IAAI,CAAC;AAAA,QAC/D,aAAa,KAAK;AAAA,MACpB,EAAE;AAAA,IACN;AAAA,IAEA,MAAM,SAAS,MAAM,MAAM,MAAM;AAC/B,YAAM,OAAO,OAAO,IAAI,IAAI;AAC5B,UAAI,CAAC,KAAM,QAAO,WAAW,iBAAiB,IAAI,IAAI,IAAI;AAE1D,UAAI;AACF,cAAM,SAAS,MAAM,aAAa,MAAM,MAAM;AAAA,UAC5C,SAAS,QAAQ;AAAA,UACjB,QAAQ,KAAK;AAAA,UACb,WAAW,QAAQ;AAAA,QACrB,CAAC;AAKD,eAAO,OAAO,KACV,iBAAiB,MAAM,OAAO,IAAI,IAClC,WAAW,OAAO,MAAM,MAAM,OAAO,MAAM;AAAA,MACjD,SAAS,OAAO;AACd,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,eAAO,WAAW,yBAAyB,OAAO,IAAI,IAAI;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AACF;AAzCgB;;;AC/ET,IAAM,uBAAuB;AAQ7B,IAAM,oBAAoB;AAGjC,IAAM,uBAAuB;AAG7B,IAAM,wBAAwB;AAG9B,IAAM,sBAAsB;AAgD5B,SAAS,GAAG,IAA0B,QAAkC;AACtE,SAAO,EAAE,SAAS,OAAO,IAAI,MAAM,MAAM,OAAO;AAClD;AAFS;AAIT,SAAS,KAAK,IAA0B,MAAc,SAAkC;AACtF,SAAO,EAAE,SAAS,OAAO,IAAI,MAAM,MAAM,OAAO,EAAE,MAAM,QAAQ,EAAE;AACpE;AAFS;AAKT,SAAS,aAAa,SAAkC;AACtD,SAAO,WAAW,QAAQ,OAAO,YAAY,YAAY,OAAO,QAAQ,WAAW;AACrF;AAFS;AAIT,eAAe,gBACb,SACA,UACA,MAC0B;AAC1B,MAAI,CAAC,KAAM,QAAO,KAAK,QAAQ,IAAI,mBAAmB,yBAAyB;AAC/E,QAAM,SAAU,QAAQ,UAAU,CAAC;AACnC,MAAI,CAAC,OAAO,KAAM,QAAO,KAAK,QAAQ,IAAI,qBAAqB,mBAAmB;AAClF,QAAM,SAAS,MAAM,SAAS,SAAS,OAAO,MAAM,OAAO,aAAa,CAAC,GAAG,IAAI;AAChF,SAAO,GAAG,QAAQ,IAAI,MAAM;AAC9B;AAVe;AAYf,SAAS,iBACP,SACA,SACiB;AACjB,SAAO,GAAG,QAAQ,IAAI;AAAA,IACpB,iBAAiB,QAAQ,mBAAmB;AAAA;AAAA;AAAA;AAAA,IAI5C,cAAc,EAAE,OAAO,CAAC,EAAE;AAAA,IAC1B,YAAY,QAAQ;AAAA,IACpB,GAAI,QAAQ,eAAe,EAAE,cAAc,QAAQ,aAAa,IAAI,CAAC;AAAA,EACvE,CAAC;AACH;AAbS;AAwBT,eAAsB,iBACpB,SACA,UACA,MACA,SACiC;AAKjC,MAAI,CAAC,aAAa,OAAO,GAAG;AAC1B,WAAO,KAAK,SAAS,MAAM,MAAM,sBAAsB,iBAAiB;AAAA,EAC1E;AACA,UAAQ,QAAQ,QAAQ;AAAA,IACtB,KAAK;AACH,aAAO,iBAAiB,SAAS,OAAO;AAAA,IAC1C,KAAK;AACH,aAAO,GAAG,QAAQ,IAAI,CAAC,CAAC;AAAA,IAC1B,KAAK;AACH,aAAO,GAAG,QAAQ,IAAI,EAAE,OAAO,SAAS,UAAU,QAAQ,MAAS,EAAE,CAAC;AAAA,IACxE,KAAK;AACH,aAAO,gBAAgB,SAAS,UAAU,IAAI;AAAA,IAChD;AAGE,UAAI,QAAQ,OAAO,WAAW,gBAAgB,EAAG,QAAO;AACxD,aAAO,KAAK,QAAQ,IAAI,uBAAuB,qBAAqB,QAAQ,MAAM,EAAE;AAAA,EACxF;AACF;AA5BsB;","names":[]}
1
+ {"version":3,"sources":["../src/openapi/refs.ts","../src/dispatch/proxy.ts","../src/server/redact.ts","../src/server/registry.ts","../src/server/jsonrpc.ts"],"sourcesContent":["import type { JsonSchema } from \"../types\";\n\n/**\n * Raised when a JSON Schema cannot be turned into a flat, self-contained tool\n * input — an unresolvable `$ref`, an unsupported pointer, or a recursive schema.\n * The MCP tool surface is deliberately finite and flat (it is handed to an LLM\n * and committed to the drift manifest), so recursion is rejected rather than\n * silently truncated.\n */\nexport class UnsupportedSchemaError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"UnsupportedSchemaError\";\n }\n}\n\n/** Local-definition containers a `$ref` may point into, in resolution order. */\nconst REF_PREFIXES = [\"#/$defs/\", \"#/definitions/\", \"#/components/schemas/\"] as const;\nconst DEF_CONTAINERS = [\"$defs\", \"definitions\"] as const;\n\n/** The definition name a supported local pointer targets, or `null` if unsupported. */\nfunction refName(ref: string): string | null {\n const prefix = REF_PREFIXES.find((candidate) => ref.startsWith(candidate));\n return prefix ? decodeURIComponent(ref.slice(prefix.length)) : null;\n}\n\n/** Collect the `$defs`/`definitions` maps hoisted onto a schema root into one lookup. */\nfunction collectDefs(root: JsonSchema): Record<string, JsonSchema> {\n const defs: Record<string, JsonSchema> = {};\n DEF_CONTAINERS.forEach((key) => {\n const container = root[key];\n if (container && typeof container === \"object\") {\n Object.assign(defs, container as Record<string, JsonSchema>);\n }\n });\n return defs;\n}\n\nfunction inlineRef(\n ref: string,\n defs: Record<string, JsonSchema>,\n active: Set<string>,\n): unknown {\n const name = refName(ref);\n if (name === null) throw new UnsupportedSchemaError(`Unsupported $ref pointer: ${ref}`);\n const target = defs[name];\n if (!target) throw new UnsupportedSchemaError(`Unresolved $ref: ${ref}`);\n if (active.has(name)) throw new UnsupportedSchemaError(`Recursive schema not supported: ${name}`);\n active.add(name);\n const resolved = walk(target, defs, active);\n active.delete(name);\n return resolved;\n}\n\n/** Deep-copy `node`, inlining every `$ref` and stripping definition containers. */\nfunction walk(node: unknown, defs: Record<string, JsonSchema>, active: Set<string>): unknown {\n if (Array.isArray(node)) return node.map((item) => walk(item, defs, active));\n if (!node || typeof node !== \"object\") return node;\n\n const obj = node as Record<string, unknown>;\n if (typeof obj.$ref === \"string\") return inlineRef(obj.$ref, defs, active);\n\n const out: Record<string, unknown> = {};\n Object.entries(obj).forEach(([key, value]) => {\n if (!DEF_CONTAINERS.includes(key as (typeof DEF_CONTAINERS)[number])) {\n out[key] = walk(value, defs, active);\n }\n });\n return out;\n}\n\n/**\n * Inline every local `$ref` in a JSON Schema and drop the now-empty `$defs`/\n * `definitions` containers, yielding a flat, self-contained schema. Diamond reuse\n * (the same definition referenced by sibling branches) is fine; only a true cycle\n * — a definition that references itself up the resolution stack — is rejected.\n *\n * A schema with no `$ref`/definitions is returned structurally unchanged, so\n * inlining an already-flat spec is a no-op (the drift gate stays a stable diff).\n */\nexport function inlineSchemaRefs(schema: JsonSchema): JsonSchema {\n const defs = collectDefs(schema);\n return walk(schema, defs, new Set<string>()) as JsonSchema;\n}\n","import type { DispatchConfig, DispatchResult, GeneratedTool } from \"../types\";\n\n/** Raised when tool arguments cannot be routed onto the HTTP request. */\nexport class DispatchInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"DispatchInputError\";\n }\n}\n\n/** Expand a path template (`/products/{id}`) using the path args, URL-encoding each. */\nfunction expandPath(\n tool: GeneratedTool,\n args: Record<string, unknown>,\n): string {\n return tool.path.replace(/\\{([^}]+)\\}/g, (_match, key: string) => {\n const value = args[key];\n if (value === undefined || value === null) {\n throw new DispatchInputError(`Missing required path parameter: ${key}`);\n }\n return encodeURIComponent(String(value));\n });\n}\n\n/**\n * Route the non-path parameters (query + header) from the flat args. A missing\n * required parameter is a hard error; a missing optional one is simply omitted.\n */\nfunction routeParams(\n tool: GeneratedTool,\n args: Record<string, unknown>,\n): { query: URLSearchParams; headers: Record<string, string> } {\n const query = new URLSearchParams();\n const headers: Record<string, string> = {};\n\n tool.parameters\n .filter((param) => param.in !== \"path\")\n .forEach((param) => {\n const value = args[param.name];\n if (value === undefined || value === null) {\n if (param.required) {\n throw new DispatchInputError(`Missing required ${param.in} parameter: ${param.name}`);\n }\n return;\n }\n if (param.in === \"query\") query.set(param.name, String(value));\n else headers[param.name] = String(value);\n });\n\n return { query, headers };\n}\n\n/**\n * Reconstruct the request body from the flat args using the routing metadata.\n * Anything not claimed by a known body property is dropped — the input schema is\n * `additionalProperties: false`, so a validated call never carries extras and an\n * unvalidated one cannot smuggle fields upstream.\n */\nfunction routeBody(tool: GeneratedTool, args: Record<string, unknown>): unknown {\n if (tool.bodyIsWhole) return args.body;\n if (!tool.bodyProps.length) return undefined;\n const payload: Record<string, unknown> = {};\n tool.bodyProps.forEach((key) => {\n if (args[key] !== undefined) payload[key] = args[key];\n });\n return Object.keys(payload).length ? payload : undefined;\n}\n\n/**\n * Execute one generated tool by proxying to its HTTP endpoint, forwarding the\n * caller's bearer verbatim. This function performs NO authorization — the\n * endpoint does, exactly as it would for a first-party request. That is the whole\n * point of the passthrough: the agent can do precisely what the user can.\n */\nexport async function dispatchTool(\n tool: GeneratedTool,\n args: Record<string, unknown>,\n config: DispatchConfig,\n): Promise<DispatchResult> {\n const doFetch = config.fetchImpl ?? fetch;\n const pathname = expandPath(tool, args);\n const { query, headers } = routeParams(tool, args);\n const body = routeBody(tool, args);\n\n const url = new URL(pathname, config.baseUrl);\n for (const [key, value] of query) url.searchParams.set(key, value);\n\n // Carry the proxy origin as the standard reverse-proxy forwarded headers. The\n // wrapped endpoint's auth guard re-derives the request origin (to check the\n // access token's `aud`) WITHOUT a request object, so it can only see the origin\n // through these headers. `baseUrl` is exactly the origin the token was minted\n // and transport-verified against, so forwarding its scheme+host makes the\n // wrapped guard reconstruct the SAME origin — the `aud` survives the replay in\n // both dev (http) and prod (any proxied https origin). Omitting them let the\n // guard default the scheme to https and 401 a valid http-minted bearer.\n const base = new URL(config.baseUrl);\n\n const init: RequestInit = {\n method: tool.method,\n headers: {\n accept: \"application/json\",\n authorization: `Bearer ${config.bearer}`,\n \"x-forwarded-proto\": base.protocol.replace(/:$/, \"\"),\n \"x-forwarded-host\": base.host,\n ...headers,\n },\n };\n if (body !== undefined) {\n (init.headers as Record<string, string>)[\"content-type\"] = \"application/json\";\n init.body = JSON.stringify(body);\n }\n\n const response = await doFetch(url.toString(), init);\n const text = await response.text();\n let parsed: unknown = text;\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"application/json\") && text) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = text;\n }\n }\n\n return { status: response.status, ok: response.ok, body: parsed };\n}\n","import type { JsonSchema } from \"../types\";\n\n/**\n * Both halves of the redaction contract: the SCHEMA a tool advertises, and the\n * BODY it returns.\n *\n * They only work as a pair, and the failure mode when they disagree is not a\n * cosmetic one. The dispatcher forwards the wrapped endpoint's response body\n * verbatim, so a narrowed `outputSchema` alone would only change what the\n * manifest CLAIMS is returned — the value still reaches the agent. Strip the\n * body but leave the schema advertising the field, and every successful call\n * fails validation against the very schema the manifest published, worst of all\n * when the field was `required`.\n *\n * So `redactResponseSchema` removes the paths at generate time and\n * `redactResponseBody` removes the same paths at dispatch, from one list. A host\n * that takes one and hand-rolls the other is back to the disagreement this pair\n * exists to prevent.\n */\n\n/** Structural keywords that wrap a value without naming a field. */\nconst SCHEMA_WRAPPERS = [\"items\", \"anyOf\", \"oneOf\", \"allOf\"] as const;\n\nfunction isSchema(value: unknown): value is JsonSchema {\n return Boolean(value) && typeof value === \"object\";\n}\n\n/** Drop `field` from an object schema's properties AND its `required` list. */\nfunction deleteProperty(schema: JsonSchema, field: string): true {\n delete (schema.properties as Record<string, JsonSchema>)[field];\n if (Array.isArray(schema.required)) {\n const kept = (schema.required as string[]).filter((name) => name !== field);\n if (kept.length) schema.required = kept;\n else delete schema.required;\n }\n return true;\n}\n\n/**\n * Descend into wrappers without consuming a segment, so `data.taxId` addresses\n * an array of rows and a `.nullable()` union branch alike.\n */\nfunction omitInWrappers(schema: JsonSchema, segments: readonly string[]): boolean {\n return SCHEMA_WRAPPERS.reduce((removed, keyword) => {\n const child = schema[keyword];\n const branches = Array.isArray(child) ? child : [child];\n return branches.reduce<boolean>(\n (acc, branch) => (isSchema(branch) && omitSchemaPath(branch, segments)) || acc,\n removed,\n );\n }, false);\n}\n\nfunction omitSchemaPath(schema: JsonSchema, segments: readonly string[]): boolean {\n const inWrappers = omitInWrappers(schema, segments);\n\n const properties = schema.properties as Record<string, JsonSchema> | undefined;\n const [head, ...rest] = segments;\n if (!properties || !head || !(head in properties)) return inWrappers;\n\n if (rest.length === 0) return deleteProperty(schema, head);\n\n const child = properties[head];\n return (isSchema(child) && omitSchemaPath(child, rest)) || inWrappers;\n}\n\n/**\n * Return `schema` without the listed dotted paths — the advertised half.\n *\n * THROWS when a path names nothing, rather than returning quietly: a typo'd or\n * stale redaction would otherwise protect nothing at all, and it would do so\n * invisibly, which is the one outcome a redaction list must never have. Failing\n * here turns it into a generator error naming the offending path.\n *\n * The input is cloned, not narrowed in place: a caller may hold the converted\n * schema for other uses (a shared `$defs` component, a schema reused across two\n * operations), and mutating it would redact those too.\n *\n * `operationId` only shapes the error message — pass it so the failure names\n * which tool declared the bad path.\n */\nexport function redactResponseSchema(\n schema: JsonSchema,\n paths: readonly string[],\n operationId?: string,\n): JsonSchema {\n if (!paths.length) return schema;\n\n const clone = structuredClone(schema);\n paths.forEach((path) => {\n if (!omitSchemaPath(clone, path.split(\".\"))) {\n const subject = operationId ? `MCP endpoint \"${operationId}\"` : \"This response schema\";\n throw new Error(\n `${subject} declares a response redaction for \"${path}\", which is not a field of its response schema`,\n );\n }\n });\n return clone;\n}\n\n/** Walk one dotted path, mapping over arrays, and delete the leaf. */\nfunction stripPath(value: unknown, segments: readonly string[]): void {\n if (value === null || typeof value !== \"object\") return;\n\n if (Array.isArray(value)) {\n value.forEach((entry) => stripPath(entry, segments));\n return;\n }\n\n const [head, ...rest] = segments;\n if (!head) return;\n\n const record = value as Record<string, unknown>;\n if (rest.length === 0) {\n delete record[head];\n return;\n }\n if (head in record) stripPath(record[head], rest);\n}\n\n/**\n * Return `body` without the listed dotted paths.\n *\n * The input is deep-cloned first: dispatch results are handed straight to the\n * JSON-RPC encoder AND reused as `structuredContent`, so mutating in place\n * could leak a half-redacted object into one of the two surfaces.\n */\nexport function redactResponseBody(\n body: unknown,\n paths: readonly string[] | undefined,\n): unknown {\n if (!paths?.length || body === null || typeof body !== \"object\") return body;\n\n const clone = structuredClone(body);\n paths.forEach((path) => stripPath(clone, path.split(\".\")));\n return clone;\n}\n","import { dispatchTool } from \"../dispatch/proxy\";\nimport type {\n GeneratedTool,\n JsonSchema,\n RequestAuth,\n ToolAnnotations,\n} from \"../types\";\nimport { redactResponseBody } from \"./redact\";\n\n/**\n * The registry is the transport-agnostic seam between the generated tools and the\n * MCP SDK. The consuming app owns the HTTP/JSON-RPC transport (mounting it at\n * `/api/mcp`) and, per request, resolves {@link RequestAuth} and calls\n * {@link ToolRegistry.listTools} / {@link ToolRegistry.callTool}. Keeping the\n * SDK out of this package means the core stays testable and portable.\n */\n\n/** An MCP tool descriptor as advertised to clients (subset of the MCP schema). */\nexport interface McpToolDescriptor {\n name: string;\n description: string;\n inputSchema: JsonSchema;\n outputSchema?: JsonSchema;\n annotations: ToolAnnotations;\n}\n\n/**\n * `_meta` key carrying the upstream HTTP status of a dispatched call.\n *\n * `isError` is one bit, and it collapses answers that mean opposite things: a\n * 404 for a record that does not exist, a 403 a guard correctly refused, a\n * domain refusal (\"this store does not use comandas\"), and a 500 where the route\n * threw all arrive identical. Callers that need to tell \"correctly refused\" from\n * \"actually broken\" — `mcp:smoke` above all — cannot, because the status is\n * known at dispatch and then dropped. Publishing it under a namespaced `_meta`\n * key (permitted by the MCP result schema) keeps `isError` as the agent-facing\n * signal while making the distinction recoverable.\n */\nexport const HTTP_STATUS_META_KEY = \"dispatch/httpStatus\";\n\n/** An MCP tool-call result (subset of the MCP schema). */\nexport interface McpToolResult {\n content: Array<{ type: \"text\"; text: string }>;\n isError: boolean;\n /** Machine-readable output matching the advertised outputSchema. */\n structuredContent?: Record<string, unknown>;\n /** Out-of-band metadata; carries {@link HTTP_STATUS_META_KEY} when dispatched. */\n _meta?: Record<string, unknown>;\n}\n\nexport interface ToolRegistry {\n listTools(auth?: RequestAuth): McpToolDescriptor[];\n callTool(\n name: string,\n args: Record<string, unknown>,\n auth: RequestAuth,\n ): Promise<McpToolResult>;\n}\n\nexport interface RegistryOptions {\n tools: GeneratedTool[];\n /** Origin the tools proxy to (usually the app's own public URL). */\n baseUrl: string;\n fetchImpl?: typeof fetch;\n /**\n * Optional visibility filter — e.g. hide mutating tools, or tools whose\n * required scope the caller lacks. Authorization is still enforced upstream;\n * this only shapes what the agent is shown.\n */\n isVisible?: (tool: GeneratedTool, auth?: RequestAuth) => boolean;\n}\n\nfunction textResult(\n value: unknown,\n isError: boolean,\n httpStatus?: number,\n): McpToolResult {\n const text =\n typeof value === \"string\" ? value : JSON.stringify(value, null, 2);\n const result: McpToolResult = { content: [{ type: \"text\", text }], isError };\n if (httpStatus !== undefined) {\n result._meta = { [HTTP_STATUS_META_KEY]: httpStatus };\n }\n return result;\n}\n\nfunction successfulResult(tool: GeneratedTool, value: unknown): McpToolResult {\n // Redact BEFORE rendering the text block: the agent reads `content` even when\n // it ignores `structuredContent`, so stripping only the latter would still\n // hand over the field.\n const safe = redactResponseBody(value, tool.redactResponse);\n const result = textResult(safe, false);\n if (\n tool.outputSchema &&\n safe !== null &&\n typeof safe === \"object\" &&\n !Array.isArray(safe)\n ) {\n result.structuredContent = safe as Record<string, unknown>;\n }\n return result;\n}\n\nexport function createToolRegistry(options: RegistryOptions): ToolRegistry {\n const byName = new Map(options.tools.map((tool) => [tool.name, tool]));\n\n return {\n listTools(auth) {\n return options.tools\n .filter((tool) =>\n options.isVisible ? options.isVisible(tool, auth) : true,\n )\n .map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n ...(tool.outputSchema ? { outputSchema: tool.outputSchema } : {}),\n annotations: tool.annotations,\n }));\n },\n\n async callTool(name, args, auth) {\n const tool = byName.get(name);\n if (!tool) return textResult(`Unknown tool: ${name}`, true);\n\n try {\n const result = await dispatchTool(tool, args, {\n baseUrl: options.baseUrl,\n bearer: auth.bearer,\n fetchImpl: options.fetchImpl,\n });\n // A non-2xx from the endpoint (e.g. 403 tenant-forbidden) is surfaced to\n // the agent as an error result, NOT thrown — the permission decision was\n // made upstream and its message is the useful signal. The status rides\n // along in `_meta` so a caller can tell a correct refusal from a break.\n return result.ok\n ? successfulResult(tool, result.body)\n : textResult(result.body, true, result.status);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return textResult(`Tool dispatch failed: ${message}`, true);\n }\n },\n };\n}\n","import type { RequestAuth } from \"../types\";\n\nimport type { ToolRegistry } from \"./registry\";\n\n/**\n * The MCP JSON-RPC 2.0 request half of the Streamable HTTP transport.\n *\n * Implemented directly rather than via the MCP SDK because the SDK's transport is\n * Node-`http` oriented, and a host serving Web `Request`/`Response` (a Next route\n * handler, a Hono route) has no `http.IncomingMessage` to hand it. It covers the\n * methods a client needs to discover and call tools: `initialize`, `tools/list`,\n * `tools/call`, plus `ping`.\n *\n * WHAT IS MECHANISM AND LIVES HERE: the envelope, the method table, the error\n * codes, the well-formedness rule, and the notification convention. None of it\n * varies per host — it is JSON-RPC 2.0 and the MCP specification.\n *\n * WHAT IS VOCABULARY AND STAYS WITH THE HOST: the server's NAME, its version, and\n * the `instructions` string an agent reads on connect. Those describe one\n * particular product's tool surface, so they arrive as {@link McpJsonRpcOptions}\n * rather than being written here.\n */\n\n/** The MCP protocol revision this transport implements. */\nexport const MCP_PROTOCOL_VERSION = \"2025-06-18\";\n\n/**\n * JSON-RPC error code for \"authentication required\", returned by `tools/call`\n * when the request carried no valid bearer. Outside the reserved -32768..-32000\n * band's *defined* codes on purpose: it is an implementation-defined server\n * error, and a host maps it to HTTP 401.\n */\nexport const UNAUTHORIZED_CODE = -32001;\n\n/** JSON-RPC \"Invalid Request\" — a payload that isn't a well-formed request object. */\nconst INVALID_REQUEST_CODE = -32600;\n\n/** JSON-RPC \"Method not found\". */\nconst METHOD_NOT_FOUND_CODE = -32601;\n\n/** JSON-RPC \"Invalid params\". */\nconst INVALID_PARAMS_CODE = -32602;\n\nexport interface JsonRpcRequest {\n jsonrpc: \"2.0\";\n id?: string | number | null;\n method: string;\n params?: unknown;\n}\n\nexport interface JsonRpcResponse {\n jsonrpc: \"2.0\";\n id: string | number | null;\n result?: unknown;\n error?: { code: number; message: string };\n}\n\n/** What a client is told it connected to, in `initialize`'s `serverInfo`. */\nexport interface McpServerInfo {\n /** The server's name, as a connected host displays it. */\n name: string;\n /**\n * The advertised surface version.\n *\n * This is the ONLY signal a client gets that the tool surface changed: the\n * transport is request/response only, so `notifications/tools/list_changed`\n * can never be sent, and a host that cached `tools/list` at the handshake has\n * no other reason to ask again. See `server/surface-lock.ts` for the guard\n * that makes forgetting to move it a build error instead of a comment.\n */\n version: string;\n}\n\nexport interface McpJsonRpcOptions {\n /** The host's identity, returned verbatim in `initialize`. */\n serverInfo: McpServerInfo;\n /**\n * Server-level guidance surfaced to the model on `initialize` (the MCP spec's\n * optional `instructions` field). Omitted from the result when absent, rather\n * than sent empty — a blank string is a claim that there is guidance.\n */\n instructions?: string;\n /**\n * Override the advertised protocol revision. Defaults to\n * {@link MCP_PROTOCOL_VERSION}; a host should not normally set it.\n */\n protocolVersion?: string;\n}\n\nfunction ok(id: JsonRpcRequest[\"id\"], result: unknown): JsonRpcResponse {\n return { jsonrpc: \"2.0\", id: id ?? null, result };\n}\n\nfunction fail(id: JsonRpcRequest[\"id\"], code: number, message: string): JsonRpcResponse {\n return { jsonrpc: \"2.0\", id: id ?? null, error: { code, message } };\n}\n\n/** A parsed body is a usable request only if it's an object carrying a string `method`. */\nfunction isWellFormed(request: JsonRpcRequest): boolean {\n return request != null && typeof request === \"object\" && typeof request.method === \"string\";\n}\n\nasync function handleToolsCall(\n request: JsonRpcRequest,\n registry: ToolRegistry,\n auth: RequestAuth | null,\n): Promise<JsonRpcResponse> {\n if (!auth) return fail(request.id, UNAUTHORIZED_CODE, \"Authentication required\");\n const params = (request.params ?? {}) as { name?: string; arguments?: Record<string, unknown> };\n if (!params.name) return fail(request.id, INVALID_PARAMS_CODE, \"Missing tool name\");\n const result = await registry.callTool(params.name, params.arguments ?? {}, auth);\n return ok(request.id, result);\n}\n\nfunction handleInitialize(\n request: JsonRpcRequest,\n options: McpJsonRpcOptions,\n): JsonRpcResponse {\n return ok(request.id, {\n protocolVersion: options.protocolVersion ?? MCP_PROTOCOL_VERSION,\n // Deliberately does NOT claim `listChanged`: this transport has no\n // server→client stream, so the notification could never be sent, and\n // advertising it would stop a host from ever re-reading `tools/list`.\n capabilities: { tools: {} },\n serverInfo: options.serverInfo,\n ...(options.instructions ? { instructions: options.instructions } : {}),\n });\n}\n\n/**\n * Handle one MCP JSON-RPC request.\n *\n * Returns `null` for notifications (no id, no reply expected). `auth` is the\n * verified caller identity, or `null` when the request carried no valid bearer —\n * `tools/call` then returns {@link UNAUTHORIZED_CODE}, which the host surfaces as\n * HTTP 401. Discovery (`initialize`, `ping`, `tools/list`) stays open, so a client\n * can read the surface before it has a token.\n */\nexport async function handleMcpJsonRpc(\n request: JsonRpcRequest,\n registry: ToolRegistry,\n auth: RequestAuth | null,\n options: McpJsonRpcOptions,\n): Promise<JsonRpcResponse | null> {\n // A host casts the parsed body to JsonRpcRequest without validating it, so a\n // malformed payload can arrive here: a `null` body/batch element, a non-object,\n // or an object with no `method`. Reject any of these as Invalid Request rather\n // than dereferencing `request`/`request.method` and throwing a 500 below.\n if (!isWellFormed(request)) {\n return fail(request?.id ?? null, INVALID_REQUEST_CODE, \"Invalid Request\");\n }\n switch (request.method) {\n case \"initialize\":\n return handleInitialize(request, options);\n case \"ping\":\n return ok(request.id, {});\n case \"tools/list\":\n return ok(request.id, { tools: registry.listTools(auth ?? undefined) });\n case \"tools/call\":\n return handleToolsCall(request, registry, auth);\n default:\n // JSON-RPC notifications (`notifications/*`) expect no reply — silently\n // ignore any we don't explicitly handle, rather than returning an error.\n if (request.method.startsWith(\"notifications/\")) return null;\n return fail(request.id, METHOD_NOT_FOUND_CODE, `Method not found: ${request.method}`);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EATlD,OASkD;AAAA;AAAA;AAAA,EAChD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAGA,IAAM,eAAe,CAAC,YAAY,kBAAkB,uBAAuB;AAC3E,IAAM,iBAAiB,CAAC,SAAS,aAAa;AAG9C,SAAS,QAAQ,KAA4B;AAC3C,QAAM,SAAS,aAAa,KAAK,CAAC,cAAc,IAAI,WAAW,SAAS,CAAC;AACzE,SAAO,SAAS,mBAAmB,IAAI,MAAM,OAAO,MAAM,CAAC,IAAI;AACjE;AAHS;AAMT,SAAS,YAAY,MAA8C;AACjE,QAAM,OAAmC,CAAC;AAC1C,iBAAe,QAAQ,CAAC,QAAQ;AAC9B,UAAM,YAAY,KAAK,GAAG;AAC1B,QAAI,aAAa,OAAO,cAAc,UAAU;AAC9C,aAAO,OAAO,MAAM,SAAuC;AAAA,IAC7D;AAAA,EACF,CAAC;AACD,SAAO;AACT;AATS;AAWT,SAAS,UACP,KACA,MACA,QACS;AACT,QAAM,OAAO,QAAQ,GAAG;AACxB,MAAI,SAAS,KAAM,OAAM,IAAI,uBAAuB,6BAA6B,GAAG,EAAE;AACtF,QAAM,SAAS,KAAK,IAAI;AACxB,MAAI,CAAC,OAAQ,OAAM,IAAI,uBAAuB,oBAAoB,GAAG,EAAE;AACvE,MAAI,OAAO,IAAI,IAAI,EAAG,OAAM,IAAI,uBAAuB,mCAAmC,IAAI,EAAE;AAChG,SAAO,IAAI,IAAI;AACf,QAAM,WAAW,KAAK,QAAQ,MAAM,MAAM;AAC1C,SAAO,OAAO,IAAI;AAClB,SAAO;AACT;AAdS;AAiBT,SAAS,KAAK,MAAe,MAAkC,QAA8B;AAC3F,MAAI,MAAM,QAAQ,IAAI,EAAG,QAAO,KAAK,IAAI,CAAC,SAAS,KAAK,MAAM,MAAM,MAAM,CAAC;AAC3E,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAE9C,QAAM,MAAM;AACZ,MAAI,OAAO,IAAI,SAAS,SAAU,QAAO,UAAU,IAAI,MAAM,MAAM,MAAM;AAEzE,QAAM,MAA+B,CAAC;AACtC,SAAO,QAAQ,GAAG,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AAC5C,QAAI,CAAC,eAAe,SAAS,GAAsC,GAAG;AACpE,UAAI,GAAG,IAAI,KAAK,OAAO,MAAM,MAAM;AAAA,IACrC;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAdS;AAyBF,SAAS,iBAAiB,QAAgC;AAC/D,QAAM,OAAO,YAAY,MAAM;AAC/B,SAAO,KAAK,QAAQ,MAAM,oBAAI,IAAY,CAAC;AAC7C;AAHgB;;;AC7ET,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAH9C,OAG8C;AAAA;AAAA;AAAA,EAC5C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAGA,SAAS,WACP,MACA,MACQ;AACR,SAAO,KAAK,KAAK,QAAQ,gBAAgB,CAAC,QAAQ,QAAgB;AAChE,UAAM,QAAQ,KAAK,GAAG;AACtB,QAAI,UAAU,UAAa,UAAU,MAAM;AACzC,YAAM,IAAI,mBAAmB,oCAAoC,GAAG,EAAE;AAAA,IACxE;AACA,WAAO,mBAAmB,OAAO,KAAK,CAAC;AAAA,EACzC,CAAC;AACH;AAXS;AAiBT,SAAS,YACP,MACA,MAC6D;AAC7D,QAAM,QAAQ,IAAI,gBAAgB;AAClC,QAAM,UAAkC,CAAC;AAEzC,OAAK,WACF,OAAO,CAAC,UAAU,MAAM,OAAO,MAAM,EACrC,QAAQ,CAAC,UAAU;AAClB,UAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,QAAI,UAAU,UAAa,UAAU,MAAM;AACzC,UAAI,MAAM,UAAU;AAClB,cAAM,IAAI,mBAAmB,oBAAoB,MAAM,EAAE,eAAe,MAAM,IAAI,EAAE;AAAA,MACtF;AACA;AAAA,IACF;AACA,QAAI,MAAM,OAAO,QAAS,OAAM,IAAI,MAAM,MAAM,OAAO,KAAK,CAAC;AAAA,QACxD,SAAQ,MAAM,IAAI,IAAI,OAAO,KAAK;AAAA,EACzC,CAAC;AAEH,SAAO,EAAE,OAAO,QAAQ;AAC1B;AAtBS;AA8BT,SAAS,UAAU,MAAqB,MAAwC;AAC9E,MAAI,KAAK,YAAa,QAAO,KAAK;AAClC,MAAI,CAAC,KAAK,UAAU,OAAQ,QAAO;AACnC,QAAM,UAAmC,CAAC;AAC1C,OAAK,UAAU,QAAQ,CAAC,QAAQ;AAC9B,QAAI,KAAK,GAAG,MAAM,OAAW,SAAQ,GAAG,IAAI,KAAK,GAAG;AAAA,EACtD,CAAC;AACD,SAAO,OAAO,KAAK,OAAO,EAAE,SAAS,UAAU;AACjD;AARS;AAgBT,eAAsB,aACpB,MACA,MACA,QACyB;AACzB,QAAM,UAAU,OAAO,aAAa;AACpC,QAAM,WAAW,WAAW,MAAM,IAAI;AACtC,QAAM,EAAE,OAAO,QAAQ,IAAI,YAAY,MAAM,IAAI;AACjD,QAAM,OAAO,UAAU,MAAM,IAAI;AAEjC,QAAM,MAAM,IAAI,IAAI,UAAU,OAAO,OAAO;AAC5C,aAAW,CAAC,KAAK,KAAK,KAAK,MAAO,KAAI,aAAa,IAAI,KAAK,KAAK;AAUjE,QAAM,OAAO,IAAI,IAAI,OAAO,OAAO;AAEnC,QAAM,OAAoB;AAAA,IACxB,QAAQ,KAAK;AAAA,IACb,SAAS;AAAA,MACP,QAAQ;AAAA,MACR,eAAe,UAAU,OAAO,MAAM;AAAA,MACtC,qBAAqB,KAAK,SAAS,QAAQ,MAAM,EAAE;AAAA,MACnD,oBAAoB,KAAK;AAAA,MACzB,GAAG;AAAA,IACL;AAAA,EACF;AACA,MAAI,SAAS,QAAW;AACtB,IAAC,KAAK,QAAmC,cAAc,IAAI;AAC3D,SAAK,OAAO,KAAK,UAAU,IAAI;AAAA,EACjC;AAEA,QAAM,WAAW,MAAM,QAAQ,IAAI,SAAS,GAAG,IAAI;AACnD,QAAM,OAAO,MAAM,SAAS,KAAK;AACjC,MAAI,SAAkB;AACtB,QAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;AAC5D,MAAI,YAAY,SAAS,kBAAkB,KAAK,MAAM;AACpD,QAAI;AACF,eAAS,KAAK,MAAM,IAAI;AAAA,IAC1B,QAAQ;AACN,eAAS;AAAA,IACX;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ,SAAS,QAAQ,IAAI,SAAS,IAAI,MAAM,OAAO;AAClE;AAnDsB;;;ACrDtB,IAAM,kBAAkB,CAAC,SAAS,SAAS,SAAS,OAAO;AAE3D,SAAS,SAAS,OAAqC;AACrD,SAAO,QAAQ,KAAK,KAAK,OAAO,UAAU;AAC5C;AAFS;AAKT,SAAS,eAAe,QAAoB,OAAqB;AAC/D,SAAQ,OAAO,WAA0C,KAAK;AAC9D,MAAI,MAAM,QAAQ,OAAO,QAAQ,GAAG;AAClC,UAAM,OAAQ,OAAO,SAAsB,OAAO,CAAC,SAAS,SAAS,KAAK;AAC1E,QAAI,KAAK,OAAQ,QAAO,WAAW;AAAA,QAC9B,QAAO,OAAO;AAAA,EACrB;AACA,SAAO;AACT;AARS;AAcT,SAAS,eAAe,QAAoB,UAAsC;AAChF,SAAO,gBAAgB,OAAO,CAAC,SAAS,YAAY;AAClD,UAAM,QAAQ,OAAO,OAAO;AAC5B,UAAM,WAAW,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AACtD,WAAO,SAAS;AAAA,MACd,CAAC,KAAK,WAAY,SAAS,MAAM,KAAK,eAAe,QAAQ,QAAQ,KAAM;AAAA,MAC3E;AAAA,IACF;AAAA,EACF,GAAG,KAAK;AACV;AATS;AAWT,SAAS,eAAe,QAAoB,UAAsC;AAChF,QAAM,aAAa,eAAe,QAAQ,QAAQ;AAElD,QAAM,aAAa,OAAO;AAC1B,QAAM,CAAC,MAAM,GAAG,IAAI,IAAI;AACxB,MAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,QAAQ,YAAa,QAAO;AAE1D,MAAI,KAAK,WAAW,EAAG,QAAO,eAAe,QAAQ,IAAI;AAEzD,QAAM,QAAQ,WAAW,IAAI;AAC7B,SAAQ,SAAS,KAAK,KAAK,eAAe,OAAO,IAAI,KAAM;AAC7D;AAXS;AA4BF,SAAS,qBACd,QACA,OACA,aACY;AACZ,MAAI,CAAC,MAAM,OAAQ,QAAO;AAE1B,QAAM,QAAQ,gBAAgB,MAAM;AACpC,QAAM,QAAQ,CAAC,SAAS;AACtB,QAAI,CAAC,eAAe,OAAO,KAAK,MAAM,GAAG,CAAC,GAAG;AAC3C,YAAM,UAAU,cAAc,iBAAiB,WAAW,MAAM;AAChE,YAAM,IAAI;AAAA,QACR,GAAG,OAAO,uCAAuC,IAAI;AAAA,MACvD;AAAA,IACF;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAjBgB;AAoBhB,SAAS,UAAU,OAAgB,UAAmC;AACpE,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU;AAEjD,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,UAAM,QAAQ,CAAC,UAAU,UAAU,OAAO,QAAQ,CAAC;AACnD;AAAA,EACF;AAEA,QAAM,CAAC,MAAM,GAAG,IAAI,IAAI;AACxB,MAAI,CAAC,KAAM;AAEX,QAAM,SAAS;AACf,MAAI,KAAK,WAAW,GAAG;AACrB,WAAO,OAAO,IAAI;AAClB;AAAA,EACF;AACA,MAAI,QAAQ,OAAQ,WAAU,OAAO,IAAI,GAAG,IAAI;AAClD;AAjBS;AA0BF,SAAS,mBACd,MACA,OACS;AACT,MAAI,CAAC,OAAO,UAAU,SAAS,QAAQ,OAAO,SAAS,SAAU,QAAO;AAExE,QAAM,QAAQ,gBAAgB,IAAI;AAClC,QAAM,QAAQ,CAAC,SAAS,UAAU,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC;AACzD,SAAO;AACT;AATgB;;;ACzFT,IAAM,uBAAuB;AAkCpC,SAAS,WACP,OACA,SACA,YACe;AACf,QAAM,OACJ,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,OAAO,MAAM,CAAC;AACnE,QAAM,SAAwB,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,KAAK,CAAC,GAAG,QAAQ;AAC3E,MAAI,eAAe,QAAW;AAC5B,WAAO,QAAQ,EAAE,CAAC,oBAAoB,GAAG,WAAW;AAAA,EACtD;AACA,SAAO;AACT;AAZS;AAcT,SAAS,iBAAiB,MAAqB,OAA+B;AAI5E,QAAM,OAAO,mBAAmB,OAAO,KAAK,cAAc;AAC1D,QAAM,SAAS,WAAW,MAAM,KAAK;AACrC,MACE,KAAK,gBACL,SAAS,QACT,OAAO,SAAS,YAChB,CAAC,MAAM,QAAQ,IAAI,GACnB;AACA,WAAO,oBAAoB;AAAA,EAC7B;AACA,SAAO;AACT;AAfS;AAiBF,SAAS,mBAAmB,SAAwC;AACzE,QAAM,SAAS,IAAI,IAAI,QAAQ,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC;AAErE,SAAO;AAAA,IACL,UAAU,MAAM;AACd,aAAO,QAAQ,MACZ;AAAA,QAAO,CAAC,SACP,QAAQ,YAAY,QAAQ,UAAU,MAAM,IAAI,IAAI;AAAA,MACtD,EACC,IAAI,CAAC,UAAU;AAAA,QACd,MAAM,KAAK;AAAA,QACX,aAAa,KAAK;AAAA,QAClB,aAAa,KAAK;AAAA,QAClB,GAAI,KAAK,eAAe,EAAE,cAAc,KAAK,aAAa,IAAI,CAAC;AAAA,QAC/D,aAAa,KAAK;AAAA,MACpB,EAAE;AAAA,IACN;AAAA,IAEA,MAAM,SAAS,MAAM,MAAM,MAAM;AAC/B,YAAM,OAAO,OAAO,IAAI,IAAI;AAC5B,UAAI,CAAC,KAAM,QAAO,WAAW,iBAAiB,IAAI,IAAI,IAAI;AAE1D,UAAI;AACF,cAAM,SAAS,MAAM,aAAa,MAAM,MAAM;AAAA,UAC5C,SAAS,QAAQ;AAAA,UACjB,QAAQ,KAAK;AAAA,UACb,WAAW,QAAQ;AAAA,QACrB,CAAC;AAKD,eAAO,OAAO,KACV,iBAAiB,MAAM,OAAO,IAAI,IAClC,WAAW,OAAO,MAAM,MAAM,OAAO,MAAM;AAAA,MACjD,SAAS,OAAO;AACd,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,eAAO,WAAW,yBAAyB,OAAO,IAAI,IAAI;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AACF;AAzCgB;;;AC/ET,IAAM,uBAAuB;AAQ7B,IAAM,oBAAoB;AAGjC,IAAM,uBAAuB;AAG7B,IAAM,wBAAwB;AAG9B,IAAM,sBAAsB;AAgD5B,SAAS,GAAG,IAA0B,QAAkC;AACtE,SAAO,EAAE,SAAS,OAAO,IAAI,MAAM,MAAM,OAAO;AAClD;AAFS;AAIT,SAAS,KAAK,IAA0B,MAAc,SAAkC;AACtF,SAAO,EAAE,SAAS,OAAO,IAAI,MAAM,MAAM,OAAO,EAAE,MAAM,QAAQ,EAAE;AACpE;AAFS;AAKT,SAAS,aAAa,SAAkC;AACtD,SAAO,WAAW,QAAQ,OAAO,YAAY,YAAY,OAAO,QAAQ,WAAW;AACrF;AAFS;AAIT,eAAe,gBACb,SACA,UACA,MAC0B;AAC1B,MAAI,CAAC,KAAM,QAAO,KAAK,QAAQ,IAAI,mBAAmB,yBAAyB;AAC/E,QAAM,SAAU,QAAQ,UAAU,CAAC;AACnC,MAAI,CAAC,OAAO,KAAM,QAAO,KAAK,QAAQ,IAAI,qBAAqB,mBAAmB;AAClF,QAAM,SAAS,MAAM,SAAS,SAAS,OAAO,MAAM,OAAO,aAAa,CAAC,GAAG,IAAI;AAChF,SAAO,GAAG,QAAQ,IAAI,MAAM;AAC9B;AAVe;AAYf,SAAS,iBACP,SACA,SACiB;AACjB,SAAO,GAAG,QAAQ,IAAI;AAAA,IACpB,iBAAiB,QAAQ,mBAAmB;AAAA;AAAA;AAAA;AAAA,IAI5C,cAAc,EAAE,OAAO,CAAC,EAAE;AAAA,IAC1B,YAAY,QAAQ;AAAA,IACpB,GAAI,QAAQ,eAAe,EAAE,cAAc,QAAQ,aAAa,IAAI,CAAC;AAAA,EACvE,CAAC;AACH;AAbS;AAwBT,eAAsB,iBACpB,SACA,UACA,MACA,SACiC;AAKjC,MAAI,CAAC,aAAa,OAAO,GAAG;AAC1B,WAAO,KAAK,SAAS,MAAM,MAAM,sBAAsB,iBAAiB;AAAA,EAC1E;AACA,UAAQ,QAAQ,QAAQ;AAAA,IACtB,KAAK;AACH,aAAO,iBAAiB,SAAS,OAAO;AAAA,IAC1C,KAAK;AACH,aAAO,GAAG,QAAQ,IAAI,CAAC,CAAC;AAAA,IAC1B,KAAK;AACH,aAAO,GAAG,QAAQ,IAAI,EAAE,OAAO,SAAS,UAAU,QAAQ,MAAS,EAAE,CAAC;AAAA,IACxE,KAAK;AACH,aAAO,gBAAgB,SAAS,UAAU,IAAI;AAAA,IAChD;AAGE,UAAI,QAAQ,OAAO,WAAW,gBAAgB,EAAG,QAAO;AACxD,aAAO,KAAK,QAAQ,IAAI,uBAAuB,qBAAqB,QAAQ,MAAM,EAAE;AAAA,EACxF;AACF;AA5BsB;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import { a as McpSigningKeyProvider, R as RefreshTokenStore, N as NewOAuthClient, S as StoredOAuthClient, b as NewRefreshToken, c as StoredRefreshToken, d as McpOauthStores, e as McpConnectionStore } from '../create-api-mcp-oauth-CwVXKK-A.js';
2
2
  export { f as ACCESS_TOKEN_TTL_SECONDS, g as AccessTokenError, h as AccessTokenErrorCode, A as ApiMcpOauth, C as CodeReplayStore, D as DEFAULT_MCP_RESOURCE_PATH, i as DEFAULT_OAUTH_PATHS, j as DEFAULT_PROVIDER_ROOTS, k as DEFAULT_SIGNING_KEY_ENV, l as DEFAULT_SIGNING_KEY_ID_ENV, m as MCP_SUPPORTED_SCOPES, n as McpConnectionRecording, M as McpOauthConfig, o as McpOauthContext, p as McpOauthHandlers, q as McpOauthPaths, r as McpOauthRoute, s as McpOauthSession, t as McpScope, u as McpSigningKey, O as OAuthClientStore, P as ProviderAttributionRule, v as PublicSigningJwk, w as RegisterClientInput, x as RegisteredClient, y as SIGNING_ALG, z as SignAccessTokenInput, B as StoredMcpConnection, T as TokenEndpointAuthMethod, V as VerifiedAccessToken, E as VerifyAccessTokenOptions, F as createApiMcpOauth, G as hashSecret, H as inProcessCodeReplayStore, I as issuer, J as loadSigningKeyFromEnv, K as matchesRedirectUri, L as originFromRequest, Q as providerFromRedirectUris, U as registerClient, W as resolveMcpOauthConfig, X as resolveTrustedOrigin, Y as resourceAudience, Z as signAccessToken, _ as signingKeyProvider, $ as trustedOriginsFromEnv, a0 as verifyAccessToken } from '../create-api-mcp-oauth-CwVXKK-A.js';
3
- import { h as AiProvider } from '../guide-DV5MQbCg.js';
3
+ import { g as AiProvider } from '../guide-KQNcXlMG.js';
4
4
  import 'jose';
5
5
 
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  providerForHostId
3
- } from "../chunk-FYEVBTDU.js";
3
+ } from "../chunk-FBUSSQSK.js";
4
4
  import {
5
5
  ACCESS_TOKEN_TTL_SECONDS,
6
6
  AUTHORIZATION_CODE_AUDIENCE,
@@ -0,0 +1,25 @@
1
+ import { A as AiCapability, b as AiConnectPromptSpec, e as AiHostGuide, i as AiPermissionModel } from './guide-KQNcXlMG.js';
2
+
3
+ /**
4
+ * The AI hosts a store owner can connect, in recommended order. Same OAuth flow
5
+ * everywhere (the host drives it) — only the menu path differs per app.
6
+ *
7
+ * A FUNCTION of the platform's name, because one step is not generic: the
8
+ * ChatGPT connector's consent screen shows an OAuth button labelled with
9
+ * whoever operates the server, and the owner is told which button to click. It
10
+ * used to name one particular STORE on one particular deployment — not even the
11
+ * product, a tenant of it — so every other adopter instructed its owners to
12
+ * click a button that does not exist.
13
+ */
14
+ declare function PT_BR_AI_HOST_GUIDES(platformName: string): readonly AiHostGuide[];
15
+ declare const PT_BR_AI_CAPABILITIES: readonly AiCapability[];
16
+ /**
17
+ * The permission model in one line, shown prominently: the assistant acts AS
18
+ * the signed-in owner (auth-passthrough) — it can do exactly what the owner
19
+ * can, nothing more, and no extra credential/API key is ever created.
20
+ */
21
+ declare const PT_BR_AI_PERMISSION_MODEL: AiPermissionModel;
22
+ /** The pt-BR paste-in prompt, built from the host's own tool names. */
23
+ declare function PT_BR_AI_CONNECT_PROMPT(spec: AiConnectPromptSpec): string;
24
+
25
+ export { PT_BR_AI_CAPABILITIES as P, PT_BR_AI_CONNECT_PROMPT as a, PT_BR_AI_HOST_GUIDES as b, PT_BR_AI_PERMISSION_MODEL as c };