@coffrify/mcp 0.1.2 → 0.3.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 (2) hide show
  1. package/dist/server.js +375 -84
  2. package/package.json +2 -2
package/dist/server.js CHANGED
@@ -3,8 +3,15 @@
3
3
  // src/server.ts
4
4
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
- import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
7
- import { Coffrify } from "@coffrify/sdk";
6
+ import {
7
+ CallToolRequestSchema,
8
+ ListToolsRequestSchema,
9
+ ListResourcesRequestSchema,
10
+ ReadResourceRequestSchema,
11
+ ListPromptsRequestSchema,
12
+ GetPromptRequestSchema
13
+ } from "@modelcontextprotocol/sdk/types.js";
14
+ import { Coffrify, COFFRIFY_EVENT_CATALOG } from "@coffrify/sdk";
8
15
  var apiKey = process.env.COFFRIFY_API_KEY;
9
16
  var apiUrl = process.env.COFFRIFY_API_URL ?? "https://api.coffrify.com";
10
17
  if (!apiKey) {
@@ -13,33 +20,44 @@ if (!apiKey) {
13
20
  }
14
21
  var coffrify = new Coffrify({ apiKey, apiUrl });
15
22
  var server = new Server(
16
- { name: "coffrify", version: "0.1.0" },
17
- { capabilities: { tools: {} } }
23
+ { name: "coffrify", version: "0.3.0" },
24
+ { capabilities: { tools: {}, resources: {}, prompts: {} } }
18
25
  );
26
+ var disabledBuiltinTools = /* @__PURE__ */ new Set();
27
+ var customActionsByName = /* @__PURE__ */ new Map();
28
+ var eventEnum = COFFRIFY_EVENT_CATALOG.map((e) => e.type);
19
29
  var TOOLS = [
20
30
  {
21
31
  name: "coffrify_list_transfers",
22
- description: "List recent encrypted file transfers in the workspace. Returns id, short_code, title, status, downloads count, expiration.",
32
+ description: "List recent encrypted file transfers in the workspace.",
23
33
  inputSchema: {
24
34
  type: "object",
25
35
  properties: {
26
- limit: { type: "number", description: "Max results (default 20)", default: 20 },
27
- status: { type: "string", description: "Filter by status: active|expired|deleted" }
36
+ limit: { type: "number", default: 20 },
37
+ status: { type: "string", description: "active|expired|deleted" }
28
38
  }
29
39
  }
30
40
  },
31
41
  {
32
- name: "coffrify_get_transfer",
33
- description: "Get full details of a transfer including files, settings, scan status.",
42
+ name: "coffrify_search_transfers",
43
+ description: "Search transfers by title substring or short_code prefix.",
34
44
  inputSchema: {
35
45
  type: "object",
36
- properties: { id: { type: "string", description: "Transfer ID (tr_...)" } },
37
- required: ["id"]
46
+ properties: {
47
+ query: { type: "string", description: "Substring (case-insensitive) to match against title or short_code" },
48
+ limit: { type: "number", default: 20 }
49
+ },
50
+ required: ["query"]
38
51
  }
39
52
  },
53
+ {
54
+ name: "coffrify_get_transfer",
55
+ description: "Get full details of a transfer (files, settings, scan status).",
56
+ inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] }
57
+ },
40
58
  {
41
59
  name: "coffrify_create_transfer",
42
- description: "Create a new encrypted file transfer. Returns the transfer + share URL. Files must be uploaded separately to the presigned URLs returned.",
60
+ description: "Create a new encrypted file transfer. Returns the transfer + share URL.",
43
61
  inputSchema: {
44
62
  type: "object",
45
63
  properties: {
@@ -59,119 +77,123 @@ var TOOLS = [
59
77
  max_downloads: { type: "number" },
60
78
  password: { type: "string" },
61
79
  transfer_title: { type: "string" },
62
- burn_after_read: { type: "boolean" }
80
+ burn_after_read: { type: "boolean" },
81
+ encryption_mode: { type: "string", enum: ["server_side", "e2e_v1"], default: "server_side" }
63
82
  },
64
83
  required: ["files"]
65
84
  }
66
85
  },
67
86
  {
68
87
  name: "coffrify_delete_transfer",
69
- description: "Delete a transfer (immediate, irreversible).",
88
+ description: "Delete a transfer (irreversible).",
89
+ inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] }
90
+ },
91
+ {
92
+ name: "coffrify_bulk_delete_transfers",
93
+ description: "Delete multiple transfers at once. Returns per-id success/failure.",
70
94
  inputSchema: {
71
95
  type: "object",
72
- properties: { id: { type: "string" } },
73
- required: ["id"]
96
+ properties: { ids: { type: "array", items: { type: "string" }, minItems: 1, maxItems: 100 } },
97
+ required: ["ids"]
74
98
  }
75
99
  },
