@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.
Files changed (176) hide show
  1. package/catalog/TEMPLATE.md +16 -11
  2. package/catalog/frontmcp-authorities/SKILL.md +116 -11
  3. package/catalog/frontmcp-authorities/references/authority-profiles.md +39 -36
  4. package/catalog/frontmcp-authorities/references/claims-mapping.md +7 -0
  5. package/catalog/frontmcp-authorities/references/custom-evaluators.md +63 -14
  6. package/catalog/frontmcp-channels/SKILL.md +36 -0
  7. package/catalog/frontmcp-channels/examples/channel-sources/file-watcher.md +8 -2
  8. package/catalog/frontmcp-channels/examples/channel-sources/replay-buffer.md +111 -30
  9. package/catalog/frontmcp-channels/examples/channel-two-way/whatsapp-bridge.md +45 -3
  10. package/catalog/frontmcp-channels/references/channel-sources.md +11 -3
  11. package/catalog/frontmcp-channels/references/channel-two-way.md +60 -89
  12. package/catalog/frontmcp-config/SKILL.md +111 -8
  13. package/catalog/frontmcp-config/examples/configure-auth-modes/local-self-signed-tokens.md +4 -4
  14. package/catalog/frontmcp-config/examples/configure-auth-modes/remote-enterprise-oauth.md +7 -1
  15. package/catalog/frontmcp-config/examples/configure-deployment-targets/distributed-ha-config.md +1 -1
  16. package/catalog/frontmcp-config/examples/configure-deployment-targets/json-schema-ide-support.md +1 -1
  17. package/catalog/frontmcp-config/examples/configure-deployment-targets/multi-target-with-security.md +12 -9
  18. package/catalog/frontmcp-config/examples/configure-http/cors-restricted-origins.md +2 -2
  19. package/catalog/frontmcp-config/examples/configure-http/entry-path-reverse-proxy.md +1 -1
  20. package/catalog/frontmcp-config/examples/configure-security-headers/csp-report-only.md +1 -1
  21. package/catalog/frontmcp-config/examples/configure-security-headers/full-production-headers.md +1 -1
  22. package/catalog/frontmcp-config/examples/configure-skills-http/audit-log-basic.md +76 -0
  23. package/catalog/frontmcp-config/examples/configure-skills-http/audit-log-redis.md +116 -0
  24. package/catalog/frontmcp-config/examples/configure-skills-http/inject-instructions.md +59 -0
  25. package/catalog/frontmcp-config/references/configure-auth-modes.md +5 -5
  26. package/catalog/frontmcp-config/references/configure-deployment-targets.md +27 -24
  27. package/catalog/frontmcp-config/references/configure-http.md +14 -10
  28. package/catalog/frontmcp-config/references/configure-security-headers.md +2 -2
  29. package/catalog/frontmcp-config/references/configure-session.md +25 -25
  30. package/catalog/frontmcp-config/references/configure-skills-http.md +157 -0
  31. package/catalog/frontmcp-config/references/configure-throttle.md +1 -1
  32. package/catalog/frontmcp-config/references/configure-transport.md +2 -2
  33. package/catalog/frontmcp-deployment/SKILL.md +112 -9
  34. package/catalog/frontmcp-deployment/examples/build-for-browser/browser-build-with-custom-entry.md +23 -11
  35. package/catalog/frontmcp-deployment/examples/build-for-browser/browser-crypto-and-storage.md +44 -17
  36. package/catalog/frontmcp-deployment/examples/build-for-browser/react-provider-setup.md +53 -21
  37. package/catalog/frontmcp-deployment/examples/build-for-cli/cli-binary-build.md +1 -1
  38. package/catalog/frontmcp-deployment/examples/build-for-cli/unix-socket-daemon.md +1 -1
  39. package/catalog/frontmcp-deployment/examples/build-for-mcpb/mcpb-bundle-build.md +1 -1
  40. package/catalog/frontmcp-deployment/examples/build-for-sdk/connect-openai.md +1 -1
  41. package/catalog/frontmcp-deployment/examples/build-for-sdk/multi-platform-connect.md +1 -1
  42. package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/basic-worker-deploy.md +7 -8
  43. package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/worker-custom-domain.md +8 -6
  44. package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/worker-with-kv-storage.md +5 -4
  45. package/catalog/frontmcp-deployment/examples/deploy-to-lambda/cdk-deployment.md +8 -5
  46. package/catalog/frontmcp-deployment/examples/deploy-to-lambda/lambda-handler-with-cors.md +20 -18
  47. package/catalog/frontmcp-deployment/examples/deploy-to-lambda/sam-template-basic.md +8 -5
  48. package/catalog/frontmcp-deployment/examples/deploy-to-node/docker-compose-with-redis.md +3 -3
  49. package/catalog/frontmcp-deployment/examples/deploy-to-node/pm2-with-nginx.md +1 -1
  50. package/catalog/frontmcp-deployment/examples/deploy-to-node/resource-limits.md +2 -2
  51. package/catalog/frontmcp-deployment/examples/deploy-to-node-dockerfile/basic-multistage-dockerfile.md +2 -2
  52. package/catalog/frontmcp-deployment/examples/deploy-to-node-dockerfile/secure-nonroot-dockerfile.md +1 -1
  53. package/catalog/frontmcp-deployment/examples/deploy-to-vercel/vercel-mcp-endpoint-test.md +23 -21
  54. package/catalog/frontmcp-deployment/examples/deploy-to-vercel/vercel-with-kv.md +25 -22
  55. package/catalog/frontmcp-deployment/examples/deploy-to-vercel/vercel-with-skills-cache.md +23 -30
  56. package/catalog/frontmcp-deployment/examples/deploy-to-vercel-config/minimal-vercel-config.md +52 -28
  57. package/catalog/frontmcp-deployment/examples/deploy-to-vercel-config/vercel-config-with-security-headers.md +32 -55
  58. package/catalog/frontmcp-deployment/examples/mcp-client-integration/http-remote.md +9 -0
  59. package/catalog/frontmcp-deployment/references/build-for-browser.md +40 -17
  60. package/catalog/frontmcp-deployment/references/build-for-cli.md +8 -8
  61. package/catalog/frontmcp-deployment/references/deploy-to-cloudflare.md +43 -24
  62. package/catalog/frontmcp-deployment/references/deploy-to-lambda.md +36 -25
  63. package/catalog/frontmcp-deployment/references/deploy-to-node-dockerfile.md +56 -14
  64. package/catalog/frontmcp-deployment/references/deploy-to-node.md +9 -6
  65. package/catalog/frontmcp-deployment/references/deploy-to-vercel-config.md +57 -58
  66. package/catalog/frontmcp-deployment/references/deploy-to-vercel.md +49 -59
  67. package/catalog/frontmcp-deployment/references/mcp-client-integration.md +2 -0
  68. package/catalog/frontmcp-development/SKILL.md +186 -11
  69. package/catalog/frontmcp-development/examples/create-agent/custom-multi-pass-agent.md +1 -1
  70. package/catalog/frontmcp-development/examples/create-agent/nested-agents-with-swarm.md +30 -27
  71. package/catalog/frontmcp-development/examples/create-job/job-with-permissions.md +13 -8
  72. package/catalog/frontmcp-development/examples/create-provider/basic-database-provider.md +33 -23
  73. package/catalog/frontmcp-development/examples/create-provider/config-and-api-providers.md +19 -10
  74. package/catalog/frontmcp-development/examples/create-tool/tool-with-rate-limiting-and-progress.md +3 -3
  75. package/catalog/frontmcp-development/examples/create-workflow/webhook-triggered-workflow.md +6 -4
  76. package/catalog/frontmcp-development/examples/decorators-guide/agent-skill-job-workflow.md +1 -1
  77. package/catalog/frontmcp-development/examples/decorators-guide/basic-server-with-app-and-tools.md +13 -8
  78. package/catalog/frontmcp-development/examples/decorators-guide/multi-app-with-plugins-and-providers.md +50 -23
  79. package/catalog/frontmcp-development/references/create-agent.md +47 -30
  80. package/catalog/frontmcp-development/references/create-job.md +69 -54
  81. package/catalog/frontmcp-development/references/create-plugin-hooks.md +45 -28
  82. package/catalog/frontmcp-development/references/create-plugin.md +10 -8
  83. package/catalog/frontmcp-development/references/create-prompt.md +3 -3
  84. package/catalog/frontmcp-development/references/create-provider.md +91 -51
  85. package/catalog/frontmcp-development/references/create-resource.md +3 -3
  86. package/catalog/frontmcp-development/references/create-skill.md +2 -2
  87. package/catalog/frontmcp-development/references/create-tool.md +7 -7
  88. package/catalog/frontmcp-development/references/create-workflow.md +8 -10
  89. package/catalog/frontmcp-development/references/decorators-guide.md +92 -56
  90. package/catalog/frontmcp-development/references/official-plugins.md +4 -3
  91. package/catalog/frontmcp-development/references/openapi-adapter.md +1 -1
  92. package/catalog/frontmcp-extensibility/SKILL.md +70 -10
  93. package/catalog/frontmcp-extensibility/examples/skill-audit-log/custom-store.md +197 -0
  94. package/catalog/frontmcp-extensibility/examples/skill-audit-log/verify-chain.md +68 -0
  95. package/catalog/frontmcp-extensibility/examples/vectoriadb/product-catalog-search.md +3 -5
  96. package/catalog/frontmcp-extensibility/examples/vectoriadb/semantic-search-with-persistence.md +4 -11
  97. package/catalog/frontmcp-extensibility/examples/vectoriadb/tfidf-keyword-search.md +41 -30
  98. package/catalog/frontmcp-extensibility/references/skill-audit-log.md +233 -0
  99. package/catalog/frontmcp-extensibility/references/vectoriadb.md +73 -63
  100. package/catalog/frontmcp-guides/SKILL.md +84 -27
  101. package/catalog/frontmcp-guides/examples/example-knowledge-base/agent-and-plugin.md +72 -62
  102. package/catalog/frontmcp-guides/examples/example-knowledge-base/vector-search-and-resources.md +32 -43
  103. package/catalog/frontmcp-guides/examples/example-task-manager/auth-and-crud-tools.md +24 -17
  104. package/catalog/frontmcp-guides/examples/example-task-manager/authenticated-e2e-tests.md +23 -21
  105. package/catalog/frontmcp-guides/examples/example-task-manager/redis-provider-with-di.md +47 -39
  106. package/catalog/frontmcp-guides/examples/example-weather-api/server-and-app-setup.md +16 -6
  107. package/catalog/frontmcp-guides/examples/example-weather-api/unit-and-e2e-tests.md +9 -8
  108. package/catalog/frontmcp-guides/references/example-knowledge-base.md +192 -265
  109. package/catalog/frontmcp-guides/references/example-task-manager.md +60 -54
  110. package/catalog/frontmcp-guides/references/example-weather-api.md +22 -24
  111. package/catalog/frontmcp-observability/SKILL.md +66 -2
  112. package/catalog/frontmcp-observability/examples/telemetry-api/skill-counters.md +100 -0
  113. package/catalog/frontmcp-observability/examples/tracing-setup/production-tracing.md +7 -2
  114. package/catalog/frontmcp-observability/examples/vendor-integrations/coralogix-setup.md +6 -2
  115. package/catalog/frontmcp-observability/references/telemetry-api.md +72 -8
  116. package/catalog/frontmcp-observability/references/testing-observability.md +33 -49
  117. package/catalog/frontmcp-observability/references/tracing-setup.md +12 -5
  118. package/catalog/frontmcp-observability/references/vendor-integrations.md +46 -1
  119. package/catalog/frontmcp-production-readiness/SKILL.md +134 -3
  120. package/catalog/frontmcp-production-readiness/examples/common-checklist/caching-and-performance.md +57 -36
  121. package/catalog/frontmcp-production-readiness/examples/common-checklist/observability-setup.md +1 -1
  122. package/catalog/frontmcp-production-readiness/examples/common-checklist/security-hardening.md +102 -6
  123. package/catalog/frontmcp-production-readiness/examples/production-cli-daemon/daemon-socket-config.md +2 -1
  124. package/catalog/frontmcp-production-readiness/examples/production-cli-daemon/graceful-shutdown-cleanup.md +66 -58
  125. package/catalog/frontmcp-production-readiness/examples/production-cli-daemon/security-and-permissions.md +5 -3
  126. package/catalog/frontmcp-production-readiness/examples/production-cloudflare/durable-objects-state.md +2 -1
  127. package/catalog/frontmcp-production-readiness/examples/production-cloudflare/wrangler-config.md +55 -76
  128. package/catalog/frontmcp-production-readiness/examples/production-lambda/cold-start-connection-reuse.md +43 -40
  129. package/catalog/frontmcp-production-readiness/examples/production-lambda/sam-template.md +63 -94
  130. package/catalog/frontmcp-production-readiness/examples/production-lambda/scaling-and-monitoring.md +28 -18
  131. package/catalog/frontmcp-production-readiness/examples/production-node-sdk/multi-instance-cleanup.md +29 -14
  132. package/catalog/frontmcp-production-readiness/examples/production-node-server/graceful-shutdown.md +58 -42
  133. package/catalog/frontmcp-production-readiness/examples/production-node-server/redis-session-scaling.md +5 -2
  134. package/catalog/frontmcp-production-readiness/examples/production-vercel/cold-start-optimization.md +41 -24
  135. package/catalog/frontmcp-production-readiness/examples/production-vercel/vercel-edge-config.md +56 -65
  136. package/catalog/frontmcp-production-readiness/references/common-checklist.md +17 -5
  137. package/catalog/frontmcp-production-readiness/references/production-cli-daemon.md +5 -5
  138. package/catalog/frontmcp-production-readiness/references/production-cloudflare.md +5 -5
  139. package/catalog/frontmcp-production-readiness/references/production-lambda.md +5 -5
  140. package/catalog/frontmcp-production-readiness/references/production-node-sdk.md +5 -5
  141. package/catalog/frontmcp-production-readiness/references/production-node-server.md +1 -1
  142. package/catalog/frontmcp-production-readiness/references/production-vercel.md +5 -5
  143. package/catalog/frontmcp-setup/SKILL.md +88 -0
  144. package/catalog/frontmcp-setup/examples/project-structure-nx/nx-workspace-with-apps.md +10 -4
  145. package/catalog/frontmcp-setup/examples/project-structure-standalone/dev-workflow-commands.md +21 -8
  146. package/catalog/frontmcp-setup/examples/readme-guide/node-server-readme.md +3 -3
  147. package/catalog/frontmcp-setup/references/multi-app-composition.md +4 -3
  148. package/catalog/frontmcp-setup/references/project-structure-nx.md +15 -6
  149. package/catalog/frontmcp-setup/references/project-structure-standalone.md +18 -15
  150. package/catalog/frontmcp-setup/references/readme-guide.md +1 -1
  151. package/catalog/frontmcp-setup/references/setup-project.md +19 -5
  152. package/catalog/frontmcp-setup/references/setup-redis.md +27 -39
  153. package/catalog/frontmcp-setup/references/setup-sqlite.md +25 -18
  154. package/catalog/frontmcp-testing/SKILL.md +102 -15
  155. package/catalog/frontmcp-testing/examples/setup-testing/unit-test-tool-resource-prompt.md +3 -3
  156. package/catalog/frontmcp-testing/examples/test-auth/oauth-flow-test.md +50 -39
  157. package/catalog/frontmcp-testing/examples/test-auth/role-based-access-test.md +52 -29
  158. package/catalog/frontmcp-testing/examples/test-auth/token-factory-test.md +37 -20
  159. package/catalog/frontmcp-testing/examples/test-direct-client/basic-create-test.md +25 -15
  160. package/catalog/frontmcp-testing/examples/test-direct-client/openai-claude-format-test.md +27 -21
  161. package/catalog/frontmcp-testing/examples/test-e2e-handler/basic-e2e-test.md +29 -20
  162. package/catalog/frontmcp-testing/examples/test-e2e-handler/manual-client-with-transport.md +5 -3
  163. package/catalog/frontmcp-testing/examples/test-e2e-handler/tool-call-and-error-e2e.md +35 -26
  164. package/catalog/frontmcp-testing/examples/test-tool-unit/basic-tool-test.md +8 -3
  165. package/catalog/frontmcp-testing/examples/test-tool-unit/schema-validation-test.md +4 -1
  166. package/catalog/frontmcp-testing/examples/test-tool-unit/tool-error-handling-test.md +6 -3
  167. package/catalog/frontmcp-testing/references/setup-testing.md +35 -39
  168. package/catalog/frontmcp-testing/references/test-auth.md +86 -43
  169. package/catalog/frontmcp-testing/references/test-browser-build.md +1 -1
  170. package/catalog/frontmcp-testing/references/test-direct-client.md +29 -19
  171. package/catalog/frontmcp-testing/references/test-e2e-handler.md +31 -19
  172. package/catalog/frontmcp-testing/references/test-tool-unit.md +6 -2
  173. package/catalog/skills-manifest.json +428 -339
  174. package/package.json +1 -1
  175. package/src/manifest.d.ts +13 -0
  176. package/src/manifest.js.map +1 -1
