cowork-os 0.3.21 → 0.3.23

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 (170) hide show
  1. package/README.md +293 -6
  2. package/connectors/README.md +20 -0
  3. package/connectors/asana-mcp/README.md +24 -0
  4. package/connectors/asana-mcp/dist/index.js +427 -0
  5. package/connectors/asana-mcp/package.json +15 -0
  6. package/connectors/asana-mcp/src/index.ts +553 -0
  7. package/connectors/asana-mcp/tsconfig.json +13 -0
  8. package/connectors/hubspot-mcp/README.md +35 -0
  9. package/connectors/hubspot-mcp/dist/index.js +454 -0
  10. package/connectors/hubspot-mcp/package.json +15 -0
  11. package/connectors/hubspot-mcp/src/index.ts +562 -0
  12. package/connectors/hubspot-mcp/tsconfig.json +13 -0
  13. package/connectors/jira-mcp/README.md +49 -0
  14. package/connectors/jira-mcp/dist/index.js +588 -0
  15. package/connectors/jira-mcp/package.json +15 -0
  16. package/connectors/jira-mcp/src/index.ts +711 -0
  17. package/connectors/jira-mcp/tsconfig.json +13 -0
  18. package/connectors/linear-mcp/README.md +22 -0
  19. package/connectors/linear-mcp/dist/index.js +402 -0
  20. package/connectors/linear-mcp/package.json +15 -0
  21. package/connectors/linear-mcp/src/index.ts +522 -0
  22. package/connectors/linear-mcp/tsconfig.json +13 -0
  23. package/connectors/okta-mcp/README.md +24 -0
  24. package/connectors/okta-mcp/dist/index.js +411 -0
  25. package/connectors/okta-mcp/package.json +15 -0
  26. package/connectors/okta-mcp/src/index.ts +520 -0
  27. package/connectors/okta-mcp/tsconfig.json +13 -0
  28. package/connectors/salesforce-mcp/README.md +47 -0
  29. package/connectors/salesforce-mcp/dist/index.js +584 -0
  30. package/connectors/salesforce-mcp/package.json +15 -0
  31. package/connectors/salesforce-mcp/src/index.ts +722 -0
  32. package/connectors/salesforce-mcp/tsconfig.json +13 -0
  33. package/connectors/servicenow-mcp/README.md +26 -0
  34. package/connectors/servicenow-mcp/dist/index.js +400 -0
  35. package/connectors/servicenow-mcp/package.json +15 -0
  36. package/connectors/servicenow-mcp/src/index.ts +500 -0
  37. package/connectors/servicenow-mcp/tsconfig.json +13 -0
  38. package/connectors/templates/mcp-connector/README.md +31 -0
  39. package/connectors/templates/mcp-connector/package.json +15 -0
  40. package/connectors/templates/mcp-connector/src/index.ts +330 -0
  41. package/connectors/templates/mcp-connector/tsconfig.json +13 -0
  42. package/connectors/zendesk-mcp/README.md +40 -0
  43. package/connectors/zendesk-mcp/dist/index.js +431 -0
  44. package/connectors/zendesk-mcp/package.json +15 -0
  45. package/connectors/zendesk-mcp/src/index.ts +543 -0
  46. package/connectors/zendesk-mcp/tsconfig.json +13 -0
  47. package/dist/electron/electron/agent/daemon.js +25 -0
  48. package/dist/electron/electron/agent/executor.js +181 -26
  49. package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
  50. package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
  51. package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
  52. package/dist/electron/electron/agent/llm/index.js +11 -1
  53. package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
  54. package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
  55. package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
  56. package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
  57. package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
  58. package/dist/electron/electron/agent/llm/provider-factory.js +318 -4
  59. package/dist/electron/electron/agent/llm/types.js +66 -1
  60. package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
  61. package/dist/electron/electron/agent/tools/box-tools.js +231 -0
  62. package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
  63. package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
  64. package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
  65. package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
  66. package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
  67. package/dist/electron/electron/agent/tools/registry.js +541 -0
  68. package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
  69. package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
  70. package/dist/electron/electron/agent/tools/x-tools.js +1 -1
  71. package/dist/electron/electron/gateway/index.js +1 -0
  72. package/dist/electron/electron/gateway/router.js +123 -143
  73. package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
  74. package/dist/electron/electron/ipc/handlers.js +627 -158
  75. package/dist/electron/electron/main.js +63 -0
  76. package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
  77. package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
  78. package/dist/electron/electron/memory/MemoryService.js +1 -1
  79. package/dist/electron/electron/preload.js +74 -1
  80. package/dist/electron/electron/settings/box-manager.js +54 -0
  81. package/dist/electron/electron/settings/dropbox-manager.js +54 -0
  82. package/dist/electron/electron/settings/google-drive-manager.js +54 -0
  83. package/dist/electron/electron/settings/notion-manager.js +56 -0
  84. package/dist/electron/electron/settings/onedrive-manager.js +54 -0
  85. package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
  86. package/dist/electron/electron/utils/box-api.js +153 -0
  87. package/dist/electron/electron/utils/dropbox-api.js +144 -0
  88. package/dist/electron/electron/utils/env-migration.js +19 -0
  89. package/dist/electron/electron/utils/google-drive-api.js +152 -0
  90. package/dist/electron/electron/utils/notion-api.js +103 -0
  91. package/dist/electron/electron/utils/onedrive-api.js +113 -0
  92. package/dist/electron/electron/utils/sharepoint-api.js +109 -0
  93. package/dist/electron/electron/utils/validation.js +82 -3
  94. package/dist/electron/electron/utils/x-cli.js +1 -1
  95. package/dist/electron/shared/channelMessages.js +284 -3
  96. package/dist/electron/shared/llm-provider-catalog.js +198 -0
  97. package/dist/electron/shared/types.js +88 -1
  98. package/package.json +12 -2
  99. package/src/electron/agent/executor.ts +205 -28
  100. package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
  101. package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
  102. package/src/electron/agent/llm/groq-provider.ts +39 -0
  103. package/src/electron/agent/llm/index.ts +5 -0
  104. package/src/electron/agent/llm/kimi-provider.ts +39 -0
  105. package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
  106. package/src/electron/agent/llm/openai-compatible.ts +133 -0
  107. package/src/electron/agent/llm/openai-oauth.ts +2 -1
  108. package/src/electron/agent/llm/openrouter-provider.ts +2 -1
  109. package/src/electron/agent/llm/provider-factory.ts +414 -6
  110. package/src/electron/agent/llm/types.ts +90 -1
  111. package/src/electron/agent/llm/xai-provider.ts +39 -0
  112. package/src/electron/agent/tools/box-tools.ts +239 -0
  113. package/src/electron/agent/tools/builtin-settings.ts +34 -0
  114. package/src/electron/agent/tools/dropbox-tools.ts +237 -0
  115. package/src/electron/agent/tools/google-drive-tools.ts +228 -0
  116. package/src/electron/agent/tools/notion-tools.ts +330 -0
  117. package/src/electron/agent/tools/onedrive-tools.ts +217 -0
  118. package/src/electron/agent/tools/registry.ts +565 -0
  119. package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
  120. package/src/electron/agent/tools/shell-tools.ts +11 -3
  121. package/src/electron/agent/tools/x-tools.ts +1 -1
  122. package/src/electron/database/SecureSettingsRepository.ts +7 -1
  123. package/src/electron/gateway/index.ts +1 -0
  124. package/src/electron/gateway/router.ts +134 -149
  125. package/src/electron/ipc/canvas-handlers.ts +10 -0
  126. package/src/electron/ipc/handlers.ts +673 -153
  127. package/src/electron/main.ts +35 -0
  128. package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
  129. package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
  130. package/src/electron/memory/MemoryService.ts +5 -1
  131. package/src/electron/preload.ts +167 -4
  132. package/src/electron/settings/box-manager.ts +58 -0
  133. package/src/electron/settings/dropbox-manager.ts +58 -0
  134. package/src/electron/settings/google-drive-manager.ts +58 -0
  135. package/src/electron/settings/notion-manager.ts +60 -0
  136. package/src/electron/settings/onedrive-manager.ts +58 -0
  137. package/src/electron/settings/sharepoint-manager.ts +58 -0
  138. package/src/electron/utils/box-api.ts +184 -0
  139. package/src/electron/utils/dropbox-api.ts +171 -0
  140. package/src/electron/utils/env-migration.ts +22 -0
  141. package/src/electron/utils/google-drive-api.ts +183 -0
  142. package/src/electron/utils/notion-api.ts +126 -0
  143. package/src/electron/utils/onedrive-api.ts +137 -0
  144. package/src/electron/utils/sharepoint-api.ts +132 -0
  145. package/src/electron/utils/validation.ts +102 -1
  146. package/src/electron/utils/x-cli.ts +1 -1
  147. package/src/renderer/App.tsx +20 -2
  148. package/src/renderer/components/BoxSettings.tsx +203 -0
  149. package/src/renderer/components/BrowserView.tsx +101 -0
  150. package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
  151. package/src/renderer/components/CanvasPreview.tsx +68 -1
  152. package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
  153. package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
  154. package/src/renderer/components/ConnectorsSettings.tsx +397 -0
  155. package/src/renderer/components/DropboxSettings.tsx +202 -0
  156. package/src/renderer/components/GoogleDriveSettings.tsx +201 -0
  157. package/src/renderer/components/MCPSettings.tsx +56 -0
  158. package/src/renderer/components/MainContent.tsx +270 -34
  159. package/src/renderer/components/NotionSettings.tsx +231 -0
  160. package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
  161. package/src/renderer/components/OnboardingModal.tsx +70 -1
  162. package/src/renderer/components/OneDriveSettings.tsx +212 -0
  163. package/src/renderer/components/Settings.tsx +611 -8
  164. package/src/renderer/components/SharePointSettings.tsx +224 -0
  165. package/src/renderer/components/Sidebar.tsx +25 -9
  166. package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
  167. package/src/renderer/styles/index.css +438 -25
  168. package/src/shared/channelMessages.ts +367 -4
  169. package/src/shared/llm-provider-catalog.ts +217 -0
  170. package/src/shared/types.ts +226 -1