76
100
  {
77
101
  name: "coffrify_list_webhooks",
78
- description: "List configured webhook endpoints in the workspace.",
102
+ description: "List configured webhook endpoints.",
79
103
  inputSchema: { type: "object", properties: {} }
80
104
  },
81
105
  {
82
106
  name: "coffrify_create_webhook",
83
- description: "Create a webhook subscription. Returns the webhook + a secret (shown once).",
107
+ description: "Create a webhook subscription. Returns the webhook + signing secret (shown once).",
84
108
  inputSchema: {
85
109
  type: "object",
86
110
  properties: {
87
111
  name: { type: "string" },
88
- url: { type: "string", description: "HTTPS endpoint" },
89
- events: {
90
- type: "array",
91
- items: {
92
- type: "string",
93
- enum: ["transfer.created", "transfer.downloaded", "transfer.expired", "transfer.deleted", "transfer.scanned", "transfer.cloned", "transfer.limit_reached"]
94
- }
95
- }
112
+ url: { type: "string" },
113
+ events: { type: "array", items: { type: "string", enum: eventEnum } },
114
+ description: { type: "string" }
96
115
  },
97
116
  required: ["name", "url", "events"]
98
117
  }
99
118
  },
100
119
  {
101
120
  name: "coffrify_test_webhook",
102
- description: "Send a test ping event to a webhook to verify the endpoint is reachable.",
103
- inputSchema: {
104
- type: "object",
105
- properties: { id: { type: "string" } },
106
- required: ["id"]
107
- }
121
+ description: "Send a test ping event to a webhook.",
122
+ inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] }
108
123
  },
109
124
  {
110
125
  name: "coffrify_list_webhook_deliveries",
111
- description: "List recent delivery attempts for a webhook (debug failures, see latency, replay).",
126
+ description: "List recent delivery attempts for a webhook.",
112
127
  inputSchema: {
113
128
  type: "object",
114
- properties: {
115
- webhook_id: { type: "string" },
116
- limit: { type: "number", default: 20 }
117
- },
129
+ properties: { webhook_id: { type: "string" }, limit: { type: "number", default: 20 } },
118
130
  required: ["webhook_id"]
119
131
  }
120
132
  },
121
133
  {
122
134
  name: "coffrify_replay_webhook_delivery",
123
- description: "Replay a past webhook delivery (creates a new event_id).",
135
+ description: "Replay a past webhook delivery.",
136
+ inputSchema: { type: "object", properties: { delivery_id: { type: "string" } }, required: ["delivery_id"] }
137
+ },
138
+ {
139
+ name: "coffrify_list_webhook_event_types",
140
+ description: "Return the catalog of every supported webhook event type with descriptions, families, stability, and required plan.",
124
141
  inputSchema: {
125
142
  type: "object",
126
- properties: { delivery_id: { type: "string" } },
127
- required: ["delivery_id"]
143
+ properties: {
144
+ family: { type: "string", description: "Filter by family: transfer|workspace|api_key|webhook|scim|saml|audit|gdpr|system|member|api_token" }
145
+ }
128
146
  }
129
147
  },
130
148
  {
131
149
  name: "coffrify_list_api_keys",
132
- description: "List API keys in the workspace (key values are hashed and never returned).",
150
+ description: "List API keys (key values are hashed and never returned).",
133
151
  inputSchema: { type: "object", properties: {} }
134
152
  },
