Create a FrontMCP Tool
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:
- The
@Tool decorator surface
- Input / output schemas and how to derive
execute() types from them
- Dependency injection, error handling, progress / notifications
- Throttling: rate-limit, concurrency, timeout
- Auth providers and the credential vault
- Platform / runtime / surface availability constraints
- Elicitation (interactive input mid-execution)
- Tool UI widgets — the
ui: block, MCP Apps / SEP-1865, .tsx FileSource, CSP, window.FrontMcpBridge
- Annotations (
readOnlyHint, destructiveHint, idempotentHint, openWorldHint)
- The
examples metadata field
- Function-style tools, remote / ESM tools
- Registration patterns
- Per-tool unit testing
For 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.
Inherited defaults
This skill ALWAYS applies these defaults — never opt out without an audited reason:
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.
When to invoke this skill
Must use
- Creating a new
*.tool.ts file
- Adding the
@Tool({...}) decorator
- Defining or changing
inputSchema / outputSchema
- Adding a
ui: block to a tool (any template type)
- Adding
annotations, rateLimit, concurrency, timeout, authProviders, availableWhen, examples to a tool
- Calling
this.elicit(...) from execute()
- Registering a tool in
@App({ tools }) or @FrontMcp({ tools })
- Writing the unit test for a tool
Recommended
- Auditing an existing tool for the inherited defaults above
- Picking between class-style and function-style (
tool({...})(handler))
- Choosing the right output-schema variant for the data you're returning
- Converting a tool's auth from a single string to the full
{ name, scopes, required } mapping
- Deciding whether a side-effecting tool needs
destructiveHint: true
Skip when
- You're not building a tool. Use the matching
create-<thing> skill.
Decision tree
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 routing table
| 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? }] |
Verification checklist
Before considering a tool "done":
References (deep dives)
| 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 |
Rules (constraints — read these once, then they're enforced)
Accessing this skill
| 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) |
Related skills
create-resource, create-prompt, create-agent, create-provider, create-job, create-workflow, create-adapter, create-plugin, decorators-guide, architecture, testing, auth
1---2name: create-tool3description: 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-wor4license: Apache-2.05---67# Create a FrontMCP Tool89Tools 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).1011This skill is the single source of truth for building tools. It owns:1213- The `@Tool` decorator surface14- Input / output schemas and how to derive `execute()` types from them15- Dependency injection, error handling, progress / notifications16- Throttling: rate-limit, concurrency, timeout17- Auth providers and the credential vault18- Platform / runtime / surface availability constraints19- Elicitation (interactive input mid-execution)20- **Tool UI widgets** — the `ui:` block, MCP Apps / SEP-1865, `.tsx` FileSource, CSP, `window.FrontMcpBridge`21- Annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`)22- The `examples` metadata field23- Function-style tools, remote / ESM tools24- Registration patterns25- Per-tool unit testing2627For everything else — resources, prompts, agents, jobs, workflows, adapters, plugins, providers, channels — use the matching `create-<thing>` skill.2829> **First time?** Start with [`references/quick-start.md`](./references/quick-start.md), then jump to the example matching your scenario via the [Decision Tree](#decision-tree) below.3031---3233## Inherited defaults3435This skill ALWAYS applies these defaults — never opt out without an audited reason:3637| Default | Source | What it enforces |38| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |39| **`inputSchema` is a Zod raw shape** | [`rules/input-schema-is-raw-shape.md`](./rules/input-schema-is-raw-shape.md) | Plain object mapping field → Zod type. Framework wraps internally. Never `z.object(...)` at the top level. |40| **`outputSchema` is always defined** | [`rules/always-define-output-schema.md`](./rules/always-define-output-schema.md) | Prevents data leaks, enables CodeCall chaining, gives compile-time type safety. |41| **`execute()` types are derived from the schemas** | [`rules/derive-execute-types.md`](./rules/derive-execute-types.md) | `ToolInputOf<>` / `ToolOutputOf<>` over the hoisted schemas. Schema is the single source of truth. |42| **`class MyTool extends ToolContext`** — no generics | [`rules/no-toolcontext-generics.md`](./rules/no-toolcontext-generics.md) | Types are auto-inferred from `@Tool`. Explicit generics are redundant and forbidden. |43| **Tool names are `snake_case`** | [`rules/snake-case-tool-names.md`](./rules/snake-case-tool-names.md) | MCP protocol convention. `get_weather`, not `getWeather`. |44| **No `try/catch` around `execute()`** | [`rules/no-try-catch-around-execute.md`](./rules/no-try-catch-around-execute.md) | The framework's flow catches and formats errors. Wrapping defeats it. |45| **`this.fail(new McpError(…))` for business errors** | [`rules/use-this-fail-for-business-errors.md`](./rules/use-this-fail-for-business-errors.md) | Triggers the error flow with proper JSON-RPC codes. Raw `throw` skips it. |46| **Register tools in `@App({ tools })`** | [`rules/register-in-app.md`](./rules/register-in-app.md) | Apps own modularity and lifecycle. Top-level `@FrontMcp({ tools })` is the simple-server escape hatch. |47| **`.tsx` widget paths use `fileURLToPath(new URL('./x.tsx', import.meta.url))`** | [`rules/widget-paths-anchor-with-import-meta-url.md`](./rules/widget-paths-anchor-with-import-meta-url.md) | Relative `FileSource` paths resolve against `process.cwd()` — the workaround is mandatory (issue #444). |48| **Leave `ui.resourceMode` unset by default** | [`rules/widget-resource-mode-host-detect.md`](./rules/widget-resource-mode-host-detect.md) | The framework host-detects: Claude → `'inline'`, others → `'cdn'` (issue #456). Set explicitly only to override. |4950If 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.5152---5354## When to invoke this skill5556### Must use5758- Creating a new `*.tool.ts` file59- Adding the `@Tool({...})` decorator60- Defining or changing `inputSchema` / `outputSchema`61- Adding a `ui:` block to a tool (any template type)62- Adding `annotations`, `rateLimit`, `concurrency`, `timeout`, `authProviders`, `availableWhen`, `examples` to a tool63- Calling `this.elicit(...)` from `execute()`64- Registering a tool in `@App({ tools })` or `@FrontMcp({ tools })`65- Writing the unit test for a tool6667### Recommended6869- Auditing an existing tool for the inherited defaults above70- Picking between class-style and function-style (`tool({...})(handler)`)71- Choosing the right output-schema variant for the data you're returning72- Converting a tool's auth from a single string to the full `{ name, scopes, required }` mapping73- Deciding whether a side-effecting tool needs `destructiveHint: true`7475### Skip when7677- You're not building a tool. Use the matching `create-<thing>` skill.7879---8081## Decision tree8283```text841. What kind of tool?85 ├── Tiny one-off → function-style: `tool({...})((input, ctx) => …)`86 │ See: examples/02-basic-function-tool.md87 ├── Anything with DI, lifecycle, hooks, or UI → class-style88 │ See: examples/01-basic-class-tool.md89 └── Externally hosted (ESM URL or remote MCP server) → Tool.esm / Tool.remote90 See: references/remote-and-esm.md91922. What does it return?93 ├── Structured JSON → outputSchema: { field: z.string(), … }94 │ See: examples/03-tool-with-zod-shape-output.md95 ├── A primitive (text/num) → outputSchema: 'string' | 'number' | 'boolean' | 'date'96 │ See: examples/05-tool-with-primitive-output.md97 ├── Media (image/audio) → outputSchema: 'image' | 'audio'98 │ See: examples/06-tool-with-media-output.md99 ├── A resource link → outputSchema: 'resource' | 'resource_link'100 │ See: examples/26-tool-with-resource-link-output.md101 └── Several content blocks → outputSchema: ['string', 'image']102 See: examples/06-tool-with-media-output.md1031043. Does it need shared services / config / clients?105 YES → register a @Provider; inject via this.get(TOKEN)106 See: examples/08-tool-with-provider-injection.md1071084. Does it call an external HTTP API?109 YES → use this.fetch(input, init?) (context propagation)110 See: examples/11-tool-with-fetch.md1111125. Does it need user credentials from an OAuth provider?113 YES → declare authProviders: ['provider'] (or full mapping)114 See: examples/13-tool-with-single-auth-provider.md, 15-tool-with-credential-vault.md1151166. Is it expensive / rate-limited / slow?117 YES → add rateLimit / concurrency / timeout118 See: examples/16-tool-with-rate-limit.md, 17-tool-with-concurrency-and-timeout.md1191207. Does it run for a while? Want progress?121 YES → call this.progress(n, total, msg)122 See: examples/18-tool-with-progress-and-notify.md1231248. Does it need a confirmation / extra input mid-run?125 YES → this.elicit('msg', { fieldSchema })126 See: examples/19-tool-with-elicitation.md1271289. Is it destructive / read-only / idempotent / open-world?129 YES → annotations: { destructiveHint, readOnlyHint, idempotentHint, openWorldHint }130 See: examples/20-tool-with-annotations.md13113210. Should it only run on certain OSes / runtimes / build targets?133 YES → availableWhen: { os, runtime, deployment, provider, target, surface, env }134 See: examples/21-tool-with-availability-constraints.md13513611. Should the result render as a widget in the host UI?137 YES → ui: { template, … }138 ├── Quick HTML → ui: { template: (ctx) => '<div>…</div>' }139 │ See: examples/22-tool-with-ui-html-template.md140 ├── React widget (file) → ui: { template: { file: widgetPath } }141 │ See: examples/23-tool-with-ui-filesource-tsx.md142 ├── Calls other tools → widgetAccessible: true + window.FrontMcpBridge143 │ See: examples/24-tool-with-ui-csp-and-bridge.md144 └── Claude target → resourceMode is auto-detected; do not set145 See: references/ui-widgets.md14614712. Does it hand off long work to a job?148 YES → kick off a job + return a tracking handle149 See: examples/25-tool-handing-off-to-job.md150```151152---153154## Scenario routing table155156| Scenario | Example | Why |157| -------------------------------------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |158| Build the simplest possible tool | [`01-basic-class-tool`](./examples/01-basic-class-tool.md) | Foundation — every other example builds on this shape |159| One-off math / formatter | [`02-basic-function-tool`](./examples/02-basic-function-tool.md) | `tool()` builder is fine for trivial pure-input tools |160| Return structured JSON | [`03-tool-with-zod-shape-output`](./examples/03-tool-with-zod-shape-output.md) | Raw Zod shape — recommended for any complex output |161| Output is a complex Zod schema | [`04-tool-with-zod-schema-output`](./examples/04-tool-with-zod-schema-output.md) | `z.object()` / `z.array()` / `z.discriminatedUnion()` for full Zod |162| Output is a primitive | [`05-tool-with-primitive-output`](./examples/05-tool-with-primitive-output.md) | `'string'` / `'number'` / `'date'` literals |163| Output is binary / multi-content | [`06-tool-with-media-output`](./examples/06-tool-with-media-output.md) | `'image'`, `'audio'`, `['string', 'image']` |164| Tool resolves dependencies via DI | [`08-tool-with-provider-injection`](./examples/08-tool-with-provider-injection.md) | `this.get(TOKEN)` against a `@Provider`-registered service |165| Tool composes multiple services | [`09-tool-with-multiple-providers`](./examples/09-tool-with-multiple-providers.md) | Realistic shape — DB + cache + config in one tool |166| Tool calls an external HTTP API | [`11-tool-with-fetch`](./examples/11-tool-with-fetch.md) | `this.fetch(url, init?)` — context propagation, error handling |167| Tool calls a flaky API with retries | [`12-tool-with-fetch-and-retries`](./examples/12-tool-with-fetch-and-retries.md) | Exponential backoff, idempotency-key, retry config |168| Tool needs OAuth credentials | [`13-tool-with-single-auth-provider`](./examples/13-tool-with-single-auth-provider.md) | `authProviders: ['github']` — string shorthand |169| Tool needs scoped / optional creds | [`14-tool-with-multiple-auth-providers`](./examples/14-tool-with-multiple-auth-providers.md) | Full mapping form with `required` + `scopes` + `alias` |170| Tool reads a per-session secret | [`15-tool-with-credential-vault`](./examples/15-tool-with-credential-vault.md) | `this.authProviders.headers(...)`, vault patterns |171| Rate-limit an expensive operation | [`16-tool-with-rate-limit`](./examples/16-tool-with-rate-limit.md) | `rateLimit: { maxRequests, windowMs }` |172| Cap concurrency + add a timeout | [`17-tool-with-concurrency-and-timeout`](./examples/17-tool-with-concurrency-and-timeout.md) | Production-ready throttling shape |173| Long-running tool with progress | [`18-tool-with-progress-and-notify`](./examples/18-tool-with-progress-and-notify.md) | `this.progress` + `this.notify` + `this.mark` |174| Tool that asks the user mid-run | [`19-tool-with-elicitation`](./examples/19-tool-with-elicitation.md) | `this.elicit` with Zod schema |175| Tool with behavioral hints for the client | [`20-tool-with-annotations`](./examples/20-tool-with-annotations.md) | `readOnlyHint` / `destructiveHint` / `idempotentHint` / `openWorldHint` |176| Tool restricted to one OS / runtime / target | [`21-tool-with-availability-constraints`](./examples/21-tool-with-availability-constraints.md) | `availableWhen` axes |177| Tool with a quick inline HTML widget | [`22-tool-with-ui-html-template`](./examples/22-tool-with-ui-html-template.md) | `ui: { template: (ctx) => '<div>…</div>' }` |178| Tool with a separate `.tsx` widget file | [`23-tool-with-ui-filesource-tsx`](./examples/23-tool-with-ui-filesource-tsx.md) | `FileSource` + `import.meta.url` anchoring |179| Tool widget that calls other tools | [`24-tool-with-ui-csp-and-bridge`](./examples/24-tool-with-ui-csp-and-bridge.md) | `widgetAccessible: true` + `window.FrontMcpBridge.callTool` |180| Tool that triggers a job + tracks it | [`25-tool-handing-off-to-job`](./examples/25-tool-handing-off-to-job.md) | Thin tool + heavy job — the right split |181| Tool that returns a resource handle | [`26-tool-with-resource-link-output`](./examples/26-tool-with-resource-link-output.md) | `outputSchema: 'resource_link'` — the host fetches the resource |182| Tool with `examples` metadata for discovery | [`27-tool-with-examples-metadata`](./examples/27-tool-with-examples-metadata.md) | `examples: [{ description, input, output? }]` |183184---185186## Verification checklist187188Before considering a tool "done":189190- [ ] Class extends `ToolContext` (no generics) OR uses `tool()` function builder191- [ ] `@Tool({ name, description, inputSchema, outputSchema })` — all four present192- [ ] `name` is `snake_case`193- [ ] `inputSchema` is a Zod raw shape (NOT wrapped in `z.object`)194- [ ] `outputSchema` is defined (Zod shape / primitive / media / array)195- [ ] `execute()` parameter and return types derived via `ToolInputOf<>` / `ToolOutputOf<>`196- [ ] No `try/catch` around `execute()` body197- [ ] Business errors use `this.fail(new SomeMcpError(…))`, not raw `throw`198- [ ] Tool registered in an `@App({ tools })` (or `@FrontMcp({ tools })` for single-app servers)199- [ ] If `ui:`: `.tsx` widget paths anchored via `fileURLToPath(new URL(...))`200- [ ] If `ui:`: `ui.resourceMode` left unset (host-detect) unless an explicit override is intentional201- [ ] Unit test in `<name>.tool.spec.ts` covering happy + at least one failure path202- [ ] Optional: `annotations`, `rateLimit` / `concurrency` / `timeout`, `authProviders`, `availableWhen`, `examples` set when the tool's behavior warrants them203204---205206## References (deep dives)207208| Reference | Covers |209| --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |210| [`quick-start.md`](./references/quick-start.md) | 60-second tour: minimal tool, registration, calling it from a test |211| [`decorator-options.md`](./references/decorator-options.md) | Every field on `@Tool({...})` — what it does, default, when to set it |212| [`input-schema.md`](./references/input-schema.md) | Raw shape vs `z.object`, refinements, defaults, optional, describe |213| [`output-schema.md`](./references/output-schema.md) | All supported output types: Zod shape, Zod schema, primitives, media, arrays |214| [`derived-types.md`](./references/derived-types.md) | `ToolInputOf` / `ToolOutputOf` patterns, file layout, schema hoisting |215| [`execution-context.md`](./references/execution-context.md) | `ToolContext` methods + properties — `this.get`, `this.fetch`, `this.notify`, `this.context`, etc. |216| [`error-handling.md`](./references/error-handling.md) | `this.fail`, MCP error classes (`PublicMcpError`, `ResourceNotFoundError`), error flow, when to throw vs `fail` |217| [`throttling.md`](./references/throttling.md) | `rateLimit`, `concurrency`, `timeout` — semantics, interaction, defaults |218| [`auth-providers.md`](./references/auth-providers.md) | `authProviders` string shorthand vs full mapping, scopes, alias, credential vault basics |219| [`availability.md`](./references/availability.md) | `availableWhen` axes (os / runtime / deployment / provider / target / surface / env), `missingAxes`, `isPlatform` |220| [`elicitation.md`](./references/elicitation.md) | `this.elicit`, server-level enable, `ElicitationDisabledError`, accept / decline / cancel |221| [`ui-widgets.md`](./references/ui-widgets.md) | `@Tool({ ui })` — template formats, `servingMode`, `resourceMode` host-detect, CSP, `widgetAccessible`, MCP Apps spec |222| [`annotations.md`](./references/annotations.md) | `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`, `title` |223| [`function-style-builder.md`](./references/function-style-builder.md) | `tool({...})(handler)` — when to pick over a class, register, ctx parameter |224| [`remote-and-esm.md`](./references/remote-and-esm.md) | `Tool.esm(...)` / `Tool.remote(...)` — load tools from ESM URLs or remote MCP servers |225| [`registration.md`](./references/registration.md) | `@App({ tools })` vs `@FrontMcp({ tools })`, multi-app composition |226| [`file-layout.md`](./references/file-layout.md) | Flat-sibling vs folder-per-tool, `<name>.schema.ts` / `<name>.tool.ts` / `<name>.tool.spec.ts` |227| [`testing.md`](./references/testing.md) | Per-tool unit tests — `@frontmcp/testing`, mocking DI, asserting output validation |228229## Rules (constraints — read these once, then they're enforced)230231| Rule | Constraint |232| ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |233| [`input-schema-is-raw-shape.md`](./rules/input-schema-is-raw-shape.md) | `inputSchema` is a raw Zod shape, never `z.object(...)` |234| [`always-define-output-schema.md`](./rules/always-define-output-schema.md) | Every tool defines `outputSchema` |235| [`derive-execute-types.md`](./rules/derive-execute-types.md) | `execute()` types come from `ToolInputOf` / `ToolOutputOf` — never duplicated inline |236| [`no-toolcontext-generics.md`](./rules/no-toolcontext-generics.md) | `class MyTool extends ToolContext` — no `<typeof inputSchema>` generic |237| [`snake-case-tool-names.md`](./rules/snake-case-tool-names.md) | Tool `name` is `snake_case` |238| [`no-try-catch-around-execute.md`](./rules/no-try-catch-around-execute.md) | The framework owns error flow — don't wrap `execute()` body |239| [`use-this-fail-for-business-errors.md`](./rules/use-this-fail-for-business-errors.md) | `this.fail(new McpError(…))` — never raw `throw` for business errors |240| [`register-in-app.md`](./rules/register-in-app.md) | Register tools in `@App({ tools })` for modularity / lifecycle |241| [`widget-paths-anchor-with-import-meta-url.md`](./rules/widget-paths-anchor-with-import-meta-url.md) | `.tsx` widget paths via `fileURLToPath(new URL(...))` — never bare relative |242| [`widget-resource-mode-host-detect.md`](./rules/widget-resource-mode-host-detect.md) | Leave `ui.resourceMode` unset — let host detect |243244## Accessing this skill245246| Mode | How |247| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |248| **Filesystem** | Read `libs/skills/catalog/create-tool/` directly. `SKILL.md` is the entry point. |249| **CLI** | `frontmcp skills list`, `frontmcp skills read create-tool`, `frontmcp skills read create-tool:references/<file>.md`, `frontmcp skills install create-tool` |250| **MCP `skill://`** | When mounted on a FrontMCP server, available at `skill://create-tool/SKILL.md`, `skill://create-tool/references/{file}.md`, etc. (SEP-2640) |251252## Related skills253254`create-resource`, `create-prompt`, `create-agent`, `create-provider`, `create-job`, `create-workflow`, `create-adapter`, `create-plugin`, `decorators-guide`, `architecture`, `testing`, `auth`