@frontmcp/skills 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +38 -29
  2. package/catalog/TEMPLATE.md +26 -0
  3. package/catalog/create-tool/SKILL.md +318 -0
  4. package/catalog/create-tool/examples/01-basic-class-tool.md +112 -0
  5. package/catalog/create-tool/examples/02-basic-function-tool.md +80 -0
  6. package/catalog/create-tool/examples/03-tool-with-zod-shape-output.md +78 -0
  7. package/catalog/create-tool/examples/04-tool-with-zod-schema-output.md +97 -0
  8. package/catalog/create-tool/examples/05-tool-with-primitive-output.md +93 -0
  9. package/catalog/create-tool/examples/06-tool-with-media-output.md +109 -0
  10. package/catalog/create-tool/examples/08-tool-with-provider-injection.md +110 -0
  11. package/catalog/create-tool/examples/09-tool-with-multiple-providers.md +107 -0
  12. package/catalog/create-tool/examples/11-tool-with-fetch.md +94 -0
  13. package/catalog/create-tool/examples/12-tool-with-fetch-and-retries.md +115 -0
  14. package/catalog/create-tool/examples/13-tool-with-single-auth-provider.md +85 -0
  15. package/catalog/create-tool/examples/14-tool-with-multiple-auth-providers.md +105 -0
  16. package/catalog/create-tool/examples/15-tool-with-credential-vault.md +115 -0
  17. package/catalog/create-tool/examples/16-tool-with-rate-limit.md +71 -0
  18. package/catalog/create-tool/examples/17-tool-with-concurrency-and-timeout.md +101 -0
  19. package/catalog/create-tool/examples/18-tool-with-progress-and-notify.md +96 -0
  20. package/catalog/create-tool/examples/19-tool-with-elicitation.md +102 -0
  21. package/catalog/create-tool/examples/20-tool-with-annotations.md +125 -0
  22. package/catalog/create-tool/examples/21-tool-with-availability-constraints.md +107 -0
  23. package/catalog/create-tool/examples/22-tool-with-ui-html-template.md +93 -0
  24. package/catalog/create-tool/examples/23-tool-with-ui-filesource-tsx.md +112 -0
  25. package/catalog/create-tool/examples/24-tool-with-ui-csp-and-bridge.md +127 -0
  26. package/catalog/create-tool/examples/25-tool-handing-off-to-job.md +143 -0
  27. package/catalog/create-tool/examples/26-tool-with-resource-link-output.md +94 -0
  28. package/catalog/create-tool/examples/27-tool-with-examples-metadata.md +90 -0
  29. package/catalog/create-tool/references/annotations.md +96 -0
  30. package/catalog/create-tool/references/auth-providers.md +167 -0
  31. package/catalog/create-tool/references/availability.md +106 -0
  32. package/catalog/create-tool/references/decorator-options.md +95 -0
  33. package/catalog/create-tool/references/derived-types.md +102 -0
  34. package/catalog/create-tool/references/elicitation.md +128 -0
  35. package/catalog/create-tool/references/error-handling.md +128 -0
  36. package/catalog/create-tool/references/execution-context.md +158 -0
  37. package/catalog/create-tool/references/file-layout.md +96 -0
  38. package/catalog/create-tool/references/function-style-builder.md +118 -0
  39. package/catalog/create-tool/references/input-schema.md +141 -0
  40. package/catalog/create-tool/references/output-schema.md +175 -0
  41. package/catalog/create-tool/references/quick-start.md +124 -0
  42. package/catalog/create-tool/references/registration.md +132 -0
  43. package/catalog/create-tool/references/remote-and-esm.md +68 -0
  44. package/catalog/create-tool/references/testing.md +59 -0
  45. package/catalog/create-tool/references/throttling.md +109 -0
  46. package/catalog/create-tool/references/ui-widgets.md +198 -0
  47. package/catalog/create-tool/rules/always-define-output-schema.md +77 -0
  48. package/catalog/create-tool/rules/derive-execute-types.md +57 -0
  49. package/catalog/create-tool/rules/input-schema-is-raw-shape.md +76 -0
  50. package/catalog/create-tool/rules/no-toolcontext-generics.md +50 -0
  51. package/catalog/create-tool/rules/no-try-catch-around-execute.md +79 -0
  52. package/catalog/create-tool/rules/register-in-app.md +76 -0
  53. package/catalog/create-tool/rules/snake-case-tool-names.md +45 -0
  54. package/catalog/create-tool/rules/use-this-fail-for-business-errors.md +75 -0
  55. package/catalog/create-tool/rules/widget-paths-anchor-with-import-meta-url.md +76 -0
  56. package/catalog/create-tool/rules/widget-resource-mode-host-detect.md +61 -0
  57. package/catalog/frontmcp-auth-ui/SKILL.md +146 -0
  58. package/catalog/frontmcp-auth-ui/examples/custom-auth-ui/login-slot.md +97 -0
  59. package/catalog/frontmcp-auth-ui/examples/custom-auth-ui/multi-step-auth-extra.md +133 -0
  60. package/catalog/frontmcp-auth-ui/references/custom-auth-ui.md +162 -0
  61. package/catalog/frontmcp-authorities/SKILL.md +55 -18
  62. package/catalog/frontmcp-authorities/references/authority-profiles.md +25 -1
  63. package/catalog/frontmcp-authorities/references/custom-evaluators.md +1 -1
  64. package/catalog/frontmcp-authorities/references/rbac-abac-rebac.md +9 -0
  65. package/catalog/frontmcp-channels/SKILL.md +7 -1
  66. package/catalog/frontmcp-config/SKILL.md +14 -7
  67. package/catalog/frontmcp-config/examples/configure-auth/local-credential-vault.md +94 -0
  68. package/catalog/frontmcp-config/examples/configure-auth/local-secure-store.md +138 -0
  69. package/catalog/frontmcp-config/examples/configure-auth/remote-oauth-with-vault.md +45 -23
  70. package/catalog/frontmcp-config/examples/configure-auth-modes/local-behind-tunnel.md +73 -0
  71. package/catalog/frontmcp-config/examples/configure-auth-modes/local-consent-enforcement.md +87 -0
  72. package/catalog/frontmcp-config/examples/configure-auth-modes/local-dcr-control.md +67 -0
  73. package/catalog/frontmcp-config/examples/configure-auth-modes/local-minimal.md +62 -0
  74. package/catalog/frontmcp-config/examples/configure-auth-modes/local-multi-provider-orchestration.md +93 -0
  75. package/catalog/frontmcp-config/examples/configure-auth-modes/local-self-signed-tokens.md +18 -20
  76. package/catalog/frontmcp-config/examples/configure-auth-modes/local-single-operator.md +66 -0
  77. package/catalog/frontmcp-config/examples/configure-auth-modes/remote-enterprise-oauth.md +37 -23
  78. package/catalog/frontmcp-config/examples/configure-http/custom-http-routes.md +98 -0
  79. package/catalog/frontmcp-config/examples/configure-skills-http/audit-log-redis.md +17 -9
  80. package/catalog/frontmcp-config/references/configure-auth-modes.md +86 -23
  81. package/catalog/frontmcp-config/references/configure-auth.md +296 -50
  82. package/catalog/frontmcp-config/references/configure-deployment-targets.md +84 -1
  83. package/catalog/frontmcp-config/references/configure-http.md +203 -14
  84. package/catalog/frontmcp-config/references/configure-session.md +14 -7
  85. package/catalog/frontmcp-deployment/SKILL.md +17 -15
  86. package/catalog/frontmcp-deployment/references/build-for-mcpb.md +1 -1
  87. package/catalog/frontmcp-deployment/references/deploy-manifest-yaml.md +308 -0
  88. package/catalog/frontmcp-deployment/references/deploy-to-cloudflare-skills-only.md +174 -0
  89. package/catalog/frontmcp-deployment/references/mcp-client-integration.md +145 -2
  90. package/catalog/frontmcp-development/SKILL.md +36 -50
  91. package/catalog/frontmcp-development/examples/create-provider/basic-database-provider.md +14 -0
  92. package/catalog/frontmcp-development/examples/create-provider/config-and-api-providers.md +85 -9
  93. package/catalog/frontmcp-development/references/create-job.md +45 -11
  94. package/catalog/frontmcp-development/references/create-provider.md +80 -8
  95. package/catalog/frontmcp-development/references/create-skill-with-tools.md +31 -0
  96. package/catalog/frontmcp-development/references/create-skill.md +45 -0
  97. package/catalog/frontmcp-development/references/decorators-guide.md +15 -15
  98. package/catalog/frontmcp-extensibility/SKILL.md +1 -1
  99. package/catalog/frontmcp-extensibility/examples/skill-audit-log/verify-chain.md +8 -6
  100. package/catalog/frontmcp-extensibility/references/skill-audit-log.md +7 -2
  101. package/catalog/frontmcp-guides/SKILL.md +8 -8
  102. package/catalog/frontmcp-observability/SKILL.md +16 -8
  103. package/catalog/frontmcp-observability/examples/metrics-endpoint/enable-metrics-endpoint.md +77 -0
  104. package/catalog/frontmcp-observability/references/metrics-endpoint.md +161 -0
  105. package/catalog/frontmcp-production-readiness/SKILL.md +1 -1
  106. package/catalog/frontmcp-production-readiness/examples/common-checklist/security-hardening.md +3 -2
  107. package/catalog/frontmcp-setup/SKILL.md +12 -12
  108. package/catalog/frontmcp-setup/examples/frontmcp-skills-usage/install-and-search-skills.md +19 -1
  109. package/catalog/frontmcp-setup/examples/multi-app-composition/per-app-auth-and-isolation.md +7 -4
  110. package/catalog/frontmcp-setup/references/frontmcp-skills-usage.md +260 -19
  111. package/catalog/frontmcp-setup/references/multi-app-composition.md +6 -5
  112. package/catalog/frontmcp-setup/references/setup-project.md +29 -0
  113. package/catalog/frontmcp-setup/references/setup-sqlite.md +68 -9
  114. package/catalog/frontmcp-testing/SKILL.md +26 -18
  115. package/catalog/frontmcp-testing/references/test-auth.md +24 -0
  116. package/catalog/skills-manifest.json +676 -146
  117. package/package.json +1 -1
  118. package/src/manifest.d.ts +72 -1
  119. package/src/manifest.js +4 -1
  120. package/src/manifest.js.map +1 -1
  121. package/catalog/frontmcp-development/examples/create-tool/basic-class-tool.md +0 -61
  122. package/catalog/frontmcp-development/examples/create-tool/tool-with-di-and-errors.md +0 -84
  123. package/catalog/frontmcp-development/examples/create-tool/tool-with-rate-limiting-and-progress.md +0 -92
  124. package/catalog/frontmcp-development/examples/create-tool-annotations/destructive-delete-tool.md +0 -92
  125. package/catalog/frontmcp-development/examples/create-tool-annotations/readonly-query-tool.md +0 -59
  126. package/catalog/frontmcp-development/examples/create-tool-output-schema-types/primitive-and-media-outputs.md +0 -101
  127. package/catalog/frontmcp-development/examples/create-tool-output-schema-types/zod-raw-shape-output.md +0 -62
  128. package/catalog/frontmcp-development/examples/create-tool-output-schema-types/zod-schema-advanced-output.md +0 -101
  129. package/catalog/frontmcp-development/references/create-tool-annotations.md +0 -48
  130. package/catalog/frontmcp-development/references/create-tool-output-schema-types.md +0 -71
  131. package/catalog/frontmcp-development/references/create-tool.md +0 -728
