@experia-labs/plugin-dev-mcp 0.1.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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +13 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/data/api-endpoints.d.ts +15 -0
  8. package/dist/data/api-endpoints.d.ts.map +1 -0
  9. package/dist/data/api-endpoints.js +80 -0
  10. package/dist/data/api-endpoints.js.map +1 -0
  11. package/dist/data/events.d.ts +13 -0
  12. package/dist/data/events.d.ts.map +1 -0
  13. package/dist/data/events.js +110 -0
  14. package/dist/data/events.js.map +1 -0
  15. package/dist/data/scopes.d.ts +10 -0
  16. package/dist/data/scopes.d.ts.map +1 -0
  17. package/dist/data/scopes.js +70 -0
  18. package/dist/data/scopes.js.map +1 -0
  19. package/dist/data/slots.d.ts +9 -0
  20. package/dist/data/slots.d.ts.map +1 -0
  21. package/dist/data/slots.js +34 -0
  22. package/dist/data/slots.js.map +1 -0
  23. package/dist/index.d.ts +16 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +10 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/manifest/starter.d.ts +16 -0
  28. package/dist/manifest/starter.d.ts.map +1 -0
  29. package/dist/manifest/starter.js +79 -0
  30. package/dist/manifest/starter.js.map +1 -0
  31. package/dist/manifest/validate.d.ts +8 -0
  32. package/dist/manifest/validate.d.ts.map +1 -0
  33. package/dist/manifest/validate.js +182 -0
  34. package/dist/manifest/validate.js.map +1 -0
  35. package/dist/server.d.ts +7 -0
  36. package/dist/server.d.ts.map +1 -0
  37. package/dist/server.js +243 -0
  38. package/dist/server.js.map +1 -0
  39. package/dist/webhook/verify.d.ts +28 -0
  40. package/dist/webhook/verify.d.ts.map +1 -0
  41. package/dist/webhook/verify.js +42 -0
  42. package/dist/webhook/verify.js.map +1 -0
  43. package/package.json +62 -0
