/** * The account-tied toolset — fourteen tools, identical on every transport. * * `deployments_upload` is not here, and the split is exactly the product's * own shape rather than a convenience: * * - **Upload is the anonymous door.** It is the one operation that works * with no account, and it is the one whose INPUT differs by transport — * a filesystem path over stdio, inline bytes over HTTP, because a Worker * has no filesystem. It also carries the Apps-SDK widget hosted-side. * So it is authored per transport, in each `server.ts`. * - **Everything else needs an identity**, and once a transport has one, * nothing about these fourteen depends on how the bytes arrived. Same * names, same schemas, same prose, same 1:1 SDK calls. * * That is why they live in the shared package: when the hosted transport * gains OAuth it registers this function and has the complete toolset, rather * than someone copying fourteen definitions into a second repo — which is the * moment the two surfaces would begin to drift. The cost of doing it after * the copy is a de-duplication under deadline; the cost of doing it before is * this file. * * **The catalogue is static; identity decides what SUCCEEDS.** These are * registered whether or not a credential is present — an anonymous caller * sees them and gets a typed authentication error naming how to authenticate * on *this* transport (the hint is `createCall`'s one per-transport argument). * A tool list that changes shape under the caller would be a second, dynamic * contract for an agent to track, and MCP clients cache the catalogue. * * **Every tool carries a `title`, and it is a gate rather than a nicety.** The * Claude connectors directory refuses submission for a tool that lacks one, so * a titleless tool is not a shabby tool — it is an unlistable product. The * style is short Title Case verb phrases naming what the USER gets ("List * Deployments", "Connect Custom Domain"); the name obeys `resource_action` for * the agent, the title reads as English for the human, and the description * carries every precision neither can. Both catalogue pins assert a title on * every tool, so the next one cannot be added without one. * * **Every tool publishes an `outputSchema`, imported from the constitution** * (`@shipstatic/types/schemas`), never written here: a schema written beside * a tool is a twin of a published type, and the SDK enforces it on every * success, so a twin that drifts fails a call that succeeded. The constitution * fences its schemas to its interfaces at compile time; this file only names * which one each tool answers with, and `call` attaches the result as * `structuredContent` so the schema has something to validate. */ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import type Ship from '@shipstatic/ship'; import { z } from 'zod'; import type { CallFn } from './call.js'; /** * THE REGISTRY: one row per tool, every fact about it that a host reads. * * Fifteen rows, in registration order, `deployments_upload` included even * though each transport authors that registration itself: its INPUT differs * per transport, its contract does not. Everything else is derived from * here. `annotate(row)` is every registration's `annotations`; * `ACCOUNT_TOOL_NAMES` is the rows whose `auth` is `required`, and the * hosted door reads the same column for `securitySchemes`; the listing * repo's justifications are held to the hints these rows produce. * * The rows are DATA the registrations read, not a table they are generated * from, and that is deliberate: a `Record` would cost the * zod→handler inference every one-liner below relies on (`({ deployment }) * => …` is typed from the `inputSchema` literal in the same call). A row * without a registration, a registration without a row, and a typo in * either all turn `tests/server.test.ts` red, through a real `tools/list`. * * Two rows are worth a second look, because their names suggest otherwise: * `domains_dns` READS the provider the platform recorded when the domain * was created (nothing is looked up on call), and `domains_validate` and * `domains_share` persist nothing. */ export declare const TOOLS: { readonly deployments_upload: { readonly auth: "optional"; readonly mutation: "add"; readonly reach: "public"; }; readonly deployments_list: { readonly auth: "required"; readonly mutation: "none"; readonly reach: "account"; }; readonly deployments_get: { readonly auth: "required"; readonly mutation: "none"; readonly reach: "account"; }; readonly deployments_set: { readonly auth: "required"; readonly mutation: "replace"; readonly reach: "account"; }; readonly deployments_delete: { readonly auth: "required"; readonly mutation: "remove"; readonly reach: "public"; }; readonly domains_set: { readonly auth: "required"; readonly mutation: "replace"; readonly reach: "public"; }; readonly domains_list: { readonly auth: "required"; readonly mutation: "none"; readonly reach: "account"; }; readonly domains_get: { readonly auth: "required"; readonly mutation: "none"; readonly reach: "account"; }; readonly domains_records: { readonly auth: "required"; readonly mutation: "none"; readonly reach: "account"; }; readonly domains_dns: { readonly auth: "required"; readonly mutation: "none"; readonly reach: "account"; }; readonly domains_share: { readonly auth: "required"; readonly mutation: "none"; readonly reach: "account"; }; readonly domains_validate: { readonly auth: "required"; readonly mutation: "none"; readonly reach: "account"; }; readonly domains_verify: { readonly auth: "required"; readonly mutation: "add"; readonly reach: "public"; }; readonly domains_delete: { readonly auth: "required"; readonly mutation: "remove"; readonly reach: "public"; }; readonly whoami: { readonly auth: "required"; readonly mutation: "none"; readonly reach: "account"; }; }; export type ToolName = keyof typeof TOOLS; /** The names whose row says an account is required. */ export type AccountToolName = { [N in ToolName]: (typeof TOOLS)[N]['auth'] extends 'required' ? N : never; }[ToolName]; /** * The fourteen, derived from the registry rather than listed beside it. * * Exported so a second transport can state its expected catalogue as * `[UPLOAD_TOOL_NAME, ...ACCOUNT_TOOL_NAMES]` instead of counting to fifteen * in a second repo. It was a hand-written list until 1.11.0, which made the * auth need a second owner beside the annotations; a row is now the only * place a tool's account requirement is stated. */ export declare const ACCOUNT_TOOL_NAMES: AccountToolName[]; /** * What `whoami` answers: exactly the keys its description names, and the * schema IS the projection. * * `Account` also carries billing state, the API-key hint, the picture and * timestamps, none of which the description mentions and none of which an * agent acts on. A tool's result is the shape its description states, so the * result is the constitution's `AccountSchema` narrowed to five keys, and * parsing the wire through it is what drops the rest: one declaration owns * both the published `outputSchema` and the projection. `suspended` is * deliberately out: it means every write is refused, and the refusal says so * itself at the moment it matters. */ export declare const ACCOUNT_SUMMARY: z.ZodObject<{ name: z.ZodNullable; email: z.ZodString; plan: z.ZodString; usage: z.ZodObject<{ deployments: z.ZodInt; platformDomains: z.ZodInt; customDomains: z.ZodInt; }, z.core.$strip>; caps: z.ZodObject<{ deployments: z.ZodInt; platformDomains: z.ZodInt; customDomains: z.ZodInt; }, z.core.$strip>; }, z.core.$strip>; export declare function registerAccountTools(server: McpServer, ship: Ship, call: CallFn): void;