135
153
  {
136
154
  name: "coffrify_create_api_key",
137
- description: "Create a new API key. Returns the key value ONCE \u2014 must be stored securely. Specify scopes for least-privilege access.",
155
+ description: "Create a new API key. Returns the key value ONCE \u2014 store securely. Specify scopes for least-privilege access.",
138
156
  inputSchema: {
139
157
  type: "object",
140
158
  properties: {
141
159
  name: { type: "string" },
142
160
  description: { type: "string" },
143
161
  environment: { type: "string", enum: ["live", "test"], default: "live" },
144
- scopes: {
145
- type: "array",
146
- items: { type: "string" },
147
- description: "e.g. ['transfers:read', 'transfers:write', 'webhooks:manage']"
148
- },
162
+ scopes: { type: "array", items: { type: "string" } },
149
163
  expires_in_days: { type: "number" },
150
- allowed_ips: { type: "array", items: { type: "string" }, description: "CIDR allowlist" }
164
+ allowed_ips: { type: "array", items: { type: "string" } }
151
165
  },
152
166
  required: ["name"]
153
167
  }
154
168
  },
155
169
  {
156
170
  name: "coffrify_revoke_api_key",
157
- description: "Revoke an API key (immediate, irreversible).",
171
+ description: "Revoke an API key (irreversible).",
172
+ inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] }
173
+ },
174
+ {
175
+ name: "coffrify_rotate_api_key",
176
+ description: "Rotate an API key \u2014 mints a new one with same scopes. Old key revokes after grace_days (default 7, max 30).",
158
177
  inputSchema: {
159
178
  type: "object",
160
- properties: { id: { type: "string" } },
179
+ properties: {
180
+ id: { type: "string" },
181
+ grace_days: { type: "number", default: 7 }
182
+ },
161
183
  required: ["id"]
162
184
  }
163
185
  },
164
186
  {
165
187
  name: "coffrify_query_audit_log",
166
- description: "Query the workspace audit log. Pro tier: 30 days. Entreprise: 365 days. Filter by action, actor, time range.",
188
+ description: "Query the workspace audit log. Filter by action, actor, time range. Supports relative since (e.g. '24h').",
167
189
  inputSchema: {
168
190
  type: "object",
169
191
  properties: {
170
- action: { type: "string", description: "e.g. 'transfer.downloaded'" },
192
+ action: { type: "string" },
171
193
  actor_id: { type: "string" },
172
194
  resource_type: { type: "string" },
173
- since: { type: "string", description: "ISO date or relative (e.g. 24h ago)" },
174
- until: { type: "string", description: "ISO date" },
195
+ since: { type: "string" },
196
+ until: { type: "string" },
175
197
  limit: { type: "number", default: 100 }
176
198
  }
177
199
  }
@@ -182,22 +204,235 @@ var TOOLS = [
182
204
  inputSchema: { type: "object", properties: {} }
183
205
  }
184
206
  ];
185
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
186
- tools: TOOLS
207
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
208
+ resources: [
209
+ { uri: "coffrify://transfers", name: "transfers", description: "All recent transfers (JSON list)", mimeType: "application/json" },
210
+ { uri: "coffrify://webhooks", name: "webhooks", description: "All webhook endpoints", mimeType: "application/json" },
211
+ { uri: "coffrify://api-keys", name: "api-keys", description: "All API keys (hashed)", mimeType: "application/json" },
212
+ { uri: "coffrify://events", name: "events", description: "Catalog of every supported webhook event type", mimeType: "application/json" },
213
+ { uri: "coffrify://workspace", name: "workspace", description: "Current workspace + scopes", mimeType: "application/json" },
214
+ { uri: "coffrify://audit", name: "audit", description: "Recent audit log entries (last 100)", mimeType: "application/json" }
215
+ ]
187
216
  }));
