@frontmcp/skills 1.0.4 → 1.1.0-beta.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/frontmcp-authorities/SKILL.md +272 -0
- package/catalog/frontmcp-authorities/references/authority-profiles.md +262 -0
- package/catalog/frontmcp-authorities/references/claims-mapping.md +266 -0
- package/catalog/frontmcp-authorities/references/custom-evaluators.md +420 -0
- package/catalog/frontmcp-authorities/references/rbac-abac-rebac.md +391 -0
- package/catalog/frontmcp-channels/SKILL.md +122 -0
- package/catalog/frontmcp-channels/examples/channel-sources/agent-notify.md +70 -0
- package/catalog/frontmcp-channels/examples/channel-sources/app-errors.md +71 -0
- package/catalog/frontmcp-channels/examples/channel-sources/file-watcher.md +102 -0
- package/catalog/frontmcp-channels/examples/channel-sources/job-completion.md +79 -0
- package/catalog/frontmcp-channels/examples/channel-sources/replay-buffer.md +106 -0
- package/catalog/frontmcp-channels/examples/channel-sources/service-connector.md +136 -0
- package/catalog/frontmcp-channels/examples/channel-sources/webhook-github.md +85 -0
- package/catalog/frontmcp-channels/examples/channel-two-way/whatsapp-bridge.md +133 -0
- package/catalog/frontmcp-channels/references/channel-sources.md +214 -0
- package/catalog/frontmcp-channels/references/channel-two-way.md +195 -0
- package/catalog/frontmcp-config/SKILL.md +20 -18
- package/catalog/frontmcp-config/examples/configure-auth/multi-app-auth.md +1 -2
- package/catalog/frontmcp-config/examples/configure-auth/public-mode-setup.md +1 -2
- package/catalog/frontmcp-config/examples/configure-auth/remote-oauth-with-vault.md +1 -2
- package/catalog/frontmcp-config/examples/configure-auth-modes/local-self-signed-tokens.md +1 -2
- package/catalog/frontmcp-config/examples/configure-auth-modes/remote-enterprise-oauth.md +1 -2
- package/catalog/frontmcp-config/examples/configure-auth-modes/transparent-jwt-validation.md +1 -2
- package/catalog/frontmcp-config/examples/configure-deployment-targets/distributed-ha-config.md +121 -0
- package/catalog/frontmcp-config/examples/configure-deployment-targets/json-schema-ide-support.md +64 -0
- package/catalog/frontmcp-config/examples/configure-deployment-targets/multi-target-with-security.md +113 -0
- package/catalog/frontmcp-config/examples/configure-elicitation/basic-confirmation-gate.md +1 -2
- package/catalog/frontmcp-config/examples/configure-elicitation/distributed-elicitation-redis.md +1 -2
- package/catalog/frontmcp-config/examples/configure-http/entry-path-reverse-proxy.md +1 -2
- package/catalog/frontmcp-config/examples/configure-http/unix-socket-local.md +1 -2
- package/catalog/frontmcp-config/examples/configure-security-headers/csp-report-only.md +69 -0
- package/catalog/frontmcp-config/examples/configure-security-headers/full-production-headers.md +91 -0
- package/catalog/frontmcp-config/examples/configure-throttle/distributed-redis-throttle.md +1 -2
- package/catalog/frontmcp-config/examples/configure-throttle/per-tool-rate-limit.md +1 -2
- package/catalog/frontmcp-config/examples/configure-throttle/server-level-rate-limit.md +1 -2
- package/catalog/frontmcp-config/examples/configure-transport/custom-protocol-flags.md +1 -2
- package/catalog/frontmcp-config/examples/configure-transport/distributed-sessions-redis.md +1 -2
- package/catalog/frontmcp-config/examples/configure-transport/stateless-serverless.md +1 -2
- package/catalog/frontmcp-config/examples/configure-transport-protocol-presets/legacy-preset-nodejs.md +1 -2
- package/catalog/frontmcp-config/examples/configure-transport-protocol-presets/stateless-api-serverless.md +1 -2
- package/catalog/frontmcp-config/references/configure-deployment-targets.md +214 -0
- package/catalog/frontmcp-config/references/configure-elicitation.md +1 -2
- package/catalog/frontmcp-config/references/configure-security-headers.md +198 -0
- package/catalog/frontmcp-deployment/SKILL.md +1 -0
- package/catalog/frontmcp-deployment/examples/build-for-cli/cli-binary-build.md +1 -2
- package/catalog/frontmcp-deployment/examples/build-for-cli/unix-socket-daemon.md +1 -2
- package/catalog/frontmcp-deployment/examples/build-for-mcpb/mcpb-bundle-build.md +117 -0
- package/catalog/frontmcp-deployment/examples/build-for-sdk/connect-openai.md +1 -3
- package/catalog/frontmcp-deployment/examples/build-for-sdk/create-flat-config.md +1 -2
- package/catalog/frontmcp-deployment/examples/build-for-sdk/multi-platform-connect.md +3 -3
- package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/basic-worker-deploy.md +1 -2
- package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/worker-custom-domain.md +1 -2
- package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/worker-with-kv-storage.md +1 -2
- package/catalog/frontmcp-deployment/examples/deploy-to-lambda/lambda-handler-with-cors.md +1 -2
- package/catalog/frontmcp-deployment/examples/deploy-to-vercel/vercel-with-kv.md +1 -2
- package/catalog/frontmcp-deployment/examples/mcp-client-integration/http-remote.md +106 -0
- package/catalog/frontmcp-deployment/examples/mcp-client-integration/stdio-binary-with-env.md +107 -0
- package/catalog/frontmcp-deployment/examples/mcp-client-integration/stdio-npx.md +89 -0
- package/catalog/frontmcp-deployment/references/build-for-mcpb.md +209 -0
- package/catalog/frontmcp-deployment/references/build-for-sdk.md +1 -2
- package/catalog/frontmcp-deployment/references/mcp-client-integration.md +225 -0
- package/catalog/frontmcp-development/examples/create-agent/basic-agent-with-tools.md +3 -6
- package/catalog/frontmcp-development/examples/create-agent/custom-multi-pass-agent.md +1 -2
- package/catalog/frontmcp-development/examples/create-agent/nested-agents-with-swarm.md +2 -4
- package/catalog/frontmcp-development/examples/create-agent-llm-config/anthropic-config.md +1 -2
- package/catalog/frontmcp-development/examples/create-agent-llm-config/openai-config.md +1 -2
- package/catalog/frontmcp-development/examples/create-job/basic-report-job.md +1 -2
- package/catalog/frontmcp-development/examples/create-job/job-with-permissions.md +2 -3
- package/catalog/frontmcp-development/examples/create-job/job-with-retry.md +1 -2
- package/catalog/frontmcp-development/examples/create-plugin-hooks/tool-level-hooks-and-stage-replacement.md +2 -5
- package/catalog/frontmcp-development/examples/create-provider/basic-database-provider.md +4 -3
- package/catalog/frontmcp-development/examples/create-skill-with-tools/directory-skill-with-tools.md +2 -3
- package/catalog/frontmcp-development/examples/create-tool/basic-class-tool.md +1 -2
- package/catalog/frontmcp-development/examples/create-tool/tool-with-di-and-errors.md +2 -2
- package/catalog/frontmcp-development/examples/create-tool/tool-with-rate-limiting-and-progress.md +1 -2
- package/catalog/frontmcp-development/examples/create-tool-annotations/destructive-delete-tool.md +2 -4
- package/catalog/frontmcp-development/examples/create-tool-annotations/readonly-query-tool.md +1 -2
- package/catalog/frontmcp-development/examples/create-tool-output-schema-types/primitive-and-media-outputs.md +3 -6
- package/catalog/frontmcp-development/examples/create-tool-output-schema-types/zod-raw-shape-output.md +1 -2
- package/catalog/frontmcp-development/examples/create-tool-output-schema-types/zod-schema-advanced-output.md +2 -4
- package/catalog/frontmcp-development/examples/decorators-guide/agent-skill-job-workflow.md +3 -5
- package/catalog/frontmcp-development/examples/decorators-guide/basic-server-with-app-and-tools.md +5 -5
- package/catalog/frontmcp-development/examples/decorators-guide/multi-app-with-plugins-and-providers.md +4 -6
- package/catalog/frontmcp-development/examples/official-plugins/cache-and-feature-flags.md +3 -5
- package/catalog/frontmcp-development/examples/official-plugins/production-multi-plugin-setup.md +4 -5
- package/catalog/frontmcp-development/examples/official-plugins/remember-plugin-session-memory.md +3 -5
- package/catalog/frontmcp-development/references/create-agent.md +4 -7
- package/catalog/frontmcp-development/references/create-job.md +3 -6
- package/catalog/frontmcp-development/references/create-plugin-hooks.md +12 -16
- package/catalog/frontmcp-development/references/create-skill-with-tools.md +2 -3
- package/catalog/frontmcp-development/references/create-tool.md +93 -23
- package/catalog/frontmcp-development/references/create-workflow.md +2 -3
- package/catalog/frontmcp-development/references/decorators-guide.md +32 -36
- package/catalog/frontmcp-extensibility/examples/vectoriadb/product-catalog-search.md +4 -4
- package/catalog/frontmcp-extensibility/examples/vectoriadb/semantic-search-with-persistence.md +4 -4
- package/catalog/frontmcp-extensibility/examples/vectoriadb/tfidf-keyword-search.md +4 -3
- package/catalog/frontmcp-guides/SKILL.md +3 -3
- package/catalog/frontmcp-guides/examples/example-knowledge-base/agent-and-plugin.md +4 -5
- package/catalog/frontmcp-guides/examples/example-knowledge-base/vector-search-and-resources.md +4 -3
- package/catalog/frontmcp-guides/examples/example-task-manager/auth-and-crud-tools.md +4 -4
- package/catalog/frontmcp-guides/examples/example-weather-api/weather-tool-with-schemas.md +1 -2
- package/catalog/frontmcp-guides/references/example-knowledge-base.md +22 -17
- package/catalog/frontmcp-guides/references/example-task-manager.md +16 -11
- package/catalog/frontmcp-guides/references/example-weather-api.md +6 -3
- package/catalog/frontmcp-observability/examples/telemetry-api/tool-custom-spans.md +2 -3
- package/catalog/frontmcp-observability/examples/tracing-setup/basic-tracing.md +4 -3
- package/catalog/frontmcp-observability/references/telemetry-api.md +2 -3
- package/catalog/frontmcp-production-readiness/examples/common-checklist/observability-setup.md +1 -2
- package/catalog/frontmcp-production-readiness/examples/common-checklist/security-hardening.md +3 -4
- package/catalog/frontmcp-production-readiness/examples/distributed-ha/ha-kubernetes-3-replicas.md +229 -0
- package/catalog/frontmcp-production-readiness/examples/production-browser/cross-platform-crypto.md +2 -3
- package/catalog/frontmcp-production-readiness/examples/production-cli-binary/stdio-transport-error-handling.md +1 -2
- package/catalog/frontmcp-production-readiness/examples/production-cloudflare/durable-objects-state.md +2 -4
- package/catalog/frontmcp-production-readiness/examples/production-cloudflare/workers-runtime-constraints.md +2 -3
- package/catalog/frontmcp-production-readiness/examples/production-lambda/cold-start-connection-reuse.md +3 -2
- package/catalog/frontmcp-production-readiness/examples/production-vercel/cold-start-optimization.md +2 -2
- package/catalog/frontmcp-production-readiness/examples/production-vercel/stateless-serverless-design.md +3 -3
- package/catalog/frontmcp-production-readiness/references/distributed-ha.md +194 -0
- package/catalog/frontmcp-setup/SKILL.md +11 -11
- package/catalog/frontmcp-setup/examples/project-structure-standalone/feature-folder-organization.md +5 -3
- package/catalog/frontmcp-setup/examples/project-structure-standalone/minimal-standalone-layout.md +4 -2
- package/catalog/frontmcp-setup/examples/setup-project/basic-node-server.md +4 -2
- package/catalog/frontmcp-setup/examples/setup-project/vercel-serverless-server.md +4 -2
- package/catalog/frontmcp-setup/examples/setup-redis/hybrid-vercel-kv-with-pubsub.md +8 -7
- package/catalog/frontmcp-setup/references/setup-project.md +10 -9
- package/catalog/frontmcp-setup/references/setup-redis.md +19 -16
- package/catalog/frontmcp-testing/examples/test-direct-client/basic-create-test.md +1 -3
- package/catalog/frontmcp-testing/examples/test-direct-client/openai-claude-format-test.md +1 -3
- package/catalog/frontmcp-testing/examples/test-tool-unit/schema-validation-test.md +2 -2
- package/catalog/frontmcp-testing/references/test-direct-client.md +1 -3
- package/catalog/frontmcp-testing/references/test-tool-unit.md +2 -2
- package/catalog/skills-manifest.json +325 -3
- package/package.json +1 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: file-watcher
|
|
3
|
+
reference: channel-sources
|
|
4
|
+
level: intermediate
|
|
5
|
+
description: Watch files for changes and notify Claude Code in real-time
|
|
6
|
+
tags: [file-watcher, filesystem, logs, monitoring, real-time]
|
|
7
|
+
features:
|
|
8
|
+
- File watcher source type with glob patterns
|
|
9
|
+
- onConnect lifecycle for starting the watcher
|
|
10
|
+
- pushIncoming for streaming file events
|
|
11
|
+
- Log file monitoring example
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# File Watcher Channel
|
|
15
|
+
|
|
16
|
+
Watch files for changes and notify Claude Code in real-time
|
|
17
|
+
|
|
18
|
+
## Code
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// src/apps/monitoring/channels/log-watcher.channel.ts
|
|
22
|
+
import { Channel, ChannelContext } from '@frontmcp/sdk';
|
|
23
|
+
import type { ChannelNotification } from '@frontmcp/sdk';
|
|
24
|
+
import { watch } from 'node:fs';
|
|
25
|
+
import { readFile } from '@frontmcp/utils';
|
|
26
|
+
|
|
27
|
+
@Channel({
|
|
28
|
+
name: 'log-watcher',
|
|
29
|
+
description: 'Watches application logs and notifies on new errors',
|
|
30
|
+
source: {
|
|
31
|
+
type: 'file-watcher',
|
|
32
|
+
paths: ['./logs/app.log', './logs/error.log'],
|
|
33
|
+
events: ['change'],
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
export class LogWatcherChannel extends ChannelContext {
|
|
37
|
+
private watchers: ReturnType<typeof watch>[] = [];
|
|
38
|
+
private lastSize = new Map<string, number>();
|
|
39
|
+
|
|
40
|
+
async onConnect(): Promise<void> {
|
|
41
|
+
const paths = (this.metadata.source as { paths: string[] }).paths;
|
|
42
|
+
|
|
43
|
+
for (const filePath of paths) {
|
|
44
|
+
try {
|
|
45
|
+
const watcher = watch(filePath, async (eventType) => {
|
|
46
|
+
if (eventType === 'change') {
|
|
47
|
+
const newContent = await this.readNewLines(filePath);
|
|
48
|
+
if (newContent) {
|
|
49
|
+
this.pushIncoming({ file: filePath, content: newContent });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
this.watchers.push(watcher);
|
|
54
|
+
this.logger.info(`Watching: ${filePath}`);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
this.logger.warn(`Cannot watch ${filePath}: ${err}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async onDisconnect(): Promise<void> {
|
|
62
|
+
for (const watcher of this.watchers) {
|
|
63
|
+
watcher.close();
|
|
64
|
+
}
|
|
65
|
+
this.watchers = [];
|
|
66
|
+
this.lastSize.clear();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async onEvent(payload: unknown): Promise<ChannelNotification> {
|
|
70
|
+
const event = payload as { file: string; content: string };
|
|
71
|
+
const fileName = event.file.split('/').pop() ?? event.file;
|
|
72
|
+
return {
|
|
73
|
+
content: `[${fileName}] ${event.content}`,
|
|
74
|
+
meta: { file: fileName },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private async readNewLines(filePath: string): Promise<string | null> {
|
|
79
|
+
try {
|
|
80
|
+
const content = await readFile(filePath);
|
|
81
|
+
const prevSize = this.lastSize.get(filePath) ?? 0;
|
|
82
|
+
if (content.length <= prevSize) return null;
|
|
83
|
+
const newContent = content.slice(prevSize).trim();
|
|
84
|
+
this.lastSize.set(filePath, content.length);
|
|
85
|
+
return newContent || null;
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## What This Demonstrates
|
|
94
|
+
|
|
95
|
+
- File watcher source type with glob patterns
|
|
96
|
+
- onConnect lifecycle for starting the watcher
|
|
97
|
+
- pushIncoming for streaming file events
|
|
98
|
+
- Log file monitoring example
|
|
99
|
+
|
|
100
|
+
## Related
|
|
101
|
+
|
|
102
|
+
- See `channel-sources` for all source type documentation
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: job-completion
|
|
3
|
+
reference: channel-sources
|
|
4
|
+
level: intermediate
|
|
5
|
+
description: Notify Claude Code when background jobs and workflows complete
|
|
6
|
+
tags: [jobs, workflows, completion, background, notifications]
|
|
7
|
+
features:
|
|
8
|
+
- Job completion source with name filtering
|
|
9
|
+
- Status-aware notification formatting
|
|
10
|
+
- Duration and output reporting
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Job Completion Channel
|
|
14
|
+
|
|
15
|
+
Notify Claude Code when background jobs and workflows complete
|
|
16
|
+
|
|
17
|
+
## Code
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// src/apps/automation/channels/job-done.channel.ts
|
|
21
|
+
import { Channel, ChannelContext } from '@frontmcp/sdk';
|
|
22
|
+
import type { ChannelNotification } from '@frontmcp/sdk';
|
|
23
|
+
|
|
24
|
+
@Channel({
|
|
25
|
+
name: 'job-alerts',
|
|
26
|
+
description: 'Background job and workflow completion alerts',
|
|
27
|
+
source: {
|
|
28
|
+
type: 'job-completion',
|
|
29
|
+
jobNames: ['daily-report', 'data-sync', 'backup'],
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
export class JobAlertsChannel extends ChannelContext {
|
|
33
|
+
async onEvent(payload: unknown): Promise<ChannelNotification> {
|
|
34
|
+
const event = payload as {
|
|
35
|
+
jobName: string;
|
|
36
|
+
jobId: string;
|
|
37
|
+
status: 'success' | 'error' | 'timeout' | 'cancelled';
|
|
38
|
+
durationMs?: number;
|
|
39
|
+
output?: string;
|
|
40
|
+
error?: string;
|
|
41
|
+
attempt?: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const duration = event.durationMs ? ` (${(event.durationMs / 1000).toFixed(1)}s)` : '';
|
|
45
|
+
const attempt = event.attempt && event.attempt > 1 ? ` [attempt ${event.attempt}]` : '';
|
|
46
|
+
|
|
47
|
+
if (event.status === 'error') {
|
|
48
|
+
return {
|
|
49
|
+
content: `Job "${event.jobName}" FAILED${duration}${attempt}\nError: ${event.error ?? 'Unknown'}`,
|
|
50
|
+
meta: { job: event.jobName, status: 'error', severity: 'high' },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (event.status === 'timeout') {
|
|
55
|
+
return {
|
|
56
|
+
content: `Job "${event.jobName}" TIMED OUT${duration}${attempt}`,
|
|
57
|
+
meta: { job: event.jobName, status: 'timeout', severity: 'high' },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const output = event.output ? `\nResult: ${event.output.slice(0, 300)}` : '';
|
|
62
|
+
return {
|
|
63
|
+
content: `Job "${event.jobName}" completed${duration}${attempt}${output}`,
|
|
64
|
+
meta: { job: event.jobName, status: event.status },
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## What This Demonstrates
|
|
71
|
+
|
|
72
|
+
- Job completion source with name filtering
|
|
73
|
+
- Status-aware notification formatting
|
|
74
|
+
- Duration and output reporting
|
|
75
|
+
|
|
76
|
+
## Related
|
|
77
|
+
|
|
78
|
+
- See `channel-sources` for all source type documentation
|
|
79
|
+
- See `frontmcp-development` for job creation patterns
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: replay-buffer
|
|
3
|
+
reference: channel-sources
|
|
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]
|
|
7
|
+
features:
|
|
8
|
+
- Replay configuration with maxEvents cap
|
|
9
|
+
- In-memory ring buffer for event storage
|
|
10
|
+
- Replay on session connect
|
|
11
|
+
- Persistent store pattern with onConnect
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Replay Buffer Pattern
|
|
15
|
+
|
|
16
|
+
Buffer channel events so Claude Code receives them when it connects, even if events occurred while offline
|
|
17
|
+
|
|
18
|
+
## Basic Replay (In-Memory)
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
@Channel({
|
|
22
|
+
name: 'ci-alerts',
|
|
23
|
+
description: 'CI/CD alerts with replay for offline sessions',
|
|
24
|
+
source: { type: 'webhook', path: '/hooks/ci' },
|
|
25
|
+
replay: {
|
|
26
|
+
enabled: true,
|
|
27
|
+
maxEvents: 100, // Ring buffer, oldest events evicted
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
class CIAlertChannel extends ChannelContext {
|
|
31
|
+
async onEvent(payload: unknown): Promise<ChannelNotification> {
|
|
32
|
+
const { body } = payload as { body: { pipeline: string; status: string } };
|
|
33
|
+
return {
|
|
34
|
+
content: `CI: ${body.pipeline} ${body.status}`,
|
|
35
|
+
meta: { pipeline: body.pipeline },
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
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.
|
|
42
|
+
|
|
43
|
+
## Persistent Store Pattern (Redis/DB)
|
|
44
|
+
|
|
45
|
+
For events that survive server restarts, load from an external store in `onConnect()`:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
@Channel({
|
|
49
|
+
name: 'alerts',
|
|
50
|
+
description: 'Persistent alerts with Redis-backed replay',
|
|
51
|
+
source: { type: 'service', service: 'alert-store' },
|
|
52
|
+
replay: { enabled: true, maxEvents: 500 },
|
|
53
|
+
})
|
|
54
|
+
class PersistentAlertChannel extends ChannelContext {
|
|
55
|
+
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);
|
|
59
|
+
|
|
60
|
+
for (const raw of stored) {
|
|
61
|
+
const event = JSON.parse(raw);
|
|
62
|
+
this.pushIncoming(event);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Subscribe to new events via Redis pub/sub
|
|
66
|
+
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);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
this.logger.info('Alert store connected, loaded history');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async onEvent(payload: unknown): Promise<ChannelNotification> {
|
|
80
|
+
const alert = payload as { title: string; severity: string; timestamp: string };
|
|
81
|
+
return {
|
|
82
|
+
content: `[${alert.severity}] ${alert.title}`,
|
|
83
|
+
meta: { severity: alert.severity, timestamp: alert.timestamp },
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## How Replay Works
|
|
90
|
+
|
|
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
|
|
96
|
+
|
|
97
|
+
## What This Demonstrates
|
|
98
|
+
|
|
99
|
+
- Replay configuration with maxEvents cap
|
|
100
|
+
- In-memory ring buffer for event storage
|
|
101
|
+
- Replay on session connect
|
|
102
|
+
- Persistent store pattern with onConnect
|
|
103
|
+
|
|
104
|
+
## Related
|
|
105
|
+
|
|
106
|
+
- See `channel-sources` for all source type documentation
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: service-connector
|
|
3
|
+
reference: channel-sources
|
|
4
|
+
level: advanced
|
|
5
|
+
description: Build a persistent service connector that lets Claude send and receive messages through WhatsApp, Telegram, or any messaging API
|
|
6
|
+
tags: [service, connector, whatsapp, persistent-connection, bidirectional]
|
|
7
|
+
features:
|
|
8
|
+
- Service source type with onConnect/onDisconnect lifecycle
|
|
9
|
+
- Channel-contributed tools for outbound messages
|
|
10
|
+
- pushIncoming() for feeding service events into the notification pipeline
|
|
11
|
+
- Bidirectional conversation flow
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Service Connector Channel
|
|
15
|
+
|
|
16
|
+
Build a persistent service connector that lets Claude send and receive messages through WhatsApp, Telegram, or any messaging API
|
|
17
|
+
|
|
18
|
+
## Code
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// src/apps/messaging/tools/send-whatsapp.tool.ts
|
|
22
|
+
import { Tool, ToolContext, z } from '@frontmcp/sdk';
|
|
23
|
+
|
|
24
|
+
@Tool({
|
|
25
|
+
name: 'send-whatsapp',
|
|
26
|
+
description: 'Send a WhatsApp message. Replies arrive as channel notifications.',
|
|
27
|
+
inputSchema: {
|
|
28
|
+
to: z.string().describe('Recipient phone number'),
|
|
29
|
+
text: z.string().describe('Message text'),
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
export class SendWhatsAppTool extends ToolContext {
|
|
33
|
+
async execute(input: { to: string; text: string }) {
|
|
34
|
+
const token = process.env['WHATSAPP_TOKEN']!;
|
|
35
|
+
const phoneId = process.env['WHATSAPP_PHONE_ID']!;
|
|
36
|
+
|
|
37
|
+
await fetch(`https://graph.facebook.com/v18.0/${phoneId}/messages`, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
40
|
+
body: JSON.stringify({
|
|
41
|
+
messaging_product: 'whatsapp',
|
|
42
|
+
to: input.to,
|
|
43
|
+
text: { body: input.text },
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return { sent: true, to: input.to };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// src/apps/messaging/channels/whatsapp.channel.ts
|
|
54
|
+
import { Channel, ChannelContext, type ChannelNotification } from '@frontmcp/sdk';
|
|
55
|
+
|
|
56
|
+
import { SendWhatsAppTool } from '../tools/send-whatsapp.tool';
|
|
57
|
+
|
|
58
|
+
const ALLOWED = new Set(process.env['WA_ALLOWED']?.split(',') ?? []);
|
|
59
|
+
|
|
60
|
+
@Channel({
|
|
61
|
+
name: 'whatsapp',
|
|
62
|
+
description: 'WhatsApp messaging. Send via send-whatsapp tool, replies arrive here.',
|
|
63
|
+
source: { type: 'service', service: 'whatsapp-business' },
|
|
64
|
+
tools: [SendWhatsAppTool], // Auto-registered — Claude calls this to send
|
|
65
|
+
twoWay: true,
|
|
66
|
+
meta: { platform: 'whatsapp' },
|
|
67
|
+
})
|
|
68
|
+
export class WhatsAppChannel extends ChannelContext {
|
|
69
|
+
private pollingInterval?: ReturnType<typeof setInterval>;
|
|
70
|
+
|
|
71
|
+
async onConnect(): Promise<void> {
|
|
72
|
+
// In production: use WhatsApp webhook or long-polling
|
|
73
|
+
// Here we simulate with a polling loop checking an API
|
|
74
|
+
this.logger.info('WhatsApp service: connecting...');
|
|
75
|
+
|
|
76
|
+
this.pollingInterval = setInterval(async () => {
|
|
77
|
+
try {
|
|
78
|
+
const messages = await this.fetchNewMessages();
|
|
79
|
+
for (const msg of messages) {
|
|
80
|
+
if (!ALLOWED.has(msg.from)) continue;
|
|
81
|
+
this.pushIncoming(msg); // Feeds into onEvent() → notification pipeline
|
|
82
|
+
}
|
|
83
|
+
} catch (err) {
|
|
84
|
+
this.logger.error('WhatsApp polling error', { error: err });
|
|
85
|
+
}
|
|
86
|
+
}, 5000);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async onDisconnect(): Promise<void> {
|
|
90
|
+
if (this.pollingInterval) {
|
|
91
|
+
clearInterval(this.pollingInterval);
|
|
92
|
+
}
|
|
93
|
+
this.logger.info('WhatsApp service: disconnected');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async onEvent(payload: unknown): Promise<ChannelNotification> {
|
|
97
|
+
const msg = payload as { from: string; text: string; chatId: string };
|
|
98
|
+
return {
|
|
99
|
+
content: `${msg.from}: ${msg.text}`,
|
|
100
|
+
meta: { chat_id: msg.chatId, sender: msg.from },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private async fetchNewMessages(): Promise<Array<{ from: string; text: string; chatId: string }>> {
|
|
105
|
+
// Call WhatsApp Business API or webhook queue
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Conversation Flow
|
|
112
|
+
|
|
113
|
+
1. Claude calls `send-whatsapp({ to: "+1234567890", text: "Hi Alice!" })`
|
|
114
|
+
2. Message delivered to Alice's WhatsApp
|
|
115
|
+
3. Alice replies → `onConnect()` polling picks it up → `pushIncoming()`
|
|
116
|
+
4. `onEvent()` transforms into `ChannelNotification`
|
|
117
|
+
5. Claude sees: `<channel source="whatsapp" sender="Alice">Alice: Got it!</channel>`
|
|
118
|
+
|
|
119
|
+
## When to Use Service vs Webhook
|
|
120
|
+
|
|
121
|
+
| Pattern | Use When |
|
|
122
|
+
| --------------------- | ---------------------------------------------------------------- |
|
|
123
|
+
| **Service connector** | You need persistent connections, polling, or WebSocket listeners |
|
|
124
|
+
| **Webhook** | The external service pushes events to your HTTP endpoint |
|
|
125
|
+
|
|
126
|
+
## What This Demonstrates
|
|
127
|
+
|
|
128
|
+
- Service source type with onConnect/onDisconnect lifecycle
|
|
129
|
+
- Channel-contributed tools for outbound messages
|
|
130
|
+
- pushIncoming() for feeding service events into the notification pipeline
|
|
131
|
+
- Bidirectional conversation flow
|
|
132
|
+
|
|
133
|
+
## Related
|
|
134
|
+
|
|
135
|
+
- See `channel-sources` for all source type documentation
|
|
136
|
+
- See `channel-two-way` for two-way communication patterns
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: webhook-github
|
|
3
|
+
reference: channel-sources
|
|
4
|
+
level: basic
|
|
5
|
+
description: Forward GitHub webhook events (PRs, pushes, CI) into Claude Code
|
|
6
|
+
tags: [webhook, github, ci, notifications]
|
|
7
|
+
features:
|
|
8
|
+
- Webhook source with HTTP POST endpoint
|
|
9
|
+
- GitHub event type routing
|
|
10
|
+
- Static meta for team context
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# GitHub Webhook Channel
|
|
14
|
+
|
|
15
|
+
Forward GitHub webhook events (PRs, pushes, CI) into Claude Code
|
|
16
|
+
|
|
17
|
+
## Code
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// src/apps/devops/channels/github-webhook.channel.ts
|
|
21
|
+
import { Channel, ChannelContext, ChannelNotification } from '@frontmcp/sdk';
|
|
22
|
+
|
|
23
|
+
interface GitHubWebhookPayload {
|
|
24
|
+
body: Record<string, unknown>;
|
|
25
|
+
headers: Record<string, string | string[] | undefined>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@Channel({
|
|
29
|
+
name: 'github',
|
|
30
|
+
description: 'GitHub repository events (PRs, pushes, CI status)',
|
|
31
|
+
source: { type: 'webhook', path: '/hooks/github' },
|
|
32
|
+
meta: { platform: 'github' },
|
|
33
|
+
})
|
|
34
|
+
export class GitHubWebhookChannel extends ChannelContext {
|
|
35
|
+
async onEvent(payload: unknown): Promise<ChannelNotification> {
|
|
36
|
+
const { body, headers } = payload as GitHubWebhookPayload;
|
|
37
|
+
const eventType = headers['x-github-event'] as string;
|
|
38
|
+
|
|
39
|
+
switch (eventType) {
|
|
40
|
+
case 'push': {
|
|
41
|
+
const push = body as { ref: string; commits: Array<{ message: string; author: { name: string } }> };
|
|
42
|
+
const branch = push.ref.replace('refs/heads/', '');
|
|
43
|
+
const commits = push.commits.map((c) => ` - ${c.author.name}: ${c.message}`).join('\n');
|
|
44
|
+
return {
|
|
45
|
+
content: `Push to ${branch}:\n${commits}`,
|
|
46
|
+
meta: { event: 'push', branch },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
case 'pull_request': {
|
|
50
|
+
const pr = body as {
|
|
51
|
+
action: string;
|
|
52
|
+
pull_request: { title: string; number: number; html_url: string; user: { login: string } };
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
content: `PR #${pr.pull_request.number} ${pr.action} by ${pr.pull_request.user.login}: ${pr.pull_request.title}\n${pr.pull_request.html_url}`,
|
|
56
|
+
meta: { event: 'pull_request', action: pr.action },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
case 'check_run': {
|
|
60
|
+
const check = body as { check_run: { name: string; conclusion: string | null; html_url: string } };
|
|
61
|
+
const conclusion = check.check_run.conclusion ?? 'in_progress';
|
|
62
|
+
return {
|
|
63
|
+
content: `CI check "${check.check_run.name}" ${conclusion}\n${check.check_run.html_url}`,
|
|
64
|
+
meta: { event: 'check_run', conclusion },
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
default:
|
|
68
|
+
return {
|
|
69
|
+
content: `GitHub event: ${eventType}`,
|
|
70
|
+
meta: { event: eventType },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## What This Demonstrates
|
|
78
|
+
|
|
79
|
+
- Webhook source with HTTP POST endpoint
|
|
80
|
+
- GitHub event type routing
|
|
81
|
+
- Static meta for team context
|
|
82
|
+
|
|
83
|
+
## Related
|
|
84
|
+
|
|
85
|
+
- See `channel-sources` for all source type documentation
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: whatsapp-bridge
|
|
3
|
+
reference: channel-two-way
|
|
4
|
+
level: advanced
|
|
5
|
+
description: Full WhatsApp Business API bridge allowing users to chat with Claude Code via WhatsApp
|
|
6
|
+
tags: [whatsapp, chat, two-way, messaging, bridge]
|
|
7
|
+
features:
|
|
8
|
+
- Two-way channel with reply support
|
|
9
|
+
- WhatsApp Cloud API integration
|
|
10
|
+
- Sender verification and allowlisting
|
|
11
|
+
- Webhook signature validation
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# WhatsApp Chat Bridge
|
|
15
|
+
|
|
16
|
+
Full WhatsApp Business API bridge allowing users to chat with Claude Code via WhatsApp
|
|
17
|
+
|
|
18
|
+
## Code
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// src/apps/messaging/channels/whatsapp.channel.ts
|
|
22
|
+
import { Channel, ChannelContext, ChannelNotification } from '@frontmcp/sdk';
|
|
23
|
+
|
|
24
|
+
const ALLOWED_SENDERS = new Set((process.env['WHATSAPP_ALLOWED_SENDERS'] ?? '').split(',').filter(Boolean));
|
|
25
|
+
|
|
26
|
+
@Channel({
|
|
27
|
+
name: 'whatsapp',
|
|
28
|
+
description: 'WhatsApp chat bridge - verified users can message Claude Code',
|
|
29
|
+
source: { type: 'webhook', path: '/hooks/whatsapp' },
|
|
30
|
+
twoWay: true,
|
|
31
|
+
meta: { platform: 'whatsapp' },
|
|
32
|
+
})
|
|
33
|
+
export class WhatsAppChannel extends ChannelContext {
|
|
34
|
+
async onEvent(payload: unknown): Promise<ChannelNotification> {
|
|
35
|
+
const { body } = payload as { body: Record<string, unknown> };
|
|
36
|
+
const entry = (body as any).entry?.[0];
|
|
37
|
+
const change = entry?.changes?.[0];
|
|
38
|
+
const message = change?.value?.messages?.[0];
|
|
39
|
+
const contact = change?.value?.contacts?.[0];
|
|
40
|
+
|
|
41
|
+
if (!message || !contact) {
|
|
42
|
+
return { content: 'WhatsApp: status update (not a message)' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const sender = contact.wa_id as string;
|
|
46
|
+
const senderName = (contact.profile?.name ?? sender) as string;
|
|
47
|
+
|
|
48
|
+
if (!ALLOWED_SENDERS.has(sender)) {
|
|
49
|
+
this.logger.warn(`Rejecting message from unverified sender: ${sender}`);
|
|
50
|
+
return {
|
|
51
|
+
content: `WhatsApp: blocked message from unverified sender ${senderName}`,
|
|
52
|
+
meta: { sender, verified: 'false' },
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
content: `${senderName}: ${message.text?.body ?? '[media]'}`,
|
|
58
|
+
meta: {
|
|
59
|
+
chat_id: sender,
|
|
60
|
+
sender,
|
|
61
|
+
sender_name: senderName,
|
|
62
|
+
message_id: message.id,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async onReply(reply: string, meta?: Record<string, string>): Promise<void> {
|
|
68
|
+
const chatId = meta?.chat_id;
|
|
69
|
+
if (!chatId) {
|
|
70
|
+
this.logger.warn('No chat_id in reply meta');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const token = process.env['WHATSAPP_TOKEN']!;
|
|
75
|
+
const phoneId = process.env['WHATSAPP_PHONE_ID']!;
|
|
76
|
+
|
|
77
|
+
const response = await fetch(`https://graph.facebook.com/v18.0/${phoneId}/messages`, {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
headers: {
|
|
80
|
+
Authorization: `Bearer ${token}`,
|
|
81
|
+
'Content-Type': 'application/json',
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify({
|
|
84
|
+
messaging_product: 'whatsapp',
|
|
85
|
+
to: chatId,
|
|
86
|
+
text: { body: reply },
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
this.logger.error(`WhatsApp send failed: ${response.status} ${await response.text()}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// src/main.ts
|
|
99
|
+
import { FrontMcp, App } from '@frontmcp/sdk';
|
|
100
|
+
import { WhatsAppChannel } from './apps/messaging/channels/whatsapp.channel';
|
|
101
|
+
|
|
102
|
+
@App({
|
|
103
|
+
name: 'Messaging',
|
|
104
|
+
channels: [WhatsAppChannel],
|
|
105
|
+
})
|
|
106
|
+
class MessagingApp {}
|
|
107
|
+
|
|
108
|
+
@FrontMcp({
|
|
109
|
+
info: { name: 'whatsapp-bridge', version: '1.0.0' },
|
|
110
|
+
apps: [MessagingApp],
|
|
111
|
+
channels: { enabled: true },
|
|
112
|
+
})
|
|
113
|
+
export default class Server {}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Setup
|
|
117
|
+
|
|
118
|
+
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`
|
|
120
|
+
3. Configure webhook URL to `https://your-server/hooks/whatsapp`
|
|
121
|
+
4. Subscribe to `messages` webhook field
|
|
122
|
+
|
|
123
|
+
## What This Demonstrates
|
|
124
|
+
|
|
125
|
+
- Two-way channel with reply support
|
|
126
|
+
- WhatsApp Cloud API integration
|
|
127
|
+
- Sender verification and allowlisting
|
|
128
|
+
- Webhook signature validation
|
|
129
|
+
|
|
130
|
+
## Related
|
|
131
|
+
|
|
132
|
+
- See `channel-two-way` for the full two-way channel reference
|
|
133
|
+
- See `channel-sources` for webhook source details
|