@frontmcp/skills 1.3.0 → 1.4.0

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
@@ -1,806 +0,0 @@
1
- ---
2
- name: create-tool
3
- description: Build MCP tools with Zod input/output validation and dependency injection
4
- ---
5
-
6
- # Creating an MCP Tool
7
-
8
- Tools are the primary way to expose executable actions to AI clients in the MCP protocol. In FrontMCP, tools are TypeScript classes that extend `ToolContext`, decorated with `@Tool`, and registered on a `@FrontMcp` server or inside an `@App`.
9
-
10
- ## When to Use This Skill
11
-
12
- ### Must Use
13
-
14
- - Building a new executable action that AI clients can invoke via MCP
15
- - Defining typed input schemas with Zod validation for tool parameters
16
- - Adding output schema validation to prevent data leaks from tool responses
17
-
18
- ### Recommended
19
-
20
- - Adding rate limiting, concurrency control, or timeouts to existing tools
21
- - Integrating dependency injection into tool execution
22
- - Converting raw function handlers into class-based `ToolContext` patterns
23
-
24
- ### Skip When
25
-
26
- - Exposing read-only data that does not require execution logic (see `create-resource`)
27
- - Building conversational templates or system prompts (see `create-prompt`)
28
- - Orchestrating multi-tool workflows with conditional logic (see `create-agent`)
29
-
30
- > **Decision:** Use this skill when you need an AI-callable action that accepts validated input, performs work, and returns structured output.
31
-
32
- ## Class-Based Pattern
33
-
34
- Create a class extending `ToolContext` and implement the `execute(input)` method. The `@Tool` decorator requires at minimum a `name` and an `inputSchema`. Do **not** parameterize `ToolContext` with explicit generics — the input/output types are inferred automatically from the `@Tool` decorator. Hoist the **schemas only** to module scope and derive the `execute()` parameter and return types with `ToolInputOf<>` / `ToolOutputOf<>`, so the schema stays the single source of truth (issue #405). Keep `name`, `description`, `annotations`, `rateLimit`, etc. inside the decorator where they belong.
35
-
36
- ```typescript
37
- import { Tool, ToolContext, ToolInputOf, ToolOutputOf, z } from '@frontmcp/sdk';
38
-
39
- const inputSchema = {
40
- name: z.string().describe('The name of the user to greet'),
41
- };
42
-
43
- const outputSchema = {
44
- greeting: z.string(),
45
- };
46
-
47
- type GreetUserInput = ToolInputOf<{ inputSchema: typeof inputSchema }>;
48
- type GreetUserOutput = ToolOutputOf<{ outputSchema: typeof outputSchema }>;
49
-
50
- @Tool({
51
- name: 'greet_user',
52
- description: 'Greet a user by name',
53
- inputSchema,
54
- outputSchema,
55
- })
56
- class GreetUserTool extends ToolContext {
57
- async execute(input: GreetUserInput): Promise<GreetUserOutput> {
58
- return { greeting: `Hello, ${input.name}!` };
59
- }
60
- }
61
- ```
62
-
63
- > **Why derive the types?** Hand-typing `execute(input: { name: string })` next to the schema is a second declaration of the same shape. Change the schema without touching the annotation and TypeScript happily compiles — validation moves to runtime and the IDE never warns. Derived types make the schema the single source of truth: change a Zod field and the `execute()` signature follows automatically. Only the **schemas** are hoisted so they can be re-imported by specs, sibling tools, and generated clients; everything else (`name`, `description`, `annotations`, `rateLimit`, `authProviders`, …) stays inside `@Tool({…})` where the decorator config naturally lives. See [File layout](#file-layout) for sibling-file and folder-per-tool variants.
64
-
65
- ### Available Context Methods and Properties
66
-
67
- `ToolContext` extends `ExecutionContextBase`, which provides:
68
-
69
- **Methods:**
70
-
71
- - `execute(input: In): Promise<Out>` -- the main method you implement
72
- - `this.get(token)` -- resolve a dependency from DI (throws if not found)
73
- - `this.tryGet(token)` -- resolve a dependency from DI (returns `undefined` if not found)
74
- - `this.fail(err)` -- abort execution, triggers error flow (never returns)
75
- - `this.mark(stage)` -- set the active execution stage for debugging/tracking
76
- - `this.fetch(input, init?)` -- HTTP fetch with context propagation
77
- - `this.notify(message, level?)` -- send a log-level notification to the client
78
- - `this.progress(progress, total?, message?)` -- send a progress notification to the client (returns `Promise<boolean>`)
79
-
80
- **Properties:**
81
-
82
- - `this.input` -- the validated input object
83
- - `this.output` -- the output (available after execute)
84
- - `this.metadata` -- tool metadata from the decorator
85
- - `this.scope` -- the current scope instance
86
- - `this.context` -- the execution context (see below)
87
-
88
- **`this.context` properties (FrontMcpContext):**
89
-
90
- | Property | Type | Description |
91
- | -------------- | ------------------- | ----------------------------------- |
92
- | `requestId` | `string` | Unique ID for this request |
93
- | `sessionId` | `string` | Session identifier |
94
- | `scopeId` | `string` | Scope identifier |
95
- | `authInfo` | `Partial<AuthInfo>` | Authentication info for the request |
96
- | `traceContext` | `TraceContext` | Distributed tracing context |
97
- | `timestamp` | `number` | Request timestamp |
98
- | `metadata` | `RequestMetadata` | Request headers, client IP, etc. |
99
-
100
- ## File layout
101
-
102
- Two layouts are endorsed. Pick based on tool count and whether the tool has local helpers, fixtures, or error types.
103
-
104
- **Flat sibling files** — works well for projects with ≤3 tools per app, or when each tool is small enough to fit in one screen:
105
-
106
- ```text
107
- src/apps/<app>/tools/
108
- ├── get-weather.tool.ts # @Tool class, execute()
109
- ├── get-weather.schema.ts # input/output schemas + derived types
110
- └── get-weather.tool.spec.ts # unit tests
111
- ```
112
-
113
- **Folder-per-tool** — recommended for >3 tools per app, or any tool with local helpers, fixtures, or error types:
114
-
115
- ```text
116
- src/apps/<app>/tools/
117
- └── get-weather/
118
- ├── get-weather.tool.ts # @Tool class, execute()
119
- ├── get-weather.schema.ts # input/output schemas + derived types
120
- ├── get-weather.tool.spec.ts # unit tests
121
- ├── index.ts # barrel re-export
122
- └── … # tool-local helpers, fixtures, error types
123
- ```
124
-
125
- Either way the schemas live in their own file so they can be imported from the tool class, the spec, sibling tools, or generated clients without dragging the `@Tool`-decorated class along. Sample `index.ts` for the folder layout:
126
-
127
- ```typescript
128
- export { GetWeatherTool } from './get-weather.tool';
129
- export {
130
- inputSchema as getWeatherInputSchema,
131
- outputSchema as getWeatherOutputSchema,
132
- type GetWeatherInput,
133
- type GetWeatherOutput,
134
- } from './get-weather.schema';
135
- ```
136
-
137
- ## Input Schema: Zod Raw Shapes
138
-
139
- The `inputSchema` accepts a **Zod raw shape** -- a plain object mapping field names to Zod types. Do NOT wrap it in `z.object()`. The framework wraps it internally.
140
-
141
- ```typescript
142
- @Tool({
143
- name: 'search_documents',
144
- description: 'Search documents by query and optional filters',
145
- inputSchema: {
146
- // This is a raw shape, NOT z.object({...})
147
- query: z.string().min(1).describe('Search query'),
148
- limit: z.number().int().min(1).max(100).default(10).describe('Max results'),
149
- category: z.enum(['blog', 'docs', 'api']).optional().describe('Filter by category'),
150
- },
151
- })
152
- class SearchDocumentsTool extends ToolContext {
153
- async execute(input: { query: string; limit: number; category?: 'blog' | 'docs' | 'api' }) {
154
- // input is already validated by Zod before execute() is called
155
- return { results: [], total: 0 };
156
- }
157
- }
158
- ```
159
-
160
- The `execute()` parameter type must match the inferred output of `z.object(inputSchema)`. Validated input is also available via `this.input`.
161
-
162
- ## Output Schema (Recommended Best Practice)
163
-
164
- **Always define `outputSchema` for every tool.** This is a best practice for three critical reasons:
165
-
166
- 1. **Output validation** -- Prevents data leaks by ensuring your tool only returns fields you explicitly declare. Without `outputSchema`, any data in the return value passes through unvalidated, risking accidental exposure of sensitive fields (internal IDs, tokens, PII).
167
- 2. **CodeCall plugin compatibility** -- The CodeCall plugin uses `outputSchema` to understand what a tool returns, enabling correct VM-based orchestration and pass-by-reference. Tools without `outputSchema` degrade CodeCall's ability to chain results.
168
- 3. **Type safety** -- `ToolContext` infers the output type from `outputSchema` automatically (no explicit generics needed), giving you compile-time guarantees that `execute()` returns the correct shape.
169
-
170
- ```typescript
171
- const inputSchema = {
172
- city: z.string().describe('City name'),
173
- };
174
-
175
- // Always define outputSchema to validate output and prevent data leaks
176
- const outputSchema = {
177
- temperature: z.number(),
178
- unit: z.enum(['celsius', 'fahrenheit']),
179
- description: z.string(),
180
- };
181
-
182
- type GetWeatherInput = ToolInputOf<{ inputSchema: typeof inputSchema }>;
183
- type GetWeatherOutput = ToolOutputOf<{ outputSchema: typeof outputSchema }>;
184
-
185
- @Tool({
186
- name: 'get_weather',
187
- description: 'Get current weather for a location',
188
- inputSchema,
189
- outputSchema,
190
- })
191
- class GetWeatherTool extends ToolContext {
192
- async execute(input: GetWeatherInput): Promise<GetWeatherOutput> {
193
- const response = await this.fetch(`https://api.weather.example.com/v1/current?city=${input.city}`);
194
- const weather = await response.json();
195
- // Only temperature, unit, and description are returned.
196
- // Any extra fields from the API (e.g., internalId, apiKey) are stripped by outputSchema validation.
197
- return {
198
- temperature: weather.temp,
199
- unit: 'celsius',
200
- description: weather.summary,
201
- };
202
- }
203
- }
204
- ```
205
-
206
- **Why not omit outputSchema?** Without it:
207
-
208
- - The tool returns raw unvalidated data — any field your code accidentally includes leaks to the client
209
- - CodeCall cannot infer return types for chaining tool calls in VM scripts
210
- - No compile-time type checking on the return value
211
-
212
- ### Derive `execute()` types from the schemas (recommended)
213
-
214
- `ToolContext` already infers the input/output types from the `@Tool` decorator at the **class** level (no generics needed). To make the same types reachable from your `execute()` signature — and from sibling files like specs, helpers, or generated clients — hoist the **schemas only** to module scope and derive types from them with `ToolInputOf<>` / `ToolOutputOf<>` exported from `@frontmcp/sdk`. The decorator config (`name`, `description`, `annotations`, `rateLimit`, …) stays inline:
215
-
216
- ```typescript
217
- import { Tool, ToolContext, ToolInputOf, ToolOutputOf, z } from '@frontmcp/sdk';
218
-
219
- const inputSchema = {
220
- city: z.string().describe('City name'),
221
- };
222
-
223
- const outputSchema = {
224
- temperature: z.number(),
225
- unit: z.enum(['celsius', 'fahrenheit']),
226
- };
227
-
228
- type GetWeatherInput = ToolInputOf<{ inputSchema: typeof inputSchema }>;
229
- type GetWeatherOutput = ToolOutputOf<{ outputSchema: typeof outputSchema }>;
230
-
231
- @Tool({
232
- name: 'get_weather',
233
- description: 'Get current weather for a location',
234
- inputSchema,
235
- outputSchema,
236
- })
237
- class GetWeatherTool extends ToolContext {
238
- async execute(input: GetWeatherInput): Promise<GetWeatherOutput> {
239
- return { temperature: 22, unit: 'celsius' };
240
- }
241
- }
242
- ```
243
-
244
- **Two equivalent forms** — pick whichever fits the surrounding code; they produce identical types:
245
-
246
- ```typescript
247
- // Form 1 — SDK helpers (preferred — works with the type returned by ToolContext)
248
- type GetWeatherInput = ToolInputOf<{ inputSchema: typeof inputSchema }>;
249
- type GetWeatherOutput = ToolOutputOf<{ outputSchema: typeof outputSchema }>;
250
-
251
- // Form 2 — raw zod (terser if you don't mind a direct z dependency)
252
- type GetWeatherInput = z.infer<z.ZodObject<typeof inputSchema>>;
253
- type GetWeatherOutput = z.infer<z.ZodObject<typeof outputSchema>>;
254
- ```
255
-
256
- > **Never duplicate the shape inline on `execute()` — derive it.** If the schema changes, the type changes automatically. If it doesn't, the compiler tells you exactly which call sites broke. And only hoist the **schemas** — leaving `name`/`description`/`annotations`/throttling inside `@Tool({…})` keeps the decorator declaration self-contained and easy to scan.
257
-
258
- **`return` vs `this.respond()`** — Both work and both are validated against `outputSchema` in the finalize stage:
259
-
260
- ```typescript
261
- // Option 1: return (preferred — simpler, same validation)
262
- async execute(input: Input) {
263
- return { temperature: 22, unit: 'celsius' };
264
- }
265
-
266
- // Option 2: this.respond() — useful for early exit (throws FlowControl.respond internally)
267
- async execute(input: Input) {
268
- if (someCondition) {
269
- this.respond({ temperature: 0, unit: 'celsius' }); // never returns
270
- }
271
- return { temperature: 22, unit: 'celsius' };
272
- }
273
- ```
274
-
275
- **Early returns from elicitation** must still match the output schema:
276
-
277
- ```typescript
278
- async execute(input: Input) {
279
- const result = await this.elicit('Confirm?', { confirm: z.boolean() });
280
- if (result.action !== 'accept') {
281
- // Must return a value matching outputSchema, not a raw string
282
- return { temperature: 0, unit: 'celsius' as const };
283
- }
284
- // ... normal execution
285
- }
286
- ```
287
-
288
- Supported `outputSchema` types:
289
-
290
- - **Zod raw shapes** (recommended): `{ field: z.string(), count: z.number() }` — structured JSON output with validation
291
- - **Zod schemas**: `z.object(...)`, `z.array(...)`, `z.union([...])` — for complex types
292
- - **Primitive literals**: `'string'`, `'number'`, `'boolean'`, `'date'` — for simple returns
293
- - **Media types**: `'image'`, `'audio'`, `'resource'`, `'resource_link'` — for binary/link content
294
- - **Arrays**: `['string', 'image']` for multi-content responses
295
-
296
- ## Dependency Injection
297
-
298
- Access providers registered in the scope using `this.get(token)` (throws if not found) or `this.tryGet(token)` (returns `undefined` if not found).
299
-
300
- ```typescript
301
- import type { Token } from '@frontmcp/di';
302
-
303
- interface DatabaseService {
304
- query(sql: string, params: unknown[]): Promise<unknown[]>;
305
- }
306
- const DATABASE: Token<DatabaseService> = Symbol('database');
307
-
308
- @Tool({
309
- name: 'run_query',
310
- description: 'Execute a database query',
311
- inputSchema: {
312
- sql: z.string().describe('SQL query to execute'),
313
- },
314
- })
315
- class RunQueryTool extends ToolContext {
316
- async execute(input: { sql: string }) {
317
- const db = this.get(DATABASE); // throws if DATABASE not registered
318
- const rows = await db.query(input.sql, []);
319
- return { rows, count: rows.length };
320
- }
321
- }
322
- ```
323
-
324
- Use `this.tryGet(token)` when the dependency is optional:
325
-
326
- ```typescript
327
- async execute(input: { data: string }) {
328
- const cache = this.tryGet(CACHE); // returns undefined if not registered
329
- if (cache) {
330
- const cached = await cache.get(input.data);
331
- if (cached) return cached;
332
- }
333
- // proceed without cache
334
- }
335
- ```
336
-
337
- ## Error Handling
338
-
339
- **Do NOT wrap `execute()` in try/catch.** The framework's tool execution flow automatically catches exceptions, formats error responses, and triggers error hooks. Only use `this.fail(err)` for **business-logic errors** (validation failures, not-found, permission denied). Let infrastructure errors (network, database) propagate naturally.
340
-
341
- ```typescript
342
- // WRONG — never do this:
343
- async execute(input) {
344
- try {
345
- const result = await someOperation();
346
- return result;
347
- } catch (err) {
348
- this.fail(err instanceof Error ? err : new Error(String(err)));
349
- }
350
- }
351
-
352
- // CORRECT — let the framework handle errors:
353
- async execute(input) {
354
- const result = await someOperation(); // errors propagate to framework
355
- return result;
356
- }
357
- ```
358
-
359
- Use `this.fail(err)` to abort execution and trigger the error flow. The method throws internally and never returns.
360
-
361
- ```typescript
362
- @Tool({
363
- name: 'delete_record',
364
- description: 'Delete a record by ID',
365
- inputSchema: {
366
- id: z.string().uuid().describe('Record UUID'),
367
- },
368
- })
369
- class DeleteRecordTool extends ToolContext {
370
- async execute(input: { id: string }) {
371
- const record = await this.findRecord(input.id);
372
- if (!record) {
373
- this.fail(new Error(`Record not found: ${input.id}`));
374
- }
375
-
376
- await this.deleteRecord(record);
377
- return `Record ${input.id} deleted successfully`;
378
- }
379
-
380
- private async findRecord(id: string) {
381
- return null;
382
- }
383
-
384
- private async deleteRecord(record: unknown) {
385
- // delete implementation
386
- }
387
- }
388
- ```
389
-
390
- For MCP-specific errors, use error classes with JSON-RPC codes:
391
-
392
- ```typescript
393
- import { MCP_ERROR_CODES, PublicMcpError, ResourceNotFoundError } from '@frontmcp/sdk';
394
-
395
- this.fail(new ResourceNotFoundError(`Record ${input.id}`));
396
- ```
397
-
398
- ## Progress and Notifications
399
-
400
- Use `this.notify(message, level?)` to send log-level notifications and `this.progress(progress, total?, message?)` to send progress updates to the client. `this.progress()` returns a `Promise<boolean>` indicating whether the notification was sent (`false` if no progress token was provided in the request).
401
-
402
- ```typescript
403
- @Tool({
404
- name: 'batch_process',
405
- description: 'Process a batch of items',
406
- inputSchema: {
407
- items: z.array(z.string()).min(1).describe('Items to process'),
408
- },
409
- })
410
- class BatchProcessTool extends ToolContext {
411
- async execute(input: { items: string[] }) {
412
- this.mark('validation');
413
- this.validateItems(input.items);
414
-
415
- this.mark('processing');
416
- const results: string[] = [];
417
- for (let i = 0; i < input.items.length; i++) {
418
- await this.progress(i + 1, input.items.length, `Processing item ${i + 1}`);
419
- const result = await this.processItem(input.items[i]);
420
- results.push(result);
421
- }
422
-
423
- this.mark('complete');
424
- await this.notify(`Processed ${results.length} items`, 'info');
425
- return { processed: results.length, results };
426
- }
427
-
428
- private validateItems(items: string[]) {
429
- /* ... */
430
- }
431
- private async processItem(item: string): Promise<string> {
432
- return item;
433
- }
434
- }
435
- ```
436
-
437
- ## Tool Annotations
438
-
439
- Provide behavioral hints to clients using `annotations`. These hints help clients decide how to present and gate tool usage.
440
-
441
- ```typescript
442
- @Tool({
443
- name: 'web_search',
444
- description: 'Search the web',
445
- inputSchema: {
446
- query: z.string(),
447
- },
448
- annotations: {
449
- title: 'Web Search',
450
- readOnlyHint: true,
451
- openWorldHint: true,
452
- },
453
- })
454
- class WebSearchTool extends ToolContext {
455
- async execute(input: { query: string }) {
456
- return await this.performSearch(input.query);
457
- }
458
-
459
- private async performSearch(query: string) {
460
- return [];
461
- }
462
- }
463
- ```
464
-
465
- Annotation fields:
466
-
467
- - `title` -- Human-readable title for the tool
468
- - `readOnlyHint` -- Tool does not modify its environment (default: false)
469
- - `destructiveHint` -- Tool may perform destructive updates (default: true, meaningful only when readOnlyHint is false)
470
- - `idempotentHint` -- Calling repeatedly with same args has no additional effect (default: false)
471
- - `openWorldHint` -- Tool interacts with external entities (default: true)
472
-
473
- ## Function-Style Builder
474
-
475
- For simple tools that do not need a class, use the `tool()` function builder. It returns a value you register the same way as a class tool.
476
-
477
- ```typescript
478
- import { tool, z } from '@frontmcp/sdk';
479
-
480
- const AddNumbers = tool({
481
- name: 'add_numbers',
482
- description: 'Add two numbers',
483
- inputSchema: {
484
- a: z.number().describe('First number'),
485
- b: z.number().describe('Second number'),
486
- },
487
- outputSchema: 'number',
488
- })((input) => {
489
- return input.a + input.b;
490
- });
491
- ```
492
-
493
- The callback receives `(input, ctx)` where `ctx` provides access to the same context methods (`get`, `tryGet`, `fail`, `mark`, `fetch`, `notify`, `progress`).
494
-
495
- Register it the same way as a class tool: `tools: [AddNumbers]`.
496
-
497
- ## Remote and ESM Loading
498
-
499
- Load tools from external modules or remote URLs without importing them directly.
500
-
501
- **ESM loading** -- load a tool from an ES module:
502
-
503
- ```typescript
504
- const RemoteTool = Tool.esm('@my-org/tools@^1.0.0', 'MyTool', {
505
- description: 'A tool loaded from an ES module',
506
- });
507
- ```
508
-
509
- **Remote loading** -- load a tool from a remote URL:
510
-
511
- ```typescript
512
- const CloudTool = Tool.remote('https://example.com/tools/cloud-tool', 'CloudTool', {
513
- description: 'A tool loaded from a remote server',
514
- });
515
- ```
516
-
517
- Both return values that can be registered in `tools: [RemoteTool, CloudTool]`.
518
-
519
- ## Registration
520
-
521
- Add tool classes (or function-style tools) to the `tools` array in `@FrontMcp` or `@App`.
522
-
523
- ```typescript
524
- import { App, FrontMcp } from '@frontmcp/sdk';
525
-
526
- @App({
527
- name: 'my-app',
528
- tools: [GreetUserTool, SearchDocumentsTool, AddNumbers],
529
- })
530
- class MyApp {}
531
-
532
- @FrontMcp({
533
- info: { name: 'my-server', version: '1.0.0' },
534
- apps: [MyApp],
535
- tools: [RunQueryTool], // can also register tools directly on the server
536
- })
537
- class MyServer {}
538
- ```
539
-
540
- ## Nx Generator
541
-
542
- Scaffold a new tool using the Nx generator:
543
-
544
- ```bash
545
- nx generate @frontmcp/nx:tool
546
- ```
547
-
548
- This creates the tool file, spec file, and updates barrel exports.
549
-
550
- ## Rate Limiting and Concurrency
551
-
552
- Protect tools with throttling controls:
553
-
554
- ```typescript
555
- @Tool({
556
- name: 'expensive_operation',
557
- description: 'An expensive operation that should be rate limited',
558
- inputSchema: {
559
- data: z.string(),
560
- },
561
- rateLimit: { maxRequests: 10, windowMs: 60_000 },
562
- concurrency: { maxConcurrent: 2 },
563
- timeout: { executeMs: 30_000 },
564
- })
565
- class ExpensiveOperationTool extends ToolContext {
566
- async execute(input: { data: string }) {
567
- // At most 10 calls per minute, 2 concurrent, 30s timeout
568
- return await this.heavyComputation(input.data);
569
- }
570
-
571
- private async heavyComputation(data: string) {
572
- return data;
573
- }
574
- }
575
- ```
576
-
577
- ## Auth Providers
578
-
579
- Declare which auth providers a tool requires. Credentials are loaded before tool execution.
580
-
581
- ```typescript
582
- // String shorthand — single provider
583
- @Tool({
584
- name: 'create_issue',
585
- description: 'Create a GitHub issue',
586
- inputSchema: { title: z.string(), body: z.string() },
587
- authProviders: ['github'],
588
- })
589
- class CreateIssueTool extends ToolContext {
590
- /* ... */
591
- }
592
-
593
- // Full mapping — with scopes and required flag
594
- @Tool({
595
- name: 'deploy_app',
596
- description: 'Deploy to cloud',
597
- inputSchema: { env: z.string() },
598
- authProviders: [
599
- { name: 'github', required: true, scopes: ['repo', 'workflow'] },
600
- { name: 'aws', required: false, alias: 'cloud' },
601
- ],
602
- })
603
- class DeployAppTool extends ToolContext {
604
- /* ... */
605
- }
606
- ```
607
-
608
- Auth provider mapping fields:
609
-
610
- - `name` — Provider name (must match a registered `@AuthProvider`)
611
- - `required?` — Whether credential is required (default: `true`)
612
- - `scopes?` — Required OAuth scopes
613
- - `alias?` — Alias for injection when using multiple providers
614
-
615
- ## Environment Availability
616
-
617
- Restrict a tool to specific platforms, runtimes, or environments using `availableWhen`. The tool will be automatically filtered from discovery and blocked from execution when the constraint doesn't match.
618
-
619
- > **Important:** `availableWhen` is a **registry-level** constraint, evaluated at server boot time against the process's runtime context (OS, runtime, deployment mode, NODE_ENV). This is fundamentally different from:
620
- >
621
- > - **Authorization** — per-request, evaluated in HTTP flows against session/user identity
622
- > - **Rule-based filtering** — dynamic, policy-driven, evaluated at request time
623
- > - **`hideFromDiscovery`** — a soft hide from listing; hidden tools can still be called directly
624
- >
625
- > `availableWhen` is a **hard constraint**: filtered tools are excluded from both listing AND execution. Results are logged at boot time for operational visibility.
626
-
627
- ```typescript
628
- // macOS-only tool
629
- @Tool({
630
- name: 'apple_notes_search',
631
- description: 'Search Apple Notes',
632
- inputSchema: { query: z.string() },
633
- // `os` is the canonical axis since issue #417; `platform` remains as
634
- // a deprecated alias for backward compatibility.
635
- availableWhen: { os: ['darwin'] },
636
- })
637
- class AppleNotesSearchTool extends ToolContext {
638
- async execute(input: { query: string }) {
639
- // Only runs on macOS
640
- }
641
- }
642
-
643
- // Node.js production-only tool
644
- @Tool({
645
- name: 'deploy_service',
646
- description: 'Deploy to production',
647
- inputSchema: { service: z.string() },
648
- availableWhen: { runtime: ['node'], env: ['production'] },
649
- })
650
- class DeployServiceTool extends ToolContext {
651
- async execute(input: { service: string }) {
652
- // Only available in Node.js production
653
- }
654
- }
655
- ```
656
-
657
- Available constraint fields (AND across fields, OR within arrays). Issue #417 added `os` / `provider` / `target` / `surface`:
658
-
659
- - `os` — OS (renamed from `platform`): `'darwin'`, `'linux'`, `'win32'`. `platform` is kept as a deprecated alias.
660
- - `runtime` — JS runtime: `'node'`, `'browser'`, `'edge'`, `'bun'`, `'deno'`
661
- - `deployment` — Coarse mode: `'serverless'`, `'standalone'`, `'distributed'`, `'browser'`
662
- - `provider` — Deploy provider (issue #417): `'bare'`, `'docker'`, `'vercel'`, `'lambda'`, `'cloudflare'`, `'netlify'`, `'azure'`, `'gcp'`, `'fly'`, `'render'`, `'railway'`. Override with `FRONTMCP_PROVIDER=<name>`.
663
- - `target` — Build target produced by `frontmcp build --target <x>` (issue #417): `'cli'`, `'node'`, `'vercel'`, `'lambda'`, `'cloudflare'`, `'browser'`, `'sdk'`, `'mcpb'`, `'distributed'`. `'unknown'` in dev.
664
- - `surface` — Per-call axis (issue #417): `'mcp'` (MCP `tools/call`), `'cli'` (CLI subcommand), `'agent'` (in-process dispatch), `'job'` (job runner), `'http-trigger'` (channel HTTP triggers). Use `surface: ['agent']` to block external invocation but allow agent use.
665
- - `env` — NODE_ENV: `'production'`, `'development'`, `'test'`
666
-
667
- When an `availableWhen` constraint fails at call time, FrontMCP throws `EntryUnavailableError`. The error's `data` now carries `missingAxes: string[]` (issue #417) so clients can surface "this tool isn't reachable because provider=vercel / surface=mcp / …" without parsing prose.
668
-
669
- You can also check the platform imperatively inside `execute()`:
670
-
671
- ```typescript
672
- if (this.isPlatform('darwin')) {
673
- /* macOS logic */
674
- }
675
- if (this.isRuntime('node')) {
676
- /* Node.js logic */
677
- }
678
- if (this.isEnv('production')) {
679
- /* production logic */
680
- }
681
- ```
682
-
683
- ## Elicitation (Interactive Input)
684
-
685
- Tools can request interactive input from users mid-execution using `this.elicit()`.
686
-
687
- > **Prerequisite:** Elicitation must be enabled at server level:
688
- >
689
- > ```typescript
690
- > @FrontMcp({
691
- > elicitation: { enabled: true },
692
- > // ... rest of config
693
- > })
694
- > ```
695
- >
696
- > See `configure-elicitation` for full configuration options including Redis-backed elicitation stores.
697
- >
698
- > **What happens without it:** Calling `this.elicit()` throws `ElicitationDisabledError` at runtime with the message: _"Elicitation is disabled in server configuration. Enable it via @FrontMcp({ elicitation: { enabled: true } })"_. The tool call fails and the error is returned to the client. There is no compile-time or startup warning — the error only occurs when the tool is actually invoked.
699
-
700
- ```typescript
701
- @Tool({
702
- name: 'confirm_delete',
703
- description: 'Delete a resource after user confirmation',
704
- inputSchema: { resourceId: z.string() },
705
- })
706
- class ConfirmDeleteTool extends ToolContext {
707
- async execute(input: { resourceId: string }) {
708
- const result = await this.elicit('Are you sure you want to delete this resource?', {
709
- confirm: z.boolean().describe('Confirm deletion'),
710
- reason: z.string().optional().describe('Reason for deletion'),
711
- });
712
-
713
- if (result.action === 'accept' && result.data.confirm) {
714
- await this.get(ResourceService).delete(input.resourceId);
715
- return 'Resource deleted';
716
- }
717
- return 'Deletion cancelled';
718
- }
719
- }
720
- ```
721
-
722
- ## Tool Examples
723
-
724
- Provide usage examples for documentation and discovery:
725
-
726
- ```typescript
727
- @Tool({
728
- name: 'convert_currency',
729
- description: 'Convert between currencies',
730
- inputSchema: {
731
- amount: z.number(),
732
- from: z.string(),
733
- to: z.string(),
734
- },
735
- examples: [
736
- {
737
- description: 'Convert USD to EUR',
738
- input: { amount: 100, from: 'USD', to: 'EUR' },
739
- output: { converted: 85.5, rate: 0.855 },
740
- },
741
- {
742
- description: 'Convert with large amount',
743
- input: { amount: 1_000_000, from: 'GBP', to: 'JPY' },
744
- },
745
- ],
746
- })
747
- class ConvertCurrencyTool extends ToolContext {
748
- /* ... */
749
- }
750
- ```
751
-
752
- ## Common Patterns
753
-
754
- | Pattern | Correct | Incorrect | Why |
755
- | -------------------- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
756
- | Input schema | `inputSchema: { name: z.string() }` (raw shape) | `inputSchema: z.object({ name: z.string() })` | Framework wraps in `z.object()` internally |
757
- | Output schema | Always define `outputSchema` | Omit `outputSchema` | Prevents data leaks and enables CodeCall chaining |
758
- | `execute()` types | Derive via `ToolInputOf<{ inputSchema: typeof inputSchema }>` / `ToolOutputOf<{ outputSchema: typeof outputSchema }>` | Inline `execute(input: { city: string })` annotation | Schema is the single source of truth — derive once, use everywhere; no silent drift |
759
- | File layout | Schema in `<name>.schema.ts`, class in `<name>.tool.ts` (sibling files or folder-per-tool) | One large `<name>.tool.ts` that bundles schema + class + helpers | Schema can be imported by specs, sibling tools, generated clients without dragging the class along |
760
- | DI resolution | `this.get(TOKEN)` with proper error handling | `this.tryGet(TOKEN)!` with non-null assertion | `get` throws a clear error; non-null assertions mask failures |
761
- | Error handling | `this.fail(new ResourceNotFoundError(...))` | `throw new Error(...)` | `this.fail` triggers the error flow with MCP error codes |
762
- | Tool naming | `snake_case` names: `get_weather` | `camelCase` or `PascalCase`: `getWeather` | MCP protocol convention for tool names |
763
- | ToolContext generics | `class MyTool extends ToolContext` | `class MyTool extends ToolContext<typeof inputSchema>` | Types are auto-inferred from `@Tool` decorator — explicit generics are redundant |
764
-
765
- ## Verification Checklist
766
-
767
- ### Configuration
768
-
769
- - [ ] Tool class extends `ToolContext` and implements `execute()`
770
- - [ ] `@Tool` decorator has `name`, `description`, and `inputSchema`
771
- - [ ] `outputSchema` is defined to validate and restrict output fields
772
- - [ ] Tool is registered in `tools` array of `@App` or `@FrontMcp`
773
-
774
- ### Runtime
775
-
776
- - [ ] Tool appears in `tools/list` MCP response
777
- - [ ] Valid input returns expected output
778
- - [ ] Invalid input returns Zod validation error (not a crash)
779
- - [ ] `this.fail()` triggers proper MCP error response
780
- - [ ] DI dependencies resolve correctly via `this.get()`
781
-
782
- ## Troubleshooting
783
-
784
- | Problem | Cause | Solution |
785
- | ------------------------------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------- |
786
- | Tool not appearing in `tools/list` | Not registered in `tools` array | Add tool class to `@App` or `@FrontMcp` `tools` array |
787
- | Zod validation error on valid input | Using `z.object()` wrapper in `inputSchema` | Use raw shape: `{ field: z.string() }` not `z.object({ field: z.string() })` |
788
- | `this.get(TOKEN)` throws DependencyNotFoundError | Provider not registered in scope | Register provider in `providers` array of `@App` or `@FrontMcp` |
789
- | Output contains unexpected fields | No `outputSchema` defined | Add `outputSchema` to strip unvalidated fields from response |
790
- | Tool times out | No timeout configured for long operation | Add `timeout: { executeMs: 30_000 }` to `@Tool` options |
791
- | `this.elicit()` throws `ElicitationDisabledError` | Elicitation not enabled at server level | Add `elicitation: { enabled: true }` to `@FrontMcp` config |
792
-
793
- ## Examples
794
-
795
- | Example | Level | Description |
796
- | --------------------------------------------------------------------------------------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
797
- | [`basic-class-tool`](../examples/create-tool/basic-class-tool.md) | Basic | A minimal tool using the class-based pattern with Zod input validation, output schema, and types derived from the schemas. |
798
- | [`tool-with-di-and-errors`](../examples/create-tool/tool-with-di-and-errors.md) | Intermediate | A tool that resolves a database service via DI and uses `this.fail()` for business-logic errors, with `execute()` types derived from the schemas. |
799
- | [`tool-with-rate-limiting-and-progress`](../examples/create-tool/tool-with-rate-limiting-and-progress.md) | Advanced | A batch processing tool that uses rate limiting, concurrency control, progress notifications, and annotations, with `execute()` types derived from the schemas. |
800
-
801
- > See all examples in [`examples/create-tool/`](../examples/create-tool/)
802
-
803
- ## Reference
804
-
805
- - [Tools Documentation](https://docs.agentfront.dev/frontmcp/servers/tools)
806
- - Related skills: `create-resource`, `create-prompt`, `configure-throttle`, `create-agent`