@frontmcp/skills 1.3.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +38 -29
  2. package/catalog/TEMPLATE.md +26 -0
  3. package/catalog/create-tool/SKILL.md +318 -0
  4. package/catalog/create-tool/examples/01-basic-class-tool.md +112 -0
  5. package/catalog/create-tool/examples/02-basic-function-tool.md +80 -0
  6. package/catalog/create-tool/examples/03-tool-with-zod-shape-output.md +78 -0
  7. package/catalog/create-tool/examples/04-tool-with-zod-schema-output.md +97 -0
  8. package/catalog/create-tool/examples/05-tool-with-primitive-output.md +93 -0
  9. package/catalog/create-tool/examples/06-tool-with-media-output.md +109 -0
  10. package/catalog/create-tool/examples/08-tool-with-provider-injection.md +110 -0
  11. package/catalog/create-tool/examples/09-tool-with-multiple-providers.md +107 -0
  12. package/catalog/create-tool/examples/11-tool-with-fetch.md +94 -0
  13. package/catalog/create-tool/examples/12-tool-with-fetch-and-retries.md +115 -0
  14. package/catalog/create-tool/examples/13-tool-with-single-auth-provider.md +85 -0
  15. package/catalog/create-tool/examples/14-tool-with-multiple-auth-providers.md +105 -0
  16. package/catalog/create-tool/examples/15-tool-with-credential-vault.md +115 -0
  17. package/catalog/create-tool/examples/16-tool-with-rate-limit.md +71 -0
  18. package/catalog/create-tool/examples/17-tool-with-concurrency-and-timeout.md +101 -0
  19. package/catalog/create-tool/examples/18-tool-with-progress-and-notify.md +96 -0
  20. package/catalog/create-tool/examples/19-tool-with-elicitation.md +102 -0
  21. package/catalog/create-tool/examples/20-tool-with-annotations.md +125 -0
  22. package/catalog/create-tool/examples/21-tool-with-availability-constraints.md +107 -0
  23. package/catalog/create-tool/examples/22-tool-with-ui-html-template.md +93 -0
  24. package/catalog/create-tool/examples/23-tool-with-ui-filesource-tsx.md +112 -0
  25. package/catalog/create-tool/examples/24-tool-with-ui-csp-and-bridge.md +127 -0
  26. package/catalog/create-tool/examples/25-tool-handing-off-to-job.md +143 -0
  27. package/catalog/create-tool/examples/26-tool-with-resource-link-output.md +94 -0
  28. package/catalog/create-tool/examples/27-tool-with-examples-metadata.md +90 -0
  29. package/catalog/create-tool/references/annotations.md +96 -0
  30. package/catalog/create-tool/references/auth-providers.md +167 -0
  31. package/catalog/create-tool/references/availability.md +106 -0
  32. package/catalog/create-tool/references/decorator-options.md +95 -0
  33. package/catalog/create-tool/references/derived-types.md +102 -0
  34. package/catalog/create-tool/references/elicitation.md +128 -0
  35. package/catalog/create-tool/references/error-handling.md +128 -0
  36. package/catalog/create-tool/references/execution-context.md +158 -0
  37. package/catalog/create-tool/references/file-layout.md +96 -0
  38. package/catalog/create-tool/references/function-style-builder.md +118 -0
  39. package/catalog/create-tool/references/input-schema.md +141 -0
  40. package/catalog/create-tool/references/output-schema.md +175 -0
  41. package/catalog/create-tool/references/quick-start.md +124 -0
  42. package/catalog/create-tool/references/registration.md +132 -0
  43. package/catalog/create-tool/references/remote-and-esm.md +68 -0
  44. package/catalog/create-tool/references/testing.md +59 -0
  45. package/catalog/create-tool/references/throttling.md +109 -0
  46. package/catalog/create-tool/references/ui-widgets.md +198 -0
  47. package/catalog/create-tool/rules/always-define-output-schema.md +77 -0
  48. package/catalog/create-tool/rules/derive-execute-types.md +57 -0
  49. package/catalog/create-tool/rules/input-schema-is-raw-shape.md +76 -0
  50. package/catalog/create-tool/rules/no-toolcontext-generics.md +50 -0
  51. package/catalog/create-tool/rules/no-try-catch-around-execute.md +79 -0
  52. package/catalog/create-tool/rules/register-in-app.md +76 -0
  53. package/catalog/create-tool/rules/snake-case-tool-names.md +45 -0
  54. package/catalog/create-tool/rules/use-this-fail-for-business-errors.md +75 -0
  55. package/catalog/create-tool/rules/widget-paths-anchor-with-import-meta-url.md +76 -0
  56. package/catalog/create-tool/rules/widget-resource-mode-host-detect.md +61 -0
  57. package/catalog/frontmcp-auth-ui/SKILL.md +146 -0
  58. package/catalog/frontmcp-auth-ui/examples/custom-auth-ui/login-slot.md +97 -0
  59. package/catalog/frontmcp-auth-ui/examples/custom-auth-ui/multi-step-auth-extra.md +133 -0
  60. package/catalog/frontmcp-auth-ui/references/custom-auth-ui.md +162 -0
  61. package/catalog/frontmcp-authorities/SKILL.md +55 -18
  62. package/catalog/frontmcp-authorities/references/authority-profiles.md +25 -1
  63. package/catalog/frontmcp-authorities/references/custom-evaluators.md +1 -1
  64. package/catalog/frontmcp-authorities/references/rbac-abac-rebac.md +9 -0
  65. package/catalog/frontmcp-channels/SKILL.md +7 -1
  66. package/catalog/frontmcp-config/SKILL.md +9 -2
  67. package/catalog/frontmcp-config/examples/configure-auth/local-credential-vault.md +94 -0
  68. package/catalog/frontmcp-config/examples/configure-auth/local-secure-store.md +138 -0
  69. package/catalog/frontmcp-config/examples/configure-auth/remote-oauth-with-vault.md +45 -23
  70. package/catalog/frontmcp-config/examples/configure-auth-modes/local-behind-tunnel.md +73 -0
  71. package/catalog/frontmcp-config/examples/configure-auth-modes/local-consent-enforcement.md +87 -0
  72. package/catalog/frontmcp-config/examples/configure-auth-modes/local-dcr-control.md +67 -0
  73. package/catalog/frontmcp-config/examples/configure-auth-modes/local-minimal.md +62 -0
  74. package/catalog/frontmcp-config/examples/configure-auth-modes/local-multi-provider-orchestration.md +93 -0
  75. package/catalog/frontmcp-config/examples/configure-auth-modes/local-self-signed-tokens.md +18 -20
  76. package/catalog/frontmcp-config/examples/configure-auth-modes/local-single-operator.md +66 -0
  77. package/catalog/frontmcp-config/examples/configure-auth-modes/remote-enterprise-oauth.md +37 -23
  78. package/catalog/frontmcp-config/examples/configure-http/custom-http-routes.md +98 -0
  79. package/catalog/frontmcp-config/examples/configure-skills-http/audit-log-redis.md +17 -9
  80. package/catalog/frontmcp-config/references/configure-auth-modes.md +86 -23
  81. package/catalog/frontmcp-config/references/configure-auth.md +296 -50
  82. package/catalog/frontmcp-config/references/configure-http.md +149 -15
  83. package/catalog/frontmcp-deployment/SKILL.md +15 -13
  84. package/catalog/frontmcp-deployment/references/deploy-manifest-yaml.md +308 -0
  85. package/catalog/frontmcp-deployment/references/deploy-to-cloudflare-skills-only.md +174 -0
  86. package/catalog/frontmcp-deployment/references/mcp-client-integration.md +38 -2
  87. package/catalog/frontmcp-development/SKILL.md +30 -44
  88. package/catalog/frontmcp-development/references/decorators-guide.md +15 -15
  89. package/catalog/frontmcp-extensibility/SKILL.md +1 -1
  90. package/catalog/frontmcp-extensibility/examples/skill-audit-log/verify-chain.md +8 -6
  91. package/catalog/frontmcp-extensibility/references/skill-audit-log.md +7 -2
  92. package/catalog/frontmcp-guides/SKILL.md +1 -1
  93. package/catalog/frontmcp-observability/SKILL.md +1 -1
  94. package/catalog/frontmcp-production-readiness/SKILL.md +1 -1
  95. package/catalog/frontmcp-production-readiness/examples/common-checklist/security-hardening.md +3 -2
  96. package/catalog/frontmcp-setup/SKILL.md +1 -1
  97. package/catalog/frontmcp-setup/examples/multi-app-composition/per-app-auth-and-isolation.md +7 -4
  98. package/catalog/frontmcp-setup/references/multi-app-composition.md +6 -5
  99. package/catalog/frontmcp-testing/SKILL.md +9 -1
  100. package/catalog/frontmcp-testing/references/test-auth.md +24 -0
  101. package/catalog/skills-manifest.json +653 -149
  102. package/package.json +1 -1
  103. package/src/manifest.d.ts +72 -1
  104. package/src/manifest.js +4 -1
  105. package/src/manifest.js.map +1 -1
  106. package/catalog/frontmcp-development/examples/create-tool/basic-class-tool.md +0 -80
  107. package/catalog/frontmcp-development/examples/create-tool/tool-with-di-and-errors.md +0 -132
  108. package/catalog/frontmcp-development/examples/create-tool/tool-with-rate-limiting-and-progress.md +0 -110
  109. package/catalog/frontmcp-development/examples/create-tool-annotations/destructive-delete-tool.md +0 -92
  110. package/catalog/frontmcp-development/examples/create-tool-annotations/readonly-query-tool.md +0 -59
  111. package/catalog/frontmcp-development/examples/create-tool-output-schema-types/primitive-and-media-outputs.md +0 -101
  112. package/catalog/frontmcp-development/examples/create-tool-output-schema-types/zod-raw-shape-output.md +0 -62
  113. package/catalog/frontmcp-development/examples/create-tool-output-schema-types/zod-schema-advanced-output.md +0 -101
  114. package/catalog/frontmcp-development/references/create-tool-annotations.md +0 -48
  115. package/catalog/frontmcp-development/references/create-tool-output-schema-types.md +0 -71
  116. package/catalog/frontmcp-development/references/create-tool.md +0 -806
package/README.md CHANGED
@@ -7,10 +7,10 @@
7
7
  </picture>
8
8
  <hr>
9
9
 
10
- **The TypeScript way to build MCP servers with decorators, DI, and Streamable HTTP.**
10
+ **The production-grade, TypeScript-first framework for building MCP servers decorators, DI, auth, and Streamable HTTP, batteries included.**
11
11
 
12
12
  [![NPM - @frontmcp/sdk](https://img.shields.io/npm/v/@frontmcp/sdk.svg?v=2)](https://www.npmjs.com/package/@frontmcp/sdk)
13
- [![Node](https://img.shields.io/badge/node-%3E%3D22-339933?logo=node.js&logoColor=white)](https://nodejs.org)
13
+ [![Node](https://img.shields.io/badge/node-%3E%3D24-339933?logo=node.js&logoColor=white)](https://nodejs.org)
14
14
  [![License](https://img.shields.io/github/license/agentfront/frontmcp.svg?v=1)](https://github.com/agentfront/frontmcp/blob/main/LICENSE)
15
15
  [![Snyk](https://snyk.io/test/github/agentfront/frontmcp/badge.svg)](https://snyk.io/test/github/agentfront/frontmcp)
16
16
 
@@ -20,12 +20,17 @@
20
20
 
21
21
  ---
22
22
 
23
- FrontMCP is a **TypeScript-first framework** for the [Model Context Protocol (MCP)](https://modelcontextprotocol.io).
24
- You write clean, typed code; FrontMCP handles the protocol, transport, DI, session/auth, and execution flow.
23
+ FrontMCP turns the [Model Context Protocol](https://modelcontextprotocol.io) into a
24
+ typed, declarative framework. You write clean `@Tool`, `@Resource`, and `@App`
25
+ classes; FrontMCP handles the protocol, transport, dependency injection, sessions,
26
+ auth, and execution flow — and the **same server runs locally and ships to
27
+ production unchanged**.
25
28
 
26
29
  ```ts
27
30
  import 'reflect-metadata';
31
+
28
32
  import { FrontMcp, LogLevel } from '@frontmcp/sdk';
33
+
29
34
  import HelloApp from './hello.app';
30
35
 
31
36
  @FrontMcp({
@@ -37,6 +42,14 @@ import HelloApp from './hello.app';
37
42
  export default class Server {}
38
43
  ```
39
44
 
45
+ ## Why FrontMCP
46
+
47
+ - **Typed by default** — decorators + Zod schemas give end-to-end types from input to output, with editor autocomplete and compile-time checks.
48
+ - **Batteries included** — auth (OAuth/JWKS/DCR), sessions, transport, discovery, and DI are built in, not bolted on.
49
+ - **Ship anywhere** — one codebase deploys to Node, Vercel, AWS Lambda, Cloudflare Workers, or a serverless bundle.
50
+ - **Production-minded** — stateful/stateless sessions, high-availability transport, structured observability, and a 95%+ tested core.
51
+ - **Extensible** — plugins, lifecycle hooks, OpenAPI adapters, and external MCP sub-apps when you outgrow the defaults.
52
+
40
53
  ## Installation
41
54
 
42
55
  **Node.js 24+** required.
@@ -50,34 +63,30 @@ npm i -D frontmcp @types/node@^24
50
63
  npx frontmcp init
51
64
  ```
52
65
 
53
- > Full setup guide: [Installation][docs-install]
66
+ > Full setup guide: [Installation][docs-install] &middot; [Quickstart][docs-quickstart]
54
67
 
55
68
  ## Capabilities
56
69
 
57
- | Capability | Description | Docs |
58
- | -------------------- | ------------------------------------------------------------------------------- | ------------------------------- |
59
- | **@FrontMcp Server** | Decorator-configured server with info, apps, HTTP, logging, session, auth | [Server][docs-server] |
60
- | **@App** | Organizational units grouping tools, resources, prompts with optional isolation | [Apps][docs-apps] |
61
- | **@Tool** | Typed actions with Zod schemas — class or function style | [Tools][docs-tools] |
62
- | **@Resource** | Read-only data exposure with static and template URIs | [Resources][docs-resources] |
63
- | **@Prompt** | Reusable message templates returning `GetPromptResult` | [Prompts][docs-prompts] |
64
- | **@Agent** | Orchestrated multi-step tool chains | [Agents][docs-agents] |
65
- | **Elicitation** | Request structured user input mid-flow | [Elicitation][docs-elicitation] |
66
- | **Skills** | HTTP-discoverable tool manifests for agent marketplaces | [Skills][docs-skills] |
67
- | **Discovery** | Automatic capability advertisement for MCP clients | [Discovery][docs-discovery] |
68
- | **Authentication** | Remote OAuth, Local OAuth, JWKS, DCR, per-app auth | [Authentication][docs-auth] |
69
- | **Sessions** | Stateful/stateless session modes with JWT or UUID transport IDs | [Server][docs-server] |
70
- | **Direct Client** | In-process `create()`, `connect()`, `connectOpenAI()`, `connectClaude()` | [Direct Client][docs-direct] |
71
- | **Transport** | Streamable HTTP + SSE with session headers | [Transport][docs-transport] |
72
- | **Ext-Apps** | Mount external MCP servers as sub-apps | [Ext-Apps][docs-ext-apps] |
73
- | **Hooks** | 5 hook families: tool, list-tools, HTTP, resource, prompt | [Hooks][docs-hooks] |
74
- | **Providers / DI** | Scoped dependency injection with GLOBAL and CONTEXT scopes | [Providers][docs-providers] |
75
- | **Plugins** | Cache, Remember, CodeCall, Dashboard — or build your own | [Plugins][docs-plugins] |
76
- | **Adapters** | Generate tools from OpenAPI specs | [Adapters][docs-adapters] |
77
- | **Testing** | E2E fixtures, matchers, HTTP mocking for MCP servers | [Testing][docs-testing] |
78
- | **UI Library** | HTML/React widgets, SSR, MCP Bridge, web components | [UI][docs-ui] |
79
- | **CLI** | `create`, `init`, `dev`, `build`, `inspector`, `doctor` | [CLI][docs-install] |
80
- | **Deployment** | Local dev, production builds, version alignment | [Deployment][docs-deploy] |
70
+ **Build** decorator-configured [`@FrontMcp` server][docs-server] and [`@App`][docs-apps]
71
+ domains; typed [`@Tool`][docs-tools], [`@Resource`][docs-resources], and
72
+ [`@Prompt`][docs-prompts] primitives; [`@Agent`][docs-agents] multi-step chains; and
73
+ scoped [Providers / DI][docs-providers].
74
+
75
+ **Secure** [Remote & Local OAuth, JWKS, DCR, per-app auth][docs-auth] with
76
+ stateful / stateless [sessions][docs-server] (JWT or UUID transport IDs).
77
+
78
+ **Connect & operate** [Streamable HTTP + SSE transport][docs-transport],
79
+ capability [discovery][docs-discovery], [elicitation][docs-elicitation],
80
+ [hooks][docs-hooks], HTTP-discoverable [skills][docs-skills],
81
+ [external MCP sub-apps][docs-ext-apps], an in-process [Direct Client][docs-direct]
82
+ (`connectOpenAI` / `connectClaude`), and first-class [deployment][docs-deploy].
83
+
84
+ **Extend & tooling** official [plugins][docs-plugins] (Cache, Remember, CodeCall,
85
+ Dashboard), the [OpenAPI adapter][docs-adapters], a [UI library][docs-ui] (HTML/React
86
+ widgets, SSR, MCP Bridge), an [E2E testing framework][docs-testing], and a
87
+ [CLI][docs-install] (`create`, `init`, `dev`, `build`, `inspect`, `doctor`).
88
+
89
+ Full reference: **[docs.agentfront.dev/frontmcp][docs-home]**
81
90
 
82
91
  ## Packages
83
92
 
@@ -166,3 +166,29 @@ frontmatter `name`.
166
166
 
167
167
  - [Documentation](https://docs.agentfront.dev/frontmcp/...)
168
168
  - Related skills: `related-skill-a`, `related-skill-b`
169
+
170
+ ---
171
+
172
+ ## Alternative: `layout: 'component'`
173
+
174
+ The template above describes the **router layout** (`layout: 'router'`, the default) — a Scenario Routing Table SKILL.md, `references/<topic>.md` files, and examples grouped under `examples/<topic>/<example>.md`.
175
+
176
+ For per-thing skills (`create-tool`, `create-resource`, `create-prompt`, etc.) use the **component layout** instead. Opt in by setting `layout: component` in the manifest entry. Differences:
177
+
178
+ | Aspect | Router layout (default) | Component layout |
179
+ | -------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
180
+ | SKILL.md frontmatter | Minimal — `name`, `description`, optional `priority`/`visibility` | **Rich** — multi-line `description:` with an explicit `Triggers:` list, `paths:` glob array, `when_to_use` block, top-level `priority`/`visibility`/`tags`/`category`. Designed for Claude Code's auto-discovery heuristics. |
181
+ | `examples/` | Grouped: `examples/<reference>/<example>.md` | Flat: `examples/<example>.md` |
182
+ | `rules/` | Not used | `rules/<rule>.md` — short DO/DON'T constraint files with `name`, `constraint`, `severity: required \| recommended` frontmatter |
183
+ | Manifest entry | `references[].examples[]` | Top-level `examples[]` + top-level `rules[]` |
184
+ | SKILL.md body | "Scenario Routing Table" pointing at references | "Scenario Routing Table" pointing at examples + a `Rules` table pointing at `rules/*.md` |
185
+
186
+ Every example file MUST still satisfy the same alignment invariants enforced by `skills-validation.spec.ts`:
187
+
188
+ - Frontmatter `description` = first paragraph after the H1.
189
+ - Frontmatter `features` = bullets under `## What This Demonstrates`.
190
+ - Manifest example entry `description` / `level` / `tags` / `features` = the file's frontmatter.
191
+
192
+ For component-layout skills the manifest sync extends to `rules[]`: the rule file's frontmatter `constraint` and `severity` must match the manifest entry.
193
+
194
+ See `create-tool/SKILL.md` for a complete working example of the component layout.
@@ -0,0 +1,318 @@
1
+ ---
2
+ name: create-tool
3
+ description: |
4
+ ALWAYS use this skill when the user asks to build, modify, or audit a FrontMCP tool.
5
+ Covers everything inside `@Tool({...})`: class and function-style tools, Zod input/output
6
+ schemas with derived `execute()` types, dependency injection (`this.get` / `this.tryGet`),
7
+ error handling (`this.fail`, MCP error classes), throttling (rate-limit / concurrency /
8
+ timeout), auth providers (single / multi / vault), availability constraints
9
+ (`availableWhen`), elicitation (`this.elicit`), interactive UI widgets via `@Tool({ ui })`
10
+ (MCP Apps / SEP-1865 — including `.tsx` FileSource, CSP, `window.FrontMcpBridge`,
11
+ host-detect `resourceMode`), annotations (`readOnlyHint` / `destructiveHint` / …),
12
+ `examples` metadata, registration in `@App({ tools })`, and per-tool unit testing.
13
+
14
+ Does NOT cover:
15
+ - Read-only data exposed via a URI — use `create-resource`
16
+ - Conversation templates / system prompts — use `create-prompt`
17
+ - Multi-tool orchestration loops — use `create-agent`
18
+ - Background work / pipelines — use `create-job` / `create-workflow`
19
+ - Server-level config (transport, sessions, auth modes) — use `config` / `auth`
20
+
21
+ Triggers: `@Tool`, ToolContext, tool decorator, MCP tool, snake_case tool name,
22
+ inputSchema, outputSchema, ToolInputOf, ToolOutputOf, `@Tool({ ui })`, tool UI widget,
23
+ MCP Apps widget, FileSource widget, `.tsx` widget, ui.csp, ui.resourceMode,
24
+ window.FrontMcpBridge, tool annotations, readOnlyHint, destructiveHint, rate-limit tool,
25
+ throttle tool, concurrency tool, tool timeout, this.fail, this.respond, this.fetch,
26
+ this.notify, this.progress, this.elicit, ElicitationDisabledError, ToolContext.execute,
27
+ this.get(TOKEN), this.tryGet, register tool in @App, tool examples metadata,
28
+ availableWhen, missingAxes, `tool()` function builder, Tool.esm, Tool.remote,
29
+ PublicMcpError, ResourceNotFoundError, MCP_ERROR_CODES, ui://widget.
30
+
31
+ when_to_use: |
32
+ Trigger when creating or editing a `*.tool.ts` / `*.tool.tsx` file, adding a `@Tool`
33
+ decorator, defining `inputSchema` / `outputSchema` for a tool, deriving `execute()`
34
+ parameter or return types, wiring dependency injection into a tool, returning
35
+ structured / media / resource content, adding a `ui:` block (HTML / MDX / React /
36
+ FileSource), configuring throttling, declaring auth providers, restricting platforms
37
+ via `availableWhen`, requesting interactive input via `this.elicit`, adding tool
38
+ `annotations`, or registering a tool in `@App({ tools })`.
39
+
40
+ paths: '**/*.tool.ts, **/*.tool.tsx, **/tools/**/*.ts, **/apps/*/tools/**, **/*.tool.spec.ts'
41
+
42
+ layout: component
43
+ license: Apache-2.0
44
+ priority: 10
45
+ visibility: public
46
+ tags:
47
+ [
48
+ development,
49
+ tool,
50
+ create-tool,
51
+ decorator,
52
+ input-schema,
53
+ output-schema,
54
+ ToolContext,
55
+ ui,
56
+ mcp-apps,
57
+ annotations,
58
+ throttling,
59
+ auth-providers,
60
+ availability,
61
+ elicitation,
62
+ ]
63
+ category: development/create
64
+ bundle: [recommended, minimal, full]
65
+ allowed-tools: Read Edit Write Grep Glob Bash
66
+
67
+ metadata:
68
+ docs: https://docs.agentfront.dev/frontmcp/servers/tools
69
+ ---
70
+
71
+ # Create a FrontMCP Tool
72
+
73
+ 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).
74
+
75
+ This skill is the single source of truth for building tools. It owns:
76
+
77
+ - The `@Tool` decorator surface
78
+ - Input / output schemas and how to derive `execute()` types from them
79
+ - Dependency injection, error handling, progress / notifications
80
+ - Throttling: rate-limit, concurrency, timeout
81
+ - Auth providers and the credential vault
82
+ - Platform / runtime / surface availability constraints
83
+ - Elicitation (interactive input mid-execution)
84
+ - **Tool UI widgets** — the `ui:` block, MCP Apps / SEP-1865, `.tsx` FileSource, CSP, `window.FrontMcpBridge`
85
+ - Annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`)
86
+ - The `examples` metadata field
87
+ - Function-style tools, remote / ESM tools
88
+ - Registration patterns
89
+ - Per-tool unit testing
90
+
91
+ For everything else — resources, prompts, agents, jobs, workflows, adapters, plugins, providers, channels — use the matching `create-<thing>` skill.
92
+
93
+ > **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.
94
+
95
+ ---
96
+
97
+ ## Inherited defaults
98
+
99
+ This skill ALWAYS applies these defaults — never opt out without an audited reason:
100
+
101
+ | Default | Source | What it enforces |
102
+ | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
103
+ | **`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. |
104
+ | **`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. |
105
+ | **`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. |
106
+ | **`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. |
107
+ | **Tool names are `snake_case`** | [`rules/snake-case-tool-names.md`](./rules/snake-case-tool-names.md) | MCP protocol convention. `get_weather`, not `getWeather`. |
108
+ | **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. |
109
+ | **`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. |
110
+ | **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. |
111
+ | **`.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). |
112
+ | **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. |
113
+
114
+ 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.
115
+
116
+ ---
117
+
118
+ ## When to invoke this skill
119
+
120
+ ### Must use
121
+
122
+ - Creating a new `*.tool.ts` file
123
+ - Adding the `@Tool({...})` decorator
124
+ - Defining or changing `inputSchema` / `outputSchema`
125
+ - Adding a `ui:` block to a tool (any template type)
126
+ - Adding `annotations`, `rateLimit`, `concurrency`, `timeout`, `authProviders`, `availableWhen`, `examples` to a tool
127
+ - Calling `this.elicit(...)` from `execute()`
128
+ - Registering a tool in `@App({ tools })` or `@FrontMcp({ tools })`
129
+ - Writing the unit test for a tool
130
+
131
+ ### Recommended
132
+
133
+ - Auditing an existing tool for the inherited defaults above
134
+ - Picking between class-style and function-style (`tool({...})(handler)`)
135
+ - Choosing the right output-schema variant for the data you're returning
136
+ - Converting a tool's auth from a single string to the full `{ name, scopes, required }` mapping
137
+ - Deciding whether a side-effecting tool needs `destructiveHint: true`
138
+
139
+ ### Skip when
140
+
141
+ - You're not building a tool. Use the matching `create-<thing>` skill.
142
+
143
+ ---
144
+
145
+ ## Decision tree
146
+
147
+ ```text
148
+ 1. What kind of tool?
149
+ ├── Tiny one-off → function-style: `tool({...})((input, ctx) => …)`
150
+ │ See: examples/02-basic-function-tool.md
151
+ ├── Anything with DI, lifecycle, hooks, or UI → class-style
152
+ │ See: examples/01-basic-class-tool.md
153
+ └── Externally hosted (ESM URL or remote MCP server) → Tool.esm / Tool.remote
154
+ See: references/remote-and-esm.md
155
+
156
+ 2. What does it return?
157
+ ├── Structured JSON → outputSchema: { field: z.string(), … }
158
+ │ See: examples/03-tool-with-zod-shape-output.md
159
+ ├── A primitive (text/num) → outputSchema: 'string' | 'number' | 'boolean' | 'date'
160
+ │ See: examples/05-tool-with-primitive-output.md
161
+ ├── Media (image/audio) → outputSchema: 'image' | 'audio'
162
+ │ See: examples/06-tool-with-media-output.md
163
+ ├── A resource link → outputSchema: 'resource' | 'resource_link'
164
+ │ See: examples/26-tool-with-resource-link-output.md
165
+ └── Several content blocks → outputSchema: ['string', 'image']
166
+ See: examples/06-tool-with-media-output.md
167
+
168
+ 3. Does it need shared services / config / clients?
169
+ YES → register a @Provider; inject via this.get(TOKEN)
170
+ See: examples/08-tool-with-provider-injection.md
171
+
172
+ 4. Does it call an external HTTP API?
173
+ YES → use this.fetch(input, init?) (context propagation)
174
+ See: examples/11-tool-with-fetch.md
175
+
176
+ 5. Does it need user credentials from an OAuth provider?
177
+ YES → declare authProviders: ['provider'] (or full mapping)
178
+ See: examples/13-tool-with-single-auth-provider.md, 15-tool-with-credential-vault.md
179
+
180
+ 6. Is it expensive / rate-limited / slow?
181
+ YES → add rateLimit / concurrency / timeout
182
+ See: examples/16-tool-with-rate-limit.md, 17-tool-with-concurrency-and-timeout.md
183
+
184
+ 7. Does it run for a while? Want progress?
185
+ YES → call this.progress(n, total, msg)
186
+ See: examples/18-tool-with-progress-and-notify.md
187
+
188
+ 8. Does it need a confirmation / extra input mid-run?
189
+ YES → this.elicit('msg', { fieldSchema })
190
+ See: examples/19-tool-with-elicitation.md
191
+
192
+ 9. Is it destructive / read-only / idempotent / open-world?
193
+ YES → annotations: { destructiveHint, readOnlyHint, idempotentHint, openWorldHint }
194
+ See: examples/20-tool-with-annotations.md
195
+
196
+ 10. Should it only run on certain OSes / runtimes / build targets?
197
+ YES → availableWhen: { os, runtime, deployment, provider, target, surface, env }
198
+ See: examples/21-tool-with-availability-constraints.md
199
+
200
+ 11. Should the result render as a widget in the host UI?
201
+ YES → ui: { template, … }
202
+ ├── Quick HTML → ui: { template: (ctx) => '<div>…</div>' }
203
+ │ See: examples/22-tool-with-ui-html-template.md
204
+ ├── React widget (file) → ui: { template: { file: widgetPath } }
205
+ │ See: examples/23-tool-with-ui-filesource-tsx.md
206
+ ├── Calls other tools → widgetAccessible: true + window.FrontMcpBridge
207
+ │ See: examples/24-tool-with-ui-csp-and-bridge.md
208
+ └── Claude target → resourceMode is auto-detected; do not set
209
+ See: references/ui-widgets.md
210
+
211
+ 12. Does it hand off long work to a job?
212
+ YES → kick off a job + return a tracking handle
213
+ See: examples/25-tool-handing-off-to-job.md
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Scenario routing table
219
+
220
+ | Scenario | Example | Why |
221
+ | -------------------------------------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
222
+ | Build the simplest possible tool | [`01-basic-class-tool`](./examples/01-basic-class-tool.md) | Foundation — every other example builds on this shape |
223
+ | One-off math / formatter | [`02-basic-function-tool`](./examples/02-basic-function-tool.md) | `tool()` builder is fine for trivial pure-input tools |
224
+ | 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 |
225
+ | 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 |
226
+ | Output is a primitive | [`05-tool-with-primitive-output`](./examples/05-tool-with-primitive-output.md) | `'string'` / `'number'` / `'date'` literals |
227
+ | Output is binary / multi-content | [`06-tool-with-media-output`](./examples/06-tool-with-media-output.md) | `'image'`, `'audio'`, `['string', 'image']` |
228
+ | 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 |
229
+ | Tool composes multiple services | [`09-tool-with-multiple-providers`](./examples/09-tool-with-multiple-providers.md) | Realistic shape — DB + cache + config in one tool |
230
+ | Tool calls an external HTTP API | [`11-tool-with-fetch`](./examples/11-tool-with-fetch.md) | `this.fetch(url, init?)` — context propagation, error handling |
231
+ | 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 |
232
+ | Tool needs OAuth credentials | [`13-tool-with-single-auth-provider`](./examples/13-tool-with-single-auth-provider.md) | `authProviders: ['github']` — string shorthand |
233
+ | 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` |
234
+ | Tool reads a per-session secret | [`15-tool-with-credential-vault`](./examples/15-tool-with-credential-vault.md) | `this.authProviders.headers(...)`, vault patterns |
235
+ | Rate-limit an expensive operation | [`16-tool-with-rate-limit`](./examples/16-tool-with-rate-limit.md) | `rateLimit: { maxRequests, windowMs }` |
236
+ | Cap concurrency + add a timeout | [`17-tool-with-concurrency-and-timeout`](./examples/17-tool-with-concurrency-and-timeout.md) | Production-ready throttling shape |
237
+ | 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` |
238
+ | Tool that asks the user mid-run | [`19-tool-with-elicitation`](./examples/19-tool-with-elicitation.md) | `this.elicit` with Zod schema |
239
+ | Tool with behavioral hints for the client | [`20-tool-with-annotations`](./examples/20-tool-with-annotations.md) | `readOnlyHint` / `destructiveHint` / `idempotentHint` / `openWorldHint` |
240
+ | Tool restricted to one OS / runtime / target | [`21-tool-with-availability-constraints`](./examples/21-tool-with-availability-constraints.md) | `availableWhen` axes |
241
+ | 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>' }` |
242
+ | 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 |
243
+ | 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` |
244
+ | 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 |
245
+ | 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 |
246
+ | Tool with `examples` metadata for discovery | [`27-tool-with-examples-metadata`](./examples/27-tool-with-examples-metadata.md) | `examples: [{ description, input, output? }]` |
247
+
248
+ ---
249
+
250
+ ## Verification checklist
251
+
252
+ Before considering a tool "done":
253
+
254
+ - [ ] Class extends `ToolContext` (no generics) OR uses `tool()` function builder
255
+ - [ ] `@Tool({ name, description, inputSchema, outputSchema })` — all four present
256
+ - [ ] `name` is `snake_case`
257
+ - [ ] `inputSchema` is a Zod raw shape (NOT wrapped in `z.object`)
258
+ - [ ] `outputSchema` is defined (Zod shape / primitive / media / array)
259
+ - [ ] `execute()` parameter and return types derived via `ToolInputOf<>` / `ToolOutputOf<>`
260
+ - [ ] No `try/catch` around `execute()` body
261
+ - [ ] Business errors use `this.fail(new SomeMcpError(…))`, not raw `throw`
262
+ - [ ] Tool registered in an `@App({ tools })` (or `@FrontMcp({ tools })` for single-app servers)
263
+ - [ ] If `ui:`: `.tsx` widget paths anchored via `fileURLToPath(new URL(...))`
264
+ - [ ] If `ui:`: `ui.resourceMode` left unset (host-detect) unless an explicit override is intentional
265
+ - [ ] Unit test in `<name>.tool.spec.ts` covering happy + at least one failure path
266
+ - [ ] Optional: `annotations`, `rateLimit` / `concurrency` / `timeout`, `authProviders`, `availableWhen`, `examples` set when the tool's behavior warrants them
267
+
268
+ ---
269
+
270
+ ## References (deep dives)
271
+
272
+ | Reference | Covers |
273
+ | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
274
+ | [`quick-start.md`](./references/quick-start.md) | 60-second tour: minimal tool, registration, calling it from a test |
275
+ | [`decorator-options.md`](./references/decorator-options.md) | Every field on `@Tool({...})` — what it does, default, when to set it |
276
+ | [`input-schema.md`](./references/input-schema.md) | Raw shape vs `z.object`, refinements, defaults, optional, describe |
277
+ | [`output-schema.md`](./references/output-schema.md) | All supported output types: Zod shape, Zod schema, primitives, media, arrays |
278
+ | [`derived-types.md`](./references/derived-types.md) | `ToolInputOf` / `ToolOutputOf` patterns, file layout, schema hoisting |
279
+ | [`execution-context.md`](./references/execution-context.md) | `ToolContext` methods + properties — `this.get`, `this.fetch`, `this.notify`, `this.context`, etc. |
280
+ | [`error-handling.md`](./references/error-handling.md) | `this.fail`, MCP error classes (`PublicMcpError`, `ResourceNotFoundError`), error flow, when to throw vs `fail` |
281
+ | [`throttling.md`](./references/throttling.md) | `rateLimit`, `concurrency`, `timeout` — semantics, interaction, defaults |
282
+ | [`auth-providers.md`](./references/auth-providers.md) | `authProviders` string shorthand vs full mapping, scopes, alias, credential vault basics |
283
+ | [`availability.md`](./references/availability.md) | `availableWhen` axes (os / runtime / deployment / provider / target / surface / env), `missingAxes`, `isPlatform` |
284
+ | [`elicitation.md`](./references/elicitation.md) | `this.elicit`, server-level enable, `ElicitationDisabledError`, accept / decline / cancel |
285
+ | [`ui-widgets.md`](./references/ui-widgets.md) | `@Tool({ ui })` — template formats, `servingMode`, `resourceMode` host-detect, CSP, `widgetAccessible`, MCP Apps spec |
286
+ | [`annotations.md`](./references/annotations.md) | `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`, `title` |
287
+ | [`function-style-builder.md`](./references/function-style-builder.md) | `tool({...})(handler)` — when to pick over a class, register, ctx parameter |
288
+ | [`remote-and-esm.md`](./references/remote-and-esm.md) | `Tool.esm(...)` / `Tool.remote(...)` — load tools from ESM URLs or remote MCP servers |
289
+ | [`registration.md`](./references/registration.md) | `@App({ tools })` vs `@FrontMcp({ tools })`, multi-app composition |
290
+ | [`file-layout.md`](./references/file-layout.md) | Flat-sibling vs folder-per-tool, `<name>.schema.ts` / `<name>.tool.ts` / `<name>.tool.spec.ts` |
291
+ | [`testing.md`](./references/testing.md) | Per-tool unit tests — `@frontmcp/testing`, mocking DI, asserting output validation |
292
+
293
+ ## Rules (constraints — read these once, then they're enforced)
294
+
295
+ | Rule | Constraint |
296
+ | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
297
+ | [`input-schema-is-raw-shape.md`](./rules/input-schema-is-raw-shape.md) | `inputSchema` is a raw Zod shape, never `z.object(...)` |
298
+ | [`always-define-output-schema.md`](./rules/always-define-output-schema.md) | Every tool defines `outputSchema` |
299
+ | [`derive-execute-types.md`](./rules/derive-execute-types.md) | `execute()` types come from `ToolInputOf` / `ToolOutputOf` — never duplicated inline |
300
+ | [`no-toolcontext-generics.md`](./rules/no-toolcontext-generics.md) | `class MyTool extends ToolContext` — no `<typeof inputSchema>` generic |
301
+ | [`snake-case-tool-names.md`](./rules/snake-case-tool-names.md) | Tool `name` is `snake_case` |
302
+ | [`no-try-catch-around-execute.md`](./rules/no-try-catch-around-execute.md) | The framework owns error flow — don't wrap `execute()` body |
303
+ | [`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 |
304
+ | [`register-in-app.md`](./rules/register-in-app.md) | Register tools in `@App({ tools })` for modularity / lifecycle |
305
+ | [`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 |
306
+ | [`widget-resource-mode-host-detect.md`](./rules/widget-resource-mode-host-detect.md) | Leave `ui.resourceMode` unset — let host detect |
307
+
308
+ ## Accessing this skill
309
+
310
+ | Mode | How |
311
+ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
312
+ | **Filesystem** | Read `libs/skills/catalog/create-tool/` directly. `SKILL.md` is the entry point. |
313
+ | **CLI** | `frontmcp skills list`, `frontmcp skills read create-tool`, `frontmcp skills read create-tool:references/<file>.md`, `frontmcp skills install create-tool` |
314
+ | **MCP `skill://`** | When mounted on a FrontMCP server, available at `skill://create-tool/SKILL.md`, `skill://create-tool/references/{file}.md`, etc. (SEP-2640) |
315
+
316
+ ## Related skills
317
+
318
+ `create-resource`, `create-prompt`, `create-agent`, `create-provider`, `create-job`, `create-workflow`, `create-adapter`, `create-plugin`, `decorators-guide`, `architecture`, `testing`, `auth`
@@ -0,0 +1,112 @@
1
+ ---
2
+ name: 01-basic-class-tool
3
+ level: basic
4
+ description: Minimal class-based tool with Zod input/output schemas and types derived from the schemas. The foundation every other example builds on.
5
+ tags: [foundation, class-tool, output-schema, derived-types]
6
+ features:
7
+ - 'Extending `ToolContext` (no generics) and implementing `execute()`'
8
+ - 'Hoisting `inputSchema` / `outputSchema` to a sibling `.schema.ts` file'
9
+ - 'Deriving the `execute()` parameter and return types via `ToolInputOf<>` / `ToolOutputOf<>`'
10
+ - 'Using a Zod raw shape for `inputSchema` (not `z.object(...)`)'
11
+ - "Always defining `outputSchema` so the tool can't accidentally leak extra fields"
12
+ - 'Registering the tool in an `@App({ tools })`'
13
+ ---
14
+
15
+ # Basic Class Tool
16
+
17
+ Minimal class-based tool with Zod input/output schemas and types derived from the schemas. The foundation every other example builds on.
18
+
19
+ The foundation. Two files (schema + tool), schemas as the single source of truth, derived `execute()` types, full output validation. Every other example in this skill builds on this shape.
20
+
21
+ ## Files
22
+
23
+ ```text
24
+ src/apps/main/tools/
25
+ ├── greet-user.schema.ts # input/output schemas + derived types
26
+ ├── greet-user.tool.ts # @Tool class, execute()
27
+ └── greet-user.tool.spec.ts # unit test
28
+ ```
29
+
30
+ ## Code
31
+
32
+ ```typescript
33
+ // src/apps/main/tools/greet-user.schema.ts
34
+ import { ToolInputOf, ToolOutputOf, z } from '@frontmcp/sdk';
35
+
36
+ export const inputSchema = {
37
+ name: z.string().min(1).describe('The name of the user to greet'),
38
+ };
39
+
40
+ export const outputSchema = {
41
+ greeting: z.string(),
42
+ };
43
+
44
+ export type GreetUserInput = ToolInputOf<{ inputSchema: typeof inputSchema }>;
45
+ export type GreetUserOutput = ToolOutputOf<{ outputSchema: typeof outputSchema }>;
46
+ ```
47
+
48
+ ```typescript
49
+ // src/apps/main/tools/greet-user.tool.ts
50
+ import { Tool, ToolContext } from '@frontmcp/sdk';
51
+
52
+ import { inputSchema, outputSchema, type GreetUserInput, type GreetUserOutput } from './greet-user.schema';
53
+
54
+ @Tool({
55
+ name: 'greet_user',
56
+ description: 'Greet a user by name',
57
+ inputSchema,
58
+ outputSchema,
59
+ })
60
+ export class GreetUserTool extends ToolContext {
61
+ async execute(input: GreetUserInput): Promise<GreetUserOutput> {
62
+ return { greeting: `Hello, ${input.name}!` };
63
+ }
64
+ }
65
+ ```
66
+
67
+ ```typescript
68
+ // src/apps/main/index.ts
69
+ import { App } from '@frontmcp/sdk';
70
+
71
+ import { GreetUserTool } from './tools/greet-user.tool';
72
+
73
+ @App({
74
+ name: 'main',
75
+ tools: [GreetUserTool],
76
+ })
77
+ export class MainApp {}
78
+ ```
79
+
80
+ > **Testing.** Per-tool tests use the `@frontmcp/testing` surface — `TestServer` + Playwright-style `test` / `expect` fixtures, with `mcpMatchers` for response assertions. The full pattern lives in the dedicated `testing` skill; this skill stays focused on building the tool itself.
81
+
82
+ ## What This Demonstrates
83
+
84
+ - Extending `ToolContext` (no generics) and implementing `execute()`
85
+ - Hoisting `inputSchema` / `outputSchema` to a sibling `.schema.ts` file
86
+ - Deriving the `execute()` parameter and return types via `ToolInputOf<>` / `ToolOutputOf<>`
87
+ - Using a Zod raw shape for `inputSchema` (not `z.object(...)`)
88
+ - Always defining `outputSchema` so the tool can't accidentally leak extra fields
89
+ - Registering the tool in an `@App({ tools })`
90
+
91
+ ## Why each choice matters
92
+
93
+ - **No `ToolContext<typeof inputSchema>` generic** — input/output types are auto-inferred from the `@Tool` decorator at the class level. Adding the generic is a smell (see `rules/no-toolcontext-generics.md`).
94
+ - **Hoist only the schemas** (not the decorator config) to `<name>.schema.ts` — specs, sibling tools, and generated clients can `import { inputSchema, GreetUserInput }` without dragging the `@Tool` class along.
95
+ - **Use `ToolInputOf<>` / `ToolOutputOf<>`** instead of inline annotations like `execute(input: { name: string })`, which silently drift when the schema changes.
96
+ - **Raw Zod shape** for `inputSchema` — `{ name: z.string() }`, not `z.object({ name: z.string() })`. The framework wraps it internally.
97
+ - **`outputSchema` always present** — without it, returning `{ greeting, leakedSecret }` would expose `leakedSecret` to the client; with it, the field is stripped before the response leaves.
98
+ - **Register in `@App({ tools })`** (not directly in `@FrontMcp({ tools })`) — apps provide per-app lifecycle, auth, and hooks; top-level registration is the simple-server escape hatch.
99
+
100
+ ## When to pick this shape over alternatives
101
+
102
+ - **Class** (this example) vs **function** (`tool({...})(handler)`) — pick class for anything with DI (`this.get`), lifecycle, hooks, or UI widgets. Pick function only for trivial pure-input tools. See [`02-basic-function-tool`](./02-basic-function-tool.md).
103
+ - **Sibling files** (this example) vs **folder-per-tool** — pick siblings for apps with ≤3 tools each. Promote to a folder once the tool has local helpers, fixtures, or its own error types. See [`references/file-layout.md`](../references/file-layout.md).
104
+
105
+ ## Related rules
106
+
107
+ - [`rules/input-schema-is-raw-shape.md`](../rules/input-schema-is-raw-shape.md)
108
+ - [`rules/always-define-output-schema.md`](../rules/always-define-output-schema.md)
109
+ - [`rules/derive-execute-types.md`](../rules/derive-execute-types.md)
110
+ - [`rules/no-toolcontext-generics.md`](../rules/no-toolcontext-generics.md)
111
+ - [`rules/snake-case-tool-names.md`](../rules/snake-case-tool-names.md)
112
+ - [`rules/register-in-app.md`](../rules/register-in-app.md)