@@ -0,0 +1,167 @@
1
+ ---
2
+ name: auth-providers
3
+ description: authProviders shorthand vs full mapping, scopes, alias, credential vault basics.
4
+ ---
5
+
6
+ # `authProviders`
7
+
8
+ Declare which OAuth (or static-credential) providers a tool requires. Credentials are loaded **before** `execute()` runs — by the time your code runs, the headers / tokens are already available via `this.authProviders.headers(name)`.
9
+
10
+ ## String shorthand (single required provider)
11
+
12
+ ```typescript
13
+ @Tool({
14
+ name: 'create_issue',
15
+ description: 'Create a GitHub issue',
16
+ inputSchema,
17
+ outputSchema,
18
+ authProviders: ['github'],
19
+ })
20
+ class CreateIssueTool extends ToolContext {
21
+ async execute(input: CreateIssueInput) {
22
+ const headers = await this.authProviders.headers('github');
23
+ const response = await this.fetch('https://api.github.com/repos/.../issues', {
24
+ method: 'POST',
25
+ headers: { ...headers, 'content-type': 'application/json' },
26
+ body: JSON.stringify({ title: input.title, body: input.body }),
27
+ });
28
+ return response.json();
29
+ }
30
+ }
31
+ ```
32
+
33
+ `headers('github')` returns `{ Authorization: 'Bearer …' }` (or similar, depending on the provider). The framework handles refresh, expiration, and storage.
34
+
35
+ ## Full mapping form
36
+
37
+ For scopes, required-vs-optional, or aliases:
38
+
39
+ ```typescript
40
+ @Tool({
41
+ name: 'deploy_app',
42
+ description: 'Deploy a service to cloud',
43
+ inputSchema,
44
+ outputSchema,
45
+ authProviders: [
46
+ { name: 'github', required: true, scopes: ['repo', 'workflow'] },
47
+ { name: 'aws', required: false, alias: 'cloud' }, // optional; injected as `cloud`
48
+ ],
49
+ })
50
+ class DeployAppTool extends ToolContext {
51
+ async execute(input: DeployInput) {
52
+ const githubHeaders = await this.authProviders.headers('github');
53
+ // `cloud` is the alias for the optional `aws` provider. `headers()` yields
54
+ // an empty object `{}` when the optional credential isn't available:
55
+ const cloudHeaders = await this.authProviders.headers('cloud');
56
+ const hasCloud = Object.keys(cloudHeaders).length > 0;
57
+ // …
58
+ }
59
+ }
60
+ ```
61
+
62
+ ### Fields
63
+
64
+ | Field | Type | Default | Meaning |
65
+ | ---------- | ---------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
66
+ | `name` | `string` | — | Must match a registered credential provider on the server |
67
+ | `required` | `boolean` | `true` | If `true`, the tool fails before `execute()` runs when no credentials are available (call-time gate). If `false`, the call proceeds; `this.authProviders.headers(name)` returns `{}` (empty) when absent — check with `Object.keys(...).length > 0` |
68
+ | `scopes` | `string[]` | — | Required OAuth scopes. Advertised in the server's Protected Resource Metadata (`scopes_supported`, RFC 9728) so clients know to request them |
69
+ | `alias` | `string` | = `name` | Local name for the provider — useful when two tools want the same provider under different labels |
70
+
71
+ `required` and `scopes` are independent axes: **`required` gates at call time** (a missing credential aborts the call before `execute()`), while **`scopes` feed PRM advertising** so the OAuth client knows which scopes the server's tools need.
72
+
73
+ ## When auth is missing
74
+
75
+ For a `required: true` provider with no credentials, the framework's call-tool
76
+ flow runs a credential gate **before** `execute()`:
77
+
78
+ 1. The framework aborts the call BEFORE `execute()` runs.
79
+ 2. The client receives a JSON-RPC error with **code `-32001`** (MCP `UNAUTHORIZED`)
80
+ and this `data` payload:
81
+
82
+ ```json
83
+ {
84
+ "tool": "deploy_app",
85
+ "providers": ["github"],
86
+ "authUrl": "https://your-server/oauth/connect?token=…",
87
+ "auth_url": "https://your-server/oauth/connect?token=…"
88
+ }
89
+ ```
90
+
91
+ - `tool` — the tool that was gated.
92
+ - `providers` — every required provider whose credential is missing.
93
+ - `authUrl` / `auth_url` — the same connect/authorize URL under both the
94
+ camelCase key (primary) and the snake_case key (matching the app-level
95
+ `authorization_required` error), so either convention resolves. Present
96
+ when the framework can mint a connect URL for the session.
97
+
98
+ 3. The user opens the URL and connects the credential.
99
+ 4. The client retries the tool call — the gate now passes and `execute()` runs.
100
+
101
+ This is handled by the framework — you don't write any of this in `execute()`.
102
+
103
+ > The gate is a no-op (no new errors) for tools with no `authProviders`, for
104
+ > `required: false` providers, and for unauthenticated / public / no-credential-vault
105
+ > requests — so adding `authProviders` never changes behavior unless a credential
106
+ > vault is actually configured and a required credential is genuinely missing.
107
+
108
+ ## Reading auth in execute()
109
+
110
+ Two patterns:
111
+
112
+ ### A. Pre-formatted headers (most common)
113
+
114
+ ```typescript
115
+ const headers = await this.authProviders.headers('github');
116
+ const response = await this.fetch(url, { headers });
117
+ ```
118
+
119
+ `headers(name)` returns a plain `Record<string, string>` (NOT a `Headers` object — read values with `headers['x-foo']`, not `headers.get(...)`). It never throws: when no credential is available it returns an empty object `{}`. For a `required: true` provider the framework already guaranteed creds before `execute()` ran, so it's non-empty; for a `required: false` provider, guard with `Object.keys(headers).length > 0`.
120
+
121
+ ### B. Raw token / credential fields (for non-HTTP transports — gRPC, WebSocket, custom)
122
+
123
+ ```typescript
124
+ const resolved = await this.authProviders.get('github');
125
+ // resolved is `ResolvedCredential | null`:
126
+ // { credential, providerId, acquiredAt, expiresAt?, isValid, scope }
127
+ // The token lives under `.credential`. For an oauth provider:
128
+ const accessToken = resolved?.credential.accessToken;
129
+ ```
130
+
131
+ ### C. Full credential record (vault access)
132
+
133
+ ```typescript
134
+ const resolved = await this.authProviders.get('github');
135
+ // resolved?.credential is the typed credential, e.g. for oauth:
136
+ // { type: 'oauth', accessToken, refreshToken?, expiresAt?, tokenType, … }
137
+ ```
138
+
139
+ Use the highest-level API that works (headers > full credential record via `get`) — `headers()` can short-circuit refresh / vault round-trips and formats the auth header for you.
140
+
141
+ ## Credential vault
142
+
143
+ For session-specific secrets the user types in (vs OAuth flows), use the credential vault:
144
+
145
+ ```typescript
146
+ @Tool({
147
+ name: 'send_to_slack',
148
+ authProviders: ['slack-webhook'], // a vault-backed provider
149
+ })
150
+ class SendToSlackTool extends ToolContext {
151
+ async execute(input: { message: string }) {
152
+ const headers = await this.authProviders.headers('slack-webhook');
153
+ // headers contains the webhook URL the user pasted in earlier:
154
+ // { 'x-slack-webhook-url': 'https://hooks.slack.com/services/…' }
155
+ // …
156
+ }
157
+ }
158
+ ```
159
+
160
+ The vault is encrypted at rest (per-session AES-256-GCM key) and never leaves the server. See the `auth` skill for vault setup, OAuth provider registration, and the credential UI.
161
+
162
+ ## See also
163
+
164
+ - [`13-tool-with-single-auth-provider`](../examples/13-tool-with-single-auth-provider.md)
165
+ - [`14-tool-with-multiple-auth-providers`](../examples/14-tool-with-multiple-auth-providers.md)
166
+ - [`15-tool-with-credential-vault`](../examples/15-tool-with-credential-vault.md)
167
+ - `auth` skill — provider registration, vault, OAuth flows
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: availability
3
+ description: availableWhen axes (os / runtime / deployment / provider / target / surface / env), missingAxes, isPlatform.
4
+ ---
5
+
6
+ # `availableWhen` — registry-level availability constraints
7
+
8
+ `availableWhen` is a **hard registry-level constraint** evaluated at server boot. Tools that don't match are filtered out of `tools/list` AND blocked from execution. Different from authorization (per-request) and from rule-based filtering (dynamic).
9
+
10
+ ## Quick example
11
+
12
+ ```typescript
13
+ @Tool({
14
+ name: 'apple_notes_search',
15
+ description: 'Search Apple Notes',
16
+ inputSchema,
17
+ outputSchema,
18
+ availableWhen: { os: ['darwin'] }, // macOS-only
19
+ })
20
+ class AppleNotesSearchTool extends ToolContext {
21
+ /* … */
22
+ }
23
+ ```
24
+
25
+ On Linux / Windows servers, this tool simply doesn't exist — it's not in `tools/list`, and calling it returns `EntryUnavailableError`.
26
+
27
+ ## Axes
28
+
29
+ | Axis | Values | Source |
30
+ | ------------ | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
31
+ | `os` | `'darwin'`, `'linux'`, `'win32'` | `process.platform` (since #417 — was previously `platform`) |
32
+ | `runtime` | `'node'`, `'browser'`, `'edge'`, `'bun'`, `'deno'` | Detected at boot |
33
+ | `deployment` | `'serverless'`, `'standalone'`, `'distributed'`, `'browser'` | Detected from `frontmcp.config` / env |
34
+ | `provider` | `'bare'`, `'docker'`, `'vercel'`, `'lambda'`, `'cloudflare'`, `'netlify'`, `'azure'`, `'gcp'`, `'fly'`, `'render'`, `'railway'` | Auto-detected; override with `FRONTMCP_PROVIDER=<name>` |
35
+ | `target` | `'cli'`, `'node'`, `'vercel'`, `'lambda'`, `'cloudflare'`, `'browser'`, `'sdk'`, `'mcpb'`, `'distributed'` | Set by `frontmcp build --target <x>`; `'unknown'` in dev |
36
+ | `surface` | `'mcp'`, `'cli'`, `'agent'`, `'job'`, `'http-trigger'` | Per-call axis — which entry point is invoking the tool |
37
+ | `env` | `'production'`, `'development'`, `'test'` | `process.env.NODE_ENV` |
38
+
39
+ ## Semantics
40
+
41
+ - **Multiple axes** are AND-ed. `{ os: ['darwin'], env: ['production'] }` means macOS in production.
42
+ - **Multiple values within an axis** are OR-ed. `os: ['darwin', 'linux']` means macOS OR Linux (not Windows).
43
+ - **Omitted axes** are wildcard. No `env` field → matches every env.
44
+
45
+ ```typescript
46
+ @Tool({
47
+ name: 'deploy_service',
48
+ // Node.js production-only:
49
+ availableWhen: { runtime: ['node'], env: ['production'] },
50
+ })
51
+ ```
52
+
53
+ ## Error shape on mismatch
54
+
55
+ When the constraint fails at call time, FrontMCP throws `EntryUnavailableError` (string code `'ENTRY_UNAVAILABLE'`, JSON-RPC `-32003` FORBIDDEN, HTTP 403). Its `data` carries `missingAxes: string[]` (since #417) so clients can surface a specific reason without parsing prose:
56
+
57
+ ```json
58
+ {
59
+ "code": -32003,
60
+ "message": "Tool 'deploy_service' is not available in this environment.",
61
+ "data": {
62
+ "missingAxes": ["env"],
63
+ "expected": { "env": ["production"] },
64
+ "actual": { "env": "development" }
65
+ }
66
+ }
67
+ ```
68
+
69
+ ## Imperative checks
70
+
71
+ You can also check the platform inside `execute()` for branches that aren't hard constraints:
72
+
73
+ ```typescript
74
+ async execute(input: Input) {
75
+ if (this.isPlatform('darwin')) {
76
+ return this.useNativeNotes(input);
77
+ }
78
+ return this.useCrossPlatformFallback(input);
79
+ }
80
+ ```
81
+
82
+ | Method | Returns |
83
+ | --------------------- | ---------------------------------------------------------------------------------- |
84
+ | `this.isPlatform(os)` | `boolean` (alias preserved: `'platform'` works as a deprecated synonym for `'os'`) |
85
+ | `this.isRuntime(rt)` | `boolean` |
86
+ | `this.isEnv(env)` | `boolean` |
87
+
88
+ These are fine for ergonomic branching. For tools that **shouldn't exist at all** on certain platforms, prefer the declarative `availableWhen` — it removes the tool from `tools/list` (clients won't even propose it).
89
+
90
+ ## `surface` — the per-call axis
91
+
92
+ `surface` is the only axis that varies per-call. Use it when a tool should be reachable by some entry points but not others:
93
+
94
+ ```typescript
95
+ @Tool({
96
+ name: 'rotate_secrets',
97
+ availableWhen: { surface: ['agent', 'job'] }, // not callable from MCP clients or CLI directly
98
+ })
99
+ ```
100
+
101
+ This is the safest way to expose internal-only tools that you want an agent / job to call but don't want a user to invoke from a chat UI.
102
+
103
+ ## See also
104
+
105
+ - [`21-tool-with-availability-constraints`](../examples/21-tool-with-availability-constraints.md)
106
+ - [`decorator-options.md`](./decorator-options.md)
@@ -0,0 +1,95 @@
1
+ ---
2
+ name: decorator-options
3
+ description: Every field on @Tool({...}) — what it does, default, when to set it.
4
+ ---
5
+
6
+ # `@Tool({...})` options
7
+
8
+ Full surface of the `@Tool` decorator. Mandatory fields are bolded.
9
+
10
+ | Field | Type | Default | When to set |
11
+ | ----------------- | ------------------------------------------------------------------------------ | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
12
+ | **`name`** | `string` (snake_case) | — | Always. MCP protocol convention. `get_weather`, not `getWeather`. |
13
+ | `description` | `string` | — | Almost always. Shows in `tools/list` and helps clients choose the right tool. |
14
+ | **`inputSchema`** | Zod raw shape | — | Always. `{ field: z.string() }` — never wrapped in `z.object`. See [`input-schema.md`](./input-schema.md). |
15
+ | `outputSchema` | Zod raw shape / Zod schema / primitive literal / media literal / array | — | **Always** — prevents data leaks and enables CodeCall chaining. See [`output-schema.md`](./output-schema.md). |
16
+ | `output` | `{ allowNonFinite?, schemaMode?, schemaDescriptionFormat? }` | `definition` | Control how `outputSchema` is exposed in `tools/list` (`'definition'` \| `'description'` \| `'both'` \| `'none'`) and `allowNonFinite`. Cascades Tool > App > server. See [`output-schema.md`](./output-schema.md#exposure-mode). |
17
+ | `annotations` | `{ title?, readOnlyHint?, destructiveHint?, idempotentHint?, openWorldHint? }` | — | When the tool has notable behavioral semantics clients should be hinted about. See [`annotations.md`](./annotations.md). |
18
+ | `rateLimit` | `{ maxRequests, windowMs }` | — | Expensive / external-dependent / abuse-prone tools. See [`throttling.md`](./throttling.md). |
19
+ | `concurrency` | `{ maxConcurrent }` | — | Tools that hold a scarce resource (DB connection, GPU). See [`throttling.md`](./throttling.md). |
20
+ | `timeout` | `{ executeMs }` | — | Any tool that could legitimately hang. See [`throttling.md`](./throttling.md). |
21
+ | `authProviders` | `string[]` \| `Array<{ name, scopes?, required?, alias? }>` | — | Tool requires user OAuth credentials. See [`auth-providers.md`](./auth-providers.md). |
22
+ | `availableWhen` | `{ os?, runtime?, deployment?, provider?, target?, surface?, env? }` | — | Hard registry-level constraint — tool is filtered out of `tools/list` and execution when context doesn't match. See [`availability.md`](./availability.md). |
23
+ | `examples` | `Array<{ description, input, output? }>` | — | Discovery / docs surfacing. The client may show these to the user. |
24
+ | `ui` | `ToolUIConfig<In, Out>` | — | Tool result should render as a widget in the host UI. See [`ui-widgets.md`](./ui-widgets.md). |
25
+ | `visibility` | `'public' \| 'hidden' \| 'internal'` | `'public'` | Control discoverability/reachability. `'public'`: listed in `tools/list` + callable via `tools/call`. `'hidden'`: not listed, still callable by name. `'internal'`: not listed AND not externally callable — only via `this.callTool(...)` from inside the SDK. |
26
+
27
+ > `hideFromDiscovery: boolean` is a **deprecated** alias for `visibility`: when `visibility` is unset, `hideFromDiscovery: true` is treated as `visibility: 'hidden'`. Prefer `visibility` in new code.
28
+
29
+ > The decorator is type-safe: `outputSchema` flows back into `ToolContext.execute()`'s return type without needing explicit generics on the class. See [`derived-types.md`](./derived-types.md).
30
+
31
+ ## Mandatory fields
32
+
33
+ - `name` — must be unique within the server, `snake_case`. Used as the lookup key for `tools/call`.
34
+ - `inputSchema` — even an empty-input tool declares `inputSchema: {}`. The framework wraps it in `z.object(...)` internally and validates every call.
35
+
36
+ ## Almost-always-set fields
37
+
38
+ - `description` — without it the tool is anonymous in `tools/list`. AI clients pick tools based on description text.
39
+ - `outputSchema` — see [`rules/always-define-output-schema.md`](../rules/always-define-output-schema.md).
40
+
41
+ ## Field interactions
42
+
43
+ - **`rateLimit` + `concurrency`** — independent. Rate-limit caps invocations over time; concurrency caps simultaneous in-flight. A "1 req/s with max 2 concurrent" tool is fine: bursts can run two at once, then back off.
44
+ - **`timeout` + `rateLimit`** — orthogonal. Timeout wraps a single call; rate-limit wraps the rate of calls.
45
+ - **`authProviders` + `ui.widgetAccessible`** — the widget bridge respects the tool's auth requirements. A widget that calls back to a tool requiring `authProviders: ['github']` will fail in the bridge if no GitHub session exists.
46
+ - **`availableWhen` + `visibility`** — `availableWhen` is a hard constraint (filtered out of `tools/list` AND blocked from execution when context doesn't match); `visibility: 'hidden'` is a soft hide (filtered from `tools/list` but still callable by name). `visibility: 'internal'` blocks external `tools/call` entirely (in-process `this.callTool` only).
47
+ - **`ui.servingMode === 'static'` + `availableWhen`** — static widgets pre-compile at startup. If a tool is filtered out by `availableWhen`, its static widget isn't compiled either.
48
+
49
+ ## Forbidden combinations
50
+
51
+ - `inputSchema: z.object(...)` at the top level — see [`rules/input-schema-is-raw-shape.md`](../rules/input-schema-is-raw-shape.md).
52
+ - `extends ToolContext<typeof inputSchema>` — explicit generics on the class. See [`rules/no-toolcontext-generics.md`](../rules/no-toolcontext-generics.md).
53
+ - Mixing function-style `tool({...})(handler)` with class-style `@Tool` + `extends ToolContext` for the same tool. Pick one.
54
+
55
+ ## Minimal vs production
56
+
57
+ ```typescript
58
+ // Minimal — fine for a prototype tool
59
+ @Tool({
60
+ name: 'ping',
61
+ description: 'Liveness check',
62
+ inputSchema: {},
63
+ outputSchema: 'string',
64
+ })
65
+ class PingTool extends ToolContext {
66
+ execute(): string {
67
+ return 'pong';
68
+ }
69
+ }
70
+ ```
71
+
72
+ ```typescript
73
+ // Production — full surface for a real action
74
+ @Tool({
75
+ name: 'create_issue',
76
+ description: 'Create a GitHub issue in the active repo',
77
+ inputSchema,
78
+ outputSchema,
79
+ annotations: { title: 'Create issue', destructiveHint: false, idempotentHint: false, openWorldHint: true },
80
+ rateLimit: { maxRequests: 30, windowMs: 60_000 },
81
+ concurrency: { maxConcurrent: 5 },
82
+ timeout: { executeMs: 30_000 },
83
+ authProviders: [{ name: 'github', required: true, scopes: ['repo'] }],
84
+ availableWhen: { surface: ['mcp', 'agent'] },
85
+ examples: [{ description: 'File a bug', input: { title: 'X is broken', body: '…' } }],
86
+ })
87
+ class CreateIssueTool extends ToolContext {
88
+ /* … */
89
+ }
90
+ ```
91
+
92
+ ## See also
93
+
94
+ - [`rules/`](../rules/) — short DO/DON'T constraints per field
95
+ - [`execution-context.md`](./execution-context.md) — what `ToolContext` provides at runtime
@@ -0,0 +1,102 @@
1
+ ---
2
+ name: derived-types
3
+ description: Derive execute() parameter and return types from the schemas via ToolInputOf / ToolOutputOf.
4
+ ---
5
+
6
+ # Derived `execute()` types
7
+
8
+ The schema is the single source of truth. Hand-typing `execute(input: { name: string })` next to a schema declaring `name: z.string()` is a second declaration of the same shape — change the schema without touching the annotation and TypeScript happily compiles while runtime validation silently rejects.
9
+
10
+ Derive types from the schemas with `ToolInputOf<>` / `ToolOutputOf<>`. The compiler catches divergence at build time.
11
+
12
+ ## Pattern
13
+
14
+ ```typescript
15
+ // src/apps/main/tools/greet-user.schema.ts
16
+ import { ToolInputOf, ToolOutputOf, z } from '@frontmcp/sdk';
17
+
18
+ export const inputSchema = {
19
+ name: z.string().describe('The name to greet'),
20
+ };
21
+
22
+ export const outputSchema = {
23
+ greeting: z.string(),
24
+ };
25
+
26
+ export type GreetUserInput = ToolInputOf<{ inputSchema: typeof inputSchema }>;
27
+ export type GreetUserOutput = ToolOutputOf<{ outputSchema: typeof outputSchema }>;
28
+ ```
29
+
30
+ ```typescript
31
+ // src/apps/main/tools/greet-user.tool.ts
32
+ import { Tool, ToolContext } from '@frontmcp/sdk';
33
+
34
+ import { inputSchema, outputSchema, type GreetUserInput, type GreetUserOutput } from './greet-user.schema';
35
+
36
+ @Tool({
37
+ name: 'greet_user',
38
+ description: 'Greet a user by name',
39
+ inputSchema,
40
+ outputSchema,
41
+ })
42
+ export class GreetUserTool extends ToolContext {
43
+ async execute(input: GreetUserInput): Promise<GreetUserOutput> {
44
+ return { greeting: `Hello, ${input.name}!` };
45
+ }
46
+ }
47
+ ```
48
+
49
+ ## Two equivalent forms
50
+
51
+ ```typescript
52
+ // Form 1 — SDK helpers (preferred — survives any future shape changes to ToolContext)
53
+ type GreetUserInput = ToolInputOf<{ inputSchema: typeof inputSchema }>;
54
+ type GreetUserOutput = ToolOutputOf<{ outputSchema: typeof outputSchema }>;
55
+
56
+ // Form 2 — raw zod (terser if you don't mind a direct z dependency)
57
+ type GreetUserInput = z.infer<z.ZodObject<typeof inputSchema>>;
58
+ type GreetUserOutput = z.infer<z.ZodObject<typeof outputSchema>>;
59
+ ```
60
+
61
+ Both produce identical types. Pick whichever fits the surrounding code. Form 1 is recommended because `ToolInputOf` / `ToolOutputOf` track any future shape changes to `ToolContext` (e.g., if metadata wrapping changes).
62
+
63
+ ## What to hoist, what to leave inline
64
+
65
+ Hoist **only the schemas** to `<name>.schema.ts`. The decorator config (`name`, `description`, `annotations`, `rateLimit`, `authProviders`, …) stays inside `@Tool({…})` where it belongs.
66
+
67
+ ```typescript
68
+ // ✅ schemas only — re-importable by specs, sibling tools, generated clients
69
+ export const inputSchema = { … };
70
+ export const outputSchema = { … };
71
+
72
+ // ❌ don't hoist the @Tool config — it's tool-specific metadata, not a shape contract
73
+ export const toolConfig = { name: 'greet_user', description: '…', inputSchema, outputSchema };
74
+ ```
75
+
76
+ ## Don't add generics to ToolContext
77
+
78
+ ```typescript
79
+ // ❌ ToolContext<typeof inputSchema> — redundant; @Tool decorator already infers them
80
+ class GreetUserTool extends ToolContext<typeof inputSchema> { … }
81
+
82
+ // ✅ Plain ToolContext — @Tool's inference flows in automatically
83
+ class GreetUserTool extends ToolContext { … }
84
+ ```
85
+
86
+ See [`rules/no-toolcontext-generics.md`](../rules/no-toolcontext-generics.md).
87
+
88
+ ## Why derive?
89
+
90
+ | Without derived types | With derived types |
91
+ | ---------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
92
+ | Change `inputSchema` → execute()'s `input:` annotation silently goes stale → runtime validation rejects calls that the compiler accepted | Change `inputSchema` → execute() signature follows → compiler catches drift at the call site |
93
+ | Specs and helpers hand-type their own shapes (third source of truth) | Specs import `GreetUserInput` from the schema file (one source of truth) |
94
+ | Generated clients drift from server contract | Generated clients import the same exported types |
95
+
96
+ ## See also
97
+
98
+ - [`input-schema.md`](./input-schema.md)
99
+ - [`output-schema.md`](./output-schema.md)
100
+ - [`file-layout.md`](./file-layout.md)
101
+ - [`rules/derive-execute-types.md`](../rules/derive-execute-types.md)
102
+ - [`rules/no-toolcontext-generics.md`](../rules/no-toolcontext-generics.md)
@@ -0,0 +1,128 @@
1
+ ---
2
+ name: elicitation
3
+ description: this.elicit — request interactive input mid-execution. Server enable + accept/decline/cancel flow.
4
+ ---
5
+
6
+ # Elicitation
7
+
8
+ `this.elicit(message, requestedSchema)` lets a tool ask for additional input mid-execution. The MCP client renders a UI (form / prompt), the user fills it in, and the response flows back to your `execute()` body. `requestedSchema` must be a Zod schema (e.g. `z.object({...})`), not a raw field map.
9
+
10
+ ## Prerequisite — enable at server level
11
+
12
+ ```typescript
13
+ @FrontMcp({
14
+ info: { name: '…', version: '1.0.0' },
15
+ apps: [MainApp],
16
+ elicitation: { enabled: true },
17
+ })
18
+ ```
19
+
20
+ Without `elicitation.enabled: true`, every `this.elicit(...)` call throws `ElicitationDisabledError` at runtime. There is no compile-time warning — the error only fires when the tool actually runs.
21
+
22
+ For production, configure a Redis-backed elicitation store via `elicitation.store: { provider: 'redis', … }` (the default in-memory store loses pending elicitations on restart).
23
+
24
+ ## Quick example
25
+
26
+ ```typescript
27
+ @Tool({
28
+ name: 'confirm_delete',
29
+ description: 'Delete a resource after explicit user confirmation',
30
+ inputSchema: { resourceId: z.string() },
31
+ outputSchema: { deleted: z.boolean() },
32
+ annotations: { destructiveHint: true, idempotentHint: true },
33
+ })
34
+ class ConfirmDeleteTool extends ToolContext {
35
+ async execute(input: { resourceId: string }) {
36
+ const result = await this.elicit(
37
+ 'Permanently delete this resource? This cannot be undone.',
38
+ z.object({
39
+ confirm: z.boolean().describe('Type true to confirm'),
40
+ reason: z.string().optional().describe('Optional reason for the audit log'),
41
+ }),
42
+ );
43
+
44
+ if (result.status !== 'accept' || !result.content?.confirm) {
45
+ return { deleted: false };
46
+ }
47
+
48
+ await this.get(ResourceService).delete(input.resourceId, { reason: result.content.reason });
49
+ return { deleted: true };
50
+ }
51
+ }
52
+ ```
53
+
54
+ ## Return shape
55
+
56
+ `this.elicit` returns:
57
+
58
+ ```typescript
59
+ interface ElicitResult<T> {
60
+ status: 'accept' | 'decline' | 'cancel';
61
+ content?: T; // present only when status === 'accept'
62
+ }
63
+ ```
64
+
65
+ Always check `result.status === 'accept'` before reading `result.content` — `content` only exists on `accept`.
66
+
67
+ ## Multiple fields, optional fields, defaults
68
+
69
+ ```typescript
70
+ const result = await this.elicit(
71
+ 'Choose deployment options',
72
+ z.object({
73
+ environment: z.enum(['staging', 'production']).default('staging'),
74
+ rollback: z.boolean().default(false).describe('Roll back on first health-check failure'),
75
+ notifyChannel: z.string().optional().describe('Notification channel (e.g. #ops)'),
76
+ }),
77
+ );
78
+
79
+ if (result.status === 'accept') {
80
+ // result.content: { environment: 'staging' | 'production'; rollback: boolean; notifyChannel?: string }
81
+ }
82
+ ```
83
+
84
+ ## What clients render
85
+
86
+ The client's UI varies — MCP Inspector shows a JSON form, Claude / ChatGPT may render a structured input dialog. Field types translate roughly:
87
+
88
+ | Zod | Typical UI |
89
+ | --------------------------------- | ----------------------------------------------------- |
90
+ | `z.string()` | Single-line text input |
91
+ | `z.string().describe('long…')` | Textarea if `describe` includes the word "multi-line" |
92
+ | `z.number()` / `z.number().int()` | Number input with stepper |
93
+ | `z.boolean()` | Checkbox |
94
+ | `z.enum([…])` | Select / radio group |
95
+ | `z.string().email()` | Email input |
96
+ | `z.string().url()` | URL input |
97
+ | `z.string().datetime()` | Date-time picker |
98
+
99
+ Treat the UI as best-effort — don't depend on a particular widget. The contract is the Zod schema; the rendering is the host's job.
100
+
101
+ ## Early return on decline / cancel
102
+
103
+ Early returns must still match `outputSchema`:
104
+
105
+ ```typescript
106
+ async execute(input: { resourceId: string }) {
107
+ const result = await this.elicit('Delete?', z.object({ confirm: z.boolean() }));
108
+ if (result.status !== 'accept') {
109
+ // Must return a value matching outputSchema — not a raw error string
110
+ return { deleted: false };
111
+ }
112
+ // …
113
+ }
114
+ ```
115
+
116
+ If declining should propagate as an error to the client (rather than a normal output), use `this.fail` instead:
117
+
118
+ ```typescript
119
+ if (result.status === 'decline') {
120
+ this.fail(new PublicMcpError('User declined the destructive action.'));
121
+ }
122
+ ```
123
+
124
+ ## See also
125
+
126
+ - [`19-tool-with-elicitation`](../examples/19-tool-with-elicitation.md)
127
+ - [`execution-context.md`](./execution-context.md)
128
+ - `config` skill — `elicitation.store` (Redis vs memory) configuration