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.
- package/README.md +293 -6
- package/connectors/README.md +20 -0
- package/connectors/asana-mcp/README.md +24 -0
- package/connectors/asana-mcp/dist/index.js +427 -0
- package/connectors/asana-mcp/package.json +15 -0
- package/connectors/asana-mcp/src/index.ts +553 -0
- package/connectors/asana-mcp/tsconfig.json +13 -0
- package/connectors/hubspot-mcp/README.md +35 -0
- package/connectors/hubspot-mcp/dist/index.js +454 -0
- package/connectors/hubspot-mcp/package.json +15 -0
- package/connectors/hubspot-mcp/src/index.ts +562 -0
- package/connectors/hubspot-mcp/tsconfig.json +13 -0
- package/connectors/jira-mcp/README.md +49 -0
- package/connectors/jira-mcp/dist/index.js +588 -0
- package/connectors/jira-mcp/package.json +15 -0
- package/connectors/jira-mcp/src/index.ts +711 -0
- package/connectors/jira-mcp/tsconfig.json +13 -0
- package/connectors/linear-mcp/README.md +22 -0
- package/connectors/linear-mcp/dist/index.js +402 -0
- package/connectors/linear-mcp/package.json +15 -0
- package/connectors/linear-mcp/src/index.ts +522 -0
- package/connectors/linear-mcp/tsconfig.json +13 -0
- package/connectors/okta-mcp/README.md +24 -0
- package/connectors/okta-mcp/dist/index.js +411 -0
- package/connectors/okta-mcp/package.json +15 -0
- package/connectors/okta-mcp/src/index.ts +520 -0
- package/connectors/okta-mcp/tsconfig.json +13 -0
- package/connectors/salesforce-mcp/README.md +47 -0
- package/connectors/salesforce-mcp/dist/index.js +584 -0
- package/connectors/salesforce-mcp/package.json +15 -0
- package/connectors/salesforce-mcp/src/index.ts +722 -0
- package/connectors/salesforce-mcp/tsconfig.json +13 -0
- package/connectors/servicenow-mcp/README.md +26 -0
- package/connectors/servicenow-mcp/dist/index.js +400 -0
- package/connectors/servicenow-mcp/package.json +15 -0
- package/connectors/servicenow-mcp/src/index.ts +500 -0
- package/connectors/servicenow-mcp/tsconfig.json +13 -0
- package/connectors/templates/mcp-connector/README.md +31 -0
- package/connectors/templates/mcp-connector/package.json +15 -0
- package/connectors/templates/mcp-connector/src/index.ts +330 -0
- package/connectors/templates/mcp-connector/tsconfig.json +13 -0
- package/connectors/zendesk-mcp/README.md +40 -0
- package/connectors/zendesk-mcp/dist/index.js +431 -0
- package/connectors/zendesk-mcp/package.json +15 -0
- package/connectors/zendesk-mcp/src/index.ts +543 -0
- package/connectors/zendesk-mcp/tsconfig.json +13 -0
- package/dist/electron/electron/agent/daemon.js +25 -0
- package/dist/electron/electron/agent/executor.js +181 -26
- package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
- package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
- package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
- package/dist/electron/electron/agent/llm/index.js +11 -1
- package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
- package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
- package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
- package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
- package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
- package/dist/electron/electron/agent/llm/provider-factory.js +318 -4
- package/dist/electron/electron/agent/llm/types.js +66 -1
- package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
- package/dist/electron/electron/agent/tools/box-tools.js +231 -0
- package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
- package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
- package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
- package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
- package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
- package/dist/electron/electron/agent/tools/registry.js +541 -0
- package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
- package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
- package/dist/electron/electron/agent/tools/x-tools.js +1 -1
- package/dist/electron/electron/gateway/index.js +1 -0
- package/dist/electron/electron/gateway/router.js +123 -143
- package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
- package/dist/electron/electron/ipc/handlers.js +627 -158
- package/dist/electron/electron/main.js +63 -0
- package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
- package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
- package/dist/electron/electron/memory/MemoryService.js +1 -1
- package/dist/electron/electron/preload.js +74 -1
- package/dist/electron/electron/settings/box-manager.js +54 -0
- package/dist/electron/electron/settings/dropbox-manager.js +54 -0
- package/dist/electron/electron/settings/google-drive-manager.js +54 -0
- package/dist/electron/electron/settings/notion-manager.js +56 -0
- package/dist/electron/electron/settings/onedrive-manager.js +54 -0
- package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
- package/dist/electron/electron/utils/box-api.js +153 -0
- package/dist/electron/electron/utils/dropbox-api.js +144 -0
- package/dist/electron/electron/utils/env-migration.js +19 -0
- package/dist/electron/electron/utils/google-drive-api.js +152 -0
- package/dist/electron/electron/utils/notion-api.js +103 -0
- package/dist/electron/electron/utils/onedrive-api.js +113 -0
- package/dist/electron/electron/utils/sharepoint-api.js +109 -0
- package/dist/electron/electron/utils/validation.js +82 -3
- package/dist/electron/electron/utils/x-cli.js +1 -1
- package/dist/electron/shared/channelMessages.js +284 -3
- package/dist/electron/shared/llm-provider-catalog.js +198 -0
- package/dist/electron/shared/types.js +88 -1
- package/package.json +12 -2
- package/src/electron/agent/executor.ts +205 -28
- package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
- package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
- package/src/electron/agent/llm/groq-provider.ts +39 -0
- package/src/electron/agent/llm/index.ts +5 -0
- package/src/electron/agent/llm/kimi-provider.ts +39 -0
- package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
- package/src/electron/agent/llm/openai-compatible.ts +133 -0
- package/src/electron/agent/llm/openai-oauth.ts +2 -1
- package/src/electron/agent/llm/openrouter-provider.ts +2 -1
- package/src/electron/agent/llm/provider-factory.ts +414 -6
- package/src/electron/agent/llm/types.ts +90 -1
- package/src/electron/agent/llm/xai-provider.ts +39 -0
- package/src/electron/agent/tools/box-tools.ts +239 -0
- package/src/electron/agent/tools/builtin-settings.ts +34 -0
- package/src/electron/agent/tools/dropbox-tools.ts +237 -0
- package/src/electron/agent/tools/google-drive-tools.ts +228 -0
- package/src/electron/agent/tools/notion-tools.ts +330 -0
- package/src/electron/agent/tools/onedrive-tools.ts +217 -0
- package/src/electron/agent/tools/registry.ts +565 -0
- package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
- package/src/electron/agent/tools/shell-tools.ts +11 -3
- package/src/electron/agent/tools/x-tools.ts +1 -1
- package/src/electron/database/SecureSettingsRepository.ts +7 -1
- package/src/electron/gateway/index.ts +1 -0
- package/src/electron/gateway/router.ts +134 -149
- package/src/electron/ipc/canvas-handlers.ts +10 -0
- package/src/electron/ipc/handlers.ts +673 -153
- package/src/electron/main.ts +35 -0
- package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
- package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
- package/src/electron/memory/MemoryService.ts +5 -1
- package/src/electron/preload.ts +167 -4
- package/src/electron/settings/box-manager.ts +58 -0
- package/src/electron/settings/dropbox-manager.ts +58 -0
- package/src/electron/settings/google-drive-manager.ts +58 -0
- package/src/electron/settings/notion-manager.ts +60 -0
- package/src/electron/settings/onedrive-manager.ts +58 -0
- package/src/electron/settings/sharepoint-manager.ts +58 -0
- package/src/electron/utils/box-api.ts +184 -0
- package/src/electron/utils/dropbox-api.ts +171 -0
- package/src/electron/utils/env-migration.ts +22 -0
- package/src/electron/utils/google-drive-api.ts +183 -0
- package/src/electron/utils/notion-api.ts +126 -0
- package/src/electron/utils/onedrive-api.ts +137 -0
- package/src/electron/utils/sharepoint-api.ts +132 -0
- package/src/electron/utils/validation.ts +102 -1
- package/src/electron/utils/x-cli.ts +1 -1
- package/src/renderer/App.tsx +20 -2
- package/src/renderer/components/BoxSettings.tsx +203 -0
- package/src/renderer/components/BrowserView.tsx +101 -0
- package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
- package/src/renderer/components/CanvasPreview.tsx +68 -1
- package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
- package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
- package/src/renderer/components/ConnectorsSettings.tsx +397 -0
- package/src/renderer/components/DropboxSettings.tsx +202 -0
- package/src/renderer/components/GoogleDriveSettings.tsx +201 -0
- package/src/renderer/components/MCPSettings.tsx +56 -0
- package/src/renderer/components/MainContent.tsx +270 -34
- package/src/renderer/components/NotionSettings.tsx +231 -0
- package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
- package/src/renderer/components/OnboardingModal.tsx +70 -1
- package/src/renderer/components/OneDriveSettings.tsx +212 -0
- package/src/renderer/components/Settings.tsx +611 -8
- package/src/renderer/components/SharePointSettings.tsx +224 -0
- package/src/renderer/components/Sidebar.tsx +25 -9
- package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
- package/src/renderer/styles/index.css +438 -25
- package/src/shared/channelMessages.ts +367 -4
- package/src/shared/llm-provider-catalog.ts +217 -0
- package/src/shared/types.ts +226 -1
|
@@ -0,0 +1,500 @@
|
|
|
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
|
+
// ==================== ServiceNow Client ====================
|
|
77
|
+
|
|
78
|
+
type ServiceNowConfig = {
|
|
79
|
+
instanceUrl?: string;
|
|
80
|
+
instance?: string;
|
|
81
|
+
username?: string;
|
|
82
|
+
password?: string;
|
|
83
|
+
accessToken?: string;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
type RequestMeta = {
|
|
87
|
+
durationMs: number;
|
|
88
|
+
vendorRequestId?: string;
|
|
89
|
+
baseUrl: string;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
type RequestResult = {
|
|
93
|
+
data: any;
|
|
94
|
+
meta: RequestMeta;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
class ServiceNowClient {
|
|
98
|
+
constructor(private config: ServiceNowConfig) {}
|
|
99
|
+
|
|
100
|
+
async health(): Promise<RequestResult> {
|
|
101
|
+
return this.requestJson('GET', 'table/sys_user?sysparm_limit=1');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async listRecords(table: string, query?: string, limit?: number, offset?: number, fields?: string[]): Promise<RequestResult> {
|
|
105
|
+
const params = new URLSearchParams();
|
|
106
|
+
if (query) params.set('sysparm_query', query);
|
|
107
|
+
if (limit !== undefined) params.set('sysparm_limit', String(limit));
|
|
108
|
+
if (offset !== undefined) params.set('sysparm_offset', String(offset));
|
|
109
|
+
if (fields && fields.length > 0) params.set('sysparm_fields', fields.join(','));
|
|
110
|
+
const queryString = params.toString();
|
|
111
|
+
return this.requestJson('GET', `table/${encodeURIComponent(table)}${queryString ? `?${queryString}` : ''}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async getRecord(table: string, sysId: string, fields?: string[]): Promise<RequestResult> {
|
|
115
|
+
const params = new URLSearchParams();
|
|
116
|
+
if (fields && fields.length > 0) params.set('sysparm_fields', fields.join(','));
|
|
117
|
+
const queryString = params.toString();
|
|
118
|
+
return this.requestJson('GET', `table/${encodeURIComponent(table)}/${encodeURIComponent(sysId)}${queryString ? `?${queryString}` : ''}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async createRecord(table: string, fields: Record<string, any>): Promise<RequestResult> {
|
|
122
|
+
return this.requestJson('POST', `table/${encodeURIComponent(table)}`, fields);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async updateRecord(table: string, sysId: string, fields: Record<string, any>): Promise<RequestResult> {
|
|
126
|
+
return this.requestJson('PATCH', `table/${encodeURIComponent(table)}/${encodeURIComponent(sysId)}`, fields);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private getBaseUrl(): string {
|
|
130
|
+
if (this.config.instanceUrl) {
|
|
131
|
+
return this.config.instanceUrl.replace(/\/$/, '');
|
|
132
|
+
}
|
|
133
|
+
if (this.config.instance) {
|
|
134
|
+
return `https://${this.config.instance}.service-now.com`;
|
|
135
|
+
}
|
|
136
|
+
throw new Error('SERVICENOW_INSTANCE_URL or SERVICENOW_INSTANCE is required');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private getAuthHeader(): string {
|
|
140
|
+
if (this.config.accessToken) {
|
|
141
|
+
return `Bearer ${this.config.accessToken}`;
|
|
142
|
+
}
|
|
143
|
+
if (this.config.username && this.config.password) {
|
|
144
|
+
const basic = Buffer.from(`${this.config.username}:${this.config.password}`).toString('base64');
|
|
145
|
+
return `Basic ${basic}`;
|
|
146
|
+
}
|
|
147
|
+
throw new Error('Missing ServiceNow credentials');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private async requestJson(method: string, path: string, body?: any): Promise<RequestResult> {
|
|
151
|
+
const start = Date.now();
|
|
152
|
+
const url = `${this.getBaseUrl()}/api/now/${path.replace(/^\//, '')}`;
|
|
153
|
+
|
|
154
|
+
const res = await fetch(url, {
|
|
155
|
+
method,
|
|
156
|
+
headers: {
|
|
157
|
+
Authorization: this.getAuthHeader(),
|
|
158
|
+
'Content-Type': 'application/json',
|
|
159
|
+
'User-Agent': 'CoWork-ServiceNow-Connector/0.1.0',
|
|
160
|
+
},
|
|
161
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const durationMs = Date.now() - start;
|
|
165
|
+
const vendorRequestId = res.headers.get('x-request-id') || undefined;
|
|
166
|
+
|
|
167
|
+
if (!res.ok) {
|
|
168
|
+
const message = await res.text();
|
|
169
|
+
throw new Error(message || `ServiceNow API error (${res.status})`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let data: any = null;
|
|
173
|
+
if (res.status !== 204) {
|
|
174
|
+
data = await res.json();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
data,
|
|
179
|
+
meta: {
|
|
180
|
+
durationMs,
|
|
181
|
+
vendorRequestId,
|
|
182
|
+
baseUrl: this.getBaseUrl(),
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ==================== MCP Stdio Server ====================
|
|
189
|
+
|
|
190
|
+
type ToolProvider = {
|
|
191
|
+
getTools(): MCPTool[];
|
|
192
|
+
executeTool(name: string, args: Record<string, any>): Promise<any>;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
class StdioMCPServer {
|
|
196
|
+
private initialized = false;
|
|
197
|
+
private rl: readline.Interface | null = null;
|
|
198
|
+
|
|
199
|
+
constructor(
|
|
200
|
+
private toolProvider: ToolProvider,
|
|
201
|
+
private serverInfo: MCPServerInfo
|
|
202
|
+
) {}
|
|
203
|
+
|
|
204
|
+
start(): void {
|
|
205
|
+
this.rl = readline.createInterface({
|
|
206
|
+
input: process.stdin,
|
|
207
|
+
output: process.stdout,
|
|
208
|
+
terminal: false,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
this.rl.on('line', (line) => this.handleLine(line));
|
|
212
|
+
this.rl.on('close', () => this.stop());
|
|
213
|
+
|
|
214
|
+
process.on('SIGINT', () => this.stop());
|
|
215
|
+
process.on('SIGTERM', () => this.stop());
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
stop(): void {
|
|
219
|
+
if (this.rl) {
|
|
220
|
+
this.rl.close();
|
|
221
|
+
this.rl = null;
|
|
222
|
+
}
|
|
223
|
+
process.exit(0);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private handleLine(line: string): void {
|
|
227
|
+
const trimmed = line.trim();
|
|
228
|
+
if (!trimmed) return;
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
const message = JSON.parse(trimmed);
|
|
232
|
+
this.handleMessage(message);
|
|
233
|
+
} catch {
|
|
234
|
+
this.sendError(0, MCP_ERROR_CODES.PARSE_ERROR, 'Parse error');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private async handleMessage(message: any): Promise<void> {
|
|
239
|
+
if ('id' in message && message.id !== null) {
|
|
240
|
+
await this.handleRequest(message as JSONRPCRequest);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if ('method' in message) {
|
|
245
|
+
await this.handleNotification(message as JSONRPCNotification);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private async handleRequest(request: JSONRPCRequest): Promise<void> {
|
|
250
|
+
const { id, method, params } = request;
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
let result: any;
|
|
254
|
+
|
|
255
|
+
switch (method) {
|
|
256
|
+
case MCP_METHODS.INITIALIZE:
|
|
257
|
+
result = this.handleInitialize(params);
|
|
258
|
+
break;
|
|
259
|
+
case MCP_METHODS.TOOLS_LIST:
|
|
260
|
+
this.requireInitialized();
|
|
261
|
+
result = this.handleToolsList();
|
|
262
|
+
break;
|
|
263
|
+
case MCP_METHODS.TOOLS_CALL:
|
|
264
|
+
this.requireInitialized();
|
|
265
|
+
result = await this.handleToolsCall(params);
|
|
266
|
+
break;
|
|
267
|
+
case MCP_METHODS.SHUTDOWN:
|
|
268
|
+
result = this.handleShutdown();
|
|
269
|
+
break;
|
|
270
|
+
default:
|
|
271
|
+
throw this.createError(MCP_ERROR_CODES.METHOD_NOT_FOUND, `Method not found: ${method}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
this.sendResult(id, result);
|
|
275
|
+
} catch (error: any) {
|
|
276
|
+
if (error.code !== undefined) {
|
|
277
|
+
this.sendError(id, error.code, error.message, error.data);
|
|
278
|
+
} else {
|
|
279
|
+
this.sendError(id, MCP_ERROR_CODES.INTERNAL_ERROR, error?.message || 'Internal error');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private async handleNotification(notification: JSONRPCNotification): Promise<void> {
|
|
285
|
+
const { method } = notification;
|
|
286
|
+
|
|
287
|
+
if (method === MCP_METHODS.INITIALIZED) {
|
|
288
|
+
this.initialized = true;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private handleInitialize(_params: any): {
|
|
293
|
+
protocolVersion: string;
|
|
294
|
+
capabilities: MCPServerInfo['capabilities'];
|
|
295
|
+
serverInfo: MCPServerInfo;
|
|
296
|
+
} {
|
|
297
|
+
if (this.initialized) {
|
|
298
|
+
throw this.createError(MCP_ERROR_CODES.INVALID_REQUEST, 'Already initialized');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
303
|
+
capabilities: this.serverInfo.capabilities,
|
|
304
|
+
serverInfo: this.serverInfo,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private handleToolsList(): { tools: MCPTool[] } {
|
|
309
|
+
return { tools: this.toolProvider.getTools() };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private async handleToolsCall(params: any): Promise<any> {
|
|
313
|
+
const { name, arguments: args } = params || {};
|
|
314
|
+
if (!name) {
|
|
315
|
+
throw this.createError(MCP_ERROR_CODES.INVALID_PARAMS, 'Tool name is required');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
const result = await this.toolProvider.executeTool(name, args || {});
|
|
320
|
+
|
|
321
|
+
if (typeof result === 'string') {
|
|
322
|
+
return { content: [{ type: 'text', text: result }] };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (result && typeof result === 'object') {
|
|
326
|
+
if (result.content && Array.isArray(result.content)) {
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return { content: [{ type: 'text', text: String(result) }] };
|
|
333
|
+
} catch (error: any) {
|
|
334
|
+
return {
|
|
335
|
+
content: [{ type: 'text', text: `Error: ${error?.message || 'Tool failed'}` }],
|
|
336
|
+
isError: true,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private handleShutdown(): Record<string, never> {
|
|
342
|
+
setImmediate(() => this.stop());
|
|
343
|
+
return {};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private sendResult(id: JSONRPCId, result: any): void {
|
|
347
|
+
const response: JSONRPCResponse = { jsonrpc: '2.0', id, result };
|
|
348
|
+
this.sendMessage(response);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private sendError(id: JSONRPCId, code: number, message: string, data?: any): void {
|
|
352
|
+
const response: JSONRPCResponse = {
|
|
353
|
+
jsonrpc: '2.0',
|
|
354
|
+
id,
|
|
355
|
+
error: { code, message, data },
|
|
356
|
+
};
|
|
357
|
+
this.sendMessage(response);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private sendMessage(message: JSONRPCResponse | JSONRPCNotification): void {
|
|
361
|
+
process.stdout.write(JSON.stringify(message) + '\n');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private requireInitialized(): void {
|
|
365
|
+
if (!this.initialized) {
|
|
366
|
+
throw this.createError(MCP_ERROR_CODES.SERVER_NOT_INITIALIZED, 'Server not initialized');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private createError(code: number, message: string, data?: any): { code: number; message: string; data?: any } {
|
|
371
|
+
return { code, message, data };
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ==================== Tool Definitions ====================
|
|
376
|
+
|
|
377
|
+
const CONNECTOR_PREFIX = 'servicenow';
|
|
378
|
+
|
|
379
|
+
const config: ServiceNowConfig = {
|
|
380
|
+
instanceUrl: process.env.SERVICENOW_INSTANCE_URL,
|
|
381
|
+
instance: process.env.SERVICENOW_INSTANCE,
|
|
382
|
+
username: process.env.SERVICENOW_USERNAME,
|
|
383
|
+
password: process.env.SERVICENOW_PASSWORD,
|
|
384
|
+
accessToken: process.env.SERVICENOW_ACCESS_TOKEN,
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const client = new ServiceNowClient(config);
|
|
388
|
+
|
|
389
|
+
const tools: MCPTool[] = [
|
|
390
|
+
{
|
|
391
|
+
name: `${CONNECTOR_PREFIX}.health`,
|
|
392
|
+
description: 'Check connector health and authentication status',
|
|
393
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
name: `${CONNECTOR_PREFIX}.list_records`,
|
|
397
|
+
description: 'List records from a ServiceNow table',
|
|
398
|
+
inputSchema: {
|
|
399
|
+
type: 'object',
|
|
400
|
+
properties: {
|
|
401
|
+
table: { type: 'string', description: 'Table name (e.g., incident)' },
|
|
402
|
+
query: { type: 'string', description: 'sysparm_query string' },
|
|
403
|
+
limit: { type: 'number', description: 'Max records to return' },
|
|
404
|
+
offset: { type: 'number', description: 'Offset for pagination' },
|
|
405
|
+
fields: { type: 'array', description: 'Fields to return', items: { type: 'string' } },
|
|
406
|
+
},
|
|
407
|
+
required: ['table'],
|
|
408
|
+
additionalProperties: false,
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: `${CONNECTOR_PREFIX}.get_record`,
|
|
413
|
+
description: 'Fetch a record by sys_id',
|
|
414
|
+
inputSchema: {
|
|
415
|
+
type: 'object',
|
|
416
|
+
properties: {
|
|
417
|
+
table: { type: 'string', description: 'Table name (e.g., incident)' },
|
|
418
|
+
sysId: { type: 'string', description: 'sys_id of the record' },
|
|
419
|
+
fields: { type: 'array', description: 'Fields to return', items: { type: 'string' } },
|
|
420
|
+
},
|
|
421
|
+
required: ['table', 'sysId'],
|
|
422
|
+
additionalProperties: false,
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: `${CONNECTOR_PREFIX}.create_record`,
|
|
427
|
+
description: 'Create a record in a table',
|
|
428
|
+
inputSchema: {
|
|
429
|
+
type: 'object',
|
|
430
|
+
properties: {
|
|
431
|
+
table: { type: 'string', description: 'Table name (e.g., incident)' },
|
|
432
|
+
fields: { type: 'object', description: 'Field map for creation' },
|
|
433
|
+
},
|
|
434
|
+
required: ['table', 'fields'],
|
|
435
|
+
additionalProperties: false,
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: `${CONNECTOR_PREFIX}.update_record`,
|
|
440
|
+
description: 'Update a record in a table',
|
|
441
|
+
inputSchema: {
|
|
442
|
+
type: 'object',
|
|
443
|
+
properties: {
|
|
444
|
+
table: { type: 'string', description: 'Table name (e.g., incident)' },
|
|
445
|
+
sysId: { type: 'string', description: 'sys_id of the record' },
|
|
446
|
+
fields: { type: 'object', description: 'Field map for update' },
|
|
447
|
+
},
|
|
448
|
+
required: ['table', 'sysId', 'fields'],
|
|
449
|
+
additionalProperties: false,
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
];
|
|
453
|
+
|
|
454
|
+
const handlers: Record<string, (args: Record<string, any>) => Promise<any>> = {
|
|
455
|
+
[`${CONNECTOR_PREFIX}.health`]: async () => buildEnvelope(await client.health()),
|
|
456
|
+
[`${CONNECTOR_PREFIX}.list_records`]: async (args) =>
|
|
457
|
+
buildEnvelope(await client.listRecords(args.table, args.query, args.limit, args.offset, args.fields)),
|
|
458
|
+
[`${CONNECTOR_PREFIX}.get_record`]: async (args) =>
|
|
459
|
+
buildEnvelope(await client.getRecord(args.table, args.sysId, args.fields)),
|
|
460
|
+
[`${CONNECTOR_PREFIX}.create_record`]: async (args) =>
|
|
461
|
+
buildEnvelope(await client.createRecord(args.table, args.fields || {})),
|
|
462
|
+
[`${CONNECTOR_PREFIX}.update_record`]: async (args) =>
|
|
463
|
+
buildEnvelope(await client.updateRecord(args.table, args.sysId, args.fields || {})),
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
const toolProvider: ToolProvider = {
|
|
467
|
+
getTools: () => tools,
|
|
468
|
+
executeTool: async (name, args) => {
|
|
469
|
+
const handler = handlers[name];
|
|
470
|
+
if (!handler) {
|
|
471
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
472
|
+
}
|
|
473
|
+
return handler(args);
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const serverInfo: MCPServerInfo = {
|
|
478
|
+
name: 'ServiceNow Connector',
|
|
479
|
+
version: '0.1.0',
|
|
480
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
481
|
+
capabilities: {
|
|
482
|
+
tools: { listChanged: false },
|
|
483
|
+
},
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const server = new StdioMCPServer(toolProvider, serverInfo);
|
|
487
|
+
server.start();
|
|
488
|
+
|
|
489
|
+
function buildEnvelope(result: RequestResult): any {
|
|
490
|
+
return {
|
|
491
|
+
ok: true,
|
|
492
|
+
data: result.data,
|
|
493
|
+
meta: {
|
|
494
|
+
durationMs: result.meta.durationMs,
|
|
495
|
+
vendorRequestId: result.meta.vendorRequestId,
|
|
496
|
+
baseUrl: result.meta.baseUrl,
|
|
497
|
+
},
|
|
498
|
+
warnings: [],
|
|
499
|
+
};
|
|
500
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# MCP Connector Template
|
|
2
|
+
|
|
3
|
+
This is a minimal MCP server template for building CoWork enterprise connectors.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
1. Install deps and build:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
npm run build
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
2. Run the server:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm start
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
3. Add to CoWork MCP Settings:
|
|
21
|
+
|
|
22
|
+
- **Command**: `node`
|
|
23
|
+
- **Args**: `/absolute/path/to/connectors/templates/mcp-connector/dist/index.js`
|
|
24
|
+
|
|
25
|
+
## Customization Steps
|
|
26
|
+
|
|
27
|
+
1. Rename the connector prefix in `src/index.ts`.
|
|
28
|
+
2. Replace the example tools with real tools.
|
|
29
|
+
3. Implement OAuth and API calls inside the handlers.
|
|
30
|
+
|
|
31
|
+
See `docs/enterprise-connectors.md` for the connector contract and tool naming.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cowork-mcp-connector-template",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc -p tsconfig.json",
|
|
9
|
+
"start": "node dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@types/node": "^20.11.30",
|
|
13
|
+
"typescript": "^5.7.3"
|
|
14
|
+
}
|
|
15
|
+
}
|