@frontmcp/skills 1.1.2-beta.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/catalog/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
|
@@ -65,7 +65,7 @@ export default class TaskManagerServer {}
|
|
|
65
65
|
// src/tasks.app.ts
|
|
66
66
|
import { App } from '@frontmcp/sdk';
|
|
67
67
|
|
|
68
|
-
import {
|
|
68
|
+
import { createTaskStoreProvider } from './providers/task-store.provider';
|
|
69
69
|
import { CreateTaskTool } from './tools/create-task.tool';
|
|
70
70
|
import { DeleteTaskTool } from './tools/delete-task.tool';
|
|
71
71
|
import { ListTasksTool } from './tools/list-tasks.tool';
|
|
@@ -74,7 +74,9 @@ import { UpdateTaskTool } from './tools/update-task.tool';
|
|
|
74
74
|
@App({
|
|
75
75
|
name: 'Tasks',
|
|
76
76
|
description: 'Task management with CRUD operations',
|
|
77
|
-
|
|
77
|
+
// The AsyncProvider factory binds the `TaskStoreProvider` class as the DI token.
|
|
78
|
+
// Tools inject the same class via `this.get(TaskStoreProvider)`.
|
|
79
|
+
providers: [createTaskStoreProvider],
|
|
78
80
|
tools: [CreateTaskTool, ListTasksTool, UpdateTaskTool, DeleteTaskTool],
|
|
79
81
|
})
|
|
80
82
|
export class TasksApp {}
|
|
@@ -100,33 +102,22 @@ export interface Task {
|
|
|
100
102
|
|
|
101
103
|
## Provider: Redis Task Store
|
|
102
104
|
|
|
105
|
+
The provider class is the DI token (`this.get(TaskStoreProvider)`). For async setup we expose an `AsyncProvider` factory so the framework can build the singleton with a live Redis connection before any tool is invoked.
|
|
106
|
+
|
|
103
107
|
```typescript
|
|
104
108
|
// src/providers/task-store.provider.ts
|
|
105
|
-
import
|
|
106
|
-
import { Provider } from '@frontmcp/sdk';
|
|
107
|
-
|
|
108
|
-
import type { Task } from '../types/task';
|
|
109
|
+
import Redis, { Redis as RedisClient } from 'ioredis';
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
list(userId: string): Promise<Task[]>;
|
|
113
|
-
update(id: string, userId: string, data: Partial<Pick<Task, 'title' | 'priority' | 'status'>>): Promise<Task>;
|
|
114
|
-
delete(id: string, userId: string): Promise<void>;
|
|
115
|
-
}
|
|
111
|
+
import { AsyncProvider, Provider, ProviderScope } from '@frontmcp/sdk';
|
|
112
|
+
import { randomUUID } from '@frontmcp/utils';
|
|
116
113
|
|
|
117
|
-
|
|
114
|
+
import type { Task } from '../types/task';
|
|
118
115
|
|
|
119
|
-
@Provider({
|
|
120
|
-
export class
|
|
121
|
-
private redis
|
|
122
|
-
|
|
123
|
-
async onInit(): Promise<void> {
|
|
124
|
-
const Redis = (await import('ioredis')).default;
|
|
125
|
-
this.redis = new Redis(process.env.REDIS_URL ?? 'redis://localhost:6379');
|
|
126
|
-
}
|
|
116
|
+
@Provider({ name: 'task-store', scope: ProviderScope.GLOBAL })
|
|
117
|
+
export class TaskStoreProvider {
|
|
118
|
+
constructor(private readonly redis: RedisClient) {}
|
|
127
119
|
|
|
128
120
|
async create(input: Omit<Task, 'id' | 'createdAt'>): Promise<Task> {
|
|
129
|
-
const { randomUUID } = await import('@frontmcp/utils');
|
|
130
121
|
const task: Task = {
|
|
131
122
|
...input,
|
|
132
123
|
id: randomUUID(),
|
|
@@ -158,10 +149,24 @@ export class RedisTaskStoreProvider implements TaskStore {
|
|
|
158
149
|
}
|
|
159
150
|
}
|
|
160
151
|
|
|
161
|
-
|
|
152
|
+
// `@Provider` has no `onDestroy` hook — expose explicit cleanup that the
|
|
153
|
+
// host calls before `server.dispose()`.
|
|
154
|
+
async disconnect(): Promise<void> {
|
|
162
155
|
await this.redis.quit();
|
|
163
156
|
}
|
|
164
157
|
}
|
|
158
|
+
|
|
159
|
+
// AsyncProvider factory — provides the same class as a token, but lets us await async setup.
|
|
160
|
+
export const createTaskStoreProvider = AsyncProvider({
|
|
161
|
+
provide: TaskStoreProvider,
|
|
162
|
+
name: 'task-store-factory',
|
|
163
|
+
scope: ProviderScope.GLOBAL,
|
|
164
|
+
inject: () => [] as const,
|
|
165
|
+
useFactory: async () => {
|
|
166
|
+
const redis = new Redis(process.env.REDIS_URL ?? 'redis://localhost:6379');
|
|
167
|
+
return new TaskStoreProvider(redis);
|
|
168
|
+
},
|
|
169
|
+
});
|
|
165
170
|
```
|
|
166
171
|
|
|
167
172
|
---
|
|
@@ -172,7 +177,7 @@ export class RedisTaskStoreProvider implements TaskStore {
|
|
|
172
177
|
// src/tools/create-task.tool.ts
|
|
173
178
|
import { Tool, ToolContext, z } from '@frontmcp/sdk';
|
|
174
179
|
|
|
175
|
-
import {
|
|
180
|
+
import { TaskStoreProvider } from '../providers/task-store.provider';
|
|
176
181
|
|
|
177
182
|
@Tool({
|
|
178
183
|
name: 'create_task',
|
|
@@ -191,8 +196,8 @@ import { TASK_STORE } from '../providers/task-store.provider';
|
|
|
191
196
|
})
|
|
192
197
|
export class CreateTaskTool extends ToolContext {
|
|
193
198
|
async execute(input: { title: string; priority: 'low' | 'medium' | 'high' }) {
|
|
194
|
-
const store = this.get(
|
|
195
|
-
const userId = this.
|
|
199
|
+
const store = this.get(TaskStoreProvider);
|
|
200
|
+
const userId = this.auth?.user.sub;
|
|
196
201
|
|
|
197
202
|
if (!userId) {
|
|
198
203
|
this.fail(new Error('Authentication required'));
|
|
@@ -224,7 +229,7 @@ export class CreateTaskTool extends ToolContext {
|
|
|
224
229
|
// src/tools/list-tasks.tool.ts
|
|
225
230
|
import { Tool, ToolContext, z } from '@frontmcp/sdk';
|
|
226
231
|
|
|
227
|
-
import {
|
|
232
|
+
import { TaskStoreProvider } from '../providers/task-store.provider';
|
|
228
233
|
|
|
229
234
|
@Tool({
|
|
230
235
|
name: 'list_tasks',
|
|
@@ -247,8 +252,8 @@ import { TASK_STORE } from '../providers/task-store.provider';
|
|
|
247
252
|
})
|
|
248
253
|
export class ListTasksTool extends ToolContext {
|
|
249
254
|
async execute(input: { status?: 'pending' | 'in_progress' | 'done' }) {
|
|
250
|
-
const store = this.get(
|
|
251
|
-
const userId = this.
|
|
255
|
+
const store = this.get(TaskStoreProvider);
|
|
256
|
+
const userId = this.auth?.user.sub;
|
|
252
257
|
|
|
253
258
|
if (!userId) {
|
|
254
259
|
this.fail(new Error('Authentication required'));
|
|
@@ -282,7 +287,7 @@ export class ListTasksTool extends ToolContext {
|
|
|
282
287
|
// src/tools/update-task.tool.ts
|
|
283
288
|
import { Tool, ToolContext, z } from '@frontmcp/sdk';
|
|
284
289
|
|
|
285
|
-
import {
|
|
290
|
+
import { TaskStoreProvider } from '../providers/task-store.provider';
|
|
286
291
|
|
|
287
292
|
@Tool({
|
|
288
293
|
name: 'update_task',
|
|
@@ -305,8 +310,8 @@ export class UpdateTaskTool extends ToolContext {
|
|
|
305
310
|
status?: 'pending' | 'in_progress' | 'done';
|
|
306
311
|
priority?: 'low' | 'medium' | 'high';
|
|
307
312
|
}) {
|
|
308
|
-
const store = this.get(
|
|
309
|
-
const userId = this.
|
|
313
|
+
const store = this.get(TaskStoreProvider);
|
|
314
|
+
const userId = this.auth?.user.sub;
|
|
310
315
|
|
|
311
316
|
if (!userId) {
|
|
312
317
|
this.fail(new Error('Authentication required'));
|
|
@@ -336,7 +341,7 @@ export class UpdateTaskTool extends ToolContext {
|
|
|
336
341
|
// src/tools/delete-task.tool.ts
|
|
337
342
|
import { Tool, ToolContext, z } from '@frontmcp/sdk';
|
|
338
343
|
|
|
339
|
-
import {
|
|
344
|
+
import { TaskStoreProvider } from '../providers/task-store.provider';
|
|
340
345
|
|
|
341
346
|
@Tool({
|
|
342
347
|
name: 'delete_task',
|
|
@@ -351,8 +356,8 @@ import { TASK_STORE } from '../providers/task-store.provider';
|
|
|
351
356
|
})
|
|
352
357
|
export class DeleteTaskTool extends ToolContext {
|
|
353
358
|
async execute(input: { id: string }) {
|
|
354
|
-
const store = this.get(
|
|
355
|
-
const userId = this.
|
|
359
|
+
const store = this.get(TaskStoreProvider);
|
|
360
|
+
const userId = this.auth?.user.sub;
|
|
356
361
|
|
|
357
362
|
if (!userId) {
|
|
358
363
|
this.fail(new Error('Authentication required'));
|
|
@@ -389,13 +394,13 @@ export class DeleteTaskTool extends ToolContext {
|
|
|
389
394
|
// test/create-task.tool.spec.ts
|
|
390
395
|
import { ToolContext } from '@frontmcp/sdk';
|
|
391
396
|
|
|
392
|
-
import {
|
|
397
|
+
import { TaskStoreProvider } from '../src/providers/task-store.provider';
|
|
393
398
|
import { CreateTaskTool } from '../src/tools/create-task.tool';
|
|
394
399
|
import type { Task } from '../src/types/task';
|
|
395
400
|
|
|
396
401
|
describe('CreateTaskTool', () => {
|
|
397
402
|
let tool: CreateTaskTool;
|
|
398
|
-
let mockStore: jest.Mocked<
|
|
403
|
+
let mockStore: jest.Mocked<TaskStoreProvider>;
|
|
399
404
|
|
|
400
405
|
beforeEach(() => {
|
|
401
406
|
tool = new CreateTaskTool();
|
|
@@ -404,13 +409,13 @@ describe('CreateTaskTool', () => {
|
|
|
404
409
|
list: jest.fn(),
|
|
405
410
|
update: jest.fn(),
|
|
406
411
|
delete: jest.fn(),
|
|
407
|
-
}
|
|
412
|
+
} as unknown as jest.Mocked<TaskStoreProvider>;
|
|
408
413
|
});
|
|
409
414
|
|
|
410
415
|
function applyContext(userId: string | undefined): void {
|
|
411
416
|
const ctx = {
|
|
412
|
-
get: jest.fn((token:
|
|
413
|
-
if (token ===
|
|
417
|
+
get: jest.fn((token: unknown) => {
|
|
418
|
+
if (token === TaskStoreProvider) return mockStore;
|
|
414
419
|
throw new Error(`Unknown token: ${String(token)}`);
|
|
415
420
|
}),
|
|
416
421
|
tryGet: jest.fn(),
|
|
@@ -418,9 +423,10 @@ describe('CreateTaskTool', () => {
|
|
|
418
423
|
throw err;
|
|
419
424
|
}),
|
|
420
425
|
mark: jest.fn(),
|
|
421
|
-
notify: jest.fn(),
|
|
422
|
-
|
|
423
|
-
|
|
426
|
+
notify: jest.fn().mockResolvedValue(true),
|
|
427
|
+
progress: jest.fn().mockResolvedValue(true),
|
|
428
|
+
// `auth` getter resolves to a FrontMcpAuthContext-like object on the real SDK; we stub it directly here.
|
|
429
|
+
auth: userId ? { user: { sub: userId }, isAnonymous: false } : undefined,
|
|
424
430
|
} as unknown as ToolContext;
|
|
425
431
|
Object.assign(tool, ctx);
|
|
426
432
|
}
|
|
@@ -489,7 +495,7 @@ describe('Task Manager E2E', () => {
|
|
|
489
495
|
});
|
|
490
496
|
|
|
491
497
|
it('should list all CRUD tools', async () => {
|
|
492
|
-
const
|
|
498
|
+
const tools = await client.tools.list();
|
|
493
499
|
const names = tools.map((t) => t.name);
|
|
494
500
|
|
|
495
501
|
expect(names).toContain('create_task');
|
|
@@ -499,16 +505,16 @@ describe('Task Manager E2E', () => {
|
|
|
499
505
|
});
|
|
500
506
|
|
|
501
507
|
it('should create and list a task', async () => {
|
|
502
|
-
const createResult = await client.
|
|
508
|
+
const createResult = await client.tools.call('create_task', {
|
|
503
509
|
title: 'E2E test task',
|
|
504
510
|
priority: 'high',
|
|
505
511
|
});
|
|
506
|
-
expect(createResult).
|
|
512
|
+
expect(createResult.isError).toBeFalsy();
|
|
507
513
|
|
|
508
|
-
const listResult = await client.
|
|
509
|
-
expect(listResult).
|
|
514
|
+
const listResult = await client.tools.call('list_tasks', {});
|
|
515
|
+
expect(listResult.isError).toBeFalsy();
|
|
510
516
|
|
|
511
|
-
const parsed =
|
|
517
|
+
const parsed = listResult.json<{ tasks: Array<{ title: string }> }>();
|
|
512
518
|
expect(parsed.tasks.length).toBeGreaterThan(0);
|
|
513
519
|
expect(parsed.tasks.some((t: { title: string }) => t.title === 'E2E test task')).toBe(true);
|
|
514
520
|
});
|
|
@@ -517,10 +523,10 @@ describe('Task Manager E2E', () => {
|
|
|
517
523
|
|
|
518
524
|
## Examples
|
|
519
525
|
|
|
520
|
-
| Example | Level | Description
|
|
521
|
-
| ---------------------------------------------------------------------------------------- | ------------ |
|
|
522
|
-
| [`auth-and-crud-tools`](../examples/example-task-manager/auth-and-crud-tools.md) | Basic | Shows how to create CRUD tools with authentication, using `this.
|
|
523
|
-
| [`authenticated-e2e-tests`](../examples/example-task-manager/authenticated-e2e-tests.md) | Advanced | Shows how to write E2E tests with authentication using `TestTokenFactory`, and unit tests for tools that require session context.
|
|
524
|
-
| [`redis-provider-with-di`](../examples/example-task-manager/redis-provider-with-di.md) | Intermediate | Shows how to create a Redis-backed provider
|
|
526
|
+
| Example | Level | Description |
|
|
527
|
+
| ---------------------------------------------------------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
528
|
+
| [`auth-and-crud-tools`](../examples/example-task-manager/auth-and-crud-tools.md) | Basic | Shows how to create CRUD tools with authentication, using `this.auth?.user.sub` (the FrontMcpAuthContext exposed on every execution context) for user isolation and `this.get()` for dependency injection. |
|
|
529
|
+
| [`authenticated-e2e-tests`](../examples/example-task-manager/authenticated-e2e-tests.md) | Advanced | Shows how to write E2E tests with authentication using `TestTokenFactory`, and unit tests for tools that require session context. |
|
|
530
|
+
| [`redis-provider-with-di`](../examples/example-task-manager/redis-provider-with-di.md) | Intermediate | Shows how to create a Redis-backed provider using the class-as-token DI pattern (`@Provider({ name, scope })`) plus an `AsyncProvider` factory that runs the async Redis setup before any tool is invoked. |
|
|
525
531
|
|
|
526
532
|
> See all examples in [`examples/example-task-manager/`](../examples/example-task-manager/)
|
|
@@ -126,7 +126,7 @@ export class GetWeatherTool extends ToolContext {
|
|
|
126
126
|
|
|
127
127
|
```typescript
|
|
128
128
|
// src/resources/cities.resource.ts
|
|
129
|
-
import { Resource, ResourceContext } from '@frontmcp/sdk';
|
|
129
|
+
import { ReadResourceResult, Resource, ResourceContext } from '@frontmcp/sdk';
|
|
130
130
|
|
|
131
131
|
const SUPPORTED_CITIES = ['London', 'Tokyo', 'New York', 'Paris', 'Sydney', 'Berlin', 'Toronto', 'Mumbai'];
|
|
132
132
|
|
|
@@ -137,8 +137,16 @@ const SUPPORTED_CITIES = ['London', 'Tokyo', 'New York', 'Paris', 'Sydney', 'Ber
|
|
|
137
137
|
mimeType: 'application/json',
|
|
138
138
|
})
|
|
139
139
|
export class CitiesResource extends ResourceContext {
|
|
140
|
-
async
|
|
141
|
-
return
|
|
140
|
+
async execute(uri: string, _params: Record<string, string>): Promise<ReadResourceResult> {
|
|
141
|
+
return {
|
|
142
|
+
contents: [
|
|
143
|
+
{
|
|
144
|
+
uri,
|
|
145
|
+
mimeType: 'application/json',
|
|
146
|
+
text: JSON.stringify(SUPPORTED_CITIES),
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
};
|
|
142
150
|
}
|
|
143
151
|
}
|
|
144
152
|
```
|
|
@@ -179,7 +187,7 @@ describe('GetWeatherTool', () => {
|
|
|
179
187
|
get: jest.fn(),
|
|
180
188
|
tryGet: jest.fn(),
|
|
181
189
|
notify: jest.fn(),
|
|
182
|
-
|
|
190
|
+
progress: jest.fn(),
|
|
183
191
|
} as unknown as ToolContext;
|
|
184
192
|
Object.assign(tool, ctx);
|
|
185
193
|
|
|
@@ -194,16 +202,6 @@ describe('GetWeatherTool', () => {
|
|
|
194
202
|
expect(ctx.fetch).toHaveBeenCalledWith(expect.stringContaining('city=London'));
|
|
195
203
|
});
|
|
196
204
|
|
|
197
|
-
it('should fail when city is empty (Zod validation)', () => {
|
|
198
|
-
const { z } = require('zod');
|
|
199
|
-
const schema = z.object({
|
|
200
|
-
city: z.string().min(1),
|
|
201
|
-
units: z.enum(['celsius', 'fahrenheit']).default('celsius'),
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
expect(() => schema.parse({ city: '' })).toThrow();
|
|
205
|
-
});
|
|
206
|
-
|
|
207
205
|
it('should fail when the weather API returns an error', async () => {
|
|
208
206
|
const mockResponse = {
|
|
209
207
|
ok: false,
|
|
@@ -221,7 +219,7 @@ describe('GetWeatherTool', () => {
|
|
|
221
219
|
get: jest.fn(),
|
|
222
220
|
tryGet: jest.fn(),
|
|
223
221
|
notify: jest.fn(),
|
|
224
|
-
|
|
222
|
+
progress: jest.fn(),
|
|
225
223
|
} as unknown as ToolContext;
|
|
226
224
|
Object.assign(tool, ctx);
|
|
227
225
|
|
|
@@ -259,22 +257,22 @@ describe('Weather Server E2E', () => {
|
|
|
259
257
|
});
|
|
260
258
|
|
|
261
259
|
it('should list tools including get_weather', async () => {
|
|
262
|
-
const
|
|
260
|
+
const tools = await client.tools.list();
|
|
263
261
|
|
|
264
262
|
expect(tools.length).toBeGreaterThan(0);
|
|
265
|
-
expect(tools).
|
|
263
|
+
expect(tools.map((t) => t.name)).toContain('get_weather');
|
|
266
264
|
});
|
|
267
265
|
|
|
268
266
|
it('should call get_weather with a valid city', async () => {
|
|
269
|
-
const result = await client.
|
|
267
|
+
const result = await client.tools.call('get_weather', {
|
|
270
268
|
city: 'London',
|
|
271
269
|
units: 'celsius',
|
|
272
270
|
});
|
|
273
271
|
|
|
274
|
-
expect(result).
|
|
275
|
-
expect(result.
|
|
272
|
+
expect(result.isError).toBeFalsy();
|
|
273
|
+
expect(result.text()).toBeDefined();
|
|
276
274
|
|
|
277
|
-
const parsed =
|
|
275
|
+
const parsed = result.json<{ temperature: number; condition: string; humidity: number; city: string }>();
|
|
278
276
|
expect(parsed).toHaveProperty('temperature');
|
|
279
277
|
expect(parsed).toHaveProperty('condition');
|
|
280
278
|
expect(parsed).toHaveProperty('humidity');
|
|
@@ -282,12 +280,12 @@ describe('Weather Server E2E', () => {
|
|
|
282
280
|
});
|
|
283
281
|
|
|
284
282
|
it('should read the cities resource', async () => {
|
|
285
|
-
const
|
|
283
|
+
const resources = await client.resources.list();
|
|
286
284
|
const citiesResource = resources.find((r) => r.uri === 'weather://cities');
|
|
287
285
|
expect(citiesResource).toBeDefined();
|
|
288
286
|
|
|
289
|
-
const result = await client.
|
|
290
|
-
const cities =
|
|
287
|
+
const result = await client.resources.read('weather://cities');
|
|
288
|
+
const cities = result.json<string[]>();
|
|
291
289
|
|
|
292
290
|
expect(Array.isArray(cities)).toBe(true);
|
|
293
291
|
expect(cities).toContain('London');
|
|
@@ -65,7 +65,7 @@ The simplest way — one config line:
|
|
|
65
65
|
})
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
This enables auto-tracing for all
|
|
68
|
+
This enables auto-tracing for all SDK flows. Add structured logging:
|
|
69
69
|
|
|
70
70
|
```typescript
|
|
71
71
|
@FrontMcp({
|
|
@@ -91,7 +91,7 @@ Follow the scenario routing table above to find the right reference for your use
|
|
|
91
91
|
| Connect to monitoring platforms | `references/vendor-integrations.md` | Coralogix, Datadog, Logz.io, Grafana — OTLP and direct |
|
|
92
92
|
| Test spans and log entries | `references/testing-observability.md` | `createTestTracer()`, `assertSpanExists()`, integration test patterns |
|
|
93
93
|
|
|
94
|
-
##
|
|
94
|
+
## Common Patterns
|
|
95
95
|
|
|
96
96
|
| Pattern | Correct | Incorrect | Why |
|
|
97
97
|
| -------------------- | ------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------- |
|
|
@@ -137,6 +137,70 @@ Follow the scenario routing table above to find the right reference for your use
|
|
|
137
137
|
- [ ] Tests verify log entries via `CallbackSink`
|
|
138
138
|
- [ ] No test isolation issues (each test resets exporter)
|
|
139
139
|
|
|
140
|
+
## Troubleshooting
|
|
141
|
+
|
|
142
|
+
| Problem | Cause | Solution |
|
|
143
|
+
| ------------------------------------------------ | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
144
|
+
| `this.telemetry` is undefined in a tool | `observability` not enabled on the parent `@FrontMcp` config | Set `observability: true` (or a config object) in the `@FrontMcp` decorator; see `tracing-setup` |
|
|
145
|
+
| Spans appear without `trace_id` in logs | Logger not connected to `StructuredLogTransport` | Use `this.logger`, not `console`; see `structured-logging` |
|
|
146
|
+
| OTLP exporter silently drops spans | Endpoint URL points at the UI, not the OTLP collector | Use the OTLP HTTP/gRPC ingest endpoint exposed by your vendor (Datadog, Coralogix, Logz, etc.); see `vendor-integrations` |
|
|
147
|
+
| Real session ID appears in span attributes | A custom span attribute writes `session.id` directly | Use the SDK-provided `mcp.session.id` (already hashed); never log the raw session token |
|
|
148
|
+
| Tests randomly fail with leftover spans | Exporter retained between tests | Reset the in-memory exporter in `afterEach`; see `testing-observability` |
|
|
149
|
+
| OTel auto-instrumentation double-traces requests | Both `setupOTel()` AND a vendor agent attached to the process | Pick one: either FrontMCP-managed OTel OR the vendor agent — not both |
|
|
150
|
+
|
|
151
|
+
## Examples
|
|
152
|
+
|
|
153
|
+
Each reference has matching examples under [`examples/<reference>/`](./examples/):
|
|
154
|
+
|
|
155
|
+
### `tracing-setup`
|
|
156
|
+
|
|
157
|
+
| Example | Level | Description |
|
|
158
|
+
| ---------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------ |
|
|
159
|
+
| [`basic-tracing`](./examples/tracing-setup/basic-tracing.md) | Basic | Enable auto-tracing and see spans printed to your terminal. |
|
|
160
|
+
| [`production-tracing`](./examples/tracing-setup/production-tracing.md) | Intermediate | Full production observability — traces to OTLP, structured logs to stdout, per-request log collection. |
|
|
161
|
+
|
|
162
|
+
### `structured-logging`
|
|
163
|
+
|
|
164
|
+
| Example | Level | Description |
|
|
165
|
+
| ----------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
166
|
+
| [`stdout-logging`](./examples/structured-logging/stdout-logging.md) | Basic | Enable NDJSON structured logging to stdout with automatic trace correlation and field redaction. |
|
|
167
|
+
| [`winston-integration`](./examples/structured-logging/winston-integration.md) | Intermediate | Forward FrontMCP structured log entries to your existing winston logger. Each entry includes trace_id and span_id as metadata. |
|
|
168
|
+
|
|
169
|
+
### `telemetry-api`
|
|
170
|
+
|
|
171
|
+
| Example | Level | Description |
|
|
172
|
+
| -------------------------------------------------------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
173
|
+
| [`tool-custom-spans`](./examples/telemetry-api/tool-custom-spans.md) | Basic | Create child spans, events, and attributes inside a tool's execute method using this.telemetry. |
|
|
174
|
+
| [`plugin-telemetry`](./examples/telemetry-api/plugin-telemetry.md) | Intermediate | Add telemetry events from a custom plugin's hooks. Events appear on the tool execution span, giving you visibility into plugin behavior within the trace. |
|
|
175
|
+
| [`agent-nested-tracing`](./examples/telemetry-api/agent-nested-tracing.md) | Advanced | Trace an agent's execution lifecycle including its nested tool calls. Every span shares the same trace ID. |
|
|
176
|
+
|
|
177
|
+
### `vendor-integrations`
|
|
178
|
+
|
|
179
|
+
| Example | Level | Description |
|
|
180
|
+
| ---------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------- |
|
|
181
|
+
| [`coralogix-setup`](./examples/vendor-integrations/coralogix-setup.md) | Intermediate | Send both traces and structured logs to Coralogix. Logs include trace_id so Coralogix links them to traces automatically. |
|
|
182
|
+
|
|
183
|
+
### `testing-observability`
|
|
184
|
+
|
|
185
|
+
| Example | Level | Description |
|
|
186
|
+
| ---------------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------- |
|
|
187
|
+
| [`test-custom-spans`](./examples/testing-observability/test-custom-spans.md) | Basic | Verify that your tool creates the expected child spans with correct attributes. |
|
|
188
|
+
| [`test-log-correlation`](./examples/testing-observability/test-log-correlation.md) | Intermediate | Verify that structured log entries include trace context fields for correlation with spans. |
|
|
189
|
+
|
|
190
|
+
## Accessing This Skill
|
|
191
|
+
|
|
192
|
+
Skills are distributed as plain SKILL.md files plus a sibling `references/`
|
|
193
|
+
and `examples/` tree, so consumers can pick whichever access mode fits:
|
|
194
|
+
|
|
195
|
+
| Mode | How it works |
|
|
196
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
197
|
+
| **Filesystem** | Read `libs/skills/catalog/frontmcp-observability/` directly from a clone of the catalog repo, or from a published `@frontmcp/skills` install. SKILL.md is the entry point. |
|
|
198
|
+
| **`frontmcp` CLI** | `frontmcp skills list`, `frontmcp skills read frontmcp-observability`, `frontmcp skills read frontmcp-observability:references/<file>.md`, `frontmcp skills install frontmcp-observability` — no server required. |
|
|
199
|
+
| **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-observability/SKILL.md`, `skill://frontmcp-observability/references/{file}.md`, etc. The server’s `skill://index.json` returns the SEP-2640 discovery document for everything mounted on it. |
|
|
200
|
+
|
|
201
|
+
The catalog itself is **not** an MCP server. The `skill://` URIs only resolve
|
|
202
|
+
when a server has been configured to host this skill.
|
|
203
|
+
|
|
140
204
|
## Reference
|
|
141
205
|
|
|
142
206
|
- [Observability Guide](https://docs.agentfront.dev/frontmcp/guides/observability)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skill-counters
|
|
3
|
+
reference: telemetry-api
|
|
4
|
+
level: intermediate
|
|
5
|
+
description: Read built-in skill counters from the in-memory snapshot for tests and wire an OTel MeterProvider so counters export to OTLP in production.
|
|
6
|
+
tags: [telemetry, counters, metrics, skills, otel, meter-provider]
|
|
7
|
+
features:
|
|
8
|
+
- 'this.telemetry.createCounter(name, description) creates a custom counter'
|
|
9
|
+
- 'counter.inc(by, attrs) increments with bounded label cardinality'
|
|
10
|
+
- 'getMetricSnapshot() reads framework counters in tests without a MeterProvider'
|
|
11
|
+
- 'metrics.setGlobalMeterProvider() wires counters into OTLP for production'
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Skill Counters and Custom Counters
|
|
15
|
+
|
|
16
|
+
Read built-in skill counters from the in-memory snapshot for tests and wire an OTel MeterProvider so counters export to OTLP in production.
|
|
17
|
+
|
|
18
|
+
## Code
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// src/main.ts — wire a MeterProvider so framework counters export via OTLP
|
|
22
|
+
import { metrics } from '@opentelemetry/api';
|
|
23
|
+
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
|
|
24
|
+
import { Resource } from '@opentelemetry/resources';
|
|
25
|
+
import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
|
|
26
|
+
|
|
27
|
+
// Now register the FrontMCP server. Built-in skill counters
|
|
28
|
+
// (frontmcp_skills_bundle_pulls_total, frontmcp_skills_signature_failures_total, etc.)
|
|
29
|
+
// are exported automatically via the global MeterProvider.
|
|
30
|
+
import { FrontMcpInstance } from '@frontmcp/sdk';
|
|
31
|
+
|
|
32
|
+
import config from './server';
|
|
33
|
+
|
|
34
|
+
const meterProvider = new MeterProvider({
|
|
35
|
+
resource: new Resource({ 'service.name': 'my-mcp-server' }),
|
|
36
|
+
readers: [
|
|
37
|
+
new PeriodicExportingMetricReader({
|
|
38
|
+
exporter: new OTLPMetricExporter({
|
|
39
|
+
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? 'http://localhost:4318/v1/metrics',
|
|
40
|
+
}),
|
|
41
|
+
exportIntervalMillis: 10_000,
|
|
42
|
+
}),
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
metrics.setGlobalMeterProvider(meterProvider);
|
|
47
|
+
|
|
48
|
+
await FrontMcpInstance.bootstrap(config);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// src/tools/process-widget.tool.ts — custom counter inside a tool
|
|
53
|
+
import { Tool, ToolContext, z } from '@frontmcp/sdk';
|
|
54
|
+
|
|
55
|
+
@Tool({
|
|
56
|
+
name: 'process_widget',
|
|
57
|
+
description: 'Process a widget and increment a counter per outcome',
|
|
58
|
+
inputSchema: { id: z.string() },
|
|
59
|
+
})
|
|
60
|
+
export class ProcessWidgetTool extends ToolContext {
|
|
61
|
+
async execute({ id }: { id: string }) {
|
|
62
|
+
const counter = this.telemetry.createCounter('my_app_widgets_total', 'Widgets processed');
|
|
63
|
+
try {
|
|
64
|
+
const result = await this.handle(id);
|
|
65
|
+
counter.inc(1, { status: 'ok' });
|
|
66
|
+
return result;
|
|
67
|
+
} catch (err) {
|
|
68
|
+
counter.inc(1, { status: 'error' });
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private async handle(id: string): Promise<{ id: string }> {
|
|
74
|
+
return { id };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// __tests__/skill-counters.spec.ts — in-memory snapshot, no MeterProvider needed
|
|
81
|
+
import { getMetricSnapshot } from '@frontmcp/observability';
|
|
82
|
+
|
|
83
|
+
it('records bundle pulls', async () => {
|
|
84
|
+
// ... exercise the server so a bundle gets pulled
|
|
85
|
+
const snapshot = getMetricSnapshot();
|
|
86
|
+
expect(snapshot['frontmcp_skills_bundle_pulls_total']).toBeGreaterThan(0);
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## What This Demonstrates
|
|
91
|
+
|
|
92
|
+
- this.telemetry.createCounter(name, description) creates a custom counter
|
|
93
|
+
- counter.inc(by, attrs) increments with bounded label cardinality
|
|
94
|
+
- getMetricSnapshot() reads framework counters in tests without a MeterProvider
|
|
95
|
+
- metrics.setGlobalMeterProvider() wires counters into OTLP for production
|
|
96
|
+
|
|
97
|
+
## Related
|
|
98
|
+
|
|
99
|
+
- See `telemetry-api` for the full `this.telemetry` reference
|
|
100
|
+
- See `vendor-integrations` for vendor-specific OTLP metrics endpoints
|
|
@@ -19,8 +19,9 @@ Full production observability — traces to OTLP, structured logs to stdout, per
|
|
|
19
19
|
|
|
20
20
|
```typescript
|
|
21
21
|
// src/server.ts
|
|
22
|
-
import { FrontMcp } from '@frontmcp/sdk';
|
|
23
22
|
import { setupOTel } from '@frontmcp/observability';
|
|
23
|
+
import { FrontMcp } from '@frontmcp/sdk';
|
|
24
|
+
|
|
24
25
|
import { MyApp } from './apps/my-app';
|
|
25
26
|
|
|
26
27
|
setupOTel({
|
|
@@ -51,7 +52,11 @@ setupOTel({
|
|
|
51
52
|
maxEntries: 500,
|
|
52
53
|
onRequestComplete: async (log) => {
|
|
53
54
|
if (log.status === 'error') {
|
|
54
|
-
|
|
55
|
+
// No `this.logger` is available in this callback. Emit a structured
|
|
56
|
+
// NDJSON line directly to stderr so it flows through the same log
|
|
57
|
+
// pipeline as the rest of the server (and so it does NOT use
|
|
58
|
+
// console.* — see the anti-pattern in SKILL.md cross-cutting table).
|
|
59
|
+
process.stderr.write(JSON.stringify({ event: 'request.error', ...log }) + '\n');
|
|
55
60
|
}
|
|
56
61
|
},
|
|
57
62
|
},
|
|
@@ -18,10 +18,13 @@ Send both traces and structured logs to Coralogix. Logs include trace_id so Cora
|
|
|
18
18
|
|
|
19
19
|
```typescript
|
|
20
20
|
// src/server.ts
|
|
21
|
-
import { FrontMcp } from '@frontmcp/sdk';
|
|
22
21
|
import { setupOTel } from '@frontmcp/observability';
|
|
22
|
+
import { FrontMcp } from '@frontmcp/sdk';
|
|
23
23
|
|
|
24
24
|
// Traces → Coralogix via OTLP
|
|
25
|
+
// Auth is supplied via OTEL_EXPORTER_OTLP_HEADERS env var (read automatically
|
|
26
|
+
// by the underlying @opentelemetry/exporter-trace-otlp-http exporter).
|
|
27
|
+
// setupOTel() itself does not accept a `headers` option.
|
|
25
28
|
setupOTel({
|
|
26
29
|
serviceName: 'my-mcp-server',
|
|
27
30
|
exporter: 'otlp',
|
|
@@ -54,11 +57,12 @@ setupOTel({
|
|
|
54
57
|
export default class Server {}
|
|
55
58
|
```
|
|
56
59
|
|
|
57
|
-
Environment variables alternative:
|
|
60
|
+
Environment variables (required for trace auth, optional alternative for the rest):
|
|
58
61
|
|
|
59
62
|
```bash
|
|
60
63
|
OTEL_SERVICE_NAME=my-mcp-server
|
|
61
64
|
OTEL_EXPORTER_OTLP_ENDPOINT=https://ingress.coralogix.com:443
|
|
65
|
+
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer ${CX_PRIVATE_KEY}"
|
|
62
66
|
CX_PRIVATE_KEY=your-coralogix-private-key
|
|
63
67
|
```
|
|
64
68
|
|