@frontmcp/skills 1.2.0 → 1.3.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 (30) hide show
  1. package/catalog/frontmcp-config/SKILL.md +5 -5
  2. package/catalog/frontmcp-config/references/configure-deployment-targets.md +84 -1
  3. package/catalog/frontmcp-config/references/configure-http.md +57 -2
  4. package/catalog/frontmcp-config/references/configure-session.md +14 -7
  5. package/catalog/frontmcp-deployment/SKILL.md +3 -3
  6. package/catalog/frontmcp-deployment/references/build-for-mcpb.md +1 -1
  7. package/catalog/frontmcp-deployment/references/mcp-client-integration.md +107 -0
  8. package/catalog/frontmcp-development/SKILL.md +7 -7
  9. package/catalog/frontmcp-development/examples/create-provider/basic-database-provider.md +14 -0
  10. package/catalog/frontmcp-development/examples/create-provider/config-and-api-providers.md +85 -9
  11. package/catalog/frontmcp-development/examples/create-tool/basic-class-tool.md +30 -11
  12. package/catalog/frontmcp-development/examples/create-tool/tool-with-di-and-errors.md +62 -14
  13. package/catalog/frontmcp-development/examples/create-tool/tool-with-rate-limiting-and-progress.md +30 -12
  14. package/catalog/frontmcp-development/references/create-job.md +45 -11
  15. package/catalog/frontmcp-development/references/create-provider.md +80 -8
  16. package/catalog/frontmcp-development/references/create-skill-with-tools.md +31 -0
  17. package/catalog/frontmcp-development/references/create-skill.md +45 -0
  18. package/catalog/frontmcp-development/references/create-tool.md +124 -46
  19. package/catalog/frontmcp-guides/SKILL.md +7 -7
  20. package/catalog/frontmcp-observability/SKILL.md +15 -7
  21. package/catalog/frontmcp-observability/examples/metrics-endpoint/enable-metrics-endpoint.md +77 -0
  22. package/catalog/frontmcp-observability/references/metrics-endpoint.md +161 -0
  23. package/catalog/frontmcp-setup/SKILL.md +11 -11
  24. package/catalog/frontmcp-setup/examples/frontmcp-skills-usage/install-and-search-skills.md +19 -1
  25. package/catalog/frontmcp-setup/references/frontmcp-skills-usage.md +260 -19
  26. package/catalog/frontmcp-setup/references/setup-project.md +29 -0
  27. package/catalog/frontmcp-setup/references/setup-sqlite.md +68 -9
  28. package/catalog/frontmcp-testing/SKILL.md +17 -17
  29. package/catalog/skills-manifest.json +32 -6
  30. package/package.json +1 -1
@@ -164,6 +164,73 @@ if (!db) {
164
164
  }