@@ -2,18 +2,23 @@
2
2
  name: replay-buffer
3
3
  reference: channel-sources
4
4
  level: advanced
5
- description: Buffer channel events so Claude Code receives them when it connects, even if events occurred while offline
6
- tags: [replay, buffer, persistence, offline, reconnect]
5
+ description: Buffer channel events so Claude Code receives them when it connects, even if events occurred while offline.
6
+ tags:
7
+ - replay
8
+ - buffer
9
+ - persistence
10
+ - offline
11
+ - reconnect
7
12
  features:
8
- - Replay configuration with maxEvents cap
13
+ - Replay configuration with `maxEvents` cap
9
14
  - In-memory ring buffer for event storage
10
- - Replay on session connect
11
- - Persistent store pattern with onConnect
15
+ - Where `replayBufferedEvents` actually lives (on `ChannelInstance`, not on `ChannelContext`)
16
+ - Persistent store pattern with `onConnect` and a user-defined Redis provider
12
17
  ---
13
18
 
14
19
  # Replay Buffer Pattern
15
20
 
16
- Buffer channel events so Claude Code receives them when it connects, even if events occurred while offline
21
+ Buffer channel events so Claude Code receives them when it connects, even if events occurred while offline.
17
22
 
18
23
  ## Basic Replay (In-Memory)
