@frontmcp/skills 1.1.2 → 1.2.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/catalog/TEMPLATE.md +16 -11
- package/catalog/frontmcp-authorities/SKILL.md +116 -11
- package/catalog/frontmcp-authorities/references/authority-profiles.md +39 -36
- package/catalog/frontmcp-authorities/references/claims-mapping.md +7 -0
- package/catalog/frontmcp-authorities/references/custom-evaluators.md +63 -14
- package/catalog/frontmcp-channels/SKILL.md +36 -0
- package/catalog/frontmcp-channels/examples/channel-sources/file-watcher.md +8 -2
- package/catalog/frontmcp-channels/examples/channel-sources/replay-buffer.md +111 -30
- package/catalog/frontmcp-channels/examples/channel-two-way/whatsapp-bridge.md +45 -3
- package/catalog/frontmcp-channels/references/channel-sources.md +11 -3
- package/catalog/frontmcp-channels/references/channel-two-way.md +60 -89
- package/catalog/frontmcp-config/SKILL.md +111 -8
- package/catalog/frontmcp-config/examples/configure-auth-modes/local-self-signed-tokens.md +4 -4
- package/catalog/frontmcp-config/examples/configure-auth-modes/remote-enterprise-oauth.md +7 -1
- package/catalog/frontmcp-config/examples/configure-deployment-targets/distributed-ha-config.md +1 -1
- package/catalog/frontmcp-config/examples/configure-deployment-targets/json-schema-ide-support.md +1 -1
- package/catalog/frontmcp-config/examples/configure-deployment-targets/multi-target-with-security.md +12 -9
- package/catalog/frontmcp-config/examples/configure-http/cors-restricted-origins.md +2 -2
- package/catalog/frontmcp-config/examples/configure-http/entry-path-reverse-proxy.md +1 -1
- package/catalog/frontmcp-config/examples/configure-security-headers/csp-report-only.md +1 -1
- package/catalog/frontmcp-config/examples/configure-security-headers/full-production-headers.md +1 -1
- package/catalog/frontmcp-config/examples/configure-skills-http/audit-log-basic.md +76 -0
- package/catalog/frontmcp-config/examples/configure-skills-http/audit-log-redis.md +116 -0
- package/catalog/frontmcp-config/examples/configure-skills-http/inject-instructions.md +59 -0
- package/catalog/frontmcp-config/references/configure-auth-modes.md +5 -5
- package/catalog/frontmcp-config/references/configure-deployment-targets.md +27 -24
- package/catalog/frontmcp-config/references/configure-http.md +14 -10
- package/catalog/frontmcp-config/references/configure-security-headers.md +2 -2
- package/catalog/frontmcp-config/references/configure-session.md +25 -25
- package/catalog/frontmcp-config/references/configure-skills-http.md +157 -0
- package/catalog/frontmcp-config/references/configure-throttle.md +1 -1
- package/catalog/frontmcp-config/references/configure-transport.md +2 -2
- package/catalog/frontmcp-deployment/SKILL.md +112 -9
- package/catalog/frontmcp-deployment/examples/build-for-browser/browser-build-with-custom-entry.md +23 -11
- package/catalog/frontmcp-deployment/examples/build-for-browser/browser-crypto-and-storage.md +44 -17
- package/catalog/frontmcp-deployment/examples/build-for-browser/react-provider-setup.md +53 -21
- package/catalog/frontmcp-deployment/examples/build-for-cli/cli-binary-build.md +1 -1
- package/catalog/frontmcp-deployment/examples/build-for-cli/unix-socket-daemon.md +1 -1
- package/catalog/frontmcp-deployment/examples/build-for-mcpb/mcpb-bundle-build.md +1 -1
- package/catalog/frontmcp-deployment/examples/build-for-sdk/connect-openai.md +1 -1
- package/catalog/frontmcp-deployment/examples/build-for-sdk/multi-platform-connect.md +1 -1
- package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/basic-worker-deploy.md +7 -8
- package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/worker-custom-domain.md +8 -6
- package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/worker-with-kv-storage.md +5 -4
- package/catalog/frontmcp-deployment/examples/deploy-to-lambda/cdk-deployment.md +8 -5
- package/catalog/frontmcp-deployment/examples/deploy-to-lambda/lambda-handler-with-cors.md +20 -18
- package/catalog/frontmcp-deployment/examples/deploy-to-lambda/sam-template-basic.md +8 -5
- package/catalog/frontmcp-deployment/examples/deploy-to-node/docker-compose-with-redis.md +3 -3
- package/catalog/frontmcp-deployment/examples/deploy-to-node/pm2-with-nginx.md +1 -1
- package/catalog/frontmcp-deployment/examples/deploy-to-node/resource-limits.md +2 -2
- package/catalog/frontmcp-deployment/examples/deploy-to-node-dockerfile/basic-multistage-dockerfile.md +2 -2
- package/catalog/frontmcp-deployment/examples/deploy-to-node-dockerfile/secure-nonroot-dockerfile.md +1 -1
- package/catalog/frontmcp-deployment/examples/deploy-to-vercel/vercel-mcp-endpoint-test.md +23 -21
- package/catalog/frontmcp-deployment/examples/deploy-to-vercel/vercel-with-kv.md +25 -22
- package/catalog/frontmcp-deployment/examples/deploy-to-vercel/vercel-with-skills-cache.md +23 -30
- package/catalog/frontmcp-deployment/examples/deploy-to-vercel-config/minimal-vercel-config.md +52 -28
- package/catalog/frontmcp-deployment/examples/deploy-to-vercel-config/vercel-config-with-security-headers.md +32 -55
- package/catalog/frontmcp-deployment/examples/mcp-client-integration/http-remote.md +9 -0
- package/catalog/frontmcp-deployment/references/build-for-browser.md +40 -17
- package/catalog/frontmcp-deployment/references/build-for-cli.md +8 -8
- package/catalog/frontmcp-deployment/references/deploy-to-cloudflare.md +43 -24
- package/catalog/frontmcp-deployment/references/deploy-to-lambda.md +36 -25
- package/catalog/frontmcp-deployment/references/deploy-to-node-dockerfile.md +56 -14
- package/catalog/frontmcp-deployment/references/deploy-to-node.md +9 -6
- package/catalog/frontmcp-deployment/references/deploy-to-vercel-config.md +57 -58
- package/catalog/frontmcp-deployment/references/deploy-to-vercel.md +49 -59
- package/catalog/frontmcp-deployment/references/mcp-client-integration.md +2 -0
- package/catalog/frontmcp-development/SKILL.md +186 -11
- package/catalog/frontmcp-development/examples/create-agent/custom-multi-pass-agent.md +1 -1
- package/catalog/frontmcp-development/examples/create-agent/nested-agents-with-swarm.md +30 -27
- package/catalog/frontmcp-development/examples/create-job/job-with-permissions.md +13 -8
- package/catalog/frontmcp-development/examples/create-provider/basic-database-provider.md +33 -23
- package/catalog/frontmcp-development/examples/create-provider/config-and-api-providers.md +19 -10
- package/catalog/frontmcp-development/examples/create-tool/tool-with-rate-limiting-and-progress.md +3 -3
- package/catalog/frontmcp-development/examples/create-workflow/webhook-triggered-workflow.md +6 -4
- package/catalog/frontmcp-development/examples/decorators-guide/agent-skill-job-workflow.md +1 -1
- package/catalog/frontmcp-development/examples/decorators-guide/basic-server-with-app-and-tools.md +13 -8
- package/catalog/frontmcp-development/examples/decorators-guide/multi-app-with-plugins-and-providers.md +50 -23
- package/catalog/frontmcp-development/references/create-agent.md +47 -30
- package/catalog/frontmcp-development/references/create-job.md +69 -54
- package/catalog/frontmcp-development/references/create-plugin-hooks.md +45 -28
- package/catalog/frontmcp-development/references/create-plugin.md +10 -8
- package/catalog/frontmcp-development/references/create-prompt.md +3 -3
- package/catalog/frontmcp-development/references/create-provider.md +91 -51
- package/catalog/frontmcp-development/references/create-resource.md +3 -3
- package/catalog/frontmcp-development/references/create-skill.md +2 -2
- package/catalog/frontmcp-development/references/create-tool.md +7 -7
- package/catalog/frontmcp-development/references/create-workflow.md +8 -10
- package/catalog/frontmcp-development/references/decorators-guide.md +92 -56
- package/catalog/frontmcp-development/references/official-plugins.md +4 -3
- package/catalog/frontmcp-development/references/openapi-adapter.md +1 -1
- package/catalog/frontmcp-extensibility/SKILL.md +70 -10
- package/catalog/frontmcp-extensibility/examples/skill-audit-log/custom-store.md +197 -0
- package/catalog/frontmcp-extensibility/examples/skill-audit-log/verify-chain.md +68 -0
- package/catalog/frontmcp-extensibility/examples/vectoriadb/product-catalog-search.md +3 -5
- package/catalog/frontmcp-extensibility/examples/vectoriadb/semantic-search-with-persistence.md +4 -11
- package/catalog/frontmcp-extensibility/examples/vectoriadb/tfidf-keyword-search.md +41 -30
- package/catalog/frontmcp-extensibility/references/skill-audit-log.md +233 -0
- package/catalog/frontmcp-extensibility/references/vectoriadb.md +73 -63
- package/catalog/frontmcp-guides/SKILL.md +84 -27
- package/catalog/frontmcp-guides/examples/example-knowledge-base/agent-and-plugin.md +72 -62
- package/catalog/frontmcp-guides/examples/example-knowledge-base/vector-search-and-resources.md +32 -43
- package/catalog/frontmcp-guides/examples/example-task-manager/auth-and-crud-tools.md +24 -17
- package/catalog/frontmcp-guides/examples/example-task-manager/authenticated-e2e-tests.md +23 -21
- package/catalog/frontmcp-guides/examples/example-task-manager/redis-provider-with-di.md +47 -39
- package/catalog/frontmcp-guides/examples/example-weather-api/server-and-app-setup.md +16 -6
- package/catalog/frontmcp-guides/examples/example-weather-api/unit-and-e2e-tests.md +9 -8
- package/catalog/frontmcp-guides/references/example-knowledge-base.md +192 -265
- package/catalog/frontmcp-guides/references/example-task-manager.md +60 -54
- package/catalog/frontmcp-guides/references/example-weather-api.md +22 -24
- package/catalog/frontmcp-observability/SKILL.md +66 -2
- package/catalog/frontmcp-observability/examples/telemetry-api/skill-counters.md +100 -0
- package/catalog/frontmcp-observability/examples/tracing-setup/production-tracing.md +7 -2
- package/catalog/frontmcp-observability/examples/vendor-integrations/coralogix-setup.md +6 -2
- package/catalog/frontmcp-observability/references/telemetry-api.md +72 -8
- package/catalog/frontmcp-observability/references/testing-observability.md +33 -49
- package/catalog/frontmcp-observability/references/tracing-setup.md +12 -5
- package/catalog/frontmcp-observability/references/vendor-integrations.md +46 -1
- package/catalog/frontmcp-production-readiness/SKILL.md +134 -3
- package/catalog/frontmcp-production-readiness/examples/common-checklist/caching-and-performance.md +57 -36
- package/catalog/frontmcp-production-readiness/examples/common-checklist/observability-setup.md +1 -1
- package/catalog/frontmcp-production-readiness/examples/common-checklist/security-hardening.md +102 -6
- package/catalog/frontmcp-production-readiness/examples/production-cli-daemon/daemon-socket-config.md +2 -1
- package/catalog/frontmcp-production-readiness/examples/production-cli-daemon/graceful-shutdown-cleanup.md +66 -58
- package/catalog/frontmcp-production-readiness/examples/production-cli-daemon/security-and-permissions.md +5 -3
- package/catalog/frontmcp-production-readiness/examples/production-cloudflare/durable-objects-state.md +2 -1
- package/catalog/frontmcp-production-readiness/examples/production-cloudflare/wrangler-config.md +55 -76
- package/catalog/frontmcp-production-readiness/examples/production-lambda/cold-start-connection-reuse.md +43 -40
- package/catalog/frontmcp-production-readiness/examples/production-lambda/sam-template.md +63 -94
- package/catalog/frontmcp-production-readiness/examples/production-lambda/scaling-and-monitoring.md +28 -18
- package/catalog/frontmcp-production-readiness/examples/production-node-sdk/multi-instance-cleanup.md +29 -14
- package/catalog/frontmcp-production-readiness/examples/production-node-server/graceful-shutdown.md +58 -42
- package/catalog/frontmcp-production-readiness/examples/production-node-server/redis-session-scaling.md +5 -2
- package/catalog/frontmcp-production-readiness/examples/production-vercel/cold-start-optimization.md +41 -24
- package/catalog/frontmcp-production-readiness/examples/production-vercel/vercel-edge-config.md +56 -65
- package/catalog/frontmcp-production-readiness/references/common-checklist.md +17 -5
- package/catalog/frontmcp-production-readiness/references/production-cli-daemon.md +5 -5
- package/catalog/frontmcp-production-readiness/references/production-cloudflare.md +5 -5
- package/catalog/frontmcp-production-readiness/references/production-lambda.md +5 -5
- package/catalog/frontmcp-production-readiness/references/production-node-sdk.md +5 -5
- package/catalog/frontmcp-production-readiness/references/production-node-server.md +1 -1
- package/catalog/frontmcp-production-readiness/references/production-vercel.md +5 -5
- package/catalog/frontmcp-setup/SKILL.md +88 -0
- package/catalog/frontmcp-setup/examples/project-structure-nx/nx-workspace-with-apps.md +10 -4
- package/catalog/frontmcp-setup/examples/project-structure-standalone/dev-workflow-commands.md +21 -8
- package/catalog/frontmcp-setup/examples/readme-guide/node-server-readme.md +3 -3
- package/catalog/frontmcp-setup/references/multi-app-composition.md +4 -3
- package/catalog/frontmcp-setup/references/project-structure-nx.md +15 -6
- package/catalog/frontmcp-setup/references/project-structure-standalone.md +18 -15
- package/catalog/frontmcp-setup/references/readme-guide.md +1 -1
- package/catalog/frontmcp-setup/references/setup-project.md +19 -5
- package/catalog/frontmcp-setup/references/setup-redis.md +27 -39
- package/catalog/frontmcp-setup/references/setup-sqlite.md +25 -18
- package/catalog/frontmcp-testing/SKILL.md +102 -15
- package/catalog/frontmcp-testing/examples/setup-testing/unit-test-tool-resource-prompt.md +3 -3
- package/catalog/frontmcp-testing/examples/test-auth/oauth-flow-test.md +50 -39
- package/catalog/frontmcp-testing/examples/test-auth/role-based-access-test.md +52 -29
- package/catalog/frontmcp-testing/examples/test-auth/token-factory-test.md +37 -20
- package/catalog/frontmcp-testing/examples/test-direct-client/basic-create-test.md +25 -15
- package/catalog/frontmcp-testing/examples/test-direct-client/openai-claude-format-test.md +27 -21
- package/catalog/frontmcp-testing/examples/test-e2e-handler/basic-e2e-test.md +29 -20
- package/catalog/frontmcp-testing/examples/test-e2e-handler/manual-client-with-transport.md +5 -3
- package/catalog/frontmcp-testing/examples/test-e2e-handler/tool-call-and-error-e2e.md +35 -26
- package/catalog/frontmcp-testing/examples/test-tool-unit/basic-tool-test.md +8 -3
- package/catalog/frontmcp-testing/examples/test-tool-unit/schema-validation-test.md +4 -1
- package/catalog/frontmcp-testing/examples/test-tool-unit/tool-error-handling-test.md +6 -3
- package/catalog/frontmcp-testing/references/setup-testing.md +35 -39
- package/catalog/frontmcp-testing/references/test-auth.md +86 -43
- package/catalog/frontmcp-testing/references/test-browser-build.md +1 -1
- package/catalog/frontmcp-testing/references/test-direct-client.md +29 -19
- package/catalog/frontmcp-testing/references/test-e2e-handler.md +31 -19
- package/catalog/frontmcp-testing/references/test-tool-unit.md +6 -2
- package/catalog/skills-manifest.json +428 -339
- package/package.json +1 -1
- package/src/manifest.d.ts +13 -0
- package/src/manifest.js.map +1 -1
|
@@ -61,32 +61,33 @@ FrontMCP uses a hierarchical decorator system. The nesting order is:
|
|
|
61
61
|
|
|
62
62
|
**Key fields:**
|
|
63
63
|
|
|
64
|
-
| Field | Description
|
|
65
|
-
| --------------- |
|
|
66
|
-
| `info` | Server name, version, and description
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
64
|
+
| Field | Description |
|
|
65
|
+
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
66
|
+
| `info` | Server name, version, and description |
|
|
67
|
+
| `instructions?` | Top-level server-level instructions (system prompt) surfaced in the MCP `initialize` response. Combined with the skill catalog summary per `skillsConfig.injectInstructions` |
|
|
68
|
+
| `apps` | Array of `@App` classes to mount |
|
|
69
|
+
| `serve?` | Auto-start HTTP server (default: `true`). Set `false` for programmatic usage |
|
|
70
|
+
| `splitByApp?` | If `true`, each app gets its own scope and basePath. Default: `false` |
|
|
71
|
+
| `redis?` | Redis / Vercel KV connection for sessions, transport persistence, auth tokens |
|
|
72
|
+
| `plugins?` | Global plugins (instantiated per scope) |
|
|
73
|
+
| `providers?` | Global DI providers available to all apps |
|
|
74
|
+
| `tools?` | Standalone tools (outside apps, merged with app tools) |
|
|
75
|
+
| `resources?` | Standalone resources (merged with app resources) |
|
|
76
|
+
| `skills?` | Standalone skills (merged with app skills) |
|
|
77
|
+
| `skillsConfig?` | Skills HTTP endpoints (`/llm.txt`, `/skills`), MCP tool config, `injectInstructions` policy, and tamper-evident `audit` log — see `configure-skills-http` |
|
|
78
|
+
| `transport?` | Transport preset (`'modern'`, `'legacy'`, `'stateless-api'`, `'full'`) or object |
|
|
79
|
+
| `auth?` | Authentication mode: `'public'`, `'transparent'`, `'local'`, `'remote'` |
|
|
80
|
+
| `http?` | HTTP server options (port, host, cors, socketPath) |
|
|
81
|
+
| `logging?` | Logging configuration (transports and levels) |
|
|
82
|
+
| `elicitation?` | Enable interactive user input during tool execution |
|
|
83
|
+
| `sqlite?` | SQLite storage for local deployments (sessions, events) |
|
|
84
|
+
| `pubsub?` | Redis pub/sub for distributed resource subscriptions across multiple instances (single-server deployments use in-memory subscriptions; falls back to `redis` config) |
|
|
85
|
+
| `jobs?` | Background jobs/workflows system (`{ enabled, store? }`) |
|
|
86
|
+
| `throttle?` | Server-level guard config (see note below) |
|
|
87
|
+
| `pagination?` | List operation pagination (`tools/list` endpoint) |
|
|
88
|
+
| `ui?` | UI rendering config (CDN overrides for widget imports) |
|
|
89
|
+
| `extApps?` | Widget-to-host MCP Apps communication (host capabilities, session validation) |
|
|
90
|
+
| `loader?` | Default npm/ESM package loader for `App.esm()` / `App.remote()` apps |
|
|
90
91
|
|
|
91
92
|
> **Throttle vs per-tool guards:** Server-level `throttle` is a `GuardConfig` object with `global`, `defaultRateLimit`, `defaultConcurrency`, `defaultTimeout` sub-fields that set server-wide defaults. Tool-level `rateLimit`, `concurrency`, `timeout` fields (on `@Tool`) override these defaults per tool.
|
|
92
93
|
|
|
@@ -220,14 +221,18 @@ import { Prompt, PromptContext } from '@frontmcp/sdk';
|
|
|
220
221
|
],
|
|
221
222
|
})
|
|
222
223
|
class CodeReviewPrompt extends PromptContext {
|
|
223
|
-
|
|
224
|
+
// The framework passes prompt arguments as Record<string, string> — all values
|
|
225
|
+
// arrive as strings. Parse/coerce inside execute() if you need other types.
|
|
226
|
+
async execute(args: Record<string, string>) {
|
|
227
|
+
const code = args.code ?? '';
|
|
228
|
+
const language = args.language ?? '';
|
|
224
229
|
return {
|
|
225
230
|
messages: [
|
|
226
231
|
{
|
|
227
232
|
role: 'user' as const,
|
|
228
233
|
content: {
|
|
229
234
|
type: 'text' as const,
|
|
230
|
-
text: `Review this ${
|
|
235
|
+
text: `Review this ${language} code:\n\n${code}`,
|
|
231
236
|
},
|
|
232
237
|
},
|
|
233
238
|
],
|
|
@@ -265,9 +270,9 @@ import { Resource, ResourceContext } from '@frontmcp/sdk';
|
|
|
265
270
|
mimeType: 'application/json',
|
|
266
271
|
})
|
|
267
272
|
class AppConfigResource extends ResourceContext {
|
|
268
|
-
async
|
|
273
|
+
async execute(uri: string) {
|
|
269
274
|
const config = await this.get(ConfigService).getAll();
|
|
270
|
-
return { contents: [{ uri
|
|
275
|
+
return { contents: [{ uri, text: JSON.stringify(config) }] };
|
|
271
276
|
}
|
|
272
277
|
}
|
|
273
278
|
```
|
|
@@ -301,7 +306,7 @@ import { ResourceContext, ResourceTemplate } from '@frontmcp/sdk';
|
|
|
301
306
|
mimeType: 'application/json',
|
|
302
307
|
})
|
|
303
308
|
class UserProfileResource extends ResourceContext {
|
|
304
|
-
async
|
|
309
|
+
async execute(uri: string, params: { userId: string }) {
|
|
305
310
|
const user = await this.get(UserService).findById(params.userId);
|
|
306
311
|
return { contents: [{ uri, text: JSON.stringify(user) }] };
|
|
307
312
|
}
|
|
@@ -457,25 +462,56 @@ class GitHubAdapter {
|
|
|
457
462
|
|
|
458
463
|
**When to use:** When you need injectable services, configuration, or factories available via `this.get(Token)`.
|
|
459
464
|
|
|
460
|
-
**Key fields:**
|
|
465
|
+
**Key fields (strict schema — only these are accepted):**
|
|
466
|
+
|
|
467
|
+
| Field | Description |
|
|
468
|
+
| -------------- | ----------------------------------------------------------------------------------- |
|
|
469
|
+
| `name` | Provider name (required) |
|
|
470
|
+
| `id?` | Optional stable identifier |
|
|
471
|
+
| `description?` | Human-readable description |
|
|
472
|
+
| `scope?` | `ProviderScope.GLOBAL` (default), `ProviderScope.SCOPE`, or `ProviderScope.REQUEST` |
|
|
461
473
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
| `useFactory` | Factory function for dynamic creation |
|
|
474
|
+
> **Important:** The `@Provider` schema is strict and rejects `provide`, `useClass`, `useValue`, `useFactory`. There are two real ways to register a provider:
|
|
475
|
+
>
|
|
476
|
+
> 1. **Class-as-token (preferred for simple cases):** Decorate the class itself with `@Provider`. The class becomes its own DI token — inject it with `this.get(MyClass)`.
|
|
477
|
+
> 2. **Factory binding (when you need a token + async construction):** Use the `AsyncProvider({ provide, name, scope, inject, useFactory })` helper instead of the `@Provider` decorator.
|
|
478
|
+
|
|
479
|
+
**Pattern 1 — Class-as-token (decorator):**
|
|
469
480
|
|
|
470
481
|
```typescript
|
|
471
|
-
import { Provider } from '@frontmcp/sdk';
|
|
482
|
+
import { Provider, ProviderScope } from '@frontmcp/sdk';
|
|
472
483
|
|
|
473
484
|
@Provider({
|
|
474
|
-
name: '
|
|
475
|
-
|
|
476
|
-
useFactory: () => new DatabaseClient(process.env.DB_URL),
|
|
485
|
+
name: 'DatabaseClient',
|
|
486
|
+
scope: ProviderScope.GLOBAL,
|
|
477
487
|
})
|
|
478
|
-
class
|
|
488
|
+
class DatabaseClient {
|
|
489
|
+
query(sql: string) {
|
|
490
|
+
/* ... */
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Use it from a tool:
|
|
495
|
+
// const db = this.get(DatabaseClient);
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
**Pattern 2 — `AsyncProvider` factory (token-based binding):**
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
import { AsyncProvider, ProviderScope } from '@frontmcp/sdk';
|
|
502
|
+
|
|
503
|
+
export const DatabaseRef = Symbol('Database') as symbol;
|
|
504
|
+
|
|
505
|
+
export const createDatabaseProvider = AsyncProvider({
|
|
506
|
+
provide: DatabaseRef,
|
|
507
|
+
name: 'DatabaseProvider',
|
|
508
|
+
scope: ProviderScope.GLOBAL,
|
|
509
|
+
inject: () => [] as const,
|
|
510
|
+
useFactory: async () => new DatabaseClient(process.env.DB_URL),
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
// Register the value returned by AsyncProvider in `providers: [createDatabaseProvider]`.
|
|
514
|
+
// Inject with `this.get(DatabaseRef)`.
|
|
479
515
|
```
|
|
480
516
|
|
|
481
517
|
---
|
|
@@ -597,7 +633,7 @@ import { Workflow } from '@frontmcp/sdk';
|
|
|
597
633
|
name: 'deploy_pipeline',
|
|
598
634
|
description: 'Full deployment pipeline',
|
|
599
635
|
trigger: 'webhook',
|
|
600
|
-
|
|
636
|
+
webhook: { path: '/hooks/deploy', secret: process.env.WEBHOOK_SECRET!, methods: ['POST'] },
|
|
601
637
|
timeout: 600_000,
|
|
602
638
|
steps: [
|
|
603
639
|
{ id: 'build', jobName: 'build_app', input: { env: 'production' } },
|
|
@@ -678,14 +714,14 @@ class AuditHooks {
|
|
|
678
714
|
|
|
679
715
|
## Common Patterns
|
|
680
716
|
|
|
681
|
-
| Pattern | Correct
|
|
682
|
-
| --------------------------- |
|
|
683
|
-
| Grouping tools into modules | Place tools inside `@App({ tools: [...] })`
|
|
684
|
-
| Exposing data to the LLM | Use `@Resource` for fixed URIs, `@ResourceTemplate` for parameterized URIs
|
|
685
|
-
| Cross-cutting concerns | Create a `@Plugin` with providers and context extensions
|
|
686
|
-
| Background processing | Use `@Job` with a cron schedule for recurring work
|
|
687
|
-
| Multi-step orchestration | Use `@Workflow` with ordered steps referencing `@Job` classes
|
|
688
|
-
| Injecting services |
|
|
717
|
+
| Pattern | Correct | Incorrect | Why |
|
|
718
|
+
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
719
|
+
| Grouping tools into modules | Place tools inside `@App({ tools: [...] })` | Register tools directly in `@FrontMcp({ tools: [...] })` for large servers | Apps provide logical grouping, scoped providers, and isolation; standalone tools in `@FrontMcp` are only appropriate for small servers or global utilities |
|
|
720
|
+
| Exposing data to the LLM | Use `@Resource` for fixed URIs, `@ResourceTemplate` for parameterized URIs | Using `@Tool` to return static data that never changes | Resources are the MCP-standard way to expose readable data; tools are for actions with side effects or dynamic computation |
|
|
721
|
+
| Cross-cutting concerns | Create a `@Plugin` with providers and context extensions | Adding logging/caching logic directly inside every tool's `execute()` method | Plugins centralize shared behavior, reduce duplication, and can be reused across servers |
|
|
722
|
+
| Background processing | Use `@Job` with a cron schedule for recurring work | Using `setTimeout` or manual polling inside a tool | Jobs integrate with the scheduler, support persistence, and are visible in server diagnostics |
|
|
723
|
+
| Multi-step orchestration | Use `@Workflow` with ordered steps referencing `@Job` classes | Chaining multiple tool calls manually from the LLM | Workflows provide built-in ordering, error handling, and rollback semantics |
|
|
724
|
+
| Injecting services | Decorate a service class with `@Provider({ name, scope })` and inject it via `this.get(MyClass)`, or use `AsyncProvider({ provide, useFactory })` for token-based factories | Passing `useFactory`/`useClass` directly to `@Provider` (the schema is strict and rejects them), or importing singletons / using global state | DI providers support testability, lifecycle management, and per-scope isolation |
|
|
689
725
|
|
|
690
726
|
---
|
|
691
727
|
|
|
@@ -701,7 +737,7 @@ class AuditHooks {
|
|
|
701
737
|
|
|
702
738
|
- [ ] Every `@Tool` has `name`, `description`, and `inputSchema` defined
|
|
703
739
|
- [ ] Every `@Resource` has `name` and `uri` with a valid scheme (e.g., `config://`, `file://`)
|
|
704
|
-
- [ ] Every `@ResourceTemplate` has `uriTemplate` with `{param}` placeholders matching the `
|
|
740
|
+
- [ ] Every `@ResourceTemplate` has `uriTemplate` with `{param}` placeholders matching the `execute(uri, params)` params argument
|
|
705
741
|
- [ ] Every `@Prompt` has `name` and at least one argument when it accepts input
|
|
706
742
|
- [ ] Every `@Agent` has `name`, `description`, and `llm` configuration
|
|
707
743
|
|
|
@@ -709,7 +745,7 @@ class AuditHooks {
|
|
|
709
745
|
|
|
710
746
|
- [ ] Tool classes extend `ToolContext` and implement `execute()`
|
|
711
747
|
- [ ] Prompt classes extend `PromptContext` and implement `execute()`
|
|
712
|
-
- [ ] Resource classes extend `ResourceContext` and implement `
|
|
748
|
+
- [ ] Resource classes extend `ResourceContext` and implement `execute(uri)` (static `@Resource`) or `execute(uri, params)` (template-style `@ResourceTemplate`)
|
|
713
749
|
- [ ] Agent classes extend `AgentContext` and implement `execute()`
|
|
714
750
|
- [ ] Job classes extend `JobContext` and implement `execute()`
|
|
715
751
|
|
|
@@ -721,7 +757,7 @@ class AuditHooks {
|
|
|
721
757
|
|
|
722
758
|
### DI and Plugins
|
|
723
759
|
|
|
724
|
-
- [ ]
|
|
760
|
+
- [ ] `@Provider`-decorated classes use only `name` / `id` / `description` / `scope` (the schema is strict). For factory bindings, use `AsyncProvider({ provide, name, scope, inject, useFactory })` instead of the decorator.
|
|
725
761
|
- [ ] Plugins are registered in `@App({ plugins: [...] })` or `@FrontMcp({ plugins: [...] })`
|
|
726
762
|
- [ ] Context extensions installed by plugins match the module augmentation declarations
|
|
727
763
|
|
|
@@ -34,12 +34,13 @@ FrontMCP ships 6 official plugins that extend server behavior with cross-cutting
|
|
|
34
34
|
All plugins follow the `DynamicPlugin` pattern and are registered via `@FrontMcp({ plugins: [...] })`.
|
|
35
35
|
|
|
36
36
|
```typescript
|
|
37
|
-
import { FrontMcp } from '@frontmcp/sdk';
|
|
38
|
-
import CodeCallPlugin from '@frontmcp/plugin-codecall';
|
|
39
|
-
import RememberPlugin from '@frontmcp/plugin-remember';
|
|
40
37
|
import { ApprovalPlugin } from '@frontmcp/plugin-approval';
|
|
41
38
|
import CachePlugin from '@frontmcp/plugin-cache';
|
|
39
|
+
import CodeCallPlugin from '@frontmcp/plugin-codecall';
|
|
42
40
|
import FeatureFlagPlugin from '@frontmcp/plugin-feature-flags';
|
|
41
|
+
import RememberPlugin from '@frontmcp/plugin-remember';
|
|
42
|
+
import { FrontMcp } from '@frontmcp/sdk';
|
|
43
|
+
|
|
43
44
|
// import DashboardPlugin from '@frontmcp/plugin-dashboard'; // Beta — not recommended for production
|
|
44
45
|
|
|
45
46
|
@App({ name: 'MyApp' })
|
|
@@ -33,8 +33,8 @@ The OpenAPI adapter converts OpenAPI 3.x specifications into MCP tools — one t
|
|
|
33
33
|
## Quick Start
|
|
34
34
|
|
|
35
35
|
```typescript
|
|
36
|
-
import { FrontMcp, App } from '@frontmcp/sdk';
|
|
37
36
|
import { OpenapiAdapter } from '@frontmcp/adapters';
|
|
37
|
+
import { App, FrontMcp } from '@frontmcp/sdk';
|
|
38
38
|
|
|
39
39
|
@App({
|
|
40
40
|
name: 'MyApp',
|
|
@@ -38,11 +38,26 @@ Patterns and examples for extending FrontMCP servers with external npm packages.
|
|
|
38
38
|
|
|
39
39
|
> **Decision:** Use this skill when integrating external libraries into your FrontMCP server as providers or tools.
|
|
40
40
|
|
|
41
|
+
## Prerequisites
|
|
42
|
+
|
|
43
|
+
- A FrontMCP project (see `frontmcp-setup` if you don't have one).
|
|
44
|
+
- The external library installed as a runtime `dependency` (not `devDependency`).
|
|
45
|
+
- Familiarity with FrontMCP providers and tools (see `create-provider`, `create-tool`).
|
|
46
|
+
|
|
47
|
+
## Steps
|
|
48
|
+
|
|
49
|
+
1. **Pick the integration shape** — provider for stateful clients (DB, search index), tool-only for stateless one-shot calls.
|
|
50
|
+
2. **Wrap the library in a provider** — declare a typed DI token and a `@Provider` class so consumers depend on the boundary, not the library.
|
|
51
|
+
3. **Register it** — add to `@App({ providers: [...] })` or `@FrontMcp({ providers: [...] })`.
|
|
52
|
+
4. **Expose via tools/resources** — call `this.get(TOKEN)` in `ToolContext`/`ResourceContext`; never import the library directly from the tool.
|
|
53
|
+
5. **Handle errors at the boundary** — translate library-specific errors into `PublicMcpError`/`InvalidInputError` so MCP clients see structured failures.
|
|
54
|
+
|
|
41
55
|
## Scenario Routing Table
|
|
42
56
|
|
|
43
57
|
| Scenario | Reference | Description |
|
|
44
58
|
| --------------------------------------------- | ----------------------------------------------- | -------------------------------------------------------- |
|
|
45
|
-
| Add in-memory semantic search with VectoriaDB | `references/vectoriadb.md` | TF-IDF
|
|
59
|
+
| Add in-memory semantic search with VectoriaDB | `references/vectoriadb.md` | TF-IDF or ML semantic indexing, provider+tool pattern |
|
|
60
|
+
| Add tamper-evident skill audit logging | `references/skill-audit-log.md` | Hash-chained, signed audit records for skill executions |
|
|
46
61
|
| Load an app from an npm package | `multi-app-composition` (in frontmcp-setup) | `App.esm('@scope/pkg@^1.0.0', 'AppName')` pattern |
|
|
47
62
|
| Connect to a remote MCP server | `multi-app-composition` (in frontmcp-setup) | `App.remote('https://...', 'ns')` pattern |
|
|
48
63
|
| Build a reusable plugin with hooks | `create-plugin-hooks` (in frontmcp-development) | `DynamicPlugin`, context extensions, lifecycle hooks |
|
|
@@ -55,12 +70,12 @@ The standard pattern for integrating any external library:
|
|
|
55
70
|
|
|
56
71
|
1. **Create a provider** — wraps the library as a singleton or scoped service
|
|
57
72
|
2. **Register the provider** — add to `@App({ providers: [...] })` or `@FrontMcp({ providers: [...] })`
|
|
58
|
-
3. **Create tools** — expose the provider's capabilities as MCP tools via `this.get(
|
|
73
|
+
3. **Create tools** — expose the provider's capabilities as MCP tools via `this.get(ProviderClass)` (the class itself is the DI token)
|
|
59
74
|
4. **Optionally create resources** — expose data as MCP resources with autocompletion
|
|
60
75
|
|
|
61
76
|
```typescript
|
|
62
|
-
// 1. Provider wraps the library
|
|
63
|
-
@Provider({ name: 'my-search',
|
|
77
|
+
// 1. Provider wraps the library (the class itself is the DI token)
|
|
78
|
+
@Provider({ name: 'my-search', scope: ProviderScope.GLOBAL })
|
|
64
79
|
export class SearchProvider {
|
|
65
80
|
private client: ExternalLibrary;
|
|
66
81
|
constructor() {
|
|
@@ -77,27 +92,72 @@ export class SearchProvider {
|
|
|
77
92
|
@Tool({ name: 'search', inputSchema: { query: z.string() } })
|
|
78
93
|
export default class SearchTool extends ToolContext {
|
|
79
94
|
async execute(input: { query: string }) {
|
|
80
|
-
return this.get(
|
|
95
|
+
return this.get(SearchProvider).search(input.query);
|
|
81
96
|
}
|
|
82
97
|
}
|
|
83
98
|
```
|
|
84
99
|
|
|
85
100
|
## Available Integrations
|
|
86
101
|
|
|
87
|
-
| Library
|
|
88
|
-
|
|
|
89
|
-
| **VectoriaDB**
|
|
102
|
+
| Library | Purpose | Reference |
|
|
103
|
+
| ------------------- | ---------------------------------------------------- | ------------------------------- |
|
|
104
|
+
| **VectoriaDB** | In-memory TF-IDF semantic search | `references/vectoriadb.md` |
|
|
105
|
+
| **Skill audit log** | Tamper-evident hash-chained audit log for skill runs | `references/skill-audit-log.md` |
|
|
90
106
|
|
|
91
107
|
More integrations can be added as references (e.g., enclave-vm, applescript, database clients).
|
|
92
108
|
|
|
109
|
+
## Common Patterns
|
|
110
|
+
|
|
111
|
+
| Pattern | Correct | Incorrect | Why |
|
|
112
|
+
| -------------------- | ------------------------------------------ | ---------------------------------------- | ------------------------------------------------------------------- |
|
|
113
|
+
| Library access | `this.get(SearchToken)` from a tool | `import { client } from 'lib'` in a tool | DI boundary lets you swap implementations and test in isolation |
|
|
114
|
+
| Provider scope | `ProviderScope.GLOBAL` for shared clients | New instance per request | Library clients (DB pools, indices) are expensive to construct |
|
|
115
|
+
| Async initialisation | `onInit()` lifecycle hook on the provider | Constructor `await` | Constructors can't be async; `onInit` is the framework's init seam |
|
|
116
|
+
| Error surfaces | Throw `PublicMcpError`/`InvalidInputError` | Re-throw raw library errors | Library stack traces leak internals and aren't JSON-RPC error-coded |
|
|
117
|
+
|
|
93
118
|
## Verification Checklist
|
|
94
119
|
|
|
95
120
|
- [ ] External library is in `dependencies` (not `devDependencies`)
|
|
96
121
|
- [ ] Provider wraps the library with proper initialization and cleanup
|
|
97
|
-
- [ ] Provider is
|
|
98
|
-
- [ ] Tools use `this.get(
|
|
122
|
+
- [ ] Provider class is listed in `@App` or `@FrontMcp` `providers: [...]` array (the class itself is the DI token)
|
|
123
|
+
- [ ] Tools use `this.get(ProviderClass)` to access the provider (not direct imports)
|
|
99
124
|
- [ ] Error handling wraps library-specific errors into MCP error classes
|
|
100
125
|
|
|
126
|
+
## Troubleshooting
|
|
127
|
+
|
|
128
|
+
| Problem | Cause | Solution |
|
|
129
|
+
| -------------------------------------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
130
|
+
| `Cannot find module '<lib>'` at runtime | Library declared as `devDependency` only | Move to `dependencies`; rebuild the bundle if deploying as MCPB/CLI |
|
|
131
|
+
| Provider constructed once per request | Default scope used; expensive client recreated each call | Set `scope: ProviderScope.GLOBAL` on the `@Provider` decorator |
|
|
132
|
+
| Tool sees `undefined` from `this.get(TOKEN)` | Provider not registered in the active `@App`/`@FrontMcp` scope | Add the provider class to the scope's `providers: [...]` array |
|
|
133
|
+
| Browser build fails with Node-only library | Library uses `node:` modules not available at the target | Gate behind `availableWhen.platform`, or move the integration into a server-only app and call it via a remote transport |
|
|
134
|
+
|
|
135
|
+
## Examples
|
|
136
|
+
|
|
137
|
+
Each reference has matching examples under [`examples/<reference>/`](./examples/):
|
|
138
|
+
|
|
139
|
+
### `vectoriadb`
|
|
140
|
+
|
|
141
|
+
| Example | Level | Description |
|
|
142
|
+
| ----------------------------------------------------------------------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
143
|
+
| [`product-catalog-search`](./examples/vectoriadb/product-catalog-search.md) | Advanced | Shows advanced VectoriaDB usage with typed document metadata, batch operations, filtered search by multiple criteria, and batch indexing of a product catalog. |
|
|
144
|
+
| [`semantic-search-with-persistence`](./examples/vectoriadb/semantic-search-with-persistence.md) | Intermediate | Shows how to use `VectoriaDB` for semantic search with transformer models, filtered search, and `FileStorageAdapter` for persistence across restarts. |
|
|
145
|
+
| [`tfidf-keyword-search`](./examples/vectoriadb/tfidf-keyword-search.md) | Basic | Shows how to use `TFIDFVectoria` for zero-dependency keyword search in a FrontMCP provider, with field weights and index building. |
|
|
146
|
+
|
|
147
|
+
## Accessing This Skill
|
|
148
|
+
|
|
149
|
+
Skills are distributed as plain SKILL.md files plus a sibling `references/`
|
|
150
|
+
and `examples/` tree, so consumers can pick whichever access mode fits:
|
|
151
|
+
|
|
152
|
+
| Mode | How it works |
|
|
153
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
154
|
+
| **Filesystem** | Read `libs/skills/catalog/frontmcp-extensibility/` directly from a clone of the catalog repo, or from a published `@frontmcp/skills` install. SKILL.md is the entry point. |
|
|
155
|
+
| **`frontmcp` CLI** | `frontmcp skills list`, `frontmcp skills read frontmcp-extensibility`, `frontmcp skills read frontmcp-extensibility:references/<file>.md`, `frontmcp skills install frontmcp-extensibility` — no server required. |
|
|
156
|
+
| **MCP `skill://`** | When a developer mounts this skill into their own FrontMCP server (`@FrontMcp({ skills: [...] })`), the SDK exposes it via SEP-2640 resources: `skill://frontmcp-extensibility/SKILL.md`, `skill://frontmcp-extensibility/references/{file}.md`, etc. The server’s `skill://index.json` returns the SEP-2640 discovery document for everything mounted on it. |
|
|
157
|
+
|
|
158
|
+
The catalog itself is **not** an MCP server. The `skill://` URIs only resolve
|
|
159
|
+
when a server has been configured to host this skill.
|
|
160
|
+
|
|
101
161
|
## Reference
|
|
102
162
|
|
|
103
163
|
- Related skills: `create-provider`, `create-tool`, `frontmcp-development`
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: custom-store
|
|
3
|
+
reference: skill-audit-log
|
|
4
|
+
level: advanced
|
|
5
|
+
description: Implement a custom SkillAuditStore that streams records to S3 with one object per sequence.
|
|
6
|
+
tags: [extensibility, audit, custom, s3, store]
|
|
7
|
+
features:
|
|
8
|
+
- 'Implements the SkillAuditStore interface (nextSequence, appendAtSequence, tail, read)'
|
|
9
|
+
- 'One S3 object per sequence keeps individual records immutable and verifiable'
|
|
10
|
+
- 'tail() returns the latest record so the writer can chain prevHash deterministically'
|
|
11
|
+
- 'read({ from, limit }) supports incremental verifyChain runs in CI'
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Custom S3-Backed Audit Store
|
|
15
|
+
|
|
16
|
+
Implement a custom SkillAuditStore that streams records to S3 with one object per sequence.
|
|
17
|
+
|
|
18
|
+
## Code
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// src/audit/s3-audit.store.ts
|
|
22
|
+
//
|
|
23
|
+
// Implements the actual SkillAuditStore contract:
|
|
24
|
+
// - nextSequence(): allocate the next monotonic sequence atomically.
|
|
25
|
+
// Backed here by an S3 conditional-put on a counter object; production
|
|
26
|
+
// deployments MUST front this with a real atomic counter (DynamoDB,
|
|
27
|
+
// Redis, etc.) since S3 alone cannot guarantee monotonic allocation
|
|
28
|
+
// under concurrency.
|
|
29
|
+
// - appendAtSequence(record): persist with IfNoneMatch:'*' so a retry
|
|
30
|
+
// after a partial failure doesn't overwrite the record.
|
|
31
|
+
// - tail(): return the most recent record for prevHash chaining.
|
|
32
|
+
// - read({ from, limit }): walk records in order for verifyChain.
|
|
33
|
+
import { GetObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
34
|
+
|
|
35
|
+
import type { SkillAuditRecord, SkillAuditStore } from '@frontmcp/adapters/skills';
|
|
36
|
+
|
|
37
|
+
const padSequence = (sequence: number): string => sequence.toString().padStart(20, '0');
|
|
38
|
+
|
|
39
|
+
export class S3AuditStore implements SkillAuditStore {
|
|
40
|
+
constructor(
|
|
41
|
+
private readonly s3: S3Client,
|
|
42
|
+
private readonly bucket: string,
|
|
43
|
+
private readonly prefix: string,
|
|
44
|
+
/**
|
|
45
|
+
* Atomic sequence allocator. S3 alone cannot allocate monotonic
|
|
46
|
+
* sequences safely under concurrency — wire this to DynamoDB
|
|
47
|
+
* `UpdateItem` with `ADD seq :one` or to a Redis `incr`. The default
|
|
48
|
+
* provided here scans the prefix and returns `max+1`, which is only
|
|
49
|
+
* safe for single-writer deployments.
|
|
50
|
+
*/
|
|
51
|
+
private readonly seq: { next(): Promise<number> } = {
|
|
52
|
+
next: async () => {
|
|
53
|
+
const all = await this.list();
|
|
54
|
+
// Use the highest observed sequence rather than count: a gap in the
|
|
55
|
+
// prefix (e.g. compacted/migrated history) would otherwise let the
|
|
56
|
+
// allocator collide with an existing key.
|
|
57
|
+
return Math.max(0, ...all.map((r) => r.sequence)) + 1;
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
) {}
|
|
61
|
+
|
|
62
|
+
async nextSequence(): Promise<number> {
|
|
63
|
+
return this.seq.next();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async appendAtSequence(record: SkillAuditRecord): Promise<void> {
|
|
67
|
+
try {
|
|
68
|
+
await this.s3.send(
|
|
69
|
+
new PutObjectCommand({
|
|
70
|
+
Bucket: this.bucket,
|
|
71
|
+
Key: `${this.prefix}${padSequence(record.sequence)}.json`,
|
|
72
|
+
Body: JSON.stringify(record),
|
|
73
|
+
ContentType: 'application/json',
|
|
74
|
+
// Block silent overwrites — chain entries are immutable.
|
|
75
|
+
IfNoneMatch: '*',
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
// S3 returns 412 PreconditionFailed when IfNoneMatch:'*' rejects the
|
|
80
|
+
// write. The SDK surfaces it via the error's `name` field — there is
|
|
81
|
+
// no dedicated S3 exception class to instanceof against.
|
|
82
|
+
if ((e as { name?: string })?.name === 'PreconditionFailed') {
|
|
83
|
+
throw new Error(`record at sequence ${record.sequence} already exists`);
|
|
84
|
+
}
|
|
85
|
+
throw e;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async tail(): Promise<SkillAuditRecord | undefined> {
|
|
90
|
+
const all = await this.read();
|
|
91
|
+
return all[all.length - 1];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async read(opts?: { from?: number; limit?: number }): Promise<SkillAuditRecord[]> {
|
|
95
|
+
const from = opts?.from ?? 1;
|
|
96
|
+
const limit = opts?.limit;
|
|
97
|
+
const startKey = from > 1 ? `${this.prefix}${padSequence(from - 1)}.json` : undefined;
|
|
98
|
+
const records = await this.list(startKey);
|
|
99
|
+
const filtered = records.filter((r) => r.sequence >= from).sort((a, b) => a.sequence - b.sequence);
|
|
100
|
+
return limit !== undefined ? filtered.slice(0, limit) : filtered;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private async list(startAfter?: string): Promise<SkillAuditRecord[]> {
|
|
104
|
+
const records: SkillAuditRecord[] = [];
|
|
105
|
+
// ListObjectsV2 caps each response at 1000 objects. Page until the
|
|
106
|
+
// bucket returns an un-truncated response so verifyChain sees the full
|
|
107
|
+
// chain even in long-lived audit logs.
|
|
108
|
+
let continuationToken: string | undefined;
|
|
109
|
+
do {
|
|
110
|
+
const page = await this.s3.send(
|
|
111
|
+
new ListObjectsV2Command({
|
|
112
|
+
Bucket: this.bucket,
|
|
113
|
+
Prefix: this.prefix,
|
|
114
|
+
// StartAfter is only meaningful on the first page; subsequent
|
|
115
|
+
// pages drive ordering via ContinuationToken.
|
|
116
|
+
StartAfter: continuationToken === undefined ? startAfter : undefined,
|
|
117
|
+
ContinuationToken: continuationToken,
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
for (const obj of page.Contents ?? []) {
|
|
121
|
+
if (!obj.Key) continue;
|
|
122
|
+
const body = await this.s3.send(new GetObjectCommand({ Bucket: this.bucket, Key: obj.Key }));
|
|
123
|
+
const text = (await body.Body?.transformToString()) ?? '';
|
|
124
|
+
records.push(JSON.parse(text) as SkillAuditRecord);
|
|
125
|
+
}
|
|
126
|
+
continuationToken = page.IsTruncated ? page.NextContinuationToken : undefined;
|
|
127
|
+
} while (continuationToken);
|
|
128
|
+
return records;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// src/server.ts
|
|
135
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
136
|
+
|
|
137
|
+
import {
|
|
138
|
+
Hs256AuditSigner,
|
|
139
|
+
MemoryAuditStore,
|
|
140
|
+
Rs256AuditSigner,
|
|
141
|
+
setSkillAuditFactory,
|
|
142
|
+
SkillAuditWriter,
|
|
143
|
+
SkillAuditWriterToken,
|
|
144
|
+
} from '@frontmcp/adapters/skills';
|
|
145
|
+
import { FrontMcp } from '@frontmcp/sdk';
|
|
146
|
+
|
|
147
|
+
import { S3AuditStore } from './audit/s3-audit.store';
|
|
148
|
+
import { MainApp } from './main.app';
|
|
149
|
+
|
|
150
|
+
// SDK constructs the writer using the positional signature
|
|
151
|
+
// `new SkillAuditWriter(store, signer, logger, metrics?, options?)`.
|
|
152
|
+
setSkillAuditFactory(() => ({
|
|
153
|
+
SkillAuditWriterToken,
|
|
154
|
+
SkillAuditWriter,
|
|
155
|
+
Hs256AuditSigner,
|
|
156
|
+
MemoryAuditStore,
|
|
157
|
+
}));
|
|
158
|
+
|
|
159
|
+
const s3Store = new S3AuditStore(new S3Client({ region: 'us-east-1' }), 'audit-prod', 'skill-audit/');
|
|
160
|
+
|
|
161
|
+
// Fail loudly at boot rather than burying a `JSON.parse(undefined)` stack
|
|
162
|
+
// trace inside the audit writer — operators wiring this up should see a
|
|
163
|
+
// descriptive config error.
|
|
164
|
+
const privateJwkJson = process.env.BUNDLE_SIGNING_PRIVATE_JWK;
|
|
165
|
+
if (!privateJwkJson) {
|
|
166
|
+
throw new Error('BUNDLE_SIGNING_PRIVATE_JWK env var is required for the RS256 audit signer');
|
|
167
|
+
}
|
|
168
|
+
const privateJwk = JSON.parse(privateJwkJson) as JsonWebKey;
|
|
169
|
+
|
|
170
|
+
@FrontMcp({
|
|
171
|
+
info: { name: 'svr', version: '1.0.0' },
|
|
172
|
+
apps: [MainApp],
|
|
173
|
+
skillsConfig: {
|
|
174
|
+
enabled: true,
|
|
175
|
+
audit: {
|
|
176
|
+
enabled: true,
|
|
177
|
+
// Constructor signature: new Rs256AuditSigner(privateJwk, keyId)
|
|
178
|
+
signer: new Rs256AuditSigner(privateJwk, 'bundle-signing-2026-01'),
|
|
179
|
+
store: s3Store,
|
|
180
|
+
subjectMode: 'hash',
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
})
|
|
184
|
+
export default class Server {}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## What This Demonstrates
|
|
188
|
+
|
|
189
|
+
- Implements the SkillAuditStore interface (nextSequence, appendAtSequence, tail, read)
|
|
190
|
+
- One S3 object per sequence keeps individual records immutable and verifiable
|
|
191
|
+
- tail() returns the latest record so the writer can chain prevHash deterministically
|
|
192
|
+
- read({ from, limit }) supports incremental verifyChain runs in CI
|
|
193
|
+
|
|
194
|
+
## Related
|
|
195
|
+
|
|
196
|
+
- See `skill-audit-log` for the full interface
|
|
197
|
+
- See `verify-chain` for periodic offline verification
|