165
165
  ```
166
166
 
167
+ ## File Layout
168
+
169
+ Once a provider grows past a single class, a flat `src/providers/<slug>.provider.ts` becomes ambiguous: helpers, internal types, schema fragments, and the spec file all need somewhere to go. The recommended convention is **one folder per provider**, co-locating the class, the spec, an optional barrel, and any helpers:
170
+
171
+ ```text
172
+ src/providers/<provider-slug>/
173
+ ├── index.ts # barrel: re-exports class, factory, public types
174
+ ├── <provider-slug>.provider.ts # @Provider class and/or AsyncProvider factory
175
+ ├── <provider-slug>.provider.spec.ts # unit tests
176
+ ├── types.ts # (optional) internal types / token interfaces
177
+ └── <helper>.ts (+ .spec.ts) # (optional) per-provider helpers
178
+ ```
179
+
180
+ Plus a top-level `src/providers/index.ts` barrel re-exporting each subfolder:
181
+
182
+ ```typescript
183
+ // src/providers/index.ts
184
+ export * from './task-store';
185
+ export * from './config';
186
+ export * from './redis';
187
+ ```
188
+
189
+ ### Naming rules
190
+
191
+ - **Folder slug**: `kebab-case`, matches the provider's primary purpose (`task-store`, `redis`, `api-client`).
192
+ - **Class file**: `<slug>.provider.ts` — matches the in-tree demo-app convention (`apps/demo/src/apps/expenses/providers/redis.provider.ts`) and what the Nx generator emits.
193
+ - **Spec file**: `<slug>.provider.spec.ts` — co-located with source per the repo's `.spec.ts` convention (CLAUDE.md).
194
+ - **Barrel**: `index.ts`, re-exporting the class, the `AsyncProvider` factory (if any), and any public types/tokens.
195
+
196
+ ### Single-file vs folder — when to fold
197
+
198
+ A trivial provider (e.g. a `Map`-based cache, a pure DTO with no helpers) does NOT need its own folder; promote to a folder as soon as the provider grows. Use this rubric:
199
+
200
+ | Provider shape | Layout | Why |
201
+ | ------------------------------------------------------ | ------------------------------------------------------- | ------------------------------------------------------------------------------ |
202
+ | Pure DTO (e.g. `extends Map`, no methods, no helpers) | Single file `<slug>.provider.ts` under `src/providers/` | A folder for a 5-line class is over-architected |
203
+ | Provider with a spec file | Folder | Keeps source and spec adjacent; matches CLAUDE.md `.spec.ts` rule |
204
+ | Provider with helpers, types, or schema fragments | Folder | Helpers/types are private to the provider; folder boundary makes that explicit |
205
+ | `AsyncProvider({ useFactory })` with non-trivial setup | Folder | Factory + class + setup helpers cluster naturally |
206
+ | Multiple related providers sharing helpers | Folder per provider + sibling `_shared/` folder | Avoids leaking helpers into the top-level `providers/` namespace |
207
+
208
+ ### Cross-provider imports
209
+
210
+ Cross-provider imports go through the **subfolder barrel**, not into another provider's internals:
211
+
212
+ <!-- prettier-ignore -->
213
+ ```typescript
214
+ // ✅ Good — imports through the subfolder barrel
215
+ import { TaskStoreProvider } from '../task-store';
216
+
217
+ // ❌ Bad — top-level barrel for sibling imports causes circular-init churn
218
+ import { TaskStoreProvider } from '..';
219
+ // ❌ Bad — reaches into another provider's implementation file
220
+ import { TaskStoreProvider } from '../task-store/task-store.provider';
221
+ ```
222
+
223
+ Tool → provider imports follow the same rule:
224
+
225
+ ```typescript
226
+ // ✅ Good — tool imports the provider's public surface from its barrel
227
+ import { TaskStoreProvider } from '../../providers/task-store';
228
+ ```
229
+
230
+ ### Same convention for tools and resources
231
+
232
+ The folder layout applies to `create-tool` and `create-resource` too. Once a tool grows a `<slug>.schema.ts` or a resource grows a content helper, promote it to `src/tools/<slug>/` or `src/resources/<slug>/` with the same barrel + spec layout. (Tracked separately in issue #405 for `create-tool`.)
233
+
167
234
  ## Common Provider Patterns
168
235
 
169
236
  ### Configuration Provider
@@ -258,6 +325,8 @@ class CacheProvider extends Map<string, unknown> {
258
325
  nx generate @frontmcp/nx:provider my-provider --project=my-app
259
326
  ```
260
327
 
