/** * Shared plumbing every tool uses, now on Slipway. * * Tool modules keep describing themselves with a Zod shape, a risk and a * handler. This adapter turns each into a Slipway tool, so the MCP server, the * CLI, the write guard, annotations and errors all come from the framework * instead of a copy kept in this repo. * * `risk` can still be a function of the arguments, because in WordPress the * same tool is harmless or irreversible depending on what it is passed. * `wp_update_post` saving a draft is an ordinary write; the same call with * `status: "publish"` puts the post in front of an RSS reader and a mailing * list. Such a tool tells clients its highest level, destructive, and Slipway's * `riskFor` decides each call, so only the publishing call needs approval. * * `ctx.client` resolves the site per call rather than at startup, which is what * lets the same tool list serve a local install reading several sites from the * environment and a hosted one holding one site's credentials per request. */ import { SlipwayError, z, type Risk, type Tool } from "@thenavidm/slipway"; import type { WpClient } from "../api/client.js"; import { HelperPluginMissingError, WordPressError } from "../api/errors.js"; import { type Config, type Site } from "../config.js"; /** Which API a tool reaches. It is also the tool's toolset, so `WORDPRESS_TOOLSETS=core` leaves out the helper's twelve. */ export type Surface = /** WordPress core, `wp/v2`. Present on every modern install. */ "core" /** The helper plugin in this repo, `wordpress-mcp/v1`. */ | "helper"; export type ToolContext = { config: Config; /** The site a call acts on, resolved from the optional `site` argument. */ site: (hint?: string) => Site; /** A client bound to that site. */ client: (hint?: string) => WpClient; }; /** * The `site` argument, on every tool. * * Optional rather than required because most people run one site and being made * to name it on every call is friction for no gain. With several configured and * no default set, `selectSite` refuses instead of guessing. */ export declare const siteArg: { site: z.ZodOptional; }; /** * Kept so tool modules read the same, but never sent: Slipway adds `confirm` * to every tool that can publish or destroy, with one description everywhere. */ export declare const confirmArg: { confirm: z.ZodOptional; }; /** Page size and page number, on every paginating tool. */ export declare const pageArgs: { per_page: z.ZodOptional; page: z.ZodOptional; }; type Shape = Record; export type ToolSpec = { name: string; /** One line, imperative. Shown in tool pickers. */ title: string; description: string; schema: S; /** A level, or a function of the arguments when the same call can be either. */ risk: Risk | ((args: z.infer>) => Risk); surface: Surface; /** True when calling twice has the same effect as calling once. */ idempotent?: boolean; handler: (args: z.infer>, ctx: ToolContext) => Promise; /** One line for the audit log and the confirm message, when this writes. */ summary?: (args: z.infer>) => string; }; export type AnyToolSpec = Tool; /** * The site's status picks the exit code: 401 and 403 (an application password * without the role for it) are 4, a missing route or post is 3, 400 is 2 and * the rest 5. A rate limit is 7 whatever the status, read from the words as * 1.1 did, since a security plugin may refuse with a 403 or a 503. A missing * helper plugin, Rank Math or its redirections module is something to set up, * not to retry, so it exits 10. WordPress's own error code, the site and the * endpoint ride along in `details`. */ export declare function toSlipway(error: WordPressError | HelperPluginMissingError): SlipwayError; export declare function defineTool(spec: ToolSpec): Tool; export declare function makeContext(createClient: (site: Site) => WpClient, config: Config): ToolContext; /** Copy only the arguments a caller actually set, so a PATCH does not blank fields. */ export declare function definedFields(source: Record, keys: readonly string[]): Record; /** Trim a value to one readable line for the audit log and the confirm message. */ export declare function snippet(text: string | undefined, length?: number): string; /** Name the site in a confirm message, since the whole risk is acting on the wrong one. */ export declare function on(ctx: ToolContext, hint?: string): string; export {};