@@ -0,0 +1,79 @@
1
+ export function generateStarterManifest(args) {
2
+ const flavor = args.flavor ?? 'attendee-app';
3
+ const base = args.baseUrl.replace(/\/+$/, '');
4
+ const common = {
5
+ key: args.key,
6
+ version: '0.1.0',
7
+ name: args.name,
8
+ tagline: 'Describe your plugin in one sentence',
9
+ developer: {
10
+ name: args.developerName,
11
+ email: args.developerEmail,
12
+ },
13
+ pricing: { model: 'free' },
14
+ lifecycle: {
15
+ onInstall: `${base}/experia/hooks/install`,
16
+ onUninstall: `${base}/experia/hooks/uninstall`,
17
+ },
18
+ timeWindow: 'always',
19
+ };
20
+ if (flavor === 'minimal') {
21
+ return {
22
+ ...common,
23
+ scopes: ['event.read'],
24
+ events: [],
25
+ extensions: {},
26
+ };
27
+ }
28
+ if (flavor === 'organizer-tool') {
29
+ return {
30
+ ...common,
31
+ scopes: ['event.read', 'event.attendees.read'],
32
+ events: ['event.started', 'event.ended'],
33
+ extensions: {
34
+ 'organizer.event.tabs': [
35
+ {
36
+ id: 'config',
37
+ label: args.name,
38
+ iframe: `${base}/organizer/config?eventId={eventId}`,
39
+ },
40
+ {
41
+ id: 'insights',
42
+ label: `${args.name} Insights`,
43
+ iframe: `${base}/organizer/insights?eventId={eventId}`,
44
+ },
45
+ ],
46
+ },
47
+ };
48
+ }
49
+ // attendee-app — covers networking-style plugins
50
+ return {
51
+ ...common,
52
+ timeWindow: 'event-live',
53
+ scopes: [
54
+ 'event.read',
55
+ 'event.attendees.read',
56
+ 'attendee.profile.read',
57
+ 'attendee.profile.write',
58
+ 'messaging.send',
59
+ ],
60
+ events: ['event.started', 'event.ended', 'attendee.checked_in'],
61
+ extensions: {
62
+ 'organizer.event.tabs': [
63
+ {
64
+ id: 'config',
65
+ label: args.name,
66
+ iframe: `${base}/organizer/config?eventId={eventId}`,
67
+ },
68
+ ],
69
+ 'experia.event.tabs': [
70
+ {
71
+ id: 'home',
72
+ label: args.name,
73
+ iframe: `${base}/experia/home?eventId={eventId}&jwt={pluginJwt}`,
74
+ },
75
+ ],
76
+ },
77
+ };
78
+ }
79
+ //# sourceMappingURL=starter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"starter.js","sourceRoot":"","sources":["../../src/manifest/starter.ts"],"names":[],"mappings":"AAgBA,MAAM,UAAU,uBAAuB,CAAC,IAAiB;IACvD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,cAAc,CAAC;IAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAE9C,MAAM,MAAM,GAAG;QACb,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,OAAO,EAAE,sCAAsC;QAC/C,SAAS,EAAE;YACT,IAAI,EAAE,IAAI,CAAC,aAAa;YACxB,KAAK,EAAE,IAAI,CAAC,cAAc;SAC3B;QACD,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;QAC1B,SAAS,EAAE;YACT,SAAS,EAAE,GAAG,IAAI,wBAAwB;YAC1C,WAAW,EAAE,GAAG,IAAI,0BAA0B;SAC/C;QACD,UAAU,EAAE,QAAQ;KACZ,CAAC;IAEX,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO;YACL,GAAG,MAAM;YACT,MAAM,EAAE,CAAC,YAAY,CAAC;YACtB,MAAM,EAAE,EAAE;YACV,UAAU,EAAE,EAAE;SACf,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,KAAK,gBAAgB,EAAE,CAAC;QAChC,OAAO;YACL,GAAG,MAAM;YACT,MAAM,EAAE,CAAC,YAAY,EAAE,sBAAsB,CAAC;YAC9C,MAAM,EAAE,CAAC,eAAe,EAAE,aAAa,CAAC;YACxC,UAAU,EAAE;gBACV,sBAAsB,EAAE;oBACtB;wBACE,EAAE,EAAE,QAAQ;wBACZ,KAAK,EAAE,IAAI,CAAC,IAAI;wBAChB,MAAM,EAAE,GAAG,IAAI,qCAAqC;qBACrD;oBACD;wBACE,EAAE,EAAE,UAAU;wBACd,KAAK,EAAE,GAAG,IAAI,CAAC,IAAI,WAAW;wBAC9B,MAAM,EAAE,GAAG,IAAI,uCAAuC;qBACvD;iBACF;aACF;SACF,CAAC;IACJ,CAAC;IAED,iDAAiD;IACjD,OAAO;QACL,GAAG,MAAM;QACT,UAAU,EAAE,YAAY;QACxB,MAAM,EAAE;YACN,YAAY;YACZ,sBAAsB;YACtB,uBAAuB;YACvB,wBAAwB;YACxB,gBAAgB;SACjB;QACD,MAAM,EAAE,CAAC,eAAe,EAAE,aAAa,EAAE,qBAAqB,CAAC;QAC/D,UAAU,EAAE;YACV,sBAAsB,EAAE;gBACtB;oBACE,EAAE,EAAE,QAAQ;oBACZ,KAAK,EAAE,IAAI,CAAC,IAAI;oBAChB,MAAM,EAAE,GAAG,IAAI,qCAAqC;iBACrD;aACF;YACD,oBAAoB,EAAE;gBACpB;oBACE,EAAE,EAAE,MAAM;oBACV,KAAK,EAAE,IAAI,CAAC,IAAI;oBAChB,MAAM,EAAE,GAAG,IAAI,iDAAiD;iBACjE;aACF;SACF;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ export interface ManifestValidationResult {
2
+ ok: boolean;
3
+ issues: string[];
4
+ /** Suggested fixes / next steps (only set when there are issues). */
5
+ hints: string[];
6
+ }
7
+ export declare function validateManifest(raw: unknown): ManifestValidationResult;
8
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/manifest/validate.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,qEAAqE;IACrE,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,wBAAwB,CAgNvE"}
@@ -0,0 +1,182 @@
1
+ // Stateless port of the platform's PluginManifestValidatorService.
2
+ // Keep these rules in lock-step with
3
+ // creatoros-api/src/features/plugin-platform/manifest/plugin-manifest.validator.service.ts
4
+ // so a manifest that passes locally also passes server-side at publish time.
5
+ import { ALL_SCOPES, isValidScope } from '../data/scopes.js';
6
+ import { VALID_DOMAIN_EVENT_TYPES, isValidDomainEvent, } from '../data/events.js';
7
+ import { ALL_SLOTS } from '../data/slots.js';
8
+ const SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/;
9
+ const KEY_RE = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/;
10
+ const HTTPS_RE = /^https:\/\/[^\s]+$/;
11
+ const VALID_TIME_WINDOWS = new Set(['always', 'event-live', 'post-event']);
12
+ const VALID_PRICING_MODELS = new Set([
13
+ 'free',
14
+ 'flat',
15
+ 'per_attendee',
16
+ 'revshare',
17
+ ]);
18
+ const VALID_EXTENSION_SLOTS = new Set(ALL_SLOTS);
19
+ export function validateManifest(raw) {
20
+ const issues = [];
21
+ const hints = [];
22
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
23
+ return {
24
+ ok: false,
25
+ issues: ['Manifest must be a non-array object'],
26
+ hints: ['Pass the parsed JSON, not a string. Use get_starter_manifest to scaffold.'],
27
+ };
28
+ }
29
+ const m = raw;
30
+ if (typeof m.key !== 'string' || !KEY_RE.test(m.key)) {
31
+ issues.push('key: must be a lowercase string of [a-z0-9._-], 1-64 chars, no leading/trailing punctuation');
32
+ hints.push('Use the pattern "<vendor>.<name>" — e.g. "acme.sponsors-lounge".');
33
+ }
34
+ if (typeof m.version !== 'string' || !SEMVER_RE.test(m.version)) {
35
+ issues.push('version: must be a valid semver string (e.g. "1.0.0")');
36
+ }
37
+ if (typeof m.name !== 'string' ||
38
+ m.name.length === 0 ||
39
+ m.name.length > 100) {
40
+ issues.push('name: must be a non-empty string, max 100 chars');
41
+ }
42
+ for (const f of ['tagline', 'description', 'category', 'iconUrl']) {
43
+ if (m[f] !== undefined && typeof m[f] !== 'string') {
44
+ issues.push(`${f}: must be a string when present`);
45
+ }
46
+ }
47
+ if (m.iconUrl !== undefined &&
48
+ typeof m.iconUrl === 'string' &&
49
+ !HTTPS_RE.test(m.iconUrl)) {
50
+ issues.push('iconUrl: must use https://');
51
+ }
52
+ if (m.screenshots !== undefined) {
53
+ if (!Array.isArray(m.screenshots)) {
54
+ issues.push('screenshots: must be an array of https URLs');
55
+ }
56
+ else {
57
+ for (const s of m.screenshots) {
58
+ if (typeof s !== 'string' || !HTTPS_RE.test(s)) {
59
+ issues.push('screenshots: every entry must be an https URL');
60
+ break;
61
+ }
62
+ }
63
+ }
64
+ }
65
+ const dev = m.developer;
66
+ if (!dev || typeof dev !== 'object') {
67
+ issues.push('developer: missing or not an object');
68
+ }
69
+ else {
70
+ if (typeof dev.name !== 'string' || dev.name.length === 0) {
71
+ issues.push('developer.name: required, non-empty string');
72
+ }
73
+ if (typeof dev.email !== 'string' || !dev.email.includes('@')) {
74
+ issues.push('developer.email: required, must be an email');
75
+ }
76
+ if (dev.url !== undefined &&
77
+ (typeof dev.url !== 'string' || !HTTPS_RE.test(dev.url))) {
78
+ issues.push('developer.url: must be an https URL when present');
79
+ }
80
+ }
81
+ const pricing = m.pricing;
82
+ if (!pricing || typeof pricing !== 'object') {
83
+ issues.push('pricing: missing or not an object');
84
+ hints.push('Add pricing: { "model": "free" } if your plugin is free.');
85
+ }
86
+ else {
87
+ if (typeof pricing.model !== 'string' ||
88
+ !VALID_PRICING_MODELS.has(pricing.model)) {
89
+ issues.push(`pricing.model: must be one of ${Array.from(VALID_PRICING_MODELS).join(', ')}`);
90
+ }
91
+ if (pricing.model === 'flat' || pricing.model === 'per_attendee') {
92
+ if (typeof pricing.amount !== 'number' || pricing.amount < 0) {
93
+ issues.push(`pricing.amount: required and >= 0 for model=${pricing.model}`);
94
+ }
95
+ }
96
+ if (pricing.model === 'revshare') {
97
+ if (typeof pricing.revshareBps !== 'number' ||
98
+ pricing.revshareBps < 0 ||
99
+ pricing.revshareBps > 10000) {
100
+ issues.push('pricing.revshareBps: required, integer between 0 and 10000');
101
+ }
102
+ }
103
+ }
104
+ if (!Array.isArray(m.scopes)) {
105
+ issues.push('scopes: must be an array of scope strings');
106
+ hints.push(`Pick from: ${ALL_SCOPES.join(', ')}`);
107
+ }
108
+ else {
109
+ for (const s of m.scopes) {
110
+ if (typeof s !== 'string' || !isValidScope(s)) {
111
+ issues.push(`scopes: "${String(s)}" is not a valid scope. Valid scopes: ${ALL_SCOPES.join(', ')}`);
112
+ }
113
+ }
114
+ }
115
+ const lc = m.lifecycle;
116
+ if (lc !== undefined) {
117
+ if (typeof lc !== 'object' || Array.isArray(lc)) {
118
+ issues.push('lifecycle: must be an object when present');
119
+ }
120
+ else {
121
+ for (const f of ['onInstall', 'onUninstall', 'onConfigure']) {
122
+ const v = lc[f];
123
+ if (v !== undefined && (typeof v !== 'string' || !HTTPS_RE.test(v))) {
124
+ issues.push(`lifecycle.${f}: must be an https URL when present`);
125
+ }
126
+ }
127
+ }
128
+ }
129
+ if (!Array.isArray(m.events)) {
130
+ issues.push('events: must be an array (use [] if no domain event subscriptions)');
131
+ }
132
+ else {
133
+ for (const e of m.events) {
134
+ if (typeof e !== 'string' || !isValidDomainEvent(e)) {
135
+ issues.push(`events: "${String(e)}" is not a valid domain event. Valid events: ${VALID_DOMAIN_EVENT_TYPES.join(', ')}`);
136
+ }
137
+ }
138
+ }
139
+ if (m.extensions !== undefined) {
140
+ if (typeof m.extensions !== 'object' || Array.isArray(m.extensions)) {
141
+ issues.push('extensions: must be an object keyed by slot name');
142
+ }
143
+ else {
144
+ for (const [slot, val] of Object.entries(m.extensions)) {
145
+ if (!VALID_EXTENSION_SLOTS.has(slot)) {
146
+ issues.push(`extensions: "${slot}" is not a known extension slot. Valid slots: ${ALL_SLOTS.join(', ')}`);
147
+ continue;
148
+ }
149
+ if (!Array.isArray(val)) {
150
+ issues.push(`extensions.${slot}: must be an array`);
151
+ continue;
152
+ }
153
+ val.forEach((ext, i) => {
154
+ if (!ext || typeof ext !== 'object') {
155
+ issues.push(`extensions.${slot}[${i}]: must be an object`);
156
+ return;
157
+ }
158
+ const e = ext;
159
+ if (typeof e.id !== 'string' || e.id.length === 0) {
160
+ issues.push(`extensions.${slot}[${i}].id: required string`);
161
+ }
162
+ if (typeof e.label !== 'string' || e.label.length === 0) {
163
+ issues.push(`extensions.${slot}[${i}].label: required string`);
164
+ }
165
+ if (typeof e.iframe !== 'string' || !HTTPS_RE.test(e.iframe)) {
166
+ issues.push(`extensions.${slot}[${i}].iframe: required https URL`);
167
+ }
168
+ });
169
+ }
170
+ }
171
+ }
172
+ if (m.configSchema !== undefined &&
173
+ (typeof m.configSchema !== 'object' || Array.isArray(m.configSchema))) {
174
+ issues.push('configSchema: must be a JSON Schema object when present');
175
+ }
176
+ if (typeof m.timeWindow !== 'string' ||
177
+ !VALID_TIME_WINDOWS.has(m.timeWindow)) {
178
+ issues.push(`timeWindow: required, one of ${Array.from(VALID_TIME_WINDOWS).join(', ')}`);
179
+ }
180
+ return { ok: issues.length === 0, issues, hints };
181
+ }
182
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/manifest/validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ /**
3
+ * Builds the MCP server. Exported so tests can drive it without
4
+ * spawning a stdio process.
5
+ */
6
+ export declare function createServer(): McpServer;
7
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAoBpE;;;GAGG;AACH,wBAAgB,YAAY,IAAI,SAAS,CAkLxC"}
package/dist/server.js ADDED
@@ -0,0 +1,243 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { validateManifest } from './manifest/validate.js';
4
+ import { generateStarterManifest } from './manifest/starter.js';
5
+ import { verifyWebhookSignature } from './webhook/verify.js';
6
+ import { SCOPE_REGISTRY } from './data/scopes.js';
7
+ import { LIFECYCLE_EVENTS, DOMAIN_EVENTS } from './data/events.js';
8
+ import { SLOT_REGISTRY } from './data/slots.js';
9
+ import { PLUGIN_API_ENDPOINTS } from './data/api-endpoints.js';
10
+ function asJson(value) {
11
+ return {
12
+ content: [{ type: 'text', text: JSON.stringify(value, null, 2) }],
13
+ };
14
+ }
15
+ function asMarkdown(text) {
16
+ return { content: [{ type: 'text', text }] };
17
+ }
18
+ /**
19
+ * Builds the MCP server. Exported so tests can drive it without
20
+ * spawning a stdio process.
21
+ */
22
+ export function createServer() {
23
+ const server = new McpServer({
24
+ name: 'experia-plugin-dev',
25
+ version: '0.1.0',
26
+ });
27
+ // ---- Discovery tools (no inputs) ----
28
+ server.registerTool('list_scopes', {
29
+ title: 'List plugin scopes',
30
+ description: 'Returns every OAuth-style scope a plugin can request in its manifest, with a description and the /plugin-api/v1 endpoints each unlocks. Use this when picking the minimal set of scopes for a plugin.',
31
+ }, async () => asJson(SCOPE_REGISTRY));
32
+ server.registerTool('list_events', {
33
+ title: 'List webhook events',
34
+ description: 'Returns the lifecycle events (always delivered) and domain events (subscribe via manifest.events) a plugin can receive, with example payloads. Use this when planning your webhook handler.',
35
+ }, async () => asJson({ lifecycle: LIFECYCLE_EVENTS, domain: DOMAIN_EVENTS }));
36
+ server.registerTool('list_extension_slots', {
37
+ title: 'List UI extension slots',
38
+ description: 'Returns every named slot where a plugin\'s iframe can render (organizer dashboard, attendee app, check-in flow). Use this when picking where in the host UI your plugin should appear.',
39
+ }, async () => asJson(SLOT_REGISTRY));
40
+ server.registerTool('list_api_endpoints', {
41
+ title: 'List /plugin-api/v1 endpoints',
42
+ description: 'Returns every endpoint your plugin can call after install (Bearer = installationToken). Each entry lists the required scope. Use this to plan API calls and the matching scope set.',
43
+ }, async () => asJson(PLUGIN_API_ENDPOINTS));
44
+ // ---- Manifest helpers ----
45
+ server.registerTool('validate_manifest', {
46
+ title: 'Validate a plugin manifest',
47
+ description: 'Validates a JSON manifest against the platform\'s rules. Identical to the server-side check at publish time — if this passes, POST /developer-portal/plugins/:id/versions will too. Returns { ok, issues, hints }.',
48
+ inputSchema: {
49
+ manifest: z
50
+ .unknown()
51
+ .describe('The parsed JSON manifest object (NOT a string).'),
52
+ },
53
+ }, async ({ manifest }) => asJson(validateManifest(manifest)));
54
+ server.registerTool('get_starter_manifest', {
55
+ title: 'Generate a starter manifest',
56
+ description: 'Returns a manifest skeleton wired for a common plugin shape. Pick "minimal" for the smallest possible plugin, "organizer-tool" for an organizer-facing config+insights pattern, or "attendee-app" for a Networking-style plugin (default).',
57
+ inputSchema: {
58
+ key: z
59
+ .string()
60
+ .describe('Vendor-prefixed plugin key, e.g. "acme.sponsors-lounge".'),
61
+ name: z.string().describe('Display name shown in the marketplace.'),
62
+ developerName: z.string().describe('Your organization name.'),
63
+ developerEmail: z.string().describe('Contact email for review feedback.'),
64
+ baseUrl: z
65
+ .string()
66
+ .describe('HTTPS base URL where your plugin is hosted (no trailing slash).'),
67
+ flavor: z
68
+ .enum(['minimal', 'attendee-app', 'organizer-tool'])
69
+ .optional()
70
+ .describe('Which template to use. Default: attendee-app.'),
71
+ },
72
+ }, async (args) => asJson(generateStarterManifest(args)));
73
+ // ---- Webhook helpers ----
74
+ server.registerTool('verify_webhook_signature', {
75
+ title: 'Verify an X-Experia-Signature header',
76
+ description: 'Verifies a webhook delivery using the per-installation `webhookSecret` you stored from plugin.installed. Use this when debugging a 401/403 from your handler — it will tell you exactly why a signature failed (malformed-header / timestamp-out-of-tolerance / signature-mismatch).',
77
+ inputSchema: {
78
+ payload: z
79
+ .string()
80
+ .describe('Raw request body, exactly as received (do not JSON.parse).'),
81
+ signatureHeader: z
82
+ .string()
83
+ .describe('Value of the X-Experia-Signature header.'),
84
+ secret: z.string().describe('Per-installation webhookSecret.'),
85
+ toleranceSeconds: z
86
+ .number()
87
+ .optional()
88
+ .describe('Replay-window tolerance in seconds. Default 300.'),
89
+ },
90
+ }, async (args) => asJson(verifyWebhookSignature(args)));
91
+ // ---- Resources (read-only docs) ----
92
+ server.registerResource('plugin-overview', 'experia://docs/overview', {
93
+ title: 'Experia Plugin Platform Overview',
94
+ description: 'High-level explanation of the plugin lifecycle, manifests, webhooks, and the public /plugin-api/v1.',
95
+ mimeType: 'text/markdown',
96
+ }, async (uri) => ({
97
+ contents: [
98
+ {
99
+ uri: uri.href,
100
+ mimeType: 'text/markdown',
101
+ text: OVERVIEW_DOC,
102
+ },
103
+ ],
104
+ }));
105
+ server.registerResource('manifest-spec', 'experia://docs/manifest', {
106
+ title: 'Manifest field-by-field reference',
107
+ description: 'Every field of the manifest, what it does, validation rules.',
108
+ mimeType: 'text/markdown',
109
+ }, async (uri) => ({
110
+ contents: [
111
+ {
112
+ uri: uri.href,
113
+ mimeType: 'text/markdown',
114
+ text: MANIFEST_DOC,
115
+ },
116
+ ],
117
+ }));
118
+ server.registerResource('webhook-spec', 'experia://docs/webhooks', {
119
+ title: 'Webhook delivery, signing, retries',
120
+ description: 'How HMAC signatures work, the retry schedule, and how to verify deliveries.',
121
+ mimeType: 'text/markdown',
122
+ }, async (uri) => ({
123
+ contents: [
124
+ {
125
+ uri: uri.href,
126
+ mimeType: 'text/markdown',
127
+ text: WEBHOOKS_DOC,
128
+ },
129
+ ],
130
+ }));
131
+ // Side-effect: silence unused-import linters for asMarkdown if no doc tool uses it.
132
+ void asMarkdown;
133
+ return server;
134
+ }
135
+ const OVERVIEW_DOC = `# Experia Plugin Platform — Overview
136
+
137
+ Plugins extend Experia for organizers and attendees. They are described by a
138
+ JSON **manifest** and talk to the platform over a public API.
139
+
140
+ ## Lifecycle
141
+
142
+ 1. You **submit a plugin** via the developer portal (creates a Plugin row in DRAFT/PRIVATE).
143
+ 2. You **publish a version** (uploads a manifest snapshot, validated by the platform).
144
+ 3. An organizer **installs** your plugin on one of their events. The platform:
145
+ - generates a one-time \`installationToken\` (Bearer for /plugin-api/v1)
146
+ - generates a per-install \`webhookSecret\` (HMAC for verifying webhooks)
147
+ - delivers them in a \`plugin.installed\` webhook to your \`lifecycle.onInstall\` URL
148
+ 4. Your plugin receives subsequent **webhook events** and calls
149
+ \`/plugin-api/v1/*\` to read/write data — limited to the **scopes** declared
150
+ in your manifest.
151
+ 5. Your plugin's iframes render in **extension slots** chosen in the manifest.
152
+
153
+ ## What you build
154
+
155
+ - **A web service** that hosts \`lifecycle.onInstall\` (and optionally
156
+ \`onUninstall\`/\`onConfigure\`) and any iframe pages declared in
157
+ \`manifest.extensions\`.
158
+ - A storage layer (your DB) keyed by \`eventPluginId\` to remember the
159
+ installation token + webhook secret.
160
+
161
+ ## What you DON'T do
162
+
163
+ - You don't host the database. Plugin data that's per-installation can use the
164
+ built-in \`storage.read\` / \`storage.write\` scopes.
165
+ - You don't manage auth. Bearer = installation token; iframe = JWT.
166
+ `;
167
+ const MANIFEST_DOC = `# Manifest reference
168
+
169
+ \`\`\`jsonc
170
+ {
171
+ "key": "vendor.name", // [a-z0-9._-], 1-64 chars
172
+ "version": "1.0.0", // semver
173
+ "name": "Display name", // ≤100 chars
174
+ "tagline": "One-sentence pitch.", // optional
175
+ "description": "Long description.", // optional
176
+ "category": "engagement", // optional
177
+ "iconUrl": "https://...", // optional, https
178
+ "screenshots": ["https://..."], // optional, all https
179
+
180
+ "developer": {
181
+ "name": "Acme Corp", // required
182
+ "email": "dev@acme.com", // required, must contain @
183
+ "url": "https://acme.com" // optional, https
184
+ },
185
+
186
+ "pricing": { "model": "free" }, // or flat/per_attendee + amount, or revshare + revshareBps (0-10000)
187
+
188
+ "scopes": ["event.read", "event.attendees.read"], // see list_scopes
189
+
190
+ "lifecycle": { // entire object optional
191
+ "onInstall": "https://api.acme.com/experia/install",
192
+ "onUninstall": "https://api.acme.com/experia/uninstall",
193
+ "onConfigure": "https://api.acme.com/experia/configure"
194
+ },
195
+
196
+ "events": ["event.started", "attendee.checked_in"], // see list_events (domain only)
197
+
198
+ "extensions": { // see list_extension_slots
199
+ "experia.event.tabs": [
200
+ { "id": "home", "label": "Network", "iframe": "https://acme.com/?eventId={eventId}&jwt={pluginJwt}" }
201
+ ]
202
+ },
203
+
204
+ "configSchema": { "type": "object", "properties": { ... } }, // optional JSON Schema
205
+ "timeWindow": "always" // always | event-live | post-event
206
+ }
207
+ \`\`\`
208
+
209
+ Use **validate_manifest** in this MCP server to confirm a manifest passes
210
+ the same rules as the platform's publish endpoint.
211
+ `;
212
+ const WEBHOOKS_DOC = `# Webhooks
213
+
214
+ Outbound webhook deliveries are signed Stripe-style:
215
+
216
+ \`\`\`
217
+ X-Experia-Signature: t=<unix_seconds>,v1=<hex_hmac_sha256>
218
+ Body: <raw JSON>
219
+ \`\`\`
220
+
221
+ Where \`v1 = HMAC_SHA256(webhookSecret, "${"$"}{t}.${"$"}{rawBody}")\`.
222
+
223
+ The \`webhookSecret\` is **per-installation** — you receive it once in the
224
+ \`plugin.installed\` payload. Persist it immediately.
225
+
226
+ ## Verification rule
227
+
228
+ 1. Parse the header into \`t\` and \`v1\`.
229
+ 2. Reject if \`abs(now - t) > 300\` (replay window).
230
+ 3. Recompute \`v1\` using your saved secret + the raw body.
231
+ 4. Constant-time compare. Reject on mismatch.
232
+
233
+ The **verify_webhook_signature** tool in this MCP server runs the exact same
234
+ algorithm — useful for debugging a 401/403 from your handler.
235
+
236
+ ## Retries
237
+
238
+ Failed deliveries are retried with exponential backoff (~1m, 5m, 30m, 2h, 12h).
239
+ After ~5 attempts the delivery is marked failed; you can replay it from the
240
+ developer portal once the issue is fixed. All deliveries are logged in
241
+ \`PluginWebhookDelivery\`.
242
+ `;
243
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ export interface VerifyWebhookArgs {
2
+ /** Raw request body as it was received (string or Buffer). */
3
+ payload: string | Buffer;
4
+ /** Value of the `X-Experia-Signature` header. Format: `t=<unix>,v1=<hex>`. */
5
+ signatureHeader: string;
6
+ /** The webhookSecret you stored from the plugin.installed payload. */
7
+ secret: string;
8
+ /** Tolerance window in seconds. Default 5 minutes. */
9
+ toleranceSeconds?: number;
10
+ /** Override "now" (testing only). */
11
+ nowSeconds?: number;
12
+ }
13
+ export interface VerifyWebhookResult {
14
+ ok: boolean;
15
+ reason?: 'malformed-header' | 'timestamp-out-of-tolerance' | 'signature-mismatch';
16
+ /** Parsed timestamp (when header parses). */
17
+ timestamp?: number;
18
+ }
19
+ /**
20
+ * Verifies an `X-Experia-Signature` header. Uses constant-time compare
21
+ * and a timestamp-tolerance window to prevent replay.
22
+ *
23
+ * Mirrors the signing rule:
24
+ * sig = HMAC_SHA256(secret, `${timestamp}.${rawPayload}`)
25
+ * header = `t=${timestamp},v1=${sig}`
26
+ */
27
+ export declare function verifyWebhookSignature(args: VerifyWebhookArgs): VerifyWebhookResult;
28
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/webhook/verify.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,8DAA8D;IAC9D,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,8EAA8E;IAC9E,eAAe,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qCAAqC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EACH,kBAAkB,GAClB,4BAA4B,GAC5B,oBAAoB,CAAC;IACzB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,iBAAiB,GACtB,mBAAmB,CAyCrB"}
@@ -0,0 +1,42 @@
1
+ import { createHmac, timingSafeEqual } from 'node:crypto';
2
+ /**
3
+ * Verifies an `X-Experia-Signature` header. Uses constant-time compare
4
+ * and a timestamp-tolerance window to prevent replay.
5
+ *
6
+ * Mirrors the signing rule:
7
+ * sig = HMAC_SHA256(secret, `${timestamp}.${rawPayload}`)
8
+ * header = `t=${timestamp},v1=${sig}`
9
+ */
10
+ export function verifyWebhookSignature(args) {
11
+ const tolerance = args.toleranceSeconds ?? 300;
12
+ const now = args.nowSeconds ?? Math.floor(Date.now() / 1000);
13
+ const parts = args.signatureHeader.split(',').reduce((acc, p) => {
14
+ const [k, v] = p.split('=');
15
+ if (k && v)
16
+ acc[k.trim()] = v.trim();
17
+ return acc;
18
+ }, {});
19
+ const t = parts['t'];
20
+ const v1 = parts['v1'];
21
+ if (!t || !v1)
22
+ return { ok: false, reason: 'malformed-header' };
23
+ const ts = Number(t);
24
+ if (!Number.isFinite(ts))
25
+ return { ok: false, reason: 'malformed-header' };
26
+ if (Math.abs(now - ts) > tolerance) {
27
+ return { ok: false, reason: 'timestamp-out-of-tolerance', timestamp: ts };
28
+ }
29
+ const payloadStr = typeof args.payload === 'string'
30
+ ? args.payload
31
+ : args.payload.toString('utf8');
32
+ const expected = createHmac('sha256', args.secret)
33
+ .update(`${t}.${payloadStr}`)
34
+ .digest('hex');
35
+ const a = Buffer.from(expected, 'hex');
36
+ const b = Buffer.from(v1, 'hex');
37
+ if (a.length !== b.length || !timingSafeEqual(a, b)) {
38
+ return { ok: false, reason: 'signature-mismatch', timestamp: ts };
39
+ }
40
+ return { ok: true, timestamp: ts };
41
+ }
42
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/webhook/verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAyB1D;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACpC,IAAuB;IAEvB,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,IAAI,GAAG,CAAC;IAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAE7D,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAClD,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;QACT,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QACrC,OAAO,GAAG,CAAC;IACb,CAAC,EACD,EAAE,CACH,CAAC;IAEF,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;IACrB,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAEhE,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACrB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAE3E,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC;QACnC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,4BAA4B,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IAC5E,CAAC;IAED,MAAM,UAAU,GACd,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ;QAC9B,CAAC,CAAC,IAAI,CAAC,OAAO;QACd,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAEpC,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC;SAC/C,MAAM,CAAC,GAAG,CAAC,IAAI,UAAU,EAAE,CAAC;SAC5B,MAAM,CAAC,KAAK,CAAC,CAAC;IAEjB,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACvC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAEjC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QACpD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IACpE,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;AACrC,CAAC"}