{ "version": 1, "skills": [ { "name": "create-tool", "category": "development/create", "description": "ALWAYS use this skill when the user asks to build, modify, or audit a FrontMCP tool. Covers everything inside `@Tool({...})`: class and function-style tools, Zod input/output schemas with derived `execute()` types, dependency injection (`this.get` / `this.tryGet`), error handling (`this.fail`, MCP error classes), throttling (rate-limit / concurrency / timeout), auth providers (single / multi / vault), availability constraints (`availableWhen`), elicitation (`this.elicit`), interactive UI widgets via `@Tool({ ui })` (MCP Apps / SEP-1865 — including `.tsx` FileSource, CSP, `window.FrontMcpBridge`, host-detect `resourceMode`), annotations (`readOnlyHint` / `destructiveHint` / …), `examples` metadata, registration in `@App({ tools })`, and per-tool unit testing.", "path": "create-tool", "targets": ["all"], "hasResources": true, "layout": "component", "tags": [ "development", "tool", "create-tool", "decorator", "input-schema", "output-schema", "ToolContext", "ui", "mcp-apps", "annotations", "throttling", "auth-providers", "availability", "elicitation" ], "bundle": ["recommended", "minimal", "full"], "priority": 10, "references": [ { "name": "annotations", "description": "readOnlyHint, destructiveHint, idempotentHint, openWorldHint, title — behavioral hints for clients." }, { "name": "auth-providers", "description": "authProviders shorthand vs full mapping, scopes, alias, credential vault basics." }, { "name": "availability", "description": "availableWhen axes (os / runtime / deployment / provider / target / surface / env), missingAxes, isPlatform, and enabled (fileSignal, FEATURE_OFF, list_changed, the surface hash, STALE_TOOL_LIST)." }, { "name": "decorator-options", "description": "Every field on @Tool({...}) — what it does, default, when to set it." }, { "name": "derived-types", "description": "Derive execute() parameter and return types from the schemas via ToolInputOf / ToolOutputOf." }, { "name": "elicitation", "description": "this.elicit — request interactive input mid-execution. Server enable + accept/decline/cancel flow." }, { "name": "error-handling", "description": "this.fail, MCP error classes, refused() and remedies, error flow — when to throw vs fail." }, { "name": "execution-context", "description": "What ToolContext provides at runtime — this.get, this.fetch, this.notify, this.context." }, { "name": "file-layout", "description": "Flat sibling vs folder-per-tool layouts. .schema.ts / .tool.ts / .tool.spec.ts." }, { "name": "function-style-builder", "description": "tool({...})(handler) — when to pick over a class, register, ctx parameter." }, { "name": "input-schema", "description": "Define the tool's input contract — raw Zod shapes, refinements, defaults, optional fields." }, { "name": "output-schema", "description": "Define the tool's output contract — Zod shape, primitives, media, multi-content arrays." }, { "name": "quick-start", "description": "60-second tour — minimal tool, schemas, registration, calling it." }, { "name": "registration", "description": "@App({ tools }) vs @FrontMcp({ tools }), multi-app composition." }, { "name": "remote-and-esm", "description": "Tool.esm / Tool.remote — load tools from npm packages or remote MCP servers." }, { "name": "testing", "description": "Per-tool unit tests — pointer to the dedicated `testing` skill for the canonical patterns." }, { "name": "throttling", "description": "rateLimit, concurrency, timeout — semantics, interaction, defaults." }, { "name": "ui-widgets", "description": "@Tool({ ui }) — template formats, trusted markup (html / escapeStringResults), servingMode, host-detect resourceMode, CSP, ignored options, MCP Apps spec." } ], "examples": [ { "name": "01-basic-class-tool", "description": "Minimal class-based tool with Zod input/output schemas and types derived from the schemas. The foundation every other example builds on.", "level": "basic", "tags": ["foundation", "class-tool", "output-schema", "derived-types"], "features": [ "Extending `ToolContext` (no generics) and implementing `execute()`", "Hoisting `inputSchema` / `outputSchema` to a sibling `.schema.ts` file", "Deriving the `execute()` parameter and return types via `ToolInputOf<>` / `ToolOutputOf<>`", "Using a Zod raw shape for `inputSchema` (not `z.object(...)`)", "Always defining `outputSchema` so the tool can't accidentally leak extra fields", "Registering the tool in an `@App({ tools })`" ] }, { "name": "02-basic-function-tool", "description": "Function-style `tool({...})(handler)` for a tiny pure-input tool — pick this over a class only when the tool needs no DI / lifecycle / UI.", "level": "basic", "tags": ["foundation", "function-tool", "tool-builder"], "features": [ "Using the `tool({...})(handler)` builder for a one-liner", "Returning a primitive via `outputSchema: 'number'`", "Registering the function-style tool in `@App({ tools })` exactly like a class tool", "When function-style is the right choice (and when it isn't)" ] }, { "name": "03-tool-with-zod-shape-output", "description": "Tool returning structured JSON declared via a Zod raw shape outputSchema — the recommended pattern for any complex output.", "level": "basic", "tags": ["output-schema", "zod-shape", "structured-output"], "features": [ "Declaring `outputSchema` as a Zod raw shape `{ field: z.string(), … }`", "Constraining values with `.int().min(0)` so invalid output is rejected at the boundary", "Letting unrelated fields returned by the implementation (e.g. an upstream API's extras) be stripped silently", "Deriving `OrderSummaryOutput` once so the type and runtime contract can't drift" ] }, { "name": "04-tool-with-zod-schema-output", "description": "Tool returning a discriminated union via a full `z.discriminatedUnion(...)` outputSchema — for outputs that branch on a kind field.", "level": "advanced", "tags": ["output-schema", "zod-schema", "discriminated-union"], "features": [ "Using a full Zod schema (`z.discriminatedUnion(...)`) as `outputSchema` instead of a raw shape", "Branching the runtime output on a discriminant `kind` literal", "Letting TypeScript narrow the return type per branch (via `as const` on the discriminant)", "Knowing when full Zod schemas are the right pick (unions, transforms) and when a raw shape is enough" ] }, { "name": "05-tool-with-primitive-output", "description": "Tool returning a single primitive — `outputSchema: 'string' | 'number' | 'boolean' | 'date'` for single-value outputs.", "level": "basic", "tags": ["output-schema", "primitive-output"], "features": [ "Using a primitive literal (`'string'`, `'number'`, `'boolean'`, `'date'`) for `outputSchema`", "Returning the bare value directly from `execute()` instead of wrapping it", "Picking primitive literals over a one-field Zod shape for ergonomic clarity", "Four concrete tools in one file (`fmt_currency`, `add`, `is_palindrome`, `now`) demonstrating each primitive form" ] }, { "name": "06-tool-with-media-output", "description": "Tool returning binary content (image / audio) or a multi-content array of `[text, image]` — for outputs that aren't plain JSON.", "level": "intermediate", "tags": ["output-schema", "media-output", "image", "multi-content"], "features": [ "Returning a base64-encoded image with `outputSchema: 'image'` and `{ type: 'image', data, mimeType }`", "Returning audio with `outputSchema: 'audio'` (same `{ type: 'audio', data, mimeType }` shape, audio MIME types)", "Returning multi-content via `outputSchema: ['string', 'image']` — text summary + annotated image in one response", "When to pick a media literal vs `'resource_link'` (host-fetched URI)" ] }, { "name": "08-tool-with-provider-injection", "description": "Tool that resolves a DI-registered service via `this.get(TOKEN)` and uses it to power `execute()` — the standard pattern for tools that talk to a database or external API.", "level": "intermediate", "tags": ["di", "provider", "this.get", "error-handling"], "features": [ "Defining a typed DI token with `Symbol('UserService')` and `Token`", "Binding the token with a factory provider (`{ name, provide, inject, useFactory }`) and registering it in the same `@App` as the tool", "Resolving the service inside `execute()` via `this.get(USER_SERVICE)` (throws when missing)", "Translating \"not found\" into `ResourceNotFoundError` via `this.fail(...)` so the client gets a proper MCP error code (-32002)" ] }, { "name": "09-tool-with-multiple-providers", "description": "Tool composing three DI services — config (env-only), cache (optional, `tryGet`), and database (required) — the realistic shape for a production tool.", "level": "intermediate", "tags": ["di", "multiple-providers", "cache-aside", "tryGet"], "features": [ "Resolving multiple providers via `this.get(TOKEN)` and `this.tryGet(TOKEN)`", "Cache-aside pattern — check `tryGet(CACHE)` first, fall back to the database", "Reading typed config from a `CONFIG` token vs `process.env` directly", "Letting the tool work in production (with cache) AND in test (without it)" ] }, { "name": "11-tool-with-fetch", "description": "Tool calling an external HTTP API with `this.fetch` — context propagation, status-code handling, and bounding the call with a tool `timeout`.", "level": "intermediate", "tags": ["fetch", "http", "external-api", "error-handling"], "features": [ "Using `this.fetch(url, init?)` so trace context propagates to the upstream service", "Translating non-2xx HTTP responses into `PublicMcpError` so the MCP client gets a clean error", "Bounding the call with a tool `timeout` (and `this.fetch`'s built-in per-request timeout) — without relying on a non-existent context abort signal", "Letting genuine network errors (DNS failure, ECONNREFUSED) propagate to the framework's error flow" ] }, { "name": "12-tool-with-fetch-and-retries", "description": "Tool calling a flaky external API with exponential backoff retries, an Idempotency-Key for safety, and respect for `Retry-After` on 429s.", "level": "advanced", "tags": ["fetch", "retries", "exponential-backoff", "idempotency-key", "429-rate-limit"], "features": [ "Retrying on transient errors (5xx + 429) with exponential backoff plus jitter", "Generating an `Idempotency-Key` so retried POSTs don't duplicate side effects on the upstream", "Respecting an upstream `Retry-After` header on 429 responses instead of guessing", "Capping the total retry budget with `timeout: { executeMs }` so a wedged upstream can't hang the call indefinitely" ] }, { "name": "13-tool-with-single-auth-provider", "description": "Tool requiring a single OAuth provider via the `authProviders: ['github']` string shorthand — credentials loaded before `execute()` runs.", "level": "intermediate", "tags": ["auth-providers", "oauth", "github", "this.authProviders"], "features": [ "Declaring a single required OAuth provider with the `authProviders: ['github']` shorthand", "Reading pre-formatted credentials via `await this.authProviders.headers('github')`", "Letting the framework reject calls whose required credential is missing **before** `execute()` runs — a JSON-RPC `-32001` (MCP `UNAUTHORIZED`) error whose `data` carries `{ tool, providers: ['github'], authUrl }` (no auth-check boilerplate)", "Trusting the framework to handle token refresh, expiration, and the connect/authorize URL" ] }, { "name": "14-tool-with-multiple-auth-providers", "description": "Tool with the full `authProviders` mapping form — one required provider with explicit scopes, one optional provider with an alias, and graceful degradation when the optional creds are missing.", "level": "advanced", "tags": ["auth-providers", "oauth", "scopes", "optional-auth", "this.authProviders.headers"], "features": [ "Using the object form of `authProviders` to set `required`, `scopes`, and `alias`", "Declaring required OAuth scopes that the server advertises in its Protected Resource Metadata (`scopes_supported`) so clients request them", "Resolving an optional provider via `await this.authProviders.headers('cloud')` (returns an empty object `{}` when absent)", "Branching the tool's behavior — full deploy when both providers are present; preview-only when the cloud provider is missing", "The required `github` provider gating the call: when its credential is missing the framework aborts before `execute()` with `-32001` and `data: { tool, providers: ['github'], authUrl }`; the optional `aws`/`cloud` provider never gates" ] }, { "name": "15-tool-with-credential-vault", "description": "Tool that reads a user-supplied static credential (a Slack webhook URL) from the per-session encrypted credential vault — the pattern for credentials that aren't OAuth.", "level": "advanced", "tags": ["auth-providers", "credential-vault", "slack-webhook", "encryption-at-rest"], "features": [ "Declaring a vault-backed auth provider with `authProviders: ['slack-webhook']`", "Reading the user's pasted-in credential via `await this.authProviders.headers('slack-webhook')` — same API as OAuth", "Letting the framework handle per-session AES-256-GCM encryption at rest (Redis or memory store)", "Knowing when to pick the vault (static secrets the user knows) vs OAuth (delegated identity)" ] }, { "name": "16-tool-with-rate-limit", "description": "Tool with `rateLimit: { maxRequests, windowMs }` capping invocations per session per minute — the protection for expensive / external-API-billed operations.", "level": "intermediate", "tags": ["throttling", "rate-limit", "abuse-protection"], "features": [ "Capping the tool to N invocations per windowMs, partitioned per session via `partitionBy: 'session'`", "Letting the framework reject over-limit calls with `RateLimitError` (code `'RATE_LIMIT_EXCEEDED'`, HTTP status 429) carrying a retry-after hint in its message that clients can back off against", "Combining `rateLimit` with `annotations.openWorldHint: true` so clients know the tool talks to billed external services", "Sizing the limit against upstream quota / billing — not just \"what feels reasonable\"" ] }, { "name": "17-tool-with-concurrency-and-timeout", "description": "Tool with `concurrency` + `timeout` for a real bottleneck (PDF rendering) — caps simultaneous in-flight work AND hard-caps per-call duration.", "level": "advanced", "tags": ["throttling", "concurrency", "timeout"], "features": [ "Capping simultaneous in-flight executions with `concurrency: { maxConcurrent }` (server-wide by default)", "Hard-bounding any single call with `timeout: { executeMs }` so a wedged invocation can't hold a concurrency slot indefinitely", "Giving long child work its own deadline, since `timeout` bounds `execute()` but does not pass an abort signal into it", "Combining `rateLimit` + `concurrency` + `timeout` as a production triple" ] }, { "name": "18-tool-with-progress-and-notify", "description": "Long-running tool emitting progress updates (`this.progress`), log notifications (`this.notify`), and stage markers (`this.mark`) — the standard pattern for jobs you don't want to feel hung.", "level": "intermediate", "tags": ["progress", "notifications", "mark", "long-running"], "features": [ "Emitting per-item progress with `await this.progress(current, total, message)`", "Sending free-form log notifications at `info` / `warning` / `error` levels with `await this.notify(message, level)`", "Marking execution stages with `this.mark(stage)` so observability tools have breadcrumbs", "Letting `this.progress(...)` return `false` cheaply when no progress token was provided (zero-cost when nobody's listening)" ] }, { "name": "19-tool-with-elicitation", "description": "Tool that pauses mid-execution to ask the user for confirmation + extra input via `this.elicit(...)` — the safe pattern for destructive or expensive actions.", "level": "advanced", "tags": ["elicitation", "this.elicit", "destructive-action", "confirmation"], "features": [ "Calling `this.elicit(message, z.object({ ... }))` to request interactive input mid-`execute()`", "Branching on `result.status` — `accept` / `decline` / `cancel` — and matching the early returns against `outputSchema`", "Pairing elicitation with `annotations.destructiveHint: true` so clients know to render the confirmation prominently", "Requiring `elicitation: { enabled: true }` at the `@FrontMcp({...})` server level — and what fails when it isn't" ] }, { "name": "20-tool-with-annotations", "description": "Four tools showing the standard annotation combinations — read-only query, destructive delete, send-email side-effecting, external-API search — and the client behavior each combination opts into.", "level": "basic", "tags": ["annotations", "readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint"], "features": [ "Setting `readOnlyHint` / `destructiveHint` / `idempotentHint` / `openWorldHint` to opt into specific client behaviors (auto-retry, confirmation gating, parallelization)", "Providing a human-readable `title` that overrides the snake_case `name` in client UIs", "Picking the conservative defaults when the annotations aren't obvious (omitting fields is safer than guessing)", "Why `send_email` sets `idempotentHint: false` (each call sends a new email) while `delete_user` sets it to `true` (deleting twice still leaves the user deleted)" ] }, { "name": "21-tool-with-availability-constraints", "description": "Three tools showing the `availableWhen` axes — macOS-only OS gate, production+Node runtime gate, and a `surface` gate that allows agent + job invocation but blocks direct MCP-client calls.", "level": "advanced", "tags": ["availableWhen", "os", "runtime", "surface", "EntryUnavailableError"], "features": [ "Restricting a tool to macOS with `availableWhen: { os: ['darwin'] }`", "Composing constraints — `runtime: ['node']` AND `env: ['production']` — both must match for the tool to be available", "Using the `surface` axis to expose an internal tool to agents and jobs while hiding it from direct user invocation", "Knowing what happens on mismatch — `EntryUnavailableError` (`-32003` FORBIDDEN) with `data.missingAxes` so clients show the right \"not available here\" reason" ] }, { "name": "22-tool-with-ui-html-template", "description": "Tool with an inline HTML function template — ``ui: { template: (ctx) => ctx.helpers.html`
…
` }`` — for a quick widget that doesn't need a separate `.tsx` file.", "level": "intermediate", "tags": ["ui", "ui-widgets", "html-template", "html-tag", "escapeStringResults", "TemplateContext"], "features": [ "Adding a `ui:` block with a function template that returns markup built with the `ctx.helpers.html` tagged template", "Annotating `ctx` explicitly to dodge the TS7006 inference gap on the union `ui.template` type", "Letting `ctx.helpers.html` escape every interpolated value so tool output can't inject markup into the widget", "Relying on the default since 1.9.2 (`escapeStringResults` unset), which escapes any plain string result while `html` results stay markup", "Reading from `ctx.output` and `ctx.helpers` — the typed runtime context the template renderer hands you" ] }, { "name": "23-tool-with-ui-filesource-tsx", "description": "Tool with a `.tsx` widget in a separate file via the `FileSource` form — the recommended pattern for any React widget. Path anchored with `import.meta.url` so it survives any cwd.", "level": "advanced", "tags": ["ui", "ui-widgets", "FileSource", "tsx", "import.meta.url", "host-detect"], "features": [ "Pointing `template` at a sibling `.tsx` file via the `FileSource` form `{ file: ... }`", "Anchoring the path to the tool source with `fileURLToPath(new URL('./...widget.tsx', import.meta.url))` so `process.cwd()` doesn't matter", "Leaving `resourceMode` unset — the framework host-detects (`'inline'` for Claude, `'cdn'` for others)", "Naming the widget `*.widget.tsx` so the scaffolded `tsconfig.json`'s `exclude` keeps it out of the server typecheck" ] }, { "name": "24-tool-with-ui-csp-and-bridge", "description": "Interactive tool widget that fetches from an allow-listed CSP origin and invokes another tool via `window.FrontMcpBridge.callTool` — the full pattern for live-data widgets that need cross-tool composition.", "level": "advanced", "tags": ["ui", "csp", "callTool", "FrontMcpBridge", "interactive-widget"], "features": [ "Restricting the widget's outbound `fetch` via `ui.csp.connectDomains` (emitted on the resource per #455)", "Calling other tools from the widget with `window.FrontMcpBridge.callTool(name, args)` instead of host-specific APIs", "Building the markup with `ctx.helpers.html` and embedding initial data into the inline `