@codespar/mcp-rd-station 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.
package/dist/index.js ADDED
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Server for RD Station — Brazilian CRM and marketing automation.
4
+ *
5
+ * Tools:
6
+ * - create_contact: Create a contact in RD Station
7
+ * - update_contact: Update a contact by UUID
8
+ * - get_contact: Get contact details by UUID or email
9
+ * - list_contacts: List contacts with pagination
10
+ * - create_event: Create a conversion event
11
+ * - list_funnels: List sales funnels
12
+ * - get_funnel: Get funnel details with stages
13
+ * - create_opportunity: Create a sales opportunity
14
+ *
15
+ * Environment:
16
+ * RD_STATION_TOKEN — Bearer token from https://app.rdstation.com/
17
+ */
18
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
19
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
20
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
21
+ const TOKEN = process.env.RD_STATION_TOKEN || "";
22
+ const BASE_URL = "https://api.rd.services";
23
+ async function rdStationRequest(method, path, body) {
24
+ const res = await fetch(`${BASE_URL}${path}`, {
25
+ method,
26
+ headers: {
27
+ "Content-Type": "application/json",
28
+ "Authorization": `Bearer ${TOKEN}`,
29
+ },
30
+ body: body ? JSON.stringify(body) : undefined,
31
+ });
32
+ if (!res.ok) {
33
+ const err = await res.text();
34
+ throw new Error(`RD Station API ${res.status}: ${err}`);
35
+ }
36
+ return res.json();
37
+ }
38
+ const server = new Server({ name: "mcp-rd-station", version: "0.1.0" }, { capabilities: { tools: {} } });
39
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
40
+ tools: [
41
+ {
42
+ name: "create_contact",
43
+ description: "Create a contact in RD Station CRM",
44
+ inputSchema: {
45
+ type: "object",
46
+ properties: {
47
+ email: { type: "string", description: "Contact email" },
48
+ name: { type: "string", description: "Contact name" },
49
+ job_title: { type: "string", description: "Job title" },
50
+ phone: { type: "string", description: "Phone number" },
51
+ company: { type: "string", description: "Company name" },
52
+ tags: {
53
+ type: "array",
54
+ items: { type: "string" },
55
+ description: "Tags to assign",
56
+ },
57
+ cf_custom_fields: { type: "object", description: "Custom fields (key-value)" },
58
+ },
59
+ required: ["email"],
60
+ },
61
+ },
62
+ {
63
+ name: "update_contact",
64
+ description: "Update a contact by UUID",
65
+ inputSchema: {
66
+ type: "object",
67
+ properties: {
68
+ uuid: { type: "string", description: "Contact UUID" },
69
+ name: { type: "string", description: "Updated name" },
70
+ email: { type: "string", description: "Updated email" },
71
+ job_title: { type: "string", description: "Updated job title" },
72
+ phone: { type: "string", description: "Updated phone" },
73
+ company: { type: "string", description: "Updated company" },
74
+ tags: { type: "array", items: { type: "string" }, description: "Updated tags" },
75
+ },
76
+ required: ["uuid"],
77
+ },
78
+ },
79
+ {
80
+ name: "get_contact",
81
+ description: "Get contact details by UUID or email",
82
+ inputSchema: {
83
+ type: "object",
84
+ properties: {
85
+ uuid: { type: "string", description: "Contact UUID" },
86
+ email: { type: "string", description: "Contact email (alternative to UUID)" },
87
+ },
88
+ },
89
+ },
90
+ {
91
+ name: "list_contacts",
92
+ description: "List contacts with pagination",
93
+ inputSchema: {
94
+ type: "object",
95
+ properties: {
96
+ page: { type: "number", description: "Page number (default 1)" },
97
+ limit: { type: "number", description: "Results per page (default 25)" },
98
+ query: { type: "string", description: "Search query" },
99
+ },
100
+ },
101
+ },
102
+ {
103
+ name: "create_event",
104
+ description: "Create a conversion event for a contact",
105
+ inputSchema: {
106
+ type: "object",
107
+ properties: {
108
+ event_type: { type: "string", enum: ["CONVERSION", "OPPORTUNITY", "SALE", "OPPORTUNITY_LOST"], description: "Event type" },
109
+ event_family: { type: "string", enum: ["CDP"], description: "Event family" },
110
+ payload: {
111
+ type: "object",
112
+ description: "Event payload",
113
+ properties: {
114
+ conversion_identifier: { type: "string", description: "Conversion identifier (e.g. form name)" },
115
+ email: { type: "string", description: "Contact email" },
116
+ name: { type: "string", description: "Contact name" },
117
+ cf_custom_fields: { type: "object", description: "Custom fields" },
118
+ },
119
+ required: ["conversion_identifier", "email"],
120
+ },
121
+ },
122
+ required: ["event_type", "event_family", "payload"],
123
+ },
124
+ },
125
+ {
126
+ name: "list_funnels",
127
+ description: "List all sales funnels",
128
+ inputSchema: { type: "object", properties: {} },
129
+ },
130
+ {
131
+ name: "get_funnel",
132
+ description: "Get funnel details with stages",
133
+ inputSchema: {
134
+ type: "object",
135
+ properties: {
136
+ id: { type: "string", description: "Funnel ID" },
137
+ },
138
+ required: ["id"],
139
+ },
140
+ },
141
+ {
142
+ name: "create_opportunity",
143
+ description: "Create a sales opportunity in a funnel",
144
+ inputSchema: {
145
+ type: "object",
146
+ properties: {
147
+ deal_stage_id: { type: "string", description: "Stage ID in the funnel" },
148
+ name: { type: "string", description: "Opportunity name" },
149
+ contact_uuid: { type: "string", description: "Contact UUID" },
150
+ amount: { type: "number", description: "Deal amount in cents" },
151
+ prediction_date: { type: "string", description: "Expected close date (YYYY-MM-DD)" },
152
+ },
153
+ required: ["deal_stage_id", "name"],
154
+ },
155
+ },
156
+ ],
157
+ }));
158
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
159
+ const { name, arguments: args } = request.params;
160
+ try {
161
+ switch (name) {
162
+ case "create_contact":
163
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("POST", "/platform/contacts", args), null, 2) }] };
164
+ case "update_contact": {
165
+ const uuid = args?.uuid;
166
+ const body = { ...args };
167
+ delete body.uuid;
168
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("PATCH", `/platform/contacts/${uuid}`, body), null, 2) }] };
169
+ }
170
+ case "get_contact": {
171
+ if (args?.uuid) {
172
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/contacts/${args.uuid}`), null, 2) }] };
173
+ }
174
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/contacts/email:${args?.email}`), null, 2) }] };
175
+ }
176
+ case "list_contacts": {
177
+ const params = new URLSearchParams();
178
+ if (args?.page)
179
+ params.set("page", String(args.page));
180
+ if (args?.limit)
181
+ params.set("limit", String(args.limit));
182
+ if (args?.query)
183
+ params.set("query", String(args.query));
184
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/contacts?${params}`), null, 2) }] };
185
+ }
186
+ case "create_event":
187
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("POST", "/platform/events", args), null, 2) }] };
188
+ case "list_funnels":
189
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", "/platform/deal_pipelines"), null, 2) }] };
190
+ case "get_funnel":
191
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/deal_pipelines/${args?.id}`), null, 2) }] };
192
+ case "create_opportunity":
193
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("POST", "/platform/deals", args), null, 2) }] };
194
+ default:
195
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
196
+ }
197
+ }
198
+ catch (err) {
199
+ return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
200
+ }
201
+ });
202
+ async function main() {
203
+ if (!TOKEN) {
204
+ console.error("RD_STATION_TOKEN environment variable is required");
205
+ process.exit(1);
206
+ }
207
+ const transport = new StdioServerTransport();
208
+ await server.connect(transport);
209
+ }
210
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@codespar/mcp-rd-station",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for RD Station — contacts, events, funnels, opportunities",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "bin": {
8
+ "mcp-rd-station": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js"
13
+ },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.0.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^25.5.0",
19
+ "typescript": "^5.8.0"
20
+ },
21
+ "license": "MIT",
22
+ "keywords": [
23
+ "mcp",
24
+ "rd-station",
25
+ "crm",
26
+ "marketing",
27
+ "brazil"
28
+ ]
29
+ }
package/src/index.ts ADDED
@@ -0,0 +1,222 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * MCP Server for RD Station — Brazilian CRM and marketing automation.
5
+ *
6
+ * Tools:
7
+ * - create_contact: Create a contact in RD Station
8
+ * - update_contact: Update a contact by UUID
9
+ * - get_contact: Get contact details by UUID or email
10
+ * - list_contacts: List contacts with pagination
11
+ * - create_event: Create a conversion event
12
+ * - list_funnels: List sales funnels
13
+ * - get_funnel: Get funnel details with stages
14
+ * - create_opportunity: Create a sales opportunity
15
+ *
16
+ * Environment:
17
+ * RD_STATION_TOKEN — Bearer token from https://app.rdstation.com/
18
+ */
19
+
20
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
21
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22
+ import {
23
+ CallToolRequestSchema,
24
+ ListToolsRequestSchema,
25
+ } from "@modelcontextprotocol/sdk/types.js";
26
+
27
+ const TOKEN = process.env.RD_STATION_TOKEN || "";
28
+ const BASE_URL = "https://api.rd.services";
29
+
30
+ async function rdStationRequest(method: string, path: string, body?: unknown): Promise<unknown> {
31
+ const res = await fetch(`${BASE_URL}${path}`, {
32
+ method,
33
+ headers: {
34
+ "Content-Type": "application/json",
35
+ "Authorization": `Bearer ${TOKEN}`,
36
+ },
37
+ body: body ? JSON.stringify(body) : undefined,
38
+ });
39
+ if (!res.ok) {
40
+ const err = await res.text();
41
+ throw new Error(`RD Station API ${res.status}: ${err}`);
42
+ }
43
+ return res.json();
44
+ }
45
+
46
+ const server = new Server(
47
+ { name: "mcp-rd-station", version: "0.1.0" },
48
+ { capabilities: { tools: {} } }
49
+ );
50
+
51
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
52
+ tools: [
53
+ {
54
+ name: "create_contact",
55
+ description: "Create a contact in RD Station CRM",
56
+ inputSchema: {
57
+ type: "object",
58
+ properties: {
59
+ email: { type: "string", description: "Contact email" },
60
+ name: { type: "string", description: "Contact name" },
61
+ job_title: { type: "string", description: "Job title" },
62
+ phone: { type: "string", description: "Phone number" },
63
+ company: { type: "string", description: "Company name" },
64
+ tags: {
65
+ type: "array",
66
+ items: { type: "string" },
67
+ description: "Tags to assign",
68
+ },
69
+ cf_custom_fields: { type: "object", description: "Custom fields (key-value)" },
70
+ },
71
+ required: ["email"],
72
+ },
73
+ },
74
+ {
75
+ name: "update_contact",
76
+ description: "Update a contact by UUID",
77
+ inputSchema: {
78
+ type: "object",
79
+ properties: {
80
+ uuid: { type: "string", description: "Contact UUID" },
81
+ name: { type: "string", description: "Updated name" },
82
+ email: { type: "string", description: "Updated email" },
83
+ job_title: { type: "string", description: "Updated job title" },
84
+ phone: { type: "string", description: "Updated phone" },
85
+ company: { type: "string", description: "Updated company" },
86
+ tags: { type: "array", items: { type: "string" }, description: "Updated tags" },
87
+ },
88
+ required: ["uuid"],
89
+ },
90
+ },
91
+ {
92
+ name: "get_contact",
93
+ description: "Get contact details by UUID or email",
94
+ inputSchema: {
95
+ type: "object",
96
+ properties: {
97
+ uuid: { type: "string", description: "Contact UUID" },
98
+ email: { type: "string", description: "Contact email (alternative to UUID)" },
99
+ },
100
+ },
101
+ },
102
+ {
103
+ name: "list_contacts",
104
+ description: "List contacts with pagination",
105
+ inputSchema: {
106
+ type: "object",
107
+ properties: {
108
+ page: { type: "number", description: "Page number (default 1)" },
109
+ limit: { type: "number", description: "Results per page (default 25)" },
110
+ query: { type: "string", description: "Search query" },
111
+ },
112
+ },
113
+ },
114
+ {
115
+ name: "create_event",
116
+ description: "Create a conversion event for a contact",
117
+ inputSchema: {
118
+ type: "object",
119
+ properties: {
120
+ event_type: { type: "string", enum: ["CONVERSION", "OPPORTUNITY", "SALE", "OPPORTUNITY_LOST"], description: "Event type" },
121
+ event_family: { type: "string", enum: ["CDP"], description: "Event family" },
122
+ payload: {
123
+ type: "object",
124
+ description: "Event payload",
125
+ properties: {
126
+ conversion_identifier: { type: "string", description: "Conversion identifier (e.g. form name)" },
127
+ email: { type: "string", description: "Contact email" },
128
+ name: { type: "string", description: "Contact name" },
129
+ cf_custom_fields: { type: "object", description: "Custom fields" },
130
+ },
131
+ required: ["conversion_identifier", "email"],
132
+ },
133
+ },
134
+ required: ["event_type", "event_family", "payload"],
135
+ },
136
+ },
137
+ {
138
+ name: "list_funnels",
139
+ description: "List all sales funnels",
140
+ inputSchema: { type: "object", properties: {} },
141
+ },
142
+ {
143
+ name: "get_funnel",
144
+ description: "Get funnel details with stages",
145
+ inputSchema: {
146
+ type: "object",
147
+ properties: {
148
+ id: { type: "string", description: "Funnel ID" },
149
+ },
150
+ required: ["id"],
151
+ },
152
+ },
153
+ {
154
+ name: "create_opportunity",
155
+ description: "Create a sales opportunity in a funnel",
156
+ inputSchema: {
157
+ type: "object",
158
+ properties: {
159
+ deal_stage_id: { type: "string", description: "Stage ID in the funnel" },
160
+ name: { type: "string", description: "Opportunity name" },
161
+ contact_uuid: { type: "string", description: "Contact UUID" },
162
+ amount: { type: "number", description: "Deal amount in cents" },
163
+ prediction_date: { type: "string", description: "Expected close date (YYYY-MM-DD)" },
164
+ },
165
+ required: ["deal_stage_id", "name"],
166
+ },
167
+ },
168
+ ],
169
+ }));
170
+
171
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
172
+ const { name, arguments: args } = request.params;
173
+
174
+ try {
175
+ switch (name) {
176
+ case "create_contact":
177
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("POST", "/platform/contacts", args), null, 2) }] };
178
+ case "update_contact": {
179
+ const uuid = args?.uuid;
180
+ const body = { ...args } as Record<string, unknown>;
181
+ delete body.uuid;
182
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("PATCH", `/platform/contacts/${uuid}`, body), null, 2) }] };
183
+ }
184
+ case "get_contact": {
185
+ if (args?.uuid) {
186
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/contacts/${args.uuid}`), null, 2) }] };
187
+ }
188
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/contacts/email:${args?.email}`), null, 2) }] };
189
+ }
190
+ case "list_contacts": {
191
+ const params = new URLSearchParams();
192
+ if (args?.page) params.set("page", String(args.page));
193
+ if (args?.limit) params.set("limit", String(args.limit));
194
+ if (args?.query) params.set("query", String(args.query));
195
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/contacts?${params}`), null, 2) }] };
196
+ }
197
+ case "create_event":
198
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("POST", "/platform/events", args), null, 2) }] };
199
+ case "list_funnels":
200
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", "/platform/deal_pipelines"), null, 2) }] };
201
+ case "get_funnel":
202
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("GET", `/platform/deal_pipelines/${args?.id}`), null, 2) }] };
203
+ case "create_opportunity":
204
+ return { content: [{ type: "text", text: JSON.stringify(await rdStationRequest("POST", "/platform/deals", args), null, 2) }] };
205
+ default:
206
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
207
+ }
208
+ } catch (err) {
209
+ return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
210
+ }
211
+ });
212
+
213
+ async function main() {
214
+ if (!TOKEN) {
215
+ console.error("RD_STATION_TOKEN environment variable is required");
216
+ process.exit(1);
217
+ }
218
+ const transport = new StdioServerTransport();
219
+ await server.connect(transport);
220
+ }
221
+
222
+ main().catch(console.error);
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "declaration": true
11
+ },
12
+ "include": ["src"]
13
+ }