19
24
 
@@ -38,42 +43,107 @@ class CIAlertChannel extends ChannelContext {
38
43
  }
39
44
  ```
40
45
 
41
- Events are automatically buffered. When a Claude Code session connects, call `replayBufferedEvents(sessionId)` to send all buffered events. Replayed events have `replayed: "true"` in their meta.
46
+ The SDK keeps a per-channel ring buffer of the last `maxEvents` notifications. Replay is delivered via `ChannelInstance.replayBufferedEvents(sessionId)` (defined in `libs/sdk/src/channel/channel.instance.ts:223`). That method lives on the **channel instance**, not on `ChannelContext`, so you cannot call `this.replayBufferedEvents(...)` from within `onEvent`/`onConnect`.
42
47
 
43
- ## Persistent Store Pattern (Redis/DB)
48
+ Replay is **not** triggered automatically when a new session subscribes — there is no caller of `replayBufferedEvents` inside the SDK today. To deliver buffered events, expose a tool that resolves the channel from the registry and triggers replay explicitly (Claude Code can call this on demand, or your application can call it from a custom hook):
44
49
 
45
- For events that survive server restarts, load from an external store in `onConnect()`:
50
+ ```typescript
51
+ // src/apps/alerts/tools/replay-ci-alerts.tool.ts
52
+ import { Tool, ToolContext, z } from '@frontmcp/sdk';
53
+ import type ChannelRegistry from '@frontmcp/sdk/channel/channel.registry';
54
+
55
+ @Tool({
56
+ name: 'replay-ci-alerts',
57
+ description: 'Replay buffered CI alerts to the current session.',
58
+ inputSchema: {
59
+ channel_name: z.string().default('ci-alerts'),
60
+ },
61
+ })
62
+ export class ReplayCIAlertsTool extends ToolContext {
63
+ async execute(input) {
64
+ // Resolve the channel registry off scope (same cast pattern as ChannelReplyTool)
65
+ const scope = this.scope as unknown as { channels?: ChannelRegistry };
66
+ const registry = scope.channels;
67
+ if (!registry) {
68
+ return { content: [{ type: 'text', text: 'Channels are not enabled on this server.' }], isError: true };
69
+ }
70
+
71
+ const channel = registry.findByName(input.channel_name);
72
+ if (!channel) {
73
+ return { content: [{ type: 'text', text: `Channel "${input.channel_name}" not found.` }], isError: true };
74
+ }
75
+
76
+ const sessionId = this.context.sessionId;
77
+ const replayed = channel.replayBufferedEvents(sessionId);
78
+ return { content: [{ type: 'text', text: `Replayed ${replayed} buffered event(s).` }] };
79
+ }
80
+ }
81
+ ```
82
+
83
+ Replayed events arrive at Claude Code with `replayed: "true"` in their meta so the model can distinguish them from live events.
84
+
85
+ ## Persistent Store Pattern (Redis)
86
+
87
+ The in-memory ring buffer does not survive process restarts. To persist events across restarts, push events into Redis and rehydrate on `onConnect()`. Inject a Redis-backed provider the same way the demo apps do (`apps/demo/src/apps/employee-time/providers/redis.provider.ts`):
46
88
 
47
89
  ```typescript
