@codespar/mcp-rd-station 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,15 +2,25 @@
2
2
  /**
3
3
  * MCP Server for RD Station — Brazilian CRM and marketing automation.
4
4
  *
5
- * Tools:
5
+ * Tools (18):
6
6
  * - create_contact: Create a contact in RD Station
7
7
  * - update_contact: Update a contact by UUID
8
+ * - upsert_contact: Upsert contact by email (Marketing API)
8
9
  * - get_contact: Get contact details by UUID or email
9
10
  * - list_contacts: List contacts with pagination
11
+ * - delete_contact: Delete a contact by UUID
10
12
  * - create_event: Create a conversion event
11
13
  * - list_funnels: List sales funnels
12
14
  * - get_funnel: Get funnel details with stages
15
+ * - list_deal_stages: List deal stages of a pipeline
13
16
  * - create_opportunity: Create a sales opportunity
17
+ * - update_deal: Update a deal/opportunity by ID
18
+ * - get_deal: Get a deal/opportunity by ID
19
+ * - list_deals: List deals with filters
20
+ * - list_segmentations: List contact segmentations
21
+ * - get_segmentation_contacts: List contacts of a segmentation
22
+ * - update_lead_scoring: Mark a contact as lead/opportunity (lead scoring)
23
+ * - create_webhook: Subscribe a webhook to RD Station events
14
24
  *
15
25
  * Environment:
16
26
  * RD_STATION_TOKEN — Bearer token from https://app.rdstation.com/
@@ -35,9 +45,20 @@ async function rdStationRequest(method, path, body) {
35
45
  const err = await res.text();
36
46
  throw new Error(`RD Station API ${res.status}: ${err}`);
37
47
  }
38
- return res.json();
48
+ // Some DELETE endpoints return 204 No Content
49
+ if (res.status === 204)
50
+ return { ok: true };
51
+ const text = await res.text();
52
+ if (!text)
53
+ return { ok: true };
54
+ try {
55
+ return JSON.parse(text);
56
+ }
57
+ catch {
58
+ return { raw: text };
59
+ }
39
60
  }
40
- const server = new Server({ name: "mcp-rd-station", version: "0.1.0" }, { capabilities: { tools: {} } });
61
+ const server = new Server({ name: "mcp-rd-station", version: "0.2.0" }, { capabilities: { tools: {} } });
41
62
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
42
63
  tools: [
43
64
  {
@@ -78,6 +99,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
78
99
  required: ["uuid"],
79
100
  },
80
101
  },
102
+ {
103
+ name: "upsert_contact",
104
+ description: "Upsert (create or update) a contact identified by email (Marketing API)",
105
+ inputSchema: {
106
+ type: "object",
107
+ properties: {
108
+ email: { type: "string", description: "Identifier email (used in path)" },
109
+ name: { type: "string", description: "Contact name" },
110
+ job_title: { type: "string", description: "Job title" },
111
+ mobile_phone: { type: "string", description: "Mobile phone" },
112
+ tags: { type: "array", items: { type: "string" }, description: "Tags" },
113
+ cf_custom_fields: { type: "object", description: "Custom fields (key-value)" },
114
+ },
115
+ required: ["email"],
116
+ },
117
+ },
81
118
  {
82
119
  name: "get_contact",
83
120
  description: "Get contact details by UUID or email",
@@ -101,6 +138,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
101
138
  },
102
139
  },
103
140
  },
141
+ {
142
+ name: "delete_contact",
143
+ description: "Delete a contact by UUID",
144
+ inputSchema: {
145
+ type: "object",
146
+ properties: {
147
+ uuid: { type: "string", description: "Contact UUID" },
148
+ },
149
+ required: ["uuid"],
150
+ },
151
+ },
104
152
  {
105
153
  name: "create_event",
106
154
  description: "Create a conversion event for a contact",
@@ -140,6 +188,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
140
188
  required: ["id"],
141
189
  },
142
190
  },
191
+ {
192
+ name: "list_deal_stages",
193
+ description: "List deal stages of a pipeline (funnel)",
194
+ inputSchema: {
195
+ type: "object",
196
+ properties: {
197
+ deal_pipeline_id: { type: "string", description: "Pipeline (funnel) ID — optional filter" },
198
+ },
199
+ },
200
+ },
143
201
  {
144
202
  name: "create_opportunity",
145
203
  description: "Create a sales opportunity in a funnel",
@@ -155,6 +213,117 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
155
213
  required: ["deal_stage_id", "name"],
156
214
  },
157
215
  },
216
+ {
217
+ name: "update_deal",
218
+ description: "Update a deal/opportunity by ID",
219
+ inputSchema: {
220
+ type: "object",
221
+ properties: {
222
+ id: { type: "string", description: "Deal ID" },
223
+ name: { type: "string", description: "Updated name" },
224
+ amount_total: { type: "number", description: "Total amount" },
225
+ deal_stage_id: { type: "string", description: "Move to this stage" },
226
+ win: { type: "boolean", description: "Mark as won" },
227
+ prediction_date: { type: "string", description: "Updated prediction date (YYYY-MM-DD)" },
228
+ },
229
+ required: ["id"],
230
+ },
231
+ },
232
+ {
233
+ name: "get_deal",
234
+ description: "Get a deal/opportunity by ID",
235
+ inputSchema: {
236
+ type: "object",
237
+ properties: {
238
+ id: { type: "string", description: "Deal ID" },
239
+ },
240
+ required: ["id"],
241
+ },
242
+ },
243
+ {
244
+ name: "list_deals",
245
+ description: "List deals with optional filters and pagination",
246
+ inputSchema: {
247
+ type: "object",
248
+ properties: {
249
+ page: { type: "number", description: "Page number (default 1)" },
250
+ limit: { type: "number", description: "Results per page (default 20)" },
251
+ deal_stage_id: { type: "string", description: "Filter by stage ID" },
252
+ deal_pipeline_id: { type: "string", description: "Filter by pipeline ID" },
253
+ user_id: { type: "string", description: "Filter by owner user ID" },
254
+ win: { type: "string", description: "Filter by win status: true | false | null" },
255
+ },
256
+ },
257
+ },
258
+ {
259
+ name: "list_segmentations",
260
+ description: "List contact segmentations",
261
+ inputSchema: {
262
+ type: "object",
263
+ properties: {
264
+ page: { type: "number", description: "Page number" },
265
+ page_size: { type: "number", description: "Page size" },
266
+ },
267
+ },
268
+ },
269
+ {
270
+ name: "get_segmentation_contacts",
271
+ description: "List contacts inside a given segmentation",
272
+ inputSchema: {
273
+ type: "object",
274
+ properties: {
275
+ segmentation_id: { type: "string", description: "Segmentation ID" },
276
+ page: { type: "number", description: "Page number" },
277
+ page_size: { type: "number", description: "Page size" },
278
+ },
279
+ required: ["segmentation_id"],
280
+ },
281
+ },
282
+ {
283
+ name: "update_lead_scoring",
284
+ description: "Mark a contact as lead, qualified lead, or opportunity (lead scoring)",
285
+ inputSchema: {
286
+ type: "object",
287
+ properties: {
288
+ email: { type: "string", description: "Contact email" },
289
+ status: {
290
+ type: "string",
291
+ enum: ["opportunity", "qualified_lead", "lead", "client"],
292
+ description: "Lifecycle status to apply",
293
+ },
294
+ value: { type: "boolean", description: "Set/unset the status (default true)" },
295
+ },
296
+ required: ["email", "status"],
297
+ },
298
+ },
299
+ {
300
+ name: "create_webhook",
301
+ description: "Subscribe a webhook to RD Station events (WEBHOOK.CONVERTED / WEBHOOK.MARKED_OPPORTUNITY)",
302
+ inputSchema: {
303
+ type: "object",
304
+ properties: {
305
+ entity_type: { type: "string", description: "Entity type (e.g. CONTACT)" },
306
+ event_type: {
307
+ type: "string",
308
+ enum: ["WEBHOOK.CONVERTED", "WEBHOOK.MARKED_OPPORTUNITY"],
309
+ description: "Event type to subscribe to",
310
+ },
311
+ event_identifiers: {
312
+ type: "array",
313
+ items: { type: "string" },
314
+ description: "Optional list of conversion identifiers",
315
+ },
316
+ url: { type: "string", description: "Destination URL" },
317
+ http_method: { type: "string", enum: ["POST", "GET"], description: "HTTP method (default POST)" },
318
+ include_relations: {
319
+ type: "array",
320
+ items: { type: "string" },
321
+ description: "Relations to include (e.g. COMPANY, CONTACT_FUNNEL)",
322
+ },
323
+ },
324
+ required: ["entity_type", "event_type", "url"],
325
+ },
326
+ },
158
327
  ],
159
328
  }));
160
329
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
@@ -169,6 +338,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
169
338
  delete body.uuid;
170
339
  return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("PATCH", `/platform/contacts/${uuid}`, body), null, 2) }] };
171
340
  }
341
+ case "upsert_contact": {
342
+ const email = args?.email;
343
+ const body = { ...args };
344
+ delete body.email;
345
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("PATCH", `/platform/contacts/email:${email}`, body), null, 2) }] };
346
+ }
172
347
  case "get_contact": {
173
348
  if (args?.uuid) {
174
349
  return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/contacts/${args.uuid}`), null, 2) }] };
@@ -185,14 +360,73 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
185
360
  params.set("query", String(args.query));
186
361
  return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/contacts?${params}`), null, 2) }] };
187
362
  }
363
+ case "delete_contact":
364
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("DELETE", `/platform/contacts/${args?.uuid}`), null, 2) }] };
188
365
  case "create_event":
189
366
  return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("POST", "/platform/events", args), null, 2) }] };
190
367
  case "list_funnels":
191
368
  return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", "/platform/deal_pipelines"), null, 2) }] };
192
369
  case "get_funnel":
193
370
  return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/deal_pipelines/${args?.id}`), null, 2) }] };
371
+ case "list_deal_stages": {
372
+ const params = new URLSearchParams();
373
+ if (args?.deal_pipeline_id)
374
+ params.set("deal_pipeline_id", String(args.deal_pipeline_id));
375
+ const qs = params.toString();
376
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/deal_stages${qs ? `?${qs}` : ""}`), null, 2) }] };
377
+ }
194
378
  case "create_opportunity":
195
379
  return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("POST", "/platform/deals", args), null, 2) }] };
380
+ case "update_deal": {
381
+ const id = args?.id;
382
+ const body = { ...args };
383
+ delete body.id;
384
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("PUT", `/platform/deals/${id}`, body), null, 2) }] };
385
+ }
386
+ case "get_deal":
387
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/deals/${args?.id}`), null, 2) }] };
388
+ case "list_deals": {
389
+ const params = new URLSearchParams();
390
+ if (args?.page)
391
+ params.set("page", String(args.page));
392
+ if (args?.limit)
393
+ params.set("limit", String(args.limit));
394
+ if (args?.deal_stage_id)
395
+ params.set("deal_stage_id", String(args.deal_stage_id));
396
+ if (args?.deal_pipeline_id)
397
+ params.set("deal_pipeline_id", String(args.deal_pipeline_id));
398
+ if (args?.user_id)
399
+ params.set("user_id", String(args.user_id));
400
+ if (args?.win !== undefined)
401
+ params.set("win", String(args.win));
402
+ const qs = params.toString();
403
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/deals${qs ? `?${qs}` : ""}`), null, 2) }] };
404
+ }
405
+ case "list_segmentations": {
406
+ const params = new URLSearchParams();
407
+ if (args?.page)
408
+ params.set("page", String(args.page));
409
+ if (args?.page_size)
410
+ params.set("page_size", String(args.page_size));
411
+ const qs = params.toString();
412
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/segmentations${qs ? `?${qs}` : ""}`), null, 2) }] };
413
+ }
414
+ case "get_segmentation_contacts": {
415
+ const params = new URLSearchParams();
416
+ if (args?.page)
417
+ params.set("page", String(args.page));
418
+ if (args?.page_size)
419
+ params.set("page_size", String(args.page_size));
420
+ const qs = params.toString();
421
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/segmentations/${args?.segmentation_id}/contacts${qs ? `?${qs}` : ""}`), null, 2) }] };
422
+ }
423
+ case "update_lead_scoring": {
424
+ const status = String(args?.status ?? "lead");
425
+ const value = args?.value === undefined ? true : Boolean(args.value);
426
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("POST", `/platform/contacts/email:${args?.email}/funnels/default`, { lifecycle_stage: status, value }), null, 2) }] };
427
+ }
428
+ case "create_webhook":
429
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("POST", "/integrations/webhooks", args), null, 2) }] };
196
430
  default:
197
431
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
198
432
  }
@@ -219,7 +453,7 @@ async function main() {
219
453
  const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } });
220
454
  t.onclose = () => { if (t.sessionId)
221
455
  transports.delete(t.sessionId); };
222
- const s = new Server({ name: "mcp-rd-station", version: "0.1.0" }, { capabilities: { tools: {} } });
456
+ const s = new Server({ name: "mcp-rd-station", version: "0.2.0" }, { capabilities: { tools: {} } });
223
457
  server._requestHandlers.forEach((v, k) => s._requestHandlers.set(k, v));
224
458
  server._notificationHandlers?.forEach((v, k) => s._notificationHandlers.set(k, v));
225
459
  await s.connect(t);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@codespar/mcp-rd-station",
3
- "version": "0.1.2",
4
- "description": "MCP server for RD Station — contacts, events, funnels, opportunities",
3
+ "version": "0.2.0",
4
+ "description": "MCP server for RD Station — contacts, events, funnels, deals, segmentations, lead scoring, webhooks",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "bin": {
package/server.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.codespar/mcp-rd-station",
4
- "description": "MCP server for RD Station — contacts, events, funnels, opportunities",
4
+ "description": "MCP server for RD Station — contacts, events, funnels, deals, segmentations, lead scoring, webhooks",
5
5
  "repository": {
6
6
  "url": "https://github.com/codespar/mcp-dev-brasil",
7
7
  "source": "github",
8
8
  "subfolder": "packages/communication/rd-station"
9
9
  },
10
- "version": "0.1.2",
10
+ "version": "0.2.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "@codespar/mcp-rd-station",
15
- "version": "0.1.2",
15
+ "version": "0.2.0",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },
package/src/index.ts CHANGED
@@ -3,15 +3,25 @@
3
3
  /**
4
4
  * MCP Server for RD Station — Brazilian CRM and marketing automation.
5
5
  *
6
- * Tools:
6
+ * Tools (18):
7
7
  * - create_contact: Create a contact in RD Station
8
8
  * - update_contact: Update a contact by UUID
9
+ * - upsert_contact: Upsert contact by email (Marketing API)
9
10
  * - get_contact: Get contact details by UUID or email
10
11
  * - list_contacts: List contacts with pagination
12
+ * - delete_contact: Delete a contact by UUID
11
13
  * - create_event: Create a conversion event
12
14
  * - list_funnels: List sales funnels
13
15
  * - get_funnel: Get funnel details with stages
16
+ * - list_deal_stages: List deal stages of a pipeline
14
17
  * - create_opportunity: Create a sales opportunity
18
+ * - update_deal: Update a deal/opportunity by ID
19
+ * - get_deal: Get a deal/opportunity by ID
20
+ * - list_deals: List deals with filters
21
+ * - list_segmentations: List contact segmentations
22
+ * - get_segmentation_contacts: List contacts of a segmentation
23
+ * - update_lead_scoring: Mark a contact as lead/opportunity (lead scoring)
24
+ * - create_webhook: Subscribe a webhook to RD Station events
15
25
  *
16
26
  * Environment:
17
27
  * RD_STATION_TOKEN — Bearer token from https://app.rdstation.com/
@@ -42,11 +52,15 @@ async function rdStationRequest(method: string, path: string, body?: unknown): P
42
52
  const err = await res.text();
43
53
  throw new Error(`RD Station API ${res.status}: ${err}`);
44
54
  }
45
- return res.json();
55
+ // Some DELETE endpoints return 204 No Content
56
+ if (res.status === 204) return { ok: true };
57
+ const text = await res.text();
58
+ if (!text) return { ok: true };
59
+ try { return JSON.parse(text); } catch { return { raw: text }; }
46
60
  }
47
61
 
48
62
  const server = new Server(
49
- { name: "mcp-rd-station", version: "0.1.0" },
63
+ { name: "mcp-rd-station", version: "0.2.0" },
50
64
  { capabilities: { tools: {} } }
51
65
  );
52
66
 
@@ -90,6 +104,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
90
104
  required: ["uuid"],
91
105
  },
92
106
  },
107
+ {
108
+ name: "upsert_contact",
109
+ description: "Upsert (create or update) a contact identified by email (Marketing API)",
110
+ inputSchema: {
111
+ type: "object",
112
+ properties: {
113
+ email: { type: "string", description: "Identifier email (used in path)" },
114
+ name: { type: "string", description: "Contact name" },
115
+ job_title: { type: "string", description: "Job title" },
116
+ mobile_phone: { type: "string", description: "Mobile phone" },
117
+ tags: { type: "array", items: { type: "string" }, description: "Tags" },
118
+ cf_custom_fields: { type: "object", description: "Custom fields (key-value)" },
119
+ },
120
+ required: ["email"],
121
+ },
122
+ },
93
123
  {
94
124
  name: "get_contact",
95
125
  description: "Get contact details by UUID or email",
@@ -113,6 +143,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
113
143
  },
114
144
  },
115
145
  },
146
+ {
147
+ name: "delete_contact",
148
+ description: "Delete a contact by UUID",
149
+ inputSchema: {
150
+ type: "object",
151
+ properties: {
152
+ uuid: { type: "string", description: "Contact UUID" },
153
+ },
154
+ required: ["uuid"],
155
+ },
156
+ },
116
157
  {
117
158
  name: "create_event",
118
159
  description: "Create a conversion event for a contact",
@@ -152,6 +193,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
152
193
  required: ["id"],
153
194
  },
154
195
  },
196
+ {
197
+ name: "list_deal_stages",
198
+ description: "List deal stages of a pipeline (funnel)",
199
+ inputSchema: {
200
+ type: "object",
201
+ properties: {
202
+ deal_pipeline_id: { type: "string", description: "Pipeline (funnel) ID — optional filter" },
203
+ },
204
+ },
205
+ },
155
206
  {
156
207
  name: "create_opportunity",
157
208
  description: "Create a sales opportunity in a funnel",
@@ -167,6 +218,117 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
167
218
  required: ["deal_stage_id", "name"],
168
219
  },
169
220
  },
221
+ {
222
+ name: "update_deal",
223
+ description: "Update a deal/opportunity by ID",
224
+ inputSchema: {
225
+ type: "object",
226
+ properties: {
227
+ id: { type: "string", description: "Deal ID" },
228
+ name: { type: "string", description: "Updated name" },
229
+ amount_total: { type: "number", description: "Total amount" },
230
+ deal_stage_id: { type: "string", description: "Move to this stage" },
231
+ win: { type: "boolean", description: "Mark as won" },
232
+ prediction_date: { type: "string", description: "Updated prediction date (YYYY-MM-DD)" },
233
+ },
234
+ required: ["id"],
235
+ },
236
+ },
237
+ {
238
+ name: "get_deal",
239
+ description: "Get a deal/opportunity by ID",
240
+ inputSchema: {
241
+ type: "object",
242
+ properties: {
243
+ id: { type: "string", description: "Deal ID" },
244
+ },
245
+ required: ["id"],
246
+ },
247
+ },
248
+ {
249
+ name: "list_deals",
250
+ description: "List deals with optional filters and pagination",
251
+ inputSchema: {
252
+ type: "object",
253
+ properties: {
254
+ page: { type: "number", description: "Page number (default 1)" },
255
+ limit: { type: "number", description: "Results per page (default 20)" },
256
+ deal_stage_id: { type: "string", description: "Filter by stage ID" },
257
+ deal_pipeline_id: { type: "string", description: "Filter by pipeline ID" },
258
+ user_id: { type: "string", description: "Filter by owner user ID" },
259
+ win: { type: "string", description: "Filter by win status: true | false | null" },
260
+ },
261
+ },
262
+ },
263
+ {
264
+ name: "list_segmentations",
265
+ description: "List contact segmentations",
266
+ inputSchema: {
267
+ type: "object",
268
+ properties: {
269
+ page: { type: "number", description: "Page number" },
270
+ page_size: { type: "number", description: "Page size" },
271
+ },
272
+ },
273
+ },
274
+ {
275
+ name: "get_segmentation_contacts",
276
+ description: "List contacts inside a given segmentation",
277
+ inputSchema: {
278
+ type: "object",
279
+ properties: {
280
+ segmentation_id: { type: "string", description: "Segmentation ID" },
281
+ page: { type: "number", description: "Page number" },
282
+ page_size: { type: "number", description: "Page size" },
283
+ },
284
+ required: ["segmentation_id"],
285
+ },
286
+ },
287
+ {
288
+ name: "update_lead_scoring",
289
+ description: "Mark a contact as lead, qualified lead, or opportunity (lead scoring)",
290
+ inputSchema: {
291
+ type: "object",
292
+ properties: {
293
+ email: { type: "string", description: "Contact email" },
294
+ status: {
295
+ type: "string",
296
+ enum: ["opportunity", "qualified_lead", "lead", "client"],
297
+ description: "Lifecycle status to apply",
298
+ },
299
+ value: { type: "boolean", description: "Set/unset the status (default true)" },
300
+ },
301
+ required: ["email", "status"],
302
+ },
303
+ },
304
+ {
305
+ name: "create_webhook",
306
+ description: "Subscribe a webhook to RD Station events (WEBHOOK.CONVERTED / WEBHOOK.MARKED_OPPORTUNITY)",
307
+ inputSchema: {
308
+ type: "object",
309
+ properties: {
310
+ entity_type: { type: "string", description: "Entity type (e.g. CONTACT)" },
311
+ event_type: {
312
+ type: "string",
313
+ enum: ["WEBHOOK.CONVERTED", "WEBHOOK.MARKED_OPPORTUNITY"],
314
+ description: "Event type to subscribe to",
315
+ },
316
+ event_identifiers: {
317
+ type: "array",
318
+ items: { type: "string" },
319
+ description: "Optional list of conversion identifiers",
320
+ },
321
+ url: { type: "string", description: "Destination URL" },
322
+ http_method: { type: "string", enum: ["POST", "GET"], description: "HTTP method (default POST)" },
323
+ include_relations: {
324
+ type: "array",
325
+ items: { type: "string" },
326
+ description: "Relations to include (e.g. COMPANY, CONTACT_FUNNEL)",
327
+ },
328
+ },
329
+ required: ["entity_type", "event_type", "url"],
330
+ },
331
+ },
170
332
  ],
171
333
  }));
172
334
 
@@ -183,6 +345,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
183
345
  delete body.uuid;
184
346
  return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("PATCH", `/platform/contacts/${uuid}`, body), null, 2) }] };
185
347
  }
348
+ case "upsert_contact": {
349
+ const email = args?.email;
350
+ const body = { ...args } as Record<string, unknown>;
351
+ delete body.email;
352
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("PATCH", `/platform/contacts/email:${email}`, body), null, 2) }] };
353
+ }
186
354
  case "get_contact": {
187
355
  if (args?.uuid) {
188
356
  return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/contacts/${args.uuid}`), null, 2) }] };
@@ -196,14 +364,62 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
196
364
  if (args?.query) params.set("query", String(args.query));
197
365
  return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/contacts?${params}`), null, 2) }] };
198
366
  }
367
+ case "delete_contact":
368
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("DELETE", `/platform/contacts/${args?.uuid}`), null, 2) }] };
199
369
  case "create_event":
200
370
  return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("POST", "/platform/events", args), null, 2) }] };
201
371
  case "list_funnels":
202
372
  return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", "/platform/deal_pipelines"), null, 2) }] };
203
373
  case "get_funnel":
204
374
  return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/deal_pipelines/${args?.id}`), null, 2) }] };
375
+ case "list_deal_stages": {
376
+ const params = new URLSearchParams();
377
+ if (args?.deal_pipeline_id) params.set("deal_pipeline_id", String(args.deal_pipeline_id));
378
+ const qs = params.toString();
379
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/deal_stages${qs ? `?${qs}` : ""}`), null, 2) }] };
380
+ }
205
381
  case "create_opportunity":
206
382
  return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("POST", "/platform/deals", args), null, 2) }] };
383
+ case "update_deal": {
384
+ const id = args?.id;
385
+ const body = { ...args } as Record<string, unknown>;
386
+ delete body.id;
387
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("PUT", `/platform/deals/${id}`, body), null, 2) }] };
388
+ }
389
+ case "get_deal":
390
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/deals/${args?.id}`), null, 2) }] };
391
+ case "list_deals": {
392
+ const params = new URLSearchParams();
393
+ if (args?.page) params.set("page", String(args.page));
394
+ if (args?.limit) params.set("limit", String(args.limit));
395
+ if (args?.deal_stage_id) params.set("deal_stage_id", String(args.deal_stage_id));
396
+ if (args?.deal_pipeline_id) params.set("deal_pipeline_id", String(args.deal_pipeline_id));
397
+ if (args?.user_id) params.set("user_id", String(args.user_id));
398
+ if (args?.win !== undefined) params.set("win", String(args.win));
399
+ const qs = params.toString();
400
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/deals${qs ? `?${qs}` : ""}`), null, 2) }] };
401
+ }
402
+ case "list_segmentations": {
403
+ const params = new URLSearchParams();
404
+ if (args?.page) params.set("page", String(args.page));
405
+ if (args?.page_size) params.set("page_size", String(args.page_size));
406
+ const qs = params.toString();
407
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/segmentations${qs ? `?${qs}` : ""}`), null, 2) }] };
408
+ }
409
+ case "get_segmentation_contacts": {
410
+ const params = new URLSearchParams();
411
+ if (args?.page) params.set("page", String(args.page));
412
+ if (args?.page_size) params.set("page_size", String(args.page_size));
413
+ const qs = params.toString();
414
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/segmentations/${args?.segmentation_id}/contacts${qs ? `?${qs}` : ""}`), null, 2) }] };
415
+ }
416
+ case "update_lead_scoring": {
417
+ const status = String(args?.status ?? "lead");
418
+ const value = args?.value === undefined ? true : Boolean(args.value);
419
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("POST", `/platform/contacts/email:${args?.email}/funnels/default`, { lifecycle_stage: status, value }), null, 2) }] };
420
+ }
421
+ case "create_webhook":
422
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("POST", "/integrations/webhooks", args), null, 2) }] };
207
423
  default:
208
424
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
209
425
  }
@@ -226,7 +442,7 @@ async function main() {
226
442
  if (!sid && isInitializeRequest(req.body)) {
227
443
  const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } });
228
444
  t.onclose = () => { if (t.sessionId) transports.delete(t.sessionId); };
229
- const s = new Server({ name: "mcp-rd-station", version: "0.1.0" }, { capabilities: { tools: {} } }); (server as any)._requestHandlers.forEach((v: any, k: any) => (s as any)._requestHandlers.set(k, v)); (server as any)._notificationHandlers?.forEach((v: any, k: any) => (s as any)._notificationHandlers.set(k, v)); await s.connect(t);
445
+ const s = new Server({ name: "mcp-rd-station", version: "0.2.0" }, { capabilities: { tools: {} } }); (server as any)._requestHandlers.forEach((v: any, k: any) => (s as any)._requestHandlers.set(k, v)); (server as any)._notificationHandlers?.forEach((v: any, k: any) => (s as any)._notificationHandlers.set(k, v)); await s.connect(t);
230
446
  await t.handleRequest(req, res, req.body); return;
231
447
  }
232
448
  res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request" }, id: null });