217
+ server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
218
+ const uri = req.params.uri;
219
+ let data;
220
+ if (uri === "coffrify://transfers") {
221
+ data = await coffrify.transfers.list({ limit: 50 });
222
+ } else if (uri.startsWith("coffrify://transfers/")) {
223
+ data = await coffrify.transfers.get(uri.split("/").pop());
224
+ } else if (uri === "coffrify://webhooks") {
225
+ data = await coffrify.webhooks.list();
226
+ } else if (uri.startsWith("coffrify://webhooks/")) {
227
+ const id = uri.split("/").pop();
228
+ const [hooks, deliveries] = await Promise.all([
229
+ coffrify.webhooks.list(),
230
+ coffrify.webhooks.deliveries(id, { limit: 10 }).catch(() => null)
231
+ ]);
232
+ const webhook = (hooks.webhooks ?? hooks.data ?? []).find((w) => w.id === id);
233
+ data = { webhook, recent_deliveries: deliveries };
234
+ } else if (uri === "coffrify://api-keys") {
235
+ data = await coffrify.apiKeys.list();
236
+ } else if (uri === "coffrify://events") {
237
+ data = { catalog: COFFRIFY_EVENT_CATALOG };
238
+ } else if (uri === "coffrify://workspace") {
239
+ data = await coffrify.http.request("GET", "/me");
240
+ } else if (uri === "coffrify://audit") {
241
+ data = await coffrify.audit.list({ limit: 100 });
242
+ } else {
243
+ throw new Error(`Unknown resource: ${uri}`);
244
+ }
245
+ return {
246
+ contents: [{ uri, mimeType: "application/json", text: JSON.stringify(data, null, 2) }]
247
+ };
248
+ });
249
+ var PROMPTS = [
250
+ {
251
+ name: "send_invoice",
252
+ description: "Compose and send an invoice file as a Coffrify transfer with sensible defaults.",
253
+ arguments: [
254
+ { name: "recipient_email", description: "Recipient email", required: true },
255
+ { name: "invoice_filename", description: "Filename of the invoice (e.g. INV-2026-001.pdf)", required: true },
256
+ { name: "expires_in_days", description: "Expiration window (default 30)", required: false }
257
+ ]
258
+ },
259
+ {
260
+ name: "audit_weekly",
261
+ description: "Pull the last 7 days of audit log and summarize unusual activity.",
262
+ arguments: []
263
+ },
264
+ {
265
+ name: "rotate_all_api_keys",
266
+ description: "Rotate every API key in the workspace with a 7-day grace window.",
267
+ arguments: []
268
+ },
269
+ {
270
+ name: "subscribe_to_security_events",
271
+ description: "Create a webhook subscribed to all security-relevant events (geo_blocked, password_failed, scan_infected, suspicious_usage, saml.login_failed).",
272
+ arguments: [
273
+ { name: "endpoint_url", description: "HTTPS endpoint to receive events", required: true }
274
+ ]
275
+ },
276
+ {
277
+ name: "investigate_transfer",
278
+ description: "Pull a transfer + its scan results + recent webhook deliveries to investigate an incident.",
279
+ arguments: [
280
+ { name: "short_code", description: "Short code or ID of the transfer", required: true }
281
+ ]
282
+ }
283
+ ];
284
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: PROMPTS }));
285
+ server.setRequestHandler(GetPromptRequestSchema, async (req) => {
286
+ const { name, arguments: args = {} } = req.params;
287
+ const a = args;
288
+ switch (name) {
289
+ case "send_invoice":
290
+ return {
291
+ description: "Send an invoice via Coffrify",
292
+ messages: [{
293
+ role: "user",
294
+ content: {
295
+ type: "text",
296
+ text: `Send the file "${a.invoice_filename}" as a Coffrify transfer.
297
+ Recipient: ${a.recipient_email}.
298
+ Expiration: ${a.expires_in_days ?? "30"} days.
299
+ Use the coffrify_create_transfer tool with: title="Invoice ${a.invoice_filename}", expires_in_hours=${parseInt(a.expires_in_days ?? "30", 10) * 24}, max_downloads=3.
300
+ Then share the returned share_url with the recipient.`
301
+ }
302
+ }]
303
+ };
304
+ case "audit_weekly":
305
+ return {
306
+ description: "Weekly audit summary",
307
+ messages: [{
308
+ role: "user",
309
+ content: {
310
+ type: "text",
311
+ text: `Use coffrify_query_audit_log with since="7d" and limit=500 to fetch the last week of activity.
312
+ Summarize:
313
+ 1. Top 5 most frequent actions
314
+ 2. Any failed/denied actions and what they indicate
315
+ 3. Unusual IP addresses or geo distributions
316
+ 4. Any api_key.suspicious_usage or saml.login_failed events
317
+ Output a brief markdown report.`
318
+ }
319
+ }]
320
+ };
321
+ case "rotate_all_api_keys":
322
+ return {
323
+ description: "Rotate every API key with 7-day grace",
324
+ messages: [{
325
+ role: "user",
326
+ content: {
327
+ type: "text",
328
+ text: `1. Call coffrify_list_api_keys to get all active keys.
329
+ 2. For each key with status=active and not already rotated, call coffrify_rotate_api_key with grace_days=7.
330
+ 3. Report: { rotated_count, new_keys: [{ name, new_prefix, grace_until }] }.
331
+ Stress that the new key values are returned ONCE \u2014 they must be saved immediately.`
332
+ }
333
+ }]
334
+ };
335
+ case "subscribe_to_security_events":
336
+ return {
337
+ description: "Wire a webhook to all security events",
338
+ messages: [{
339
+ role: "user",
340
+ content: {
341
+ type: "text",
342
+ text: `Call coffrify_create_webhook with:
343
+ name = "Security events"
344
+ url = "${a.endpoint_url}"
345
+ events = ["transfer.password_failed", "transfer.geo_blocked", "transfer.scan_infected", "transfer.scan_quarantined",
346
+ "api_key.suspicious_usage", "api_key.expired", "api_key.revoked",
347
+ "saml.login_failed", "audit.policy_violated",
348
+ "webhook.endpoint_disabled", "workspace.payment_failed"]
349
+ Print the returned signing secret to the user \u2014 it's shown only once.`
350
+ }
351
+ }]
352
+ };
353
+ case "investigate_transfer":
354
+ return {
355
+ description: "Investigate a transfer incident",
356
+ messages: [{
357
+ role: "user",
358
+ content: {
359
+ type: "text",
360
+ text: `Investigate transfer "${a.short_code}":
361
+ 1. Use coffrify_search_transfers query="${a.short_code}" to locate it.
362
+ 2. Call coffrify_get_transfer with the id to fetch full details (scan_status, downloads, password_protected).
363
+ 3. Call coffrify_query_audit_log with resource_id=<that id> since="30d" to pull related audit entries.
364
+ 4. List any recent webhook deliveries that mention this transfer.
365
+ 5. Output a timeline + a diagnosis of what happened.`
366
+ }
367
+ }]
368
+ };
369
+ default:
370
+ throw new Error(`Unknown prompt: ${name}`);
371
+ }
372
+ });
373
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
374
+ const enabledBuiltins = TOOLS.filter((t) => !disabledBuiltinTools.has(t.name));
375
+ const customTools = [...customActionsByName.values()].map((a) => ({
376
+ name: a.name,
377
+ description: a.runtime_kind === "typescript" ? `[TS, awaiting v0.4 runtime] ${a.description}` : a.description,
378
+ inputSchema: a.input_schema
379
+ }));
380
+ return { tools: [...enabledBuiltins, ...customTools] };
381
+ });
382
+ async function invokeCustomAction(action, input) {
383
+ if (action.runtime_kind === "typescript") {
384
+ return {
385
+ _stub: true,
386
+ message: "TypeScript runtime arrives in @coffrify/mcp v0.4.0 (Vercel Sandbox). Code persisted in DB but not executed yet.",
387
+ action_name: action.name,
388
+ received_input: input
389
+ };
390
+ }
391
+ let path = action.endpoint_path;
392
+ const queryParams = {};
393
+ let body = null;
394
+ for (const mapping of action.param_mapping ?? []) {
395
+ const value = input[mapping.name];
396
+ if (value === void 0 || value === null) continue;
397
+ if (mapping.source === "path") {
398
+ path = path.replace(`{${mapping.name}}`, encodeURIComponent(String(value)));
399
+ } else if (mapping.source === "query") {
400
+ queryParams[mapping.name] = String(value);
401
+ } else if (mapping.source === "body") {
402
+ body = body ?? {};
403
+ body[mapping.name] = value;
404
+ }
405
+ }
406
+ if (Object.keys(queryParams).length > 0) {
407
+ const qs = new URLSearchParams(queryParams).toString();
408
+ path = `${path}${path.includes("?") ? "&" : "?"}${qs}`;
409
+ }
410
+ const trimmedPath = path.replace(/^\/v1/, "") || "/";
411
+ return await coffrify.http.request(action.endpoint_method, trimmedPath, body ?? void 0);
412
+ }
188
413
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
189
414
  const { name, arguments: args = {} } = request.params;
