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
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 |
|
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