328
+ The generator currently writes a single `<slug>.provider.ts` directly into `src/providers/`. Promote it to the folder layout above as soon as you add a spec file or helpers — see [File Layout](#file-layout).
329
+
261
330
  ## Verification
262
331
 
263
332
  ```bash
@@ -270,13 +339,15 @@ frontmcp dev
270
339
 
271
340
  ## Common Patterns
272
341
 
273
- | Pattern | Correct | Incorrect | Why |
274
- | ------------------ | ---------------------------------------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------- |
275
- | Token definition | `const DB: Token<DbService> = Symbol('DbService')` (typed Symbol) | `const DB = 'database'` (string literal) | Typed `Token<T>` enables compile-time type checking on `this.get()` |
276
- | DI resolution | `this.get(TOKEN)` with error handling | `this.tryGet(TOKEN)!` with non-null assertion | `get` throws a clear `DependencyNotFoundError`; non-null assertions hide failures |
277
- | Lifecycle | `AsyncProvider({ useFactory })` for async setup; constructor for sync | Using `onInit()` / `onDestroy()` lifecycle hooks | `@Provider` has no lifecycle hooks; `AsyncProvider` factories are awaited before resolution |
278
- | Registration scope | Register at `@App` level for app-scoped, `@FrontMcp` for server-scoped | Registering same provider in multiple apps | Server-scoped providers are shared; duplicating causes multiple instances |
279
- | Config provider | `readonly` properties from `process.env` | Mutable properties that change at runtime | Providers are singletons; mutable state can cause race conditions |
342
+ | Pattern | Correct | Incorrect | Why |
343
+ | --------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
344
+ | Token definition | `const DB: Token<DbService> = Symbol('DbService')` (typed Symbol) | `const DB = 'database'` (string literal) | Typed `Token<T>` enables compile-time type checking on `this.get()` |
345
+ | DI resolution | `this.get(TOKEN)` with error handling | `this.tryGet(TOKEN)!` with non-null assertion | `get` throws a clear `DependencyNotFoundError`; non-null assertions hide failures |
346
+ | Lifecycle | `AsyncProvider({ useFactory })` for async setup; constructor for sync | Using `onInit()` / `onDestroy()` lifecycle hooks | `@Provider` has no lifecycle hooks; `AsyncProvider` factories are awaited before resolution |
347
+ | Registration scope | Register at `@App` level for app-scoped, `@FrontMcp` for server-scoped | Registering same provider in multiple apps | Server-scoped providers are shared; duplicating causes multiple instances |
348
+ | Config provider | `readonly` properties from `process.env` | Mutable properties that change at runtime | Providers are singletons; mutable state can cause race conditions |
349
+ | File layout | `src/providers/<slug>/` folder with `index.ts` + `<slug>.provider.ts` + `<slug>.provider.spec.ts` | Flat `src/providers/<slug>.provider.ts` once helpers or a spec exist | Co-locates source, tests, helpers; barrel hides internals — see [File Layout](#file-layout) |
350
+ | Cross-provider import | `import { TaskStoreProvider } from '../task-store'` (subfolder barrel) | `import { TaskStoreProvider } from '../task-store/task-store.provider'` | Subfolder barrel hides internals; reaching past it couples consumers to implementation files |
280
351
 
281
352
  ## Verification Checklist
282
353
 
@@ -287,6 +358,7 @@ frontmcp dev
287
358
  - [ ] Provider (class or `AsyncProvider` factory) is registered in `providers` array of `@App` or `@FrontMcp`
288
359
  - [ ] Sync setup happens in the constructor (throws fast on missing config)
289
360
  - [ ] Async setup uses `AsyncProvider({ useFactory })`; the framework awaits it before resolution
361
+ - [ ] Each provider lives in its own `src/providers/<slug>/` folder once it has a spec, helpers, or internal types (single-file is fine for trivial providers — see the [File Layout](#file-layout) rubric)
290
362
 
291
363
  ### Runtime
292
364
 
@@ -312,7 +384,7 @@ frontmcp dev
312
384
  | Example | Level | Description |
313
385
  | ------------------------------------------------------------------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
314
386
  | [`basic-database-provider`](../examples/create-provider/basic-database-provider.md) | Basic | A provider that manages a database connection pool, bound through `AsyncProvider({ useFactory })` so the pool is opened before any tool runs. |
315
- | [`config-and-api-providers`](../examples/create-provider/config-and-api-providers.md) | Intermediate | A configuration provider with readonly environment settings and an HTTP API client provider. |
387
+ | [`config-and-api-providers`](../examples/create-provider/config-and-api-providers.md) | Intermediate | A configuration provider and an HTTP API client provider, organized as one folder per provider with co-located specs and barrels. |
316
388
 
317
389
  > See all examples in [`examples/create-provider/`](../examples/create-provider/)
318
390
 
@@ -462,6 +462,37 @@ nx generate @frontmcp/nx:skill-dir
462
462
 
463
463
  The class generator creates the skill file, spec file, and updates barrel exports. The directory generator creates the full directory structure ready for `skillDir()`.
464
464
 
465
+ ## Installing on a User's Machine
466
+
467
+ Tool-enabled skills install the same way as instruction-only skills —
468
+ the tools they reference are exposed through the same MCP server, so
469
+ copying the `SKILL.md` (with the decorator metadata in its frontmatter)
470
+ under `.claude/skills/<name>/` is all Claude Code needs to load it.
471
+
472
+ ```bash
473
+ # Install one named skill from a local project entry
474
+ frontmcp skills install deploy-service --from-entry src/main.ts -p claude
475
+
476
+ # Install every @Skill the project exposes
477
+ frontmcp skills install --from-entry src/main.ts --all -p claude
478
+
479
+ # Install from a published package (resolves the package main entry)
480
+ frontmcp skills install --from-package my-devops-server --all -p claude
481
+ ```
482
+
483
+ The CLI bundles the entry, enumerates `@Skill` entries via the SDK's
484
+ in-memory client, and copies each skill's `SKILL.md` and resource
485
+ directories. When a skill declares `allowedTools` in its decorator, that
486
+ list ends up in the synthesized SKILL.md frontmatter so Claude Code
487
+ pre-approves those tools during the skill session. See
488
+ `frontmcp-skills-usage` for the full selector matrix, and `create-skill`
489
+ for the analogous note on instruction-only skills.
490
+
491
+ > Shipping slash commands alongside the skills? Use the full per-bin
492
+ > install instead: `my-devops-server install -p claude` writes the
493
+ > plugin manifest, `commands/` directory, and `skills/` directory in a
494
+ > single pass.
495
+
465
496
  ## HTTP Endpoints for Skill Discovery
466
497
 
467
498
  When skills have `visibility` set to `'http'` or `'both'`, they are discoverable via HTTP endpoints:
@@ -396,6 +396,51 @@ class StandardsApp {}
396
396
  class MyServer {}
397
397
  ```
398
398
 
399
+ ## Installing a Project's `@Skill` on a User's Machine
400
+
401
+ A `@Skill` registered on your server is reachable two ways:
402
+
403
+ 1. **Over MCP** — clients that speak the SEP-2640 `skill://` URI scheme
404
+ (Claude Desktop, custom MCP clients) discover registered skills
405
+ automatically when they connect to your server.
406
+ 2. **On the filesystem** — Claude Code's plugin/filesystem loader looks
407
+ for `SKILL.md` files under `.claude/skills/<name>/`. Decorator-only
408
+ registration is **not enough**; the SKILL.md (and any
409
+ `references/` / `examples/` directories) has to be copied to disk.
410
+
411
+ The `frontmcp` CLI bridges the two via `frontmcp skills install
412
+ --from-entry` / `--from-package`. The command bundles the entry file,
413
+ boots the SDK in-memory, enumerates every `@Skill` you registered, and
414
+ writes a Claude-Code-loadable `SKILL.md` for each (synthesizing
415
+ frontmatter from the decorator metadata when the source markdown
416
+ doesn't already have one):
417
+
418
+ ```bash
419
+ # From the project checkout, during development
420
+ frontmcp skills install example-project --from-entry src/main.ts -p claude
421
+ frontmcp skills install --from-entry src/main.ts --all -p claude
422
+
423
+ # From a published package the user has installed
424
+ frontmcp skills install --from-package my-frontmcp-server --all -p claude
425
+ ```
426
+
427
+ The resulting `.claude/skills/<skill-name>/` tree is the same shape as a
428
+ catalog-installed skill, so the CLAUDE.md auto-generated block, the
429
+ `/plugins` listing in Claude Code, and Cursor/Windsurf exports all work
430
+ uniformly. See `frontmcp-skills-usage` for the full flag list and
431
+ selector matrix.
432
+
433
+ If you also want to ship slash commands and a `.claude-plugin/plugin.json`
434
+ manifest, install the project as a Claude Code plugin instead of just the
435
+ skills:
436
+
437
+ ```bash
438
+ my-frontmcp-server install -p claude # MCP server + commands + skills, all in one
439
+ ```
440
+
441
+ The plugin path internally uses the same `@Skill` enumeration, so
442
+ nothing about the decorator changes.
443
+
399
444
  ## HTTP Discovery
400
445
 
401
446
  When skills have `visibility` set to `'http'` or `'both'`, they are discoverable via HTTP endpoints.
@@ -31,25 +31,37 @@ Tools are the primary way to expose executable actions to AI clients in the MCP
31
31
 
32
32
  ## Class-Based Pattern
33
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.
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
35
 
36
36
  ```typescript
37
- import { Tool, ToolContext, z } from '@frontmcp/sdk';
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 }>;
38
49
 
39
50
  @Tool({
40
51
  name: 'greet_user',
41
52
  description: 'Greet a user by name',
42
- inputSchema: {
43
- name: z.string().describe('The name of the user to greet'),
44
- },
53
+ inputSchema,
54
+ outputSchema,
45
55
  })
46
56
  class GreetUserTool extends ToolContext {
47
- async execute(input: { name: string }) {
48
- return `Hello, ${input.name}!`;
57
+ async execute(input: GreetUserInput): Promise<GreetUserOutput> {
58
+ return { greeting: `Hello, ${input.name}!` };
49
59
  }
50
60
  }
51
61
  ```
52
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
+
53
65
  ### Available Context Methods and Properties
54
66
 
55
67
  `ToolContext` extends `ExecutionContextBase`, which provides:
@@ -85,6 +97,43 @@ class GreetUserTool extends ToolContext {
85
97
  | `timestamp` | `number` | Request timestamp |
86
98
  | `metadata` | `RequestMetadata` | Request headers, client IP, etc. |
87
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
+
88
137
  ## Input Schema: Zod Raw Shapes
89
138
 
90
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.
@@ -119,25 +168,28 @@ The `execute()` parameter type must match the inferred output of `z.object(input
119
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.
120
169
 
121
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
+
122
185
  @Tool({
123
186
  name: 'get_weather',
124
187
  description: 'Get current weather for a location',
125
- inputSchema: {
126
- city: z.string().describe('City name'),
127
- },
128
- // Always define outputSchema to validate output and prevent data leaks
129
- outputSchema: {
130
- temperature: z.number(),
131
- unit: z.enum(['celsius', 'fahrenheit']),
132
- description: z.string(),
133
- },
188
+ inputSchema,
189
+ outputSchema,
134
190
  })
135
191
  class GetWeatherTool extends ToolContext {
136
- async execute(input: { city: string }): Promise<{
137
- temperature: number;
138
- unit: 'celsius' | 'fahrenheit';
139
- description: string;
140
- }> {
192
+ async execute(input: GetWeatherInput): Promise<GetWeatherOutput> {
141
193
  const response = await this.fetch(`https://api.weather.example.com/v1/current?city=${input.city}`);
142
194
  const weather = await response.json();
143
195
  // Only temperature, unit, and description are returned.
@@ -157,13 +209,15 @@ class GetWeatherTool extends ToolContext {
157
209
  - CodeCall cannot infer return types for chaining tool calls in VM scripts
158
210
  - No compile-time type checking on the return value
159
211
 
160
- ### Typed Output Patterns
212
+ ### Derive `execute()` types from the schemas (recommended)
161
213
 
162
- `ToolContext` infers both input and output types from the `@Tool` decorator's `inputSchema` / `outputSchema`. Do not pass explicit generics — the inference is automatic and gives you full type safety with no `as any` casts:
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:
163
215
 
164
216
  ```typescript
217
+ import { Tool, ToolContext, ToolInputOf, ToolOutputOf, z } from '@frontmcp/sdk';
218
+
165
219
  const inputSchema = {
166
- city: z.string(),
220
+ city: z.string().describe('City name'),
167
221
  };
168
222
 
169
223
  const outputSchema = {
@@ -171,21 +225,36 @@ const outputSchema = {
171
225
  unit: z.enum(['celsius', 'fahrenheit']),
172
226
  };
173
227
 
228
+ type GetWeatherInput = ToolInputOf<{ inputSchema: typeof inputSchema }>;
229
+ type GetWeatherOutput = ToolOutputOf<{ outputSchema: typeof outputSchema }>;
230
+
174
231
  @Tool({
175
232
  name: 'get_weather',
233
+ description: 'Get current weather for a location',
176
234
  inputSchema,
177
235
  outputSchema,
178
236
  })
179
- // No generics needed — ToolContext infers types from the @Tool decorator
180
237
  class GetWeatherTool extends ToolContext {
181
- async execute(input: { city: string }) {
182
- // Return type is inferred as { temperature: number; unit: 'celsius' | 'fahrenheit' }
183
- // TypeScript will error if you return the wrong shape
184
- return { temperature: 22, unit: 'celsius' as const };
238
+ async execute(input: GetWeatherInput): Promise<GetWeatherOutput> {
239
+ return { temperature: 22, unit: 'celsius' };
185
240
  }
186
241
  }
187
242
  ```
188
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
+
189
258
  **`return` vs `this.respond()`** — Both work and both are validated against `outputSchema` in the finalize stage:
190
259
 
191
260
  ```typescript
@@ -561,7 +630,9 @@ Restrict a tool to specific platforms, runtimes, or environments using `availabl
561
630
  name: 'apple_notes_search',
562
631
  description: 'Search Apple Notes',
563
632
  inputSchema: { query: z.string() },
564
- availableWhen: { platform: ['darwin'] },
633
+ // `os` is the canonical axis since issue #417; `platform` remains as
634
+ // a deprecated alias for backward compatibility.
635
+ availableWhen: { os: ['darwin'] },
565
636
  })
566
637
  class AppleNotesSearchTool extends ToolContext {
567
638
  async execute(input: { query: string }) {
@@ -583,13 +654,18 @@ class DeployServiceTool extends ToolContext {
583
654
  }
584
655
  ```
585
656
 
586
- Available constraint fields (AND across fields, OR within arrays):
657
+ Available constraint fields (AND across fields, OR within arrays). Issue #417 added `os` / `provider` / `target` / `surface`:
587
658
 
588
- - `platform` — OS: `'darwin'`, `'linux'`, `'win32'`
659
+ - `os` — OS (renamed from `platform`): `'darwin'`, `'linux'`, `'win32'`. `platform` is kept as a deprecated alias.
589
660
  - `runtime` — JS runtime: `'node'`, `'browser'`, `'edge'`, `'bun'`, `'deno'`
590
- - `deployment` — Mode: `'serverless'`, `'standalone'`
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.
591
665
  - `env` — NODE_ENV: `'production'`, `'development'`, `'test'`
592
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
+
593
669
  You can also check the platform imperatively inside `execute()`:
594
670
 
595
671
  ```typescript
@@ -675,14 +751,16 @@ class ConvertCurrencyTool extends ToolContext {
675
751
 
676
752
  ## Common Patterns
677
753
 
678
- | Pattern | Correct | Incorrect | Why |
679
- | -------------------- | ----------------------------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------- |
680
- | Input schema | `inputSchema: { name: z.string() }` (raw shape) | `inputSchema: z.object({ name: z.string() })` | Framework wraps in `z.object()` internally |
681
- | Output schema | Always define `outputSchema` | Omit `outputSchema` | Prevents data leaks and enables CodeCall chaining |
682
- | 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 |
683
- | Error handling | `this.fail(new ResourceNotFoundError(...))` | `throw new Error(...)` | `this.fail` triggers the error flow with MCP error codes |
684
- | Tool naming | `snake_case` names: `get_weather` | `camelCase` or `PascalCase`: `getWeather` | MCP protocol convention for tool names |
685
- | ToolContext generics | `class MyTool extends ToolContext` | `class MyTool extends ToolContext<typeof inputSchema>` | Types are auto-inferred from `@Tool` decorator explicit generics are redundant |
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 |
686
764
 
687
765
  ## Verification Checklist
688
766
 
@@ -714,11 +792,11 @@ class ConvertCurrencyTool extends ToolContext {
714
792
 
715
793
  ## Examples
716
794
 
717
- | Example | Level | Description |
718
- | --------------------------------------------------------------------------------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------- |
719
- | [`basic-class-tool`](../examples/create-tool/basic-class-tool.md) | Basic | A minimal tool using the class-based pattern with Zod input validation and output schema. |
720
- | [`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. |
721
- | [`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. |
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. |
722
800
 
723
801
  > See all examples in [`examples/create-tool/`](../examples/create-tool/)
724
802
 
@@ -39,11 +39,11 @@ Complete build walkthroughs and best practices for FrontMCP servers. Each exampl
39
39
 
40
40
  ### Skip When
41
41
 
42
- - You need to learn one specific component type (use the specific skill, e.g., `create-tool`)
43
- - Looking for the right skill for a task (use domain routers: `frontmcp-development`, `frontmcp-deployment`, etc.)
42
+ - You need to learn one specific component type (use the specific reference, e.g., `create-tool` under `frontmcp-development/references/`)
43
+ - Looking for the right reference for a task (use domain routers: `frontmcp-development`, `frontmcp-deployment`, etc.)
44
44
  - You need CLI/install instructions for the skills system (see `frontmcp-skills-usage`)
45
45
 
46
- > **Decision:** Use this skill when you want to see how everything fits together. Use individual skills when you need focused instruction.
46
+ > **Decision:** Use this skill when you want to see how everything fits together. Open individual references under each router's `references/` directory when you need focused instruction.
47
47
 
48
48
  ## Prerequisites
49
49
 
@@ -362,7 +362,7 @@ export class ResearcherAgent extends AgentContext {}
362
362
 
363
363
  ### Planning
364
364
 
365
- | Practice | Why | Skill Reference |
365
+ | Practice | Why | Reference |
366
366
  | ------------------------------------------------------ | ----------------------------------------------------------------- | ------------------------------------- |
367
367
  | Start with the `@App` boundaries, not individual tools | Apps define module boundaries; tools are implementation details | `multi-app-composition` |
368
368
  | Choose auth mode and storage before writing tools | Auth affects session handling, which affects storage requirements | `configure-auth`, `configure-session` |
@@ -370,7 +370,7 @@ export class ResearcherAgent extends AgentContext {}
370
370
 
371
371
  ### Organizing Code
372
372
 
373
- | Practice | Why | Skill Reference |
373
+ | Practice | Why | Reference |
374
374
  | ------------------------------------------------- | ----------------------------------------------------------- | ------------------------------ |
375
375
  | One class per file with `<name>.<type>.ts` naming | Consistency, generator compatibility, clear imports | `project-structure-standalone` |
376
376
  | Group by feature, not by type, for 10+ components | Feature folders scale better than flat `tools/` directories | `project-structure-standalone` |
@@ -378,7 +378,7 @@ export class ResearcherAgent extends AgentContext {}
378
378
 
379
379
  ### Writing Code
380
380
 
381
- | Practice | Why | Skill Reference |
381
+ | Practice | Why | Reference |
382
382
  | ----------------------------------------------- | ------------------------------------------------------------- | ----------------- |
383
383
  | Always define `outputSchema` on tools | Prevents data leaks, enables CodeCall chaining | `create-tool` |
384
384
  | Use `this.fail()` with MCP error classes | Proper error codes in protocol responses | `create-tool` |
@@ -473,5 +473,5 @@ when a server has been configured to host this skill.
473
473
 
474
474
  - [Your First Tool](https://docs.agentfront.dev/frontmcp/guides/your-first-tool)
475
475
  - Domain routers: `frontmcp-development`, `frontmcp-deployment`, `frontmcp-testing`, `frontmcp-config`
476
- - Core skills: `setup-project`, `create-tool`, `create-resource`, `create-provider`, `create-agent`, `configure-auth`, `setup-testing`
476
+ - Core references: `setup-project`, `create-tool`, `create-resource`, `create-provider`, `create-agent`, `configure-auth`, `setup-testing` (each lives under its parent router's `references/` directory, e.g. `frontmcp-development/references/create-tool.md`)
477
477
  - Mandatory boundaries: import MCP protocol types and `McpError` from `@frontmcp/protocol` (never directly from `@modelcontextprotocol/sdk`); use `@frontmcp/utils` for crypto and file-system operations.
@@ -54,6 +54,7 @@ Router for adding observability to FrontMCP servers. Covers distributed tracing
54
54
  | Create custom spans in tools/plugins | `references/telemetry-api.md` |
55
55
  | Connect Coralogix, Datadog, Logz.io, Grafana | `references/vendor-integrations.md` |
56
56
  | Test that spans and logs are correct | `references/testing-observability.md` |
57
+ | Expose Prometheus `/metrics` endpoint | `references/metrics-endpoint.md` |
57
58
 
58
59
  ## Step 2: Enable Observability
59
60
 
@@ -83,13 +84,14 @@ Follow the scenario routing table above to find the right reference for your use
83
84
 
84
85
  ## Scenario Routing Table
85
86
 
86
- | Scenario | Reference | Description |
87
- | ------------------------------------ | ------------------------------------- | --------------------------------------------------------------------------- |
88
- | Enable OpenTelemetry tracing | `references/tracing-setup.md` | Zero-config auto-instrumentation, setupOTel(), span hierarchy |
89
- | Add JSON logs with trace correlation | `references/structured-logging.md` | Sinks (stdout, console, OTLP, winston, pino), redaction, log format |
90
- | Custom spans in tools/plugins | `references/telemetry-api.md` | `this.telemetry.startSpan()`, `withSpan()`, `addEvent()`, `setAttributes()` |
91
- | Connect to monitoring platforms | `references/vendor-integrations.md` | Coralogix, Datadog, Logz.io, Grafana — OTLP and direct |
92
- | Test spans and log entries | `references/testing-observability.md` | `createTestTracer()`, `assertSpanExists()`, integration test patterns |
87
+ | Scenario | Reference | Description |
88
+ | ------------------------------------- | ------------------------------------- | --------------------------------------------------------------------------- |
89
+ | Enable OpenTelemetry tracing | `references/tracing-setup.md` | Zero-config auto-instrumentation, setupOTel(), span hierarchy |
90
+ | Add JSON logs with trace correlation | `references/structured-logging.md` | Sinks (stdout, console, OTLP, winston, pino), redaction, log format |
91
+ | Custom spans in tools/plugins | `references/telemetry-api.md` | `this.telemetry.startSpan()`, `withSpan()`, `addEvent()`, `setAttributes()` |
92
+ | Connect to monitoring platforms | `references/vendor-integrations.md` | Coralogix, Datadog, Logz.io, Grafana — OTLP and direct |
93
+ | Test spans and log entries | `references/testing-observability.md` | `createTestTracer()`, `assertSpanExists()`, integration test patterns |
94
+ | Expose Prometheus `/metrics` endpoint | `references/metrics-endpoint.md` | Off-by-default Prometheus scrape endpoint with process + framework counters |
93
95
 
94
96
  ## Common Patterns
95
97
 
@@ -187,6 +189,12 @@ Each reference has matching examples under [`examples/<reference>/`](./examples/
187
189
  | [`test-custom-spans`](./examples/testing-observability/test-custom-spans.md) | Basic | Verify that your tool creates the expected child spans with correct attributes. |
188
190
  | [`test-log-correlation`](./examples/testing-observability/test-log-correlation.md) | Intermediate | Verify that structured log entries include trace context fields for correlation with spans. |
189
191
 
192
+ ### `metrics-endpoint`
193
+
194
+ | Example | Level | Description |
195
+ | ----------------------------------------------------------------------------------- | ----- | -------------------------------------------------------------------- |
196
+ | [`enable-metrics-endpoint`](./examples/metrics-endpoint/enable-metrics-endpoint.md) | Basic | Turn on the /metrics endpoint with defaults and scrape it with curl. |
197
+
190
198
  ## Accessing This Skill
191
199
 
192
200
  Skills are distributed as plain SKILL.md files plus a sibling `references/`