90
+ // src/apps/alerts/providers/alerts-redis.provider.ts
91
+ import Redis, { Redis as RedisClient } from 'ioredis';
92
+
93
+ import { AsyncProvider, ProviderScope } from '@frontmcp/sdk';
94
+
95
+ export default class AlertsRedisProvider {
96
+ readonly client: RedisClient;
97
+
98
+ constructor() {
99
+ this.client = new Redis({ host: 'localhost', port: 6379 });
100
+ }
101
+ }
102
+
103
+ export const createAlertsRedisProvider = AsyncProvider({
104
+ provide: AlertsRedisProvider,
105
+ name: 'AlertsRedisProvider',
106
+ scope: ProviderScope.GLOBAL,
107
+ inject: () => [] as const,
108
+ useFactory: async () => new AlertsRedisProvider(),
109
+ });
110
+ ```
111
+
112
+ Resolve the provider inside the channel via `this.get(AlertsRedisProvider)` and use it to load + record events:
113
+
114
+ ```typescript
115
+ // src/apps/alerts/channels/persistent-alert.channel.ts
116
+ import { Channel, ChannelContext, type ChannelNotification } from '@frontmcp/sdk';
117
+
118
+ import AlertsRedisProvider from '../providers/alerts-redis.provider';
119
+
48
120
  @Channel({
49
121
  name: 'alerts',
50
122
  description: 'Persistent alerts with Redis-backed replay',
51
123
  source: { type: 'service', service: 'alert-store' },
52
124
  replay: { enabled: true, maxEvents: 500 },
53
125
  })