@@ -0,0 +1,553 @@
1
+ import * as readline from 'readline';
2
+
3
+ // ==================== MCP Types ====================
4
+
5
+ type JSONRPCId = string | number;
6
+
7
+ type JSONRPCRequest = {
8
+ jsonrpc: '2.0';
9
+ id: JSONRPCId;
10
+ method: string;
11
+ params?: Record<string, any>;
12
+ };
13
+
14
+ type JSONRPCNotification = {
15
+ jsonrpc: '2.0';
16
+ method: string;
17
+ params?: Record<string, any>;
18
+ };
19
+
20
+ type JSONRPCResponse = {
21
+ jsonrpc: '2.0';
22
+ id: JSONRPCId;
23
+ result?: any;
24
+ error?: { code: number; message: string; data?: any };
25
+ };
26
+
27
+ type MCPToolProperty = {
28
+ type: string;
29
+ description?: string;
30
+ enum?: string[];
31
+ default?: any;
32
+ items?: MCPToolProperty;
33
+ properties?: Record<string, MCPToolProperty>;
34
+ required?: string[];
35
+ };
36
+
37
+ type MCPTool = {
38
+ name: string;
39
+ description?: string;
40
+ inputSchema: {
41
+ type: 'object';
42
+ properties?: Record<string, MCPToolProperty>;
43
+ required?: string[];
44
+ additionalProperties?: boolean;
45
+ };
46
+ };
47
+
48
+ type MCPServerInfo = {
49
+ name: string;
50
+ version: string;
51
+ protocolVersion?: string;
52
+ capabilities?: {
53
+ tools?: { listChanged?: boolean };
54
+ };
55
+ };
56
+
57
+ const PROTOCOL_VERSION = '2024-11-05';
58
+
59
+ const MCP_METHODS = {
60
+ INITIALIZE: 'initialize',
61
+ INITIALIZED: 'notifications/initialized',
62
+ SHUTDOWN: 'shutdown',
63
+ TOOLS_LIST: 'tools/list',
64
+ TOOLS_CALL: 'tools/call',
65
+ } as const;
66
+
67
+ const MCP_ERROR_CODES = {
68
+ PARSE_ERROR: -32700,
69
+ INVALID_REQUEST: -32600,
70
+ METHOD_NOT_FOUND: -32601,
71
+ INVALID_PARAMS: -32602,
72
+ INTERNAL_ERROR: -32603,
73
+ SERVER_NOT_INITIALIZED: -32002,
74
+ } as const;
75
+
76
+ // ==================== Asana Client ====================
77
+
78
+ type AsanaConfig = {
79
+ baseUrl: string;
80
+ accessToken?: string;
81
+ };
82
+
83
+ type RequestMeta = {
84
+ durationMs: number;
85
+ vendorRequestId?: string;
86
+ baseUrl: string;
87
+ };
88
+
89
+ type RequestResult = {
90
+ data: any;
91
+ meta: RequestMeta;
92
+ nextCursor?: string;
93
+ };
94
+
95
+ class AsanaClient {
96
+ constructor(private config: AsanaConfig) {}
97
+
98
+ async health(): Promise<RequestResult> {
99
+ return this.requestJson('GET', 'users/me');
100
+ }
101
+
102
+ async listProjects(workspaceGid: string, limit?: number, offset?: string, archived?: boolean): Promise<RequestResult> {
103
+ const params = new URLSearchParams();
104
+ if (limit !== undefined) params.set('limit', String(limit));
105
+ if (offset) params.set('offset', offset);
106
+ if (archived !== undefined) params.set('archived', archived ? 'true' : 'false');
107
+ const query = params.toString();
108
+ return this.requestJson(
109
+ 'GET',
110
+ `workspaces/${encodeURIComponent(workspaceGid)}/projects${query ? `?${query}` : ''}`
111
+ );
112
+ }
113
+
114
+ async getTask(taskId: string, fields?: string[]): Promise<RequestResult> {
115
+ const params = new URLSearchParams();
116
+ if (fields && fields.length > 0) {
117
+ params.set('opt_fields', fields.join(','));
118
+ }
119
+ const query = params.toString();
120
+ return this.requestJson('GET', `tasks/${encodeURIComponent(taskId)}${query ? `?${query}` : ''}`);
121
+ }
122
+
123
+ async searchTasks(
124
+ workspaceGid: string,
125
+ text?: string,
126
+ assigneeGid?: string,
127
+ projectGid?: string,
128
+ completed?: boolean,
129
+ limit?: number,
130
+ offset?: string,
131
+ fields?: string[]
132
+ ): Promise<RequestResult> {
133
+ const params = new URLSearchParams();
134
+ if (fields && fields.length > 0) {
135
+ params.set('opt_fields', fields.join(','));
136
+ }
137
+ const query = params.toString();
138
+
139
+ const payload: Record<string, any> = {};
140
+ if (text) payload.text = text;
141
+ if (assigneeGid) payload.assignee = assigneeGid;
142
+ if (projectGid) payload.projects = [projectGid];
143
+ if (completed !== undefined) payload.completed = completed;
144
+ if (limit !== undefined) payload.limit = limit;
145
+ if (offset) payload.offset = offset;
146
+
147
+ return this.requestJson(
148
+ 'POST',
149
+ `workspaces/${encodeURIComponent(workspaceGid)}/tasks/search${query ? `?${query}` : ''}`,
150
+ { data: payload }
151
+ );
152
+ }
153
+
154
+ async createTask(data: Record<string, any>): Promise<RequestResult> {
155
+ return this.requestJson('POST', 'tasks', { data });
156
+ }
157
+
158
+ async updateTask(taskId: string, data: Record<string, any>): Promise<RequestResult> {
159
+ return this.requestJson('PUT', `tasks/${encodeURIComponent(taskId)}`, { data });
160
+ }
161
+
162
+ private getBaseUrl(): string {
163
+ return this.config.baseUrl.replace(/\/$/, '');
164
+ }
165
+
166
+ private getAuthHeader(): string {
167
+ if (!this.config.accessToken) {
168
+ throw new Error('ASANA_ACCESS_TOKEN is required');
169
+ }
170
+ return `Bearer ${this.config.accessToken}`;
171
+ }
172
+
173
+ private async requestJson(method: string, path: string, body?: any): Promise<RequestResult> {
174
+ const start = Date.now();
175
+ const url = `${this.getBaseUrl()}/${path.replace(/^\//, '')}`;
176
+
177
+ const res = await fetch(url, {
178
+ method,
179
+ headers: {
180
+ Authorization: this.getAuthHeader(),
181
+ 'Content-Type': 'application/json',
182
+ 'User-Agent': 'CoWork-Asana-Connector/0.1.0',
183
+ },
184
+ body: body ? JSON.stringify(body) : undefined,
185
+ });
186
+
187
+ const durationMs = Date.now() - start;
188
+ const vendorRequestId = res.headers.get('x-request-id') || undefined;
189
+
190
+ if (!res.ok) {
191
+ const message = await res.text();
192
+ throw new Error(message || `Asana API error (${res.status})`);
193
+ }
194
+
195
+ let data: any = null;
196
+ if (res.status !== 204) {
197
+ data = await res.json();
198
+ }
199
+
200
+ const nextCursor = data?.next_page?.offset;
201
+
202
+ return {
203
+ data,
204
+ meta: {
205
+ durationMs,
206
+ vendorRequestId,
207
+ baseUrl: this.config.baseUrl,
208
+ },
209
+ nextCursor,
210
+ };
211
+ }
212
+ }
213
+
214
+ // ==================== MCP Stdio Server ====================
215
+
216
+ type ToolProvider = {
217
+ getTools(): MCPTool[];
218
+ executeTool(name: string, args: Record<string, any>): Promise<any>;
219
+ };
220
+
221
+ class StdioMCPServer {
222
+ private initialized = false;
223
+ private rl: readline.Interface | null = null;
224
+
225
+ constructor(
226
+ private toolProvider: ToolProvider,
227
+ private serverInfo: MCPServerInfo
228
+ ) {}
229
+
230
+ start(): void {
231
+ this.rl = readline.createInterface({
232
+ input: process.stdin,
233
+ output: process.stdout,
234
+ terminal: false,
235
+ });
236
+
237
+ this.rl.on('line', (line) => this.handleLine(line));
238
+ this.rl.on('close', () => this.stop());
239
+
240
+ process.on('SIGINT', () => this.stop());
241
+ process.on('SIGTERM', () => this.stop());
242
+ }
243
+
244
+ stop(): void {
245
+ if (this.rl) {
246
+ this.rl.close();
247
+ this.rl = null;
248
+ }
249
+ process.exit(0);
250
+ }
251
+
252
+ private handleLine(line: string): void {
253
+ const trimmed = line.trim();
254
+ if (!trimmed) return;
255
+
256
+ try {
257
+ const message = JSON.parse(trimmed);
258
+ this.handleMessage(message);
259
+ } catch {
260
+ this.sendError(0, MCP_ERROR_CODES.PARSE_ERROR, 'Parse error');
261
+ }
262
+ }
263
+
264
+ private async handleMessage(message: any): Promise<void> {
265
+ if ('id' in message && message.id !== null) {
266
+ await this.handleRequest(message as JSONRPCRequest);
267
+ return;
268
+ }
269
+
270
+ if ('method' in message) {
271
+ await this.handleNotification(message as JSONRPCNotification);
272
+ }
273
+ }
274
+
275
+ private async handleRequest(request: JSONRPCRequest): Promise<void> {
276
+ const { id, method, params } = request;
277
+
278
+ try {
279
+ let result: any;
280
+
281
+ switch (method) {
282
+ case MCP_METHODS.INITIALIZE:
283
+ result = this.handleInitialize(params);
284
+ break;
285
+ case MCP_METHODS.TOOLS_LIST:
286
+ this.requireInitialized();
287
+ result = this.handleToolsList();
288
+ break;
289
+ case MCP_METHODS.TOOLS_CALL:
290
+ this.requireInitialized();
291
+ result = await this.handleToolsCall(params);
292
+ break;
293
+ case MCP_METHODS.SHUTDOWN:
294
+ result = this.handleShutdown();
295
+ break;
296
+ default:
297
+ throw this.createError(MCP_ERROR_CODES.METHOD_NOT_FOUND, `Method not found: ${method}`);
298
+ }
299
+
300
+ this.sendResult(id, result);
301
+ } catch (error: any) {
302
+ if (error.code !== undefined) {
303
+ this.sendError(id, error.code, error.message, error.data);
304
+ } else {
305
+ this.sendError(id, MCP_ERROR_CODES.INTERNAL_ERROR, error?.message || 'Internal error');
306
+ }
307
+ }
308
+ }
309
+
310
+ private async handleNotification(notification: JSONRPCNotification): Promise<void> {
311
+ const { method } = notification;
312
+
313
+ if (method === MCP_METHODS.INITIALIZED) {
314
+ this.initialized = true;
315
+ }
316
+ }
317
+
318
+ private handleInitialize(_params: any): {
319
+ protocolVersion: string;
320
+ capabilities: MCPServerInfo['capabilities'];
321
+ serverInfo: MCPServerInfo;
322
+ } {
323
+ if (this.initialized) {
324
+ throw this.createError(MCP_ERROR_CODES.INVALID_REQUEST, 'Already initialized');
325
+ }
326
+
327
+ return {
328
+ protocolVersion: PROTOCOL_VERSION,
329
+ capabilities: this.serverInfo.capabilities,
330
+ serverInfo: this.serverInfo,
331
+ };
332
+ }
333
+
334
+ private handleToolsList(): { tools: MCPTool[] } {
335
+ return { tools: this.toolProvider.getTools() };
336
+ }
337
+
338
+ private async handleToolsCall(params: any): Promise<any> {
339
+ const { name, arguments: args } = params || {};
340
+ if (!name) {
341
+ throw this.createError(MCP_ERROR_CODES.INVALID_PARAMS, 'Tool name is required');
342
+ }
343
+
344
+ try {
345
+ const result = await this.toolProvider.executeTool(name, args || {});
346
+
347
+ if (typeof result === 'string') {
348
+ return { content: [{ type: 'text', text: result }] };
349
+ }
350
+
351
+ if (result && typeof result === 'object') {
352
+ if (result.content && Array.isArray(result.content)) {
353
+ return result;
354
+ }
355
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
356
+ }
357
+
358
+ return { content: [{ type: 'text', text: String(result) }] };
359
+ } catch (error: any) {
360
+ return {
361
+ content: [{ type: 'text', text: `Error: ${error?.message || 'Tool failed'}` }],
362
+ isError: true,
363
+ };
364
+ }
365
+ }
366
+
367
+ private handleShutdown(): Record<string, never> {
368
+ setImmediate(() => this.stop());
369
+ return {};
370
+ }
371
+
372
+ private sendResult(id: JSONRPCId, result: any): void {
373
+ const response: JSONRPCResponse = { jsonrpc: '2.0', id, result };
374
+ this.sendMessage(response);
375
+ }
376
+
377
+ private sendError(id: JSONRPCId, code: number, message: string, data?: any): void {
378
+ const response: JSONRPCResponse = {
379
+ jsonrpc: '2.0',
380
+ id,
381
+ error: { code, message, data },
382
+ };
383
+ this.sendMessage(response);
384
+ }
385
+
386
+ private sendMessage(message: JSONRPCResponse | JSONRPCNotification): void {
387
+ process.stdout.write(JSON.stringify(message) + '\n');
388
+ }
389
+
390
+ private requireInitialized(): void {
391
+ if (!this.initialized) {
392
+ throw this.createError(MCP_ERROR_CODES.SERVER_NOT_INITIALIZED, 'Server not initialized');
393
+ }
394
+ }
395
+
396
+ private createError(code: number, message: string, data?: any): { code: number; message: string; data?: any } {
397
+ return { code, message, data };
398
+ }
399
+ }
400
+
401
+ // ==================== Tool Definitions ====================
402
+
403
+ const CONNECTOR_PREFIX = 'asana';
404
+ const DEFAULT_BASE_URL = 'https://app.asana.com/api/1.0';
405
+
406
+ const tools: MCPTool[] = [
407
+ {
408
+ name: `${CONNECTOR_PREFIX}.health`,
409
+ description: 'Check connector health and authentication status',
410
+ inputSchema: { type: 'object', properties: {}, additionalProperties: false },
411
+ },
412
+ {
413
+ name: `${CONNECTOR_PREFIX}.list_projects`,
414
+ description: 'List projects in a workspace',
415
+ inputSchema: {
416
+ type: 'object',
417
+ properties: {
418
+ workspaceGid: { type: 'string', description: 'Workspace GID' },
419
+ limit: { type: 'number', description: 'Max projects to return' },
420
+ offset: { type: 'string', description: 'Pagination offset' },
421
+ archived: { type: 'boolean', description: 'Include archived projects' },
422
+ },
423
+ required: ['workspaceGid'],
424
+ additionalProperties: false,
425
+ },
426
+ },
427
+ {
428
+ name: `${CONNECTOR_PREFIX}.get_task`,
429
+ description: 'Fetch a task by id',
430
+ inputSchema: {
431
+ type: 'object',
432
+ properties: {
433
+ taskGid: { type: 'string', description: 'Task GID' },
434
+ fields: { type: 'array', description: 'Fields to return', items: { type: 'string' } },
435
+ },
436
+ required: ['taskGid'],
437
+ additionalProperties: false,
438
+ },
439
+ },
440
+ {
441
+ name: `${CONNECTOR_PREFIX}.search_tasks`,
442
+ description: 'Search tasks in a workspace',
443
+ inputSchema: {
444
+ type: 'object',
445
+ properties: {
446
+ workspaceGid: { type: 'string', description: 'Workspace GID' },
447
+ text: { type: 'string', description: 'Search text' },
448
+ assigneeGid: { type: 'string', description: 'Assignee GID' },
449
+ projectGid: { type: 'string', description: 'Project GID' },
450
+ completed: { type: 'boolean', description: 'Filter by completion' },
451
+ limit: { type: 'number', description: 'Max tasks to return' },
452
+ offset: { type: 'string', description: 'Pagination offset' },
453
+ fields: { type: 'array', description: 'Fields to return', items: { type: 'string' } },
454
+ },
455
+ required: ['workspaceGid'],
456
+ additionalProperties: false,
457
+ },
458
+ },
459
+ {
460
+ name: `${CONNECTOR_PREFIX}.create_task`,
461
+ description: 'Create a task',
462
+ inputSchema: {
463
+ type: 'object',
464
+ properties: {
465
+ data: { type: 'object', description: 'Task payload (Asana task fields)' },
466
+ },
467
+ required: ['data'],
468
+ additionalProperties: false,
469
+ },
470
+ },
471
+ {
472
+ name: `${CONNECTOR_PREFIX}.update_task`,
473
+ description: 'Update a task',
474
+ inputSchema: {
475
+ type: 'object',
476
+ properties: {
477
+ taskGid: { type: 'string', description: 'Task GID' },
478
+ data: { type: 'object', description: 'Task payload to update' },
479
+ },
480
+ required: ['taskGid', 'data'],
481
+ additionalProperties: false,
482
+ },
483
+ },
484
+ ];
485
+
486
+ const config: AsanaConfig = {
487
+ baseUrl: process.env.ASANA_BASE_URL || DEFAULT_BASE_URL,
488
+ accessToken: process.env.ASANA_ACCESS_TOKEN,
489
+ };
490
+
491
+ const client = new AsanaClient(config);
492
+
493
+ const handlers: Record<string, (args: Record<string, any>) => Promise<any>> = {
494
+ [`${CONNECTOR_PREFIX}.health`]: async () => buildEnvelope(await client.health()),
495
+ [`${CONNECTOR_PREFIX}.list_projects`]: async (args) =>
496
+ buildEnvelope(await client.listProjects(args.workspaceGid, args.limit, args.offset, args.archived)),
497
+ [`${CONNECTOR_PREFIX}.get_task`]: async (args) =>
498
+ buildEnvelope(await client.getTask(args.taskGid, args.fields)),
499
+ [`${CONNECTOR_PREFIX}.search_tasks`]: async (args) =>
500
+ buildEnvelope(
501
+ await client.searchTasks(
502
+ args.workspaceGid,
503
+ args.text,
504
+ args.assigneeGid,
505
+ args.projectGid,
506
+ args.completed,
507
+ args.limit,
508
+ args.offset,
509
+ args.fields
510
+ )
511
+ ),
512
+ [`${CONNECTOR_PREFIX}.create_task`]: async (args) =>
513
+ buildEnvelope(await client.createTask(args.data || {})),
514
+ [`${CONNECTOR_PREFIX}.update_task`]: async (args) =>
515
+ buildEnvelope(await client.updateTask(args.taskGid, args.data || {})),
516
+ };
517
+
518
+ const toolProvider: ToolProvider = {
519
+ getTools: () => tools,
520
+ executeTool: async (name, args) => {
521
+ const handler = handlers[name];
522
+ if (!handler) {
523
+ throw new Error(`Unknown tool: ${name}`);
524
+ }
525
+ return handler(args);
526
+ },
527
+ };
528
+
529
+ const serverInfo: MCPServerInfo = {
530
+ name: 'Asana Connector',
531
+ version: '0.1.0',
532
+ protocolVersion: PROTOCOL_VERSION,
533
+ capabilities: {
534
+ tools: { listChanged: false },
535
+ },
536
+ };
537
+
538
+ const server = new StdioMCPServer(toolProvider, serverInfo);
539
+ server.start();
540
+
541
+ function buildEnvelope(result: RequestResult): any {
542
+ return {
543
+ ok: true,
544
+ data: result.data,
545
+ meta: {
546
+ durationMs: result.meta.durationMs,
547
+ vendorRequestId: result.meta.vendorRequestId,
548
+ baseUrl: result.meta.baseUrl,
549
+ },
550
+ nextCursor: result.nextCursor,
551
+ warnings: [],
552
+ };
553
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "rootDir": "src",
6
+ "outDir": "dist",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "lib": ["ES2020", "DOM"]
11
+ },
12
+ "include": ["src"]
13
+ }
@@ -0,0 +1,35 @@
1
+ # HubSpot MCP Connector (MVP)
2
+
3
+ This connector exposes HubSpot CRM APIs to CoWork OS through MCP tools.
4
+
5
+ ## Requirements
6
+
7
+ Provide credentials via environment variables:
8
+
9
+ - `HUBSPOT_ACCESS_TOKEN` (required)
10
+ - Optional refresh: `HUBSPOT_CLIENT_ID`, `HUBSPOT_CLIENT_SECRET`, `HUBSPOT_REFRESH_TOKEN`
11
+ - `HUBSPOT_BASE_URL` (optional, default: `https://api.hubapi.com`)
12
+
13
+ ## Build & Run
14
+
15
+ ```bash
16
+ npm install
17
+ npm run build
18
+ npm start
19
+ ```
20
+
21
+ ## Add to CoWork MCP Settings
22
+
23
+ - **Command**: `node`
24
+ - **Args**: `/absolute/path/to/connectors/hubspot-mcp/dist/index.js`
25
+ - **Env**: set the variables above
26
+
27
+ ## Tools
28
+
29
+ - `hubspot.health`
30
+ - `hubspot.search_objects`
31
+ - `hubspot.get_object`
32
+ - `hubspot.create_object`
33
+ - `hubspot.update_object`
34
+
35
+ See `docs/enterprise-connectors.md` for the connector contract.