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,520 @@
|
|
|
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
|
+
// ==================== Okta Client ====================
|
|
77
|
+
|
|
78
|
+
type OktaConfig = {
|
|
79
|
+
baseUrl?: string;
|
|
80
|
+
apiToken?: string;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
type RateLimitInfo = {
|
|
84
|
+
limit?: number;
|
|
85
|
+
remaining?: number;
|
|
86
|
+
resetAt?: string;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
type RequestMeta = {
|
|
90
|
+
durationMs: number;
|
|
91
|
+
rateLimit?: RateLimitInfo;
|
|
92
|
+
vendorRequestId?: string;
|
|
93
|
+
baseUrl?: string;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
type RequestResult = {
|
|
97
|
+
data: any;
|
|
98
|
+
meta: RequestMeta;
|
|
99
|
+
nextCursor?: string;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
class OktaClient {
|
|
103
|
+
constructor(private config: OktaConfig) {}
|
|
104
|
+
|
|
105
|
+
async health(): Promise<RequestResult> {
|
|
106
|
+
return this.requestJson('GET', 'users/me');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async listUsers(limit?: number, after?: string, q?: string): Promise<RequestResult> {
|
|
110
|
+
const params = new URLSearchParams();
|
|
111
|
+
if (limit !== undefined) params.set('limit', String(limit));
|
|
112
|
+
if (after) params.set('after', after);
|
|
113
|
+
if (q) params.set('q', q);
|
|
114
|
+
const query = params.toString();
|
|
115
|
+
return this.requestJson('GET', `users${query ? `?${query}` : ''}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async getUser(userId: string): Promise<RequestResult> {
|
|
119
|
+
return this.requestJson('GET', `users/${encodeURIComponent(userId)}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async createUser(payload: Record<string, any>, activate?: boolean): Promise<RequestResult> {
|
|
123
|
+
const query = activate === undefined ? '' : `?activate=${activate ? 'true' : 'false'}`;
|
|
124
|
+
return this.requestJson('POST', `users${query}`, payload);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async updateUser(userId: string, payload: Record<string, any>): Promise<RequestResult> {
|
|
128
|
+
return this.requestJson('POST', `users/${encodeURIComponent(userId)}`, payload);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private getBaseUrl(): string {
|
|
132
|
+
if (!this.config.baseUrl) {
|
|
133
|
+
throw new Error('OKTA_BASE_URL is required');
|
|
134
|
+
}
|
|
135
|
+
return `${this.config.baseUrl.replace(/\/$/, '')}/api/v1`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private getAuthHeader(): string {
|
|
139
|
+
if (!this.config.apiToken) {
|
|
140
|
+
throw new Error('OKTA_API_TOKEN is required');
|
|
141
|
+
}
|
|
142
|
+
return `SSWS ${this.config.apiToken}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private extractRateLimit(headers: Headers): RateLimitInfo | undefined {
|
|
146
|
+
const limit = headers.get('x-rate-limit-limit');
|
|
147
|
+
const remaining = headers.get('x-rate-limit-remaining');
|
|
148
|
+
const reset = headers.get('x-rate-limit-reset');
|
|
149
|
+
|
|
150
|
+
if (!limit && !remaining && !reset) return undefined;
|
|
151
|
+
|
|
152
|
+
const resetAt = reset ? new Date(Number(reset) * 1000).toISOString() : undefined;
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
limit: limit ? Number(limit) : undefined,
|
|
156
|
+
remaining: remaining ? Number(remaining) : undefined,
|
|
157
|
+
resetAt,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private extractNextCursor(headers: Headers): string | undefined {
|
|
162
|
+
const link = headers.get('link');
|
|
163
|
+
if (!link) return undefined;
|
|
164
|
+
|
|
165
|
+
const match = link.match(/<([^>]+)>;\s*rel="next"/i);
|
|
166
|
+
if (!match) return undefined;
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const url = new URL(match[1]);
|
|
170
|
+
return url.searchParams.get('after') || undefined;
|
|
171
|
+
} catch {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private async requestJson(method: string, path: string, body?: any): Promise<RequestResult> {
|
|
177
|
+
const start = Date.now();
|
|
178
|
+
const url = `${this.getBaseUrl()}/${path.replace(/^\//, '')}`;
|
|
179
|
+
|
|
180
|
+
const res = await fetch(url, {
|
|
181
|
+
method,
|
|
182
|
+
headers: {
|
|
183
|
+
Authorization: this.getAuthHeader(),
|
|
184
|
+
'Content-Type': 'application/json',
|
|
185
|
+
'User-Agent': 'CoWork-Okta-Connector/0.1.0',
|
|
186
|
+
},
|
|
187
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const durationMs = Date.now() - start;
|
|
191
|
+
const vendorRequestId = res.headers.get('x-okta-request-id') || undefined;
|
|
192
|
+
|
|
193
|
+
if (!res.ok) {
|
|
194
|
+
const message = await res.text();
|
|
195
|
+
throw new Error(message || `Okta API error (${res.status})`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let data: any = null;
|
|
199
|
+
if (res.status !== 204) {
|
|
200
|
+
data = await res.json();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
data,
|
|
205
|
+
meta: {
|
|
206
|
+
durationMs,
|
|
207
|
+
vendorRequestId,
|
|
208
|
+
rateLimit: this.extractRateLimit(res.headers),
|
|
209
|
+
baseUrl: this.config.baseUrl,
|
|
210
|
+
},
|
|
211
|
+
nextCursor: this.extractNextCursor(res.headers),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ==================== MCP Stdio Server ====================
|
|
217
|
+
|
|
218
|
+
type ToolProvider = {
|
|
219
|
+
getTools(): MCPTool[];
|
|
220
|
+
executeTool(name: string, args: Record<string, any>): Promise<any>;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
class StdioMCPServer {
|
|
224
|
+
private initialized = false;
|
|
225
|
+
private rl: readline.Interface | null = null;
|
|
226
|
+
|
|
227
|
+
constructor(
|
|
228
|
+
private toolProvider: ToolProvider,
|
|
229
|
+
private serverInfo: MCPServerInfo
|
|
230
|
+
) {}
|
|
231
|
+
|
|
232
|
+
start(): void {
|
|
233
|
+
this.rl = readline.createInterface({
|
|
234
|
+
input: process.stdin,
|
|
235
|
+
output: process.stdout,
|
|
236
|
+
terminal: false,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
this.rl.on('line', (line) => this.handleLine(line));
|
|
240
|
+
this.rl.on('close', () => this.stop());
|
|
241
|
+
|
|
242
|
+
process.on('SIGINT', () => this.stop());
|
|
243
|
+
process.on('SIGTERM', () => this.stop());
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
stop(): void {
|
|
247
|
+
if (this.rl) {
|
|
248
|
+
this.rl.close();
|
|
249
|
+
this.rl = null;
|
|
250
|
+
}
|
|
251
|
+
process.exit(0);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private handleLine(line: string): void {
|
|
255
|
+
const trimmed = line.trim();
|
|
256
|
+
if (!trimmed) return;
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const message = JSON.parse(trimmed);
|
|
260
|
+
this.handleMessage(message);
|
|
261
|
+
} catch {
|
|
262
|
+
this.sendError(0, MCP_ERROR_CODES.PARSE_ERROR, 'Parse error');
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private async handleMessage(message: any): Promise<void> {
|
|
267
|
+
if ('id' in message && message.id !== null) {
|
|
268
|
+
await this.handleRequest(message as JSONRPCRequest);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if ('method' in message) {
|
|
273
|
+
await this.handleNotification(message as JSONRPCNotification);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private async handleRequest(request: JSONRPCRequest): Promise<void> {
|
|
278
|
+
const { id, method, params } = request;
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
let result: any;
|
|
282
|
+
|
|
283
|
+
switch (method) {
|
|
284
|
+
case MCP_METHODS.INITIALIZE:
|
|
285
|
+
result = this.handleInitialize(params);
|
|
286
|
+
break;
|
|
287
|
+
case MCP_METHODS.TOOLS_LIST:
|
|
288
|
+
this.requireInitialized();
|
|
289
|
+
result = this.handleToolsList();
|
|
290
|
+
break;
|
|
291
|
+
case MCP_METHODS.TOOLS_CALL:
|
|
292
|
+
this.requireInitialized();
|
|
293
|
+
result = await this.handleToolsCall(params);
|
|
294
|
+
break;
|
|
295
|
+
case MCP_METHODS.SHUTDOWN:
|
|
296
|
+
result = this.handleShutdown();
|
|
297
|
+
break;
|
|
298
|
+
default:
|
|
299
|
+
throw this.createError(MCP_ERROR_CODES.METHOD_NOT_FOUND, `Method not found: ${method}`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
this.sendResult(id, result);
|
|
303
|
+
} catch (error: any) {
|
|
304
|
+
if (error.code !== undefined) {
|
|
305
|
+
this.sendError(id, error.code, error.message, error.data);
|
|
306
|
+
} else {
|
|
307
|
+
this.sendError(id, MCP_ERROR_CODES.INTERNAL_ERROR, error?.message || 'Internal error');
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private async handleNotification(notification: JSONRPCNotification): Promise<void> {
|
|
313
|
+
const { method } = notification;
|
|
314
|
+
|
|
315
|
+
if (method === MCP_METHODS.INITIALIZED) {
|
|
316
|
+
this.initialized = true;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private handleInitialize(_params: any): {
|
|
321
|
+
protocolVersion: string;
|
|
322
|
+
capabilities: MCPServerInfo['capabilities'];
|
|
323
|
+
serverInfo: MCPServerInfo;
|
|
324
|
+
} {
|
|
325
|
+
if (this.initialized) {
|
|
326
|
+
throw this.createError(MCP_ERROR_CODES.INVALID_REQUEST, 'Already initialized');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
331
|
+
capabilities: this.serverInfo.capabilities,
|
|
332
|
+
serverInfo: this.serverInfo,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private handleToolsList(): { tools: MCPTool[] } {
|
|
337
|
+
return { tools: this.toolProvider.getTools() };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private async handleToolsCall(params: any): Promise<any> {
|
|
341
|
+
const { name, arguments: args } = params || {};
|
|
342
|
+
if (!name) {
|
|
343
|
+
throw this.createError(MCP_ERROR_CODES.INVALID_PARAMS, 'Tool name is required');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
try {
|
|
347
|
+
const result = await this.toolProvider.executeTool(name, args || {});
|
|
348
|
+
|
|
349
|
+
if (typeof result === 'string') {
|
|
350
|
+
return { content: [{ type: 'text', text: result }] };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (result && typeof result === 'object') {
|
|
354
|
+
if (result.content && Array.isArray(result.content)) {
|
|
355
|
+
return result;
|
|
356
|
+
}
|
|
357
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return { content: [{ type: 'text', text: String(result) }] };
|
|
361
|
+
} catch (error: any) {
|
|
362
|
+
return {
|
|
363
|
+
content: [{ type: 'text', text: `Error: ${error?.message || 'Tool failed'}` }],
|
|
364
|
+
isError: true,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private handleShutdown(): Record<string, never> {
|
|
370
|
+
setImmediate(() => this.stop());
|
|
371
|
+
return {};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private sendResult(id: JSONRPCId, result: any): void {
|
|
375
|
+
const response: JSONRPCResponse = { jsonrpc: '2.0', id, result };
|
|
376
|
+
this.sendMessage(response);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private sendError(id: JSONRPCId, code: number, message: string, data?: any): void {
|
|
380
|
+
const response: JSONRPCResponse = {
|
|
381
|
+
jsonrpc: '2.0',
|
|
382
|
+
id,
|
|
383
|
+
error: { code, message, data },
|
|
384
|
+
};
|
|
385
|
+
this.sendMessage(response);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private sendMessage(message: JSONRPCResponse | JSONRPCNotification): void {
|
|
389
|
+
process.stdout.write(JSON.stringify(message) + '\n');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private requireInitialized(): void {
|
|
393
|
+
if (!this.initialized) {
|
|
394
|
+
throw this.createError(MCP_ERROR_CODES.SERVER_NOT_INITIALIZED, 'Server not initialized');
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private createError(code: number, message: string, data?: any): { code: number; message: string; data?: any } {
|
|
399
|
+
return { code, message, data };
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// ==================== Tool Definitions ====================
|
|
404
|
+
|
|
405
|
+
const CONNECTOR_PREFIX = 'okta';
|
|
406
|
+
|
|
407
|
+
const tools: MCPTool[] = [
|
|
408
|
+
{
|
|
409
|
+
name: `${CONNECTOR_PREFIX}.health`,
|
|
410
|
+
description: 'Check connector health and authentication status',
|
|
411
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
name: `${CONNECTOR_PREFIX}.list_users`,
|
|
415
|
+
description: 'List users',
|
|
416
|
+
inputSchema: {
|
|
417
|
+
type: 'object',
|
|
418
|
+
properties: {
|
|
419
|
+
limit: { type: 'number', description: 'Max users to return' },
|
|
420
|
+
after: { type: 'string', description: 'Pagination cursor' },
|
|
421
|
+
q: { type: 'string', description: 'Search query' },
|
|
422
|
+
},
|
|
423
|
+
additionalProperties: false,
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
name: `${CONNECTOR_PREFIX}.get_user`,
|
|
428
|
+
description: 'Fetch a user by id',
|
|
429
|
+
inputSchema: {
|
|
430
|
+
type: 'object',
|
|
431
|
+
properties: {
|
|
432
|
+
id: { type: 'string', description: 'User id' },
|
|
433
|
+
},
|
|
434
|
+
required: ['id'],
|
|
435
|
+
additionalProperties: false,
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: `${CONNECTOR_PREFIX}.create_user`,
|
|
440
|
+
description: 'Create a user',
|
|
441
|
+
inputSchema: {
|
|
442
|
+
type: 'object',
|
|
443
|
+
properties: {
|
|
444
|
+
payload: { type: 'object', description: 'User payload' },
|
|
445
|
+
activate: { type: 'boolean', description: 'Activate user immediately' },
|
|
446
|
+
},
|
|
447
|
+
required: ['payload'],
|
|
448
|
+
additionalProperties: false,
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
name: `${CONNECTOR_PREFIX}.update_user`,
|
|
453
|
+
description: 'Update a user',
|
|
454
|
+
inputSchema: {
|
|
455
|
+
type: 'object',
|
|
456
|
+
properties: {
|
|
457
|
+
id: { type: 'string', description: 'User id' },
|
|
458
|
+
payload: { type: 'object', description: 'User payload updates' },
|
|
459
|
+
},
|
|
460
|
+
required: ['id', 'payload'],
|
|
461
|
+
additionalProperties: false,
|
|
462
|
+
},
|
|
463
|
+
},
|
|
464
|
+
];
|
|
465
|
+
|
|
466
|
+
const config: OktaConfig = {
|
|
467
|
+
baseUrl: process.env.OKTA_BASE_URL,
|
|
468
|
+
apiToken: process.env.OKTA_API_TOKEN,
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
const client = new OktaClient(config);
|
|
472
|
+
|
|
473
|
+
const handlers: Record<string, (args: Record<string, any>) => Promise<any>> = {
|
|
474
|
+
[`${CONNECTOR_PREFIX}.health`]: async () => buildEnvelope(await client.health()),
|
|
475
|
+
[`${CONNECTOR_PREFIX}.list_users`]: async (args) =>
|
|
476
|
+
buildEnvelope(await client.listUsers(args.limit, args.after, args.q)),
|
|
477
|
+
[`${CONNECTOR_PREFIX}.get_user`]: async (args) => buildEnvelope(await client.getUser(args.id)),
|
|
478
|
+
[`${CONNECTOR_PREFIX}.create_user`]: async (args) =>
|
|
479
|
+
buildEnvelope(await client.createUser(args.payload || {}, args.activate)),
|
|
480
|
+
[`${CONNECTOR_PREFIX}.update_user`]: async (args) =>
|
|
481
|
+
buildEnvelope(await client.updateUser(args.id, args.payload || {})),
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
const toolProvider: ToolProvider = {
|
|
485
|
+
getTools: () => tools,
|
|
486
|
+
executeTool: async (name, args) => {
|
|
487
|
+
const handler = handlers[name];
|
|
488
|
+
if (!handler) {
|
|
489
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
490
|
+
}
|
|
491
|
+
return handler(args);
|
|
492
|
+
},
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
const serverInfo: MCPServerInfo = {
|
|
496
|
+
name: 'Okta Connector',
|
|
497
|
+
version: '0.1.0',
|
|
498
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
499
|
+
capabilities: {
|
|
500
|
+
tools: { listChanged: false },
|
|
501
|
+
},
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
const server = new StdioMCPServer(toolProvider, serverInfo);
|
|
505
|
+
server.start();
|
|
506
|
+
|
|
507
|
+
function buildEnvelope(result: RequestResult): any {
|
|
508
|
+
return {
|
|
509
|
+
ok: true,
|
|
510
|
+
data: result.data,
|
|
511
|
+
meta: {
|
|
512
|
+
durationMs: result.meta.durationMs,
|
|
513
|
+
vendorRequestId: result.meta.vendorRequestId,
|
|
514
|
+
rateLimit: result.meta.rateLimit,
|
|
515
|
+
baseUrl: result.meta.baseUrl,
|
|
516
|
+
},
|
|
517
|
+
nextCursor: result.nextCursor,
|
|
518
|
+
warnings: [],
|
|
519
|
+
};
|
|
520
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Salesforce MCP Connector (MVP)
|
|
2
|
+
|
|
3
|
+
This connector exposes Salesforce APIs to CoWork OS through MCP tools.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
Provide credentials via environment variables:
|
|
8
|
+
|
|
9
|
+
- `SALESFORCE_INSTANCE_URL` (required)
|
|
10
|
+
- `SALESFORCE_ACCESS_TOKEN` (recommended)
|
|
11
|
+
|
|
12
|
+
Optional OAuth refresh flow:
|
|
13
|
+
|
|
14
|
+
- `SALESFORCE_CLIENT_ID`
|
|
15
|
+
- `SALESFORCE_CLIENT_SECRET`
|
|
16
|
+
- `SALESFORCE_REFRESH_TOKEN`
|
|
17
|
+
- `SALESFORCE_LOGIN_URL` (default: `https://login.salesforce.com`)
|
|
18
|
+
|
|
19
|
+
Optional:
|
|
20
|
+
|
|
21
|
+
- `SALESFORCE_API_VERSION` (default: `60.0`)
|
|
22
|
+
|
|
23
|
+
## Build & Run
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install
|
|
27
|
+
npm run build
|
|
28
|
+
npm start
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Add to CoWork MCP Settings
|
|
32
|
+
|
|
33
|
+
- **Command**: `node`
|
|
34
|
+
- **Args**: `/absolute/path/to/connectors/salesforce-mcp/dist/index.js`
|
|
35
|
+
- **Env**: set the variables above
|
|
36
|
+
|
|
37
|
+
## Tools
|
|
38
|
+
|
|
39
|
+
- `salesforce.health`
|
|
40
|
+
- `salesforce.list_objects`
|
|
41
|
+
- `salesforce.describe_object`
|
|
42
|
+
- `salesforce.get_record`
|
|
43
|
+
- `salesforce.search_records`
|
|
44
|
+
- `salesforce.create_record`
|
|
45
|
+
- `salesforce.update_record`
|
|
46
|
+
|
|
47
|
+
See `docs/enterprise-connectors.md` for the contract.
|