54
- class PersistentAlertChannel extends ChannelContext {
126
+ export class PersistentAlertChannel extends ChannelContext {
55
127
  async onConnect(): Promise<void> {
56
- // Load missed events from Redis on startup
57
- const redis = this.get(RedisClientToken);
58
- const stored = await redis.lrange('channel:alerts:buffer', 0, -1);
128
+ const redis = this.get(AlertsRedisProvider).client;
59
129
 
130
+ // Replay history from Redis into the channel pipeline
131
+ const stored = await redis.lrange('channel:alerts:buffer', 0, -1);
60
132
  for (const raw of stored) {
61
133
  const event = JSON.parse(raw);
62
134
  this.pushIncoming(event);
63
135
  }
64
136
 
65
- // Subscribe to new events via Redis pub/sub
137
+ // Subscribe to new events and persist them as they arrive
66
138
  const subscriber = redis.duplicate();
67
- await subscriber.subscribe('channel:alerts', (message) => {
68
- const event = JSON.parse(message);
69
- // Store for future replays
70
- redis.rpush('channel:alerts:buffer', message);
71
- redis.ltrim('channel:alerts:buffer', -500, -1);
72
- // Push to connected Claude sessions
73
- this.pushIncoming(event);
139
+ await subscriber.subscribe('channel:alerts');
140
+ subscriber.on('message', async (_channel, message) => {
141
+ await redis.rpush('channel:alerts:buffer', message);
142
+ await redis.ltrim('channel:alerts:buffer', -500, -1);
143
+ this.pushIncoming(JSON.parse(message));
74
144
  });
75
145
 
76
- this.logger.info('Alert store connected, loaded history');
146
+ this.logger.info('Alert store connected, history loaded');
77
147
  }
78
148
 
79
149
  async onEvent(payload: unknown): Promise<ChannelNotification> {
@@ -86,20 +156,31 @@ class PersistentAlertChannel extends ChannelContext {
86
156
  }
87
157
  ```
88
158
 
159
+ Register the provider on the app so DI can resolve it:
160
+
161
+ ```typescript
162
+ @App({
163
+ name: 'Alerts',
164
+ channels: [PersistentAlertChannel],
165
+ providers: [createAlertsRedisProvider],
166
+ })
167
+ class AlertsApp {}
168
+ ```
169
+
89
170
  ## How Replay Works
90
171
 
91
- 1. Events arrive via any source `onEvent()` transforms them notification pushed
92
- 2. If `replay.enabled`, the notification is also stored in a ring buffer (FIFO, capped at `maxEvents`)
93
- 3. When a new Claude Code session connects, the server can call `channel.replayBufferedEvents(sessionId)`
94
- 4. All buffered events are sent to the new session with `replayed: "true"` in meta
95
- 5. Claude can distinguish live events from replayed ones via the meta field
172
+ 1. Events arrive via any source -> `onEvent()` transforms them -> notification pushed to live sessions.
173
+ 2. If `replay.enabled`, each pushed notification is also stored in the channel's ring buffer (FIFO, capped at `maxEvents`).
174
+ 3. Replay is **user-triggered**: call `ChannelInstance.replayBufferedEvents(sessionId)` yourself — typically from a tool (see `ReplayCIAlertsTool` above) or from a custom session lifecycle hook in your app.
175
+ 4. Each buffered notification is sent to the target session with `replayed: "true"` injected into its meta.
176
+ 5. The persistent-store pattern shown above is independent of the in-memory buffer: you replay from Redis on `onConnect()` (which fires once when the channel boots), while the in-memory ring buffer is what `replayBufferedEvents` reads from when invoked.
96
177
 
97
178
  ## What This Demonstrates
98
179
 
99
- - Replay configuration with maxEvents cap
180
+ - Replay configuration with `maxEvents` cap
100
181
  - In-memory ring buffer for event storage
101
- - Replay on session connect
102
- - Persistent store pattern with onConnect
182
+ - Where `replayBufferedEvents` actually lives (on `ChannelInstance`, not on `ChannelContext`)
183
+ - Persistent store pattern with `onConnect` and a user-defined Redis provider
103
184
 
104
185
  ## Related
105
186
 
@@ -20,9 +20,30 @@ Full WhatsApp Business API bridge allowing users to chat with Claude Code via Wh
20
20
  ```typescript
21
21
  // src/apps/messaging/channels/whatsapp.channel.ts
22
22
  import { Channel, ChannelContext, ChannelNotification } from '@frontmcp/sdk';
23
+ import { hmacSha256 } from '@frontmcp/utils';
23
24
 
24
25
  const ALLOWED_SENDERS = new Set((process.env['WHATSAPP_ALLOWED_SENDERS'] ?? '').split(',').filter(Boolean));
25
26
 
27
+ /**
28
+ * Verify the X-Hub-Signature-256 header WhatsApp Cloud API attaches to every
29
+ * webhook delivery. The header is `sha256=<hex>` where the digest is HMAC-SHA256
30
+ * of the raw request body keyed by the app secret. Uses @frontmcp/utils so the
31
+ * same code runs in Node and Edge runtimes.
32
+ */
33
+ function verifyWhatsAppSignature(rawBody: string, header: string | undefined, appSecret: string): boolean {
34
+ if (!header?.startsWith('sha256=')) return false;
35
+ const expected = header.slice('sha256='.length);
36
+
37
+ const digest = hmacSha256(new TextEncoder().encode(appSecret), new TextEncoder().encode(rawBody));
38
+ const actual = Array.from(digest, (b) => b.toString(16).padStart(2, '0')).join('');
39
+
40
+ // Constant-time compare to avoid timing leaks
41
+ if (actual.length !== expected.length) return false;
42
+ let diff = 0;
43
+ for (let i = 0; i < actual.length; i++) diff |= actual.charCodeAt(i) ^ expected.charCodeAt(i);
44
+ return diff === 0;
45
+ }
46
+
26
47
  @Channel({
27
48
  name: 'whatsapp',
28
49
  description: 'WhatsApp chat bridge - verified users can message Claude Code',
@@ -32,7 +53,27 @@ const ALLOWED_SENDERS = new Set((process.env['WHATSAPP_ALLOWED_SENDERS'] ?? '').
32
53
  })
33
54
  export class WhatsAppChannel extends ChannelContext {
34
55
  async onEvent(payload: unknown): Promise<ChannelNotification> {
35
- const { body } = payload as { body: Record<string, unknown> };
56
+ const { body, headers } = payload as {
57
+ body: Record<string, unknown>;
58
+ headers: Record<string, string | string[] | undefined>;
59
+ };
60
+
61
+ // 1. Verify webhook signature before trusting anything in the payload.
62
+ //
63
+ // NOTE: Meta's X-Hub-Signature-256 is computed over the EXACT raw HTTP body bytes.
64
+ // FrontMCP's `WebhookPayload.body` is the parsed JSON, so re-serializing here is
65
+ // approximate — re-stringification can mismatch when Meta's payload contains
66
+ // characters Meta encoded as `\uXXXX` escapes. For production, capture the raw
67
+ // body with a transport-level middleware (e.g. an Express `verify` hook on
68
+ // `express.json()`) and pass that string into `verifyWhatsAppSignature` instead.
69
+ const sigHeader = headers['x-hub-signature-256'];
70
+ const sig = Array.isArray(sigHeader) ? sigHeader[0] : sigHeader;
71
+ const appSecret = process.env['WHATSAPP_APP_SECRET'];
72
+ if (!appSecret || !verifyWhatsAppSignature(JSON.stringify(body), sig, appSecret)) {
73
+ this.logger.warn('WhatsApp: signature mismatch, dropping payload');
74
+ return { content: '', meta: { verified: 'false', reason: 'bad_signature' } };
75
+ }
76
+
36
77
  const entry = (body as any).entry?.[0];
37
78
  const change = entry?.changes?.[0];
38
79
  const message = change?.value?.messages?.[0];
@@ -96,7 +137,8 @@ export class WhatsAppChannel extends ChannelContext {
96
137
 
97
138
  ```typescript
98
139
  // src/main.ts
99
- import { FrontMcp, App } from '@frontmcp/sdk';
140
+ import { App, FrontMcp } from '@frontmcp/sdk';
141
+
100
142
  import { WhatsAppChannel } from './apps/messaging/channels/whatsapp.channel';
101
143
 
102
144
  @App({
@@ -116,7 +158,7 @@ export default class Server {}
116
158
  ## Setup
117
159
 
118
160
  1. Create a WhatsApp Business App at [developers.facebook.com](https://developers.facebook.com)
119
- 2. Set environment variables: `WHATSAPP_TOKEN`, `WHATSAPP_PHONE_ID`, `WHATSAPP_ALLOWED_SENDERS`
161
+ 2. Set environment variables: `WHATSAPP_TOKEN`, `WHATSAPP_PHONE_ID`, `WHATSAPP_APP_SECRET`, `WHATSAPP_ALLOWED_SENDERS`
120
162
  3. Configure webhook URL to `https://your-server/hooks/whatsapp`
121
163
  4. Subscribe to `messages` webhook field
122
164
 
@@ -12,6 +12,14 @@ Every channel has a source that determines how events flow into it. FrontMCP sup
12
12
  Registers an HTTP POST endpoint. External services (GitHub, CI/CD, monitoring) send payloads to this endpoint.
13
13
 
14
14
  ```typescript
15
+ // Webhook payloads have a known shape: { body, headers, method, query? }.
16
+ // Define the slice you need locally rather than importing an internal type.
17
+ interface CIWebhookBody {
18
+ pipeline: string;
19
+ status: string;
20
+ url: string;
21
+ }
22
+
15
23
  @Channel({
16
24
  name: 'ci-alerts',
17
25
  description: 'CI/CD pipeline notifications',
@@ -19,8 +27,8 @@ Registers an HTTP POST endpoint. External services (GitHub, CI/CD, monitoring) s
19
27
  })
20
28
  class CIAlertChannel extends ChannelContext {
21
29
  async onEvent(payload: unknown): Promise<ChannelNotification> {
22
- const { body } = payload as WebhookPayload;
23
- const data = body as { pipeline: string; status: string; url: string };
30
+ const { body } = payload as { body: unknown };
31
+ const data = body as CIWebhookBody;
24
32
  return {
25
33
  content: `CI pipeline "${data.pipeline}" ${data.status}.\nDetails: ${data.url}`,
26
34
  meta: { pipeline: data.pipeline, status: data.status },
@@ -209,6 +217,6 @@ Buffered events are replayed when a new session connects, with `replayed: "true"
209
217
  | [`job-completion`](../examples/channel-sources/job-completion.md) | Intermediate | Notify Claude Code when background jobs and workflows complete |
210
218
  | [`service-connector`](../examples/channel-sources/service-connector.md) | Advanced | Build a persistent service connector that lets Claude send and receive messages through WhatsApp, Telegram, or any messaging API |
211
219
  | [`file-watcher`](../examples/channel-sources/file-watcher.md) | Intermediate | Watch files for changes and notify Claude Code in real-time |
212
- | [`replay-buffer`](../examples/channel-sources/replay-buffer.md) | Advanced | Buffer channel events so Claude Code receives them when it connects, even if events occurred while offline |
220
+ | [`replay-buffer`](../examples/channel-sources/replay-buffer.md) | Advanced | Buffer channel events so Claude Code receives them when it connects, even if events occurred while offline. |
213
221
 
214
222
  > See all examples in [`examples/channel-sources/`](../examples/channel-sources/)
@@ -15,129 +15,63 @@ Two-way channels let external users communicate with Claude Code through messagi
15
15
  4. **Reply**: Claude calls `channel-reply` tool with response text
16
16
  5. **Forward**: `onReply()` sends the reply back to the external platform
17
17
 
18
- ## WhatsApp Business API Bridge
18
+ ## API Surface
19
19
 
20
- ```typescript
21
- import { Channel, ChannelContext, ChannelNotification } from '@frontmcp/sdk';
22
-
23
- // Store verified senders for security
24
- const allowedSenders = new Set<string>();
20
+ A two-way channel sets `twoWay: true` and implements two hooks on top of the regular `ChannelContext` interface:
25
21
 
22
+ ```typescript
26
23
  @Channel({
27
24
  name: 'whatsapp',
28
- description: 'WhatsApp Business API bridge for Claude Code',
29
25
  source: { type: 'webhook', path: '/hooks/whatsapp' },
30
26
  twoWay: true,
31
27
  meta: { platform: 'whatsapp' },
32
28
  })
33
29
  export class WhatsAppChannel extends ChannelContext {
30
+ // Inbound: shape the external payload into a notification for Claude
34
31
  async onEvent(payload: unknown): Promise<ChannelNotification> {
35
- const { body } = payload as { body: Record<string, unknown> };
36
-
37
- // WhatsApp Cloud API webhook payload structure
38
- const entry = (body as any).entry?.[0];
39
- const change = entry?.changes?.[0];
40
- const message = change?.value?.messages?.[0];
41
- const contact = change?.value?.contacts?.[0];
42
-
43
- if (!message || !contact) {
44
- return { content: 'WhatsApp: received non-message webhook (status update)' };
45
- }
46
-
47
- const sender = contact.wa_id;
48
- const senderName = contact.profile?.name ?? sender;
49
-
50
- // Security: only allow verified senders
51
- if (!allowedSenders.has(sender)) {
52
- this.logger.warn(`WhatsApp: rejecting message from unverified sender ${sender}`);
53
- return {
54
- content: `WhatsApp: message from unverified sender ${senderName} (${sender}). Add to allowlist to process.`,
55
- meta: { sender, sender_name: senderName, verified: 'false' },
56
- };
57
- }
58
-
59
- const text = message.text?.body ?? '[non-text message]';
60
-
61
- return {
62
- content: `${senderName}: ${text}`,
63
- meta: {
64
- sender,
65
- sender_name: senderName,
66
- message_id: message.id,
67
- chat_id: sender,
68
- },
69
- };
32
+ /* see whatsapp-bridge example */
70
33
  }
71
34
 
35
+ // Outbound: forward Claude's reply back to the platform.
36
+ // `meta` carries the keys you returned from onEvent (chat_id, sender, ...).
72
37
  async onReply(reply: string, meta?: Record<string, string>): Promise<void> {
73
- const chatId = meta?.chat_id;
74
- if (!chatId) {
75
- this.logger.warn('WhatsApp reply: no chat_id in meta');
76
- return;
77
- }
78
-
79
- // Send via WhatsApp Cloud API
80
- const token = process.env['WHATSAPP_TOKEN'];
81
- const phoneNumberId = process.env['WHATSAPP_PHONE_ID'];
82
-
83
- await fetch(`https://graph.facebook.com/v18.0/${phoneNumberId}/messages`, {
84
- method: 'POST',
85
- headers: {
86
- Authorization: `Bearer ${token}`,
87
- 'Content-Type': 'application/json',
88
- },
89
- body: JSON.stringify({
90
- messaging_product: 'whatsapp',
91
- to: chatId,
92
- text: { body: reply },
93
- }),
94
- });
38
+ /* call platform send-message API */
95
39
  }
96
40
  }
97
41
  ```
98
42
 
43
+ The full WhatsApp implementation, including sender allowlisting and the WhatsApp Cloud API call, lives in [`examples/channel-two-way/whatsapp-bridge.md`](../examples/channel-two-way/whatsapp-bridge.md).
44
+
99
45
  ## Telegram Bot Bridge
100
46
 
47
+ Telegram works the same way — different webhook path, different reply API:
48
+
101
49
  ```typescript
102
50
  @Channel({
103
51
  name: 'telegram',
104
- description: 'Telegram bot bridge for Claude Code',
105
52
  source: { type: 'webhook', path: '/hooks/telegram' },
106
53
  twoWay: true,
107
54
  meta: { platform: 'telegram' },
108
55
  })
109
56
  export class TelegramChannel extends ChannelContext {
110
57
  async onEvent(payload: unknown): Promise<ChannelNotification> {
111
- const { body } = payload as { body: Record<string, unknown> };
112
- const update = body as {
113
- message?: {
114
- chat: { id: number };
115
- from: { username?: string; first_name: string; id: number };
116
- text?: string;
58
+ const { body } = payload as {
59
+ body: {
60
+ message?: { chat: { id: number }; from: { username?: string; first_name: string; id: number }; text?: string };
117
61
  };
118
62
  };
119
-
120
- const msg = update.message;
121
- if (!msg?.text) {
122
- return { content: 'Telegram: non-text update received' };
123
- }
124
-
63
+ const msg = body.message;
64
+ if (!msg?.text) return { content: 'Telegram: non-text update' };
125
65
  const sender = msg.from.username ?? msg.from.first_name;
126
-
127
66
  return {
128
67
  content: `${sender}: ${msg.text}`,
129
- meta: {
130
- chat_id: String(msg.chat.id),
131
- sender: String(msg.from.id),
132
- sender_name: sender,
133
- },
68
+ meta: { chat_id: String(msg.chat.id), sender: String(msg.from.id), sender_name: sender },
134
69
  };
135
70
  }
136
71
 
137
72
  async onReply(reply: string, meta?: Record<string, string>): Promise<void> {
138
73
  const chatId = meta?.chat_id;
139
74
  if (!chatId) return;
140
-
141
75
  const token = process.env['TELEGRAM_BOT_TOKEN'];
142
76
  await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
143
77
  method: 'POST',
@@ -166,16 +100,53 @@ if (isGroupMember(chatId)) {
166
100
  }
167
101
  ```
168
102
 
169
- ### Webhook Verification
103
+ ### Webhook Signature Verification
170
104
 
171
- Validate webhook signatures for each platform:
105
+ Webhook handlers must verify the request actually came from the platform. Use the HMAC helper from `@frontmcp/utils` so the same code runs in Node and Edge runtimes:
172
106
 
173
107
  ```typescript
174
- // WhatsApp: verify X-Hub-Signature-256 header
175
- // Telegram: verify secret_token parameter
176
- // Slack: verify X-Slack-Signature header
108
+ import { hmacSha256 } from '@frontmcp/utils';
109
+
110
+ function verifyWhatsAppSignature(rawBody: string, header: string | undefined, appSecret: string): boolean {
111
+ if (!header?.startsWith('sha256=')) return false;
112
+ const expected = header.slice('sha256='.length);
113
+
114
+ const key = new TextEncoder().encode(appSecret);
115
+ const data = new TextEncoder().encode(rawBody);
116
+ const digest = hmacSha256(key, data);
117
+
118
+ // Constant-time hex compare
119
+ const actual = Array.from(digest, (b) => b.toString(16).padStart(2, '0')).join('');
120
+ if (actual.length !== expected.length) return false;
121
+ let diff = 0;
122
+ for (let i = 0; i < actual.length; i++) diff |= actual.charCodeAt(i) ^ expected.charCodeAt(i);
123
+ return diff === 0;
124
+ }
125
+
126
+ // In onEvent:
127
+ //
128
+ // NOTE: WebhookPayload.body is the parsed JSON. For maximum signature fidelity
129
+ // (Meta computes X-Hub-Signature-256 over the exact raw bytes), capture the raw
130
+ // body in a transport-level middleware (e.g. express.json `verify` hook) and
131
+ // pass that string to `verifyWhatsAppSignature` instead of `JSON.stringify(body)`.
132
+ const { body, headers } = payload as { body: unknown; headers: Record<string, string | string[] | undefined> };
133
+ const sig = headers['x-hub-signature-256'];
134
+ if (
135
+ !verifyWhatsAppSignature(JSON.stringify(body), Array.isArray(sig) ? sig[0] : sig, process.env['WHATSAPP_APP_SECRET']!)
136
+ ) {
137
+ this.logger.warn('WhatsApp: signature mismatch, dropping');
138
+ return { content: '', meta: { verified: 'false' } };
139
+ }
177
140
  ```
178
141
 
142
+ Per-platform header names:
143
+
144
+ | Platform | Header / mechanism |
145
+ | -------- | --------------------------------------------------------------- |
146
+ | WhatsApp | `X-Hub-Signature-256` (HMAC-SHA256 of raw body with app secret) |
147
+ | Telegram | `secret_token` query parameter (compare with the value you set) |
148
+ | Slack | `X-Slack-Signature` + `X-Slack-Request-Timestamp` (HMAC-SHA256) |
149
+
179
150
  ### Pairing Codes
180
151
 
181
152
  For bootstrapping trust, use pairing codes: