@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.
- package/catalog/frontmcp-config/SKILL.md +5 -5
- package/catalog/frontmcp-config/references/configure-deployment-targets.md +84 -1
- package/catalog/frontmcp-config/references/configure-http.md +57 -2
- package/catalog/frontmcp-config/references/configure-session.md +14 -7
- package/catalog/frontmcp-deployment/SKILL.md +3 -3
- package/catalog/frontmcp-deployment/references/build-for-mcpb.md +1 -1
- package/catalog/frontmcp-deployment/references/mcp-client-integration.md +107 -0
- package/catalog/frontmcp-development/SKILL.md +7 -7
- package/catalog/frontmcp-development/examples/create-provider/basic-database-provider.md +14 -0
- package/catalog/frontmcp-development/examples/create-provider/config-and-api-providers.md +85 -9
- package/catalog/frontmcp-development/examples/create-tool/basic-class-tool.md +30 -11
- package/catalog/frontmcp-development/examples/create-tool/tool-with-di-and-errors.md +62 -14
- package/catalog/frontmcp-development/examples/create-tool/tool-with-rate-limiting-and-progress.md +30 -12
- package/catalog/frontmcp-development/references/create-job.md +45 -11
- package/catalog/frontmcp-development/references/create-provider.md +80 -8
- package/catalog/frontmcp-development/references/create-skill-with-tools.md +31 -0
- package/catalog/frontmcp-development/references/create-skill.md +45 -0
- package/catalog/frontmcp-development/references/create-tool.md +124 -46
- package/catalog/frontmcp-guides/SKILL.md +7 -7
- package/catalog/frontmcp-observability/SKILL.md +15 -7
- package/catalog/frontmcp-observability/examples/metrics-endpoint/enable-metrics-endpoint.md +77 -0
- package/catalog/frontmcp-observability/references/metrics-endpoint.md +161 -0
- package/catalog/frontmcp-setup/SKILL.md +11 -11
- package/catalog/frontmcp-setup/examples/frontmcp-skills-usage/install-and-search-skills.md +19 -1
- package/catalog/frontmcp-setup/references/frontmcp-skills-usage.md +260 -19
- package/catalog/frontmcp-setup/references/setup-project.md +29 -0
- package/catalog/frontmcp-setup/references/setup-sqlite.md +68 -9
- package/catalog/frontmcp-testing/SKILL.md +17 -17
- package/catalog/skills-manifest.json +32 -6
- 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
|
|
274
|
-
|
|
|
275
|
-
| Token definition
|
|
276
|
-
| DI resolution
|
|
277
|
-
| Lifecycle
|
|
278
|
-
| Registration scope
|
|
279
|
-
| Config provider
|
|
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
|
|
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
|
-
|
|
44
|
-
},
|
|
53
|
+
inputSchema,
|
|
54
|
+
outputSchema,
|
|
45
55
|
})
|
|
46
56
|
class GreetUserTool extends ToolContext {
|
|
47
|
-
async execute(input:
|
|
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
|
-
|
|
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:
|
|
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
|
-
###
|
|
212
|
+
### Derive `execute()` types from the schemas (recommended)
|
|
161
213
|
|
|
162
|
-
`ToolContext` infers
|
|
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:
|
|
182
|
-
|
|
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
|
-
|
|
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
|
-
- `
|
|
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` —
|
|
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
|
|
679
|
-
| -------------------- |
|
|
680
|
-
| Input schema | `inputSchema: { name: z.string() }` (raw shape)
|
|
681
|
-
| Output schema | Always define `outputSchema`
|
|
682
|
-
|
|
|
683
|
-
|
|
|
684
|
-
|
|
|
685
|
-
|
|
|
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
|
|
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
|
|
43
|
-
- Looking for the right
|
|
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.
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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
|
|
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
|
|
87
|
-
|
|
|
88
|
-
| Enable OpenTelemetry tracing
|
|
89
|
-
| Add JSON logs with trace correlation
|
|
90
|
-
| Custom spans in tools/plugins
|
|
91
|
-
| Connect to monitoring platforms
|
|
92
|
-
| Test spans and log entries
|
|
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/`
|