190
415
  try {
191
416
  let result;
417
+ const a = args;
192
418
  switch (name) {
193
419
  case "coffrify_list_transfers":
194
- result = await coffrify.transfers.list(args);
420
+ result = await coffrify.transfers.list(a);
421
+ break;
422
+ case "coffrify_search_transfers": {
423
+ const list = await coffrify.transfers.list({ limit: 100 });
424
+ const arr = list.transfers ?? list.data ?? [];
425
+ const q = a.query.toLowerCase();
426
+ const matches = arr.filter(
427
+ (t) => (t.transfer_title ?? "").toLowerCase().includes(q) || (t.short_code ?? "").toLowerCase().startsWith(q)
428
+ ).slice(0, a.limit ?? 20);
429
+ result = { matches, total: matches.length };
195
430
  break;
431
+ }
196
432
  case "coffrify_get_transfer":
197
- result = await coffrify.transfers.get(args.id);
433
+ result = await coffrify.transfers.get(a.id);
198
434
  break;
199
- case "coffrify_create_transfer": {
200
- const a = args;
435
+ case "coffrify_create_transfer":
201
436
  result = await coffrify.transfers.create(a.files, {
202
437
  expires_in_hours: a.expires_in_hours,
203
438
  max_downloads: a.max_downloads,
@@ -206,68 +441,124 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
206
441
  burn_after_read: a.burn_after_read
207
442
  });
208
443
  break;
209
- }
210
444
  case "coffrify_delete_transfer":
211
- result = await coffrify.transfers.delete(args.id);
445
+ result = await coffrify.transfers.delete(a.id);
446
+ break;
447
+ case "coffrify_bulk_delete_transfers": {
448
+ const out = [];
449
+ for (const id of a.ids) {
450
+ try {
451
+ await coffrify.transfers.delete(id);
452
+ out.push({ id, ok: true });
453
+ } catch (e) {
454
+ out.push({ id, ok: false, error: e?.message ?? String(e) });
455
+ }
456
+ }
457
+ result = { results: out, ok_count: out.filter((r) => r.ok).length };
212
458
  break;
459
+ }
213
460
  case "coffrify_list_webhooks":
214
461
  result = await coffrify.webhooks.list();
215
462
  break;
216
- case "coffrify_create_webhook": {
217
- const a = args;
218
- result = await coffrify.webhooks.create({ name: a.name, url: a.url, events: a.events });
463
+ case "coffrify_create_webhook":
464
+ result = await coffrify.webhooks.create({
465
+ name: a.name,
466
+ url: a.url,
467
+ events: a.events,
468
+ description: a.description
469
+ });
219
470
  break;
220
- }
221
471
  case "coffrify_test_webhook":
222
- result = await coffrify.webhooks.test(args.id);
472
+ result = await coffrify.webhooks.test(a.id);
223
473
  break;
224
- case "coffrify_list_webhook_deliveries": {
225
- const a = args;
474
+ case "coffrify_list_webhook_deliveries":
226
475
  result = await coffrify.webhooks.deliveries(a.webhook_id, { limit: a.limit });
227
476
  break;
228
- }
229
477
  case "coffrify_replay_webhook_delivery":
230
- result = await coffrify.webhooks.replay(args.delivery_id);
478
+ result = await coffrify.webhooks.replay(a.delivery_id);
479
+ break;
480
+ case "coffrify_list_webhook_event_types": {
481
+ const filtered = a.family ? COFFRIFY_EVENT_CATALOG.filter((e) => e.family === a.family) : COFFRIFY_EVENT_CATALOG;
482
+ result = { events: filtered, total: filtered.length };
231
483
  break;
484
+ }
232
485
  case "coffrify_list_api_keys":
233
486
  result = await coffrify.apiKeys.list();
234
487
  break;
235
488
  case "coffrify_create_api_key":
236
- result = await coffrify.apiKeys.create(args);
489
+ result = await coffrify.apiKeys.create(a);
237
490
  break;
238
491
  case "coffrify_revoke_api_key":
239
- result = await coffrify.apiKeys.revoke(args.id);
492
+ result = await coffrify.apiKeys.revoke(a.id);
493
+ break;
494
+ case "coffrify_rotate_api_key":
495
+ result = await coffrify.http.request(
496
+ "POST",
497
+ `/api-keys/${encodeURIComponent(a.id)}/rotate`,
498
+ { grace_days: a.grace_days ?? 7 }
499
+ );
240
500
  break;
241
501
  case "coffrify_query_audit_log": {
242
- const a = args;
243
502
  let since = a.since;
244
503
  if (since && /^\d+[hdw]$/.test(since)) {
245
504
  const m = since.match(/^(\d+)([hdw])$/);
246
- const n = parseInt(m[1]);
505
+ const n = parseInt(m[1], 10);
247
506
  const ms = m[2] === "h" ? n * 36e5 : m[2] === "d" ? n * 864e5 : n * 7 * 864e5;
248
507
  since = new Date(Date.now() - ms).toISOString();
249
508
  }
250
509
  result = await coffrify.audit.list({ ...a, since });
251
510
  break;
252
511
  }
253
- case "coffrify_get_workspace_info": {
512
+ case "coffrify_get_workspace_info":
254
513
  result = await coffrify.http.request("GET", "/me");
255
514
  break;
256
- }
257
- default:
515
+ default: {
516
+ const custom = customActionsByName.get(name);
517
+ if (custom) {
518
+ result = await invokeCustomAction(custom, a);
519
+ break;
520
+ }
258
521
  throw new Error(`Unknown tool: ${name}`);
522
+ }
259
523
  }
260
- return {
261
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
262
- };
524
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
263
525
  } catch (e) {
264
526
  return {
265
- content: [{ type: "text", text: `Error: ${e?.message ?? "unknown"}
266
- ${JSON.stringify(e?.details ?? {}, null, 2)}` }],
527
+ content: [{
528
+ type: "text",
529
+ text: `Error: ${e?.message ?? "unknown"}
530
+ ${JSON.stringify(e?.details ?? {}, null, 2)}`
531
+ }],
267
532
  isError: true
268
533
  };
269
534
  }
