libs/skills/catalog/create-tool/SKILL.md
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. Does NOT cover: - Read-only data exposed via a URI — use `create-resource` - Conversation templates / system prompts — use `create-prompt` - Multi-tool orchestration loops — use `create-agent` - Background work / pipelines — use `create-job` / `create-workflow` - Server-level config (transport, sessions, auth modes) — use `config` / `auth` Triggers: `@Tool`, ToolContext, tool decorator, MCP tool, snake_case tool name, inputSchema, outputSchema, ToolInputOf, ToolOutputOf, `@Tool({ ui })`, tool UI widget, MCP Apps widget, FileSource widget, `.tsx` widget, ui.csp, ui.resourceMode, window.FrontMcpBridge, tool annotations, readOnlyHint, destructiveHint, rate-limit tool, throttle tool, concurrency tool, tool timeout, this.fail, this.respond, this.fetch, this.notify, this.progress, this.elicit, ElicitationDisabledError, ToolContext.execute, this.get(TOKEN), this.tryGet, register tool in @App, tool examples metadata, availableWhen, missingAxes, `tool()` function builder, Tool.esm, Tool.remote, PublicMcpError, ResourceNotFoundError, MCP_ERROR_CODES, ui://widget.
npx skillsauth add agentfront/frontmcp create-toolInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Tools are the primary way to expose executable actions to AI clients in the MCP protocol. In FrontMCP, every tool is a TypeScript class that extends ToolContext, decorated with @Tool({...}), and registered on an @App (or directly on @FrontMcp for simple servers).
This skill is the single source of truth for building tools. It owns:
@Tool decorator surfaceexecute() types from themui: block, MCP Apps / SEP-1865, .tsx FileSource, CSP, window.FrontMcpBridgereadOnlyHint, destructiveHint, idempotentHint, openWorldHint)examples metadata fieldFor everything else — resources, prompts, agents, jobs, workflows, adapters, plugins, providers, channels — use the matching create-<thing> skill.
First time? Start with
references/quick-start.md, then jump to the example matching your scenario via the Decision Tree below.
This skill ALWAYS applies these defaults — never opt out without an audited reason:
| Default | Source | What it enforces |
| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| inputSchema is a Zod raw shape | rules/input-schema-is-raw-shape.md | Plain object mapping field → Zod type. Framework wraps internally. Never z.object(...) at the top level. |
| outputSchema is always defined | rules/always-define-output-schema.md | Prevents data leaks, enables CodeCall chaining, gives compile-time type safety. |
| execute() types are derived from the schemas | rules/derive-execute-types.md | ToolInputOf<> / ToolOutputOf<> over the hoisted schemas. Schema is the single source of truth. |
| class MyTool extends ToolContext — no generics | rules/no-toolcontext-generics.md | Types are auto-inferred from @Tool. Explicit generics are redundant and forbidden. |
| Tool names are snake_case | rules/snake-case-tool-names.md | MCP protocol convention. get_weather, not getWeather. |
| No try/catch around execute() | rules/no-try-catch-around-execute.md | The framework's flow catches and formats errors. Wrapping defeats it. |
| this.fail(new McpError(…)) for business errors | rules/use-this-fail-for-business-errors.md | Triggers the error flow with proper JSON-RPC codes. Raw throw skips it. |
| Register tools in @App({ tools }) | rules/register-in-app.md | Apps own modularity and lifecycle. Top-level @FrontMcp({ tools }) is the simple-server escape hatch. |
| .tsx widget paths use fileURLToPath(new URL('./x.tsx', import.meta.url)) | rules/widget-paths-anchor-with-import-meta-url.md | Relative FileSource paths resolve against process.cwd() — the workaround is mandatory (issue #444). |
| Leave ui.resourceMode unset by default | rules/widget-resource-mode-host-detect.md | The framework host-detects: Claude → 'inline', others → 'cdn' (issue #456). Set explicitly only to override. |
If a request seems to conflict with an inherited default (e.g., "wrap inputSchema in z.object to use refinements", or "use try/catch to swallow upstream errors"), stop and ask — never silently override.
*.tool.ts file@Tool({...}) decoratorinputSchema / outputSchemaui: block to a tool (any template type)annotations, rateLimit, concurrency, timeout, authProviders, availableWhen, examples to a toolthis.elicit(...) from execute()@App({ tools }) or @FrontMcp({ tools })tool({...})(handler)){ name, scopes, required } mappingdestructiveHint: truecreate-<thing> skill.1. What kind of tool?
├── Tiny one-off → function-style: `tool({...})((input, ctx) => …)`
│ See: examples/02-basic-function-tool.md
├── Anything with DI, lifecycle, hooks, or UI → class-style
│ See: examples/01-basic-class-tool.md
└── Externally hosted (ESM URL or remote MCP server) → Tool.esm / Tool.remote
See: references/remote-and-esm.md
2. What does it return?
├── Structured JSON → outputSchema: { field: z.string(), … }
│ See: examples/03-tool-with-zod-shape-output.md
├── A primitive (text/num) → outputSchema: 'string' | 'number' | 'boolean' | 'date'
│ See: examples/05-tool-with-primitive-output.md
├── Media (image/audio) → outputSchema: 'image' | 'audio'
│ See: examples/06-tool-with-media-output.md
├── A resource link → outputSchema: 'resource' | 'resource_link'
│ See: examples/26-tool-with-resource-link-output.md
└── Several content blocks → outputSchema: ['string', 'image']
See: examples/06-tool-with-media-output.md
3. Does it need shared services / config / clients?
YES → register a @Provider; inject via this.get(TOKEN)
See: examples/08-tool-with-provider-injection.md
4. Does it call an external HTTP API?
YES → use this.fetch(input, init?) (context propagation)
See: examples/11-tool-with-fetch.md
5. Does it need user credentials from an OAuth provider?
YES → declare authProviders: ['provider'] (or full mapping)
See: examples/13-tool-with-single-auth-provider.md, 15-tool-with-credential-vault.md
6. Is it expensive / rate-limited / slow?
YES → add rateLimit / concurrency / timeout
See: examples/16-tool-with-rate-limit.md, 17-tool-with-concurrency-and-timeout.md
7. Does it run for a while? Want progress?
YES → call this.progress(n, total, msg)
See: examples/18-tool-with-progress-and-notify.md
8. Does it need a confirmation / extra input mid-run?
YES → this.elicit('msg', { fieldSchema })
See: examples/19-tool-with-elicitation.md
9. Is it destructive / read-only / idempotent / open-world?
YES → annotations: { destructiveHint, readOnlyHint, idempotentHint, openWorldHint }
See: examples/20-tool-with-annotations.md
10. Should it only run on certain OSes / runtimes / build targets?
YES → availableWhen: { os, runtime, deployment, provider, target, surface, env }
See: examples/21-tool-with-availability-constraints.md
11. Should the result render as a widget in the host UI?
YES → ui: { template, … }
├── Quick HTML → ui: { template: (ctx) => '<div>…</div>' }
│ See: examples/22-tool-with-ui-html-template.md
├── React widget (file) → ui: { template: { file: widgetPath } }
│ See: examples/23-tool-with-ui-filesource-tsx.md
├── Calls other tools → widgetAccessible: true + window.FrontMcpBridge
│ See: examples/24-tool-with-ui-csp-and-bridge.md
└── Claude target → resourceMode is auto-detected; do not set
See: references/ui-widgets.md
12. Does it hand off long work to a job?
YES → kick off a job + return a tracking handle
See: examples/25-tool-handing-off-to-job.md
| Scenario | Example | Why |
| -------------------------------------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| Build the simplest possible tool | 01-basic-class-tool | Foundation — every other example builds on this shape |
| One-off math / formatter | 02-basic-function-tool | tool() builder is fine for trivial pure-input tools |
| Return structured JSON | 03-tool-with-zod-shape-output | Raw Zod shape — recommended for any complex output |
| Output is a complex Zod schema | 04-tool-with-zod-schema-output | z.object() / z.array() / z.discriminatedUnion() for full Zod |
| Output is a primitive | 05-tool-with-primitive-output | 'string' / 'number' / 'date' literals |
| Output is binary / multi-content | 06-tool-with-media-output | 'image', 'audio', ['string', 'image'] |
| Tool resolves dependencies via DI | 08-tool-with-provider-injection | this.get(TOKEN) against a @Provider-registered service |
| Tool composes multiple services | 09-tool-with-multiple-providers | Realistic shape — DB + cache + config in one tool |
| Tool calls an external HTTP API | 11-tool-with-fetch | this.fetch(url, init?) — context propagation, error handling |
| Tool calls a flaky API with retries | 12-tool-with-fetch-and-retries | Exponential backoff, idempotency-key, retry config |
| Tool needs OAuth credentials | 13-tool-with-single-auth-provider | authProviders: ['github'] — string shorthand |
| Tool needs scoped / optional creds | 14-tool-with-multiple-auth-providers | Full mapping form with required + scopes + alias |
| Tool reads a per-session secret | 15-tool-with-credential-vault | this.authProviders.headers(...), vault patterns |
| Rate-limit an expensive operation | 16-tool-with-rate-limit | rateLimit: { maxRequests, windowMs } |
| Cap concurrency + add a timeout | 17-tool-with-concurrency-and-timeout | Production-ready throttling shape |
| Long-running tool with progress | 18-tool-with-progress-and-notify | this.progress + this.notify + this.mark |
| Tool that asks the user mid-run | 19-tool-with-elicitation | this.elicit with Zod schema |
| Tool with behavioral hints for the client | 20-tool-with-annotations | readOnlyHint / destructiveHint / idempotentHint / openWorldHint |
| Tool restricted to one OS / runtime / target | 21-tool-with-availability-constraints | availableWhen axes |
| Tool with a quick inline HTML widget | 22-tool-with-ui-html-template | ui: { template: (ctx) => '<div>…</div>' } |
| Tool with a separate .tsx widget file | 23-tool-with-ui-filesource-tsx | FileSource + import.meta.url anchoring |
| Tool widget that calls other tools | 24-tool-with-ui-csp-and-bridge | widgetAccessible: true + window.FrontMcpBridge.callTool |
| Tool that triggers a job + tracks it | 25-tool-handing-off-to-job | Thin tool + heavy job — the right split |
| Tool that returns a resource handle | 26-tool-with-resource-link-output | outputSchema: 'resource_link' — the host fetches the resource |
| Tool with examples metadata for discovery | 27-tool-with-examples-metadata | examples: [{ description, input, output? }] |
Before considering a tool "done":
ToolContext (no generics) OR uses tool() function builder@Tool({ name, description, inputSchema, outputSchema }) — all four presentname is snake_caseinputSchema is a Zod raw shape (NOT wrapped in z.object)outputSchema is defined (Zod shape / primitive / media / array)execute() parameter and return types derived via ToolInputOf<> / ToolOutputOf<>try/catch around execute() bodythis.fail(new SomeMcpError(…)), not raw throw@App({ tools }) (or @FrontMcp({ tools }) for single-app servers)ui:: .tsx widget paths anchored via fileURLToPath(new URL(...))ui:: ui.resourceMode left unset (host-detect) unless an explicit override is intentional<name>.tool.spec.ts covering happy + at least one failure pathannotations, rateLimit / concurrency / timeout, authProviders, availableWhen, examples set when the tool's behavior warrants them| Reference | Covers |
| --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| quick-start.md | 60-second tour: minimal tool, registration, calling it from a test |
| decorator-options.md | Every field on @Tool({...}) — what it does, default, when to set it |
| input-schema.md | Raw shape vs z.object, refinements, defaults, optional, describe |
| output-schema.md | All supported output types: Zod shape, Zod schema, primitives, media, arrays |
| derived-types.md | ToolInputOf / ToolOutputOf patterns, file layout, schema hoisting |
| execution-context.md | ToolContext methods + properties — this.get, this.fetch, this.notify, this.context, etc. |
| error-handling.md | this.fail, MCP error classes (PublicMcpError, ResourceNotFoundError), error flow, when to throw vs fail |
| throttling.md | rateLimit, concurrency, timeout — semantics, interaction, defaults |
| auth-providers.md | authProviders string shorthand vs full mapping, scopes, alias, credential vault basics |
| availability.md | availableWhen axes (os / runtime / deployment / provider / target / surface / env), missingAxes, isPlatform |
| elicitation.md | this.elicit, server-level enable, ElicitationDisabledError, accept / decline / cancel |
| ui-widgets.md | @Tool({ ui }) — template formats, servingMode, resourceMode host-detect, CSP, widgetAccessible, MCP Apps spec |
| annotations.md | readOnlyHint, destructiveHint, idempotentHint, openWorldHint, title |
| function-style-builder.md | tool({...})(handler) — when to pick over a class, register, ctx parameter |
| remote-and-esm.md | Tool.esm(...) / Tool.remote(...) — load tools from ESM URLs or remote MCP servers |
| registration.md | @App({ tools }) vs @FrontMcp({ tools }), multi-app composition |
| file-layout.md | Flat-sibling vs folder-per-tool, <name>.schema.ts / <name>.tool.ts / <name>.tool.spec.ts |
| testing.md | Per-tool unit tests — @frontmcp/testing, mocking DI, asserting output validation |
| Rule | Constraint |
| ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| input-schema-is-raw-shape.md | inputSchema is a raw Zod shape, never z.object(...) |
| always-define-output-schema.md | Every tool defines outputSchema |
| derive-execute-types.md | execute() types come from ToolInputOf / ToolOutputOf — never duplicated inline |
| no-toolcontext-generics.md | class MyTool extends ToolContext — no <typeof inputSchema> generic |
| snake-case-tool-names.md | Tool name is snake_case |
| no-try-catch-around-execute.md | The framework owns error flow — don't wrap execute() body |
| use-this-fail-for-business-errors.md | this.fail(new McpError(…)) — never raw throw for business errors |
| register-in-app.md | Register tools in @App({ tools }) for modularity / lifecycle |
| widget-paths-anchor-with-import-meta-url.md | .tsx widget paths via fileURLToPath(new URL(...)) — never bare relative |
| widget-resource-mode-host-detect.md | Leave ui.resourceMode unset — let host detect |
| Mode | How |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Filesystem | Read libs/skills/catalog/create-tool/ directly. SKILL.md is the entry point. |
| CLI | frontmcp skills list, frontmcp skills read create-tool, frontmcp skills read create-tool:references/<file>.md, frontmcp skills install create-tool |
| MCP skill:// | When mounted on a FrontMCP server, available at skill://create-tool/SKILL.md, skill://create-tool/references/{file}.md, etc. (SEP-2640) |
create-resource, create-prompt, create-agent, create-provider, create-job, create-workflow, create-adapter, create-plugin, decorators-guide, architecture, testing, auth
tools
Use when you want to add tracing, structured logging, or monitoring to your FrontMCP server. Covers OpenTelemetry instrumentation, vendor integrations (Coralogix, Datadog, Logz.io, Grafana), this.telemetry API for custom spans, structured JSON logging with sinks, and testing observability. Triggers: observability, telemetry, tracing, logging, monitoring, opentelemetry, otel, spans, datadog, coralogix, logz, grafana, winston, pino.
development
Use when implementing authorization, access control, RBAC, ABAC, or ReBAC for tools, resources, prompts, or skills. Covers JWT claims mapping, authority profiles, and policy enforcement.
testing
Use when you want to write tests, run tests, add e2e tests, improve test coverage, test a tool, test a resource, or learn how to test any FrontMCP component. The skill for ALL testing needs.
tools
Domain router for project setup, scaffolding, and organization. Use this skill whenever someone asks to create a new FrontMCP project, set up an Nx monorepo, configure Redis or SQLite storage, organize project structure, compose multiple apps into one server, or manage the skills system. Also triggers for questions like 'how do I start', 'project layout', 'folder structure', 'add redis', 'set up database', or 'create a new app'.