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