270
535
  });
536
+ async function bootstrapWorkspaceConfig() {
537
+ const httpClient = coffrify.http;
538
+ try {
539
+ const toolsRes = await httpClient.request("GET", "/mcp/tools");
540
+ for (const t of toolsRes?.data ?? []) {
541
+ if (t.disabled) disabledBuiltinTools.add(t.name);
542
+ }
543
+ } catch (e) {
544
+ const msg = e?.message ?? String(e);
545
+ console.error(`[coffrify-mcp] bootstrap: tool-overrides fetch failed (${msg}). Continuing with all builtins enabled.`);
546
+ }
547
+ try {
548
+ const actionsRes = await httpClient.request("GET", "/mcp/custom-actions");
549
+ for (const a of actionsRes?.data ?? []) {
550
+ if (a.is_active) customActionsByName.set(a.name, a);
551
+ }
552
+ } catch (e) {
553
+ const msg = e?.message ?? String(e);
554
+ console.error(`[coffrify-mcp] bootstrap: custom-actions fetch failed (${msg}). Continuing with builtins only.`);
555
+ }
556
+ }
557
+ await bootstrapWorkspaceConfig();
271
558
  var transport = new StdioServerTransport();
272
559
  await server.connect(transport);
273
- console.error("[coffrify-mcp] Server connected via stdio");
560
+ var enabledBuiltinCount = TOOLS.filter((t) => !disabledBuiltinTools.has(t.name)).length;
561
+ var customCount = customActionsByName.size;
562
+ console.error(
563
+ `[coffrify-mcp] v0.3.0 connected via stdio \u2014 ${enabledBuiltinCount}/${TOOLS.length} builtins enabled, ${customCount} custom action${customCount === 1 ? "" : "s"}.`
564
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffrify/mcp",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "Coffrify MCP server — gives Claude/Cursor/Windsurf agents direct access to Coffrify (transfers, webhooks, audit, API keys).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,7 +29,7 @@
29
29
  "dependencies": {
30
30
  "@modelcontextprotocol/sdk": "^1.0.0",
31
31
  "zod": "^3.23.8",
32
- "@coffrify/sdk": "0.3.6"
32
+ "@coffrify/sdk": "0.5.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "^22.0.0",