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,522 @@
|
|
|
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
|
+
// ==================== Linear Client ====================
|
|
77
|
+
|
|
78
|
+
type LinearConfig = {
|
|
79
|
+
baseUrl: string;
|
|
80
|
+
apiKey?: 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
|
+
type GraphQLResponse = {
|
|
96
|
+
data?: any;
|
|
97
|
+
errors?: Array<{ message?: string }>;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
class LinearClient {
|
|
101
|
+
constructor(private config: LinearConfig) {}
|
|
102
|
+
|
|
103
|
+
async health(): Promise<RequestResult> {
|
|
104
|
+
const query = `query Viewer { viewer { id name email } }`;
|
|
105
|
+
return this.requestGraphQL(query, undefined, 'viewer');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async listProjects(limit?: number, cursor?: string): Promise<RequestResult> {
|
|
109
|
+
const query = `query Projects($first: Int, $after: String) {
|
|
110
|
+
projects(first: $first, after: $after) {
|
|
111
|
+
nodes { id name state { name } }
|
|
112
|
+
pageInfo { hasNextPage endCursor }
|
|
113
|
+
}
|
|
114
|
+
}`;
|
|
115
|
+
|
|
116
|
+
return this.requestGraphQL(query, { first: limit, after: cursor }, 'projects');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async searchIssues(
|
|
120
|
+
queryText: string,
|
|
121
|
+
limit?: number,
|
|
122
|
+
cursor?: string,
|
|
123
|
+
projectId?: string,
|
|
124
|
+
teamId?: string
|
|
125
|
+
): Promise<RequestResult> {
|
|
126
|
+
const filter: Record<string, any> = {
|
|
127
|
+
title: { contains: queryText },
|
|
128
|
+
};
|
|
129
|
+
if (projectId) {
|
|
130
|
+
filter.project = { id: { eq: projectId } };
|
|
131
|
+
}
|
|
132
|
+
if (teamId) {
|
|
133
|
+
filter.team = { id: { eq: teamId } };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const query = `query Issues($first: Int, $after: String, $filter: IssueFilter) {
|
|
137
|
+
issues(first: $first, after: $after, filter: $filter) {
|
|
138
|
+
nodes {
|
|
139
|
+
id
|
|
140
|
+
title
|
|
141
|
+
identifier
|
|
142
|
+
url
|
|
143
|
+
state { id name }
|
|
144
|
+
project { id name }
|
|
145
|
+
team { id name }
|
|
146
|
+
}
|
|
147
|
+
pageInfo { hasNextPage endCursor }
|
|
148
|
+
}
|
|
149
|
+
}`;
|
|
150
|
+
|
|
151
|
+
return this.requestGraphQL(query, { first: limit, after: cursor, filter }, 'issues');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async getIssue(issueId: string): Promise<RequestResult> {
|
|
155
|
+
const query = `query Issue($id: String!) {
|
|
156
|
+
issue(id: $id) {
|
|
157
|
+
id
|
|
158
|
+
title
|
|
159
|
+
identifier
|
|
160
|
+
url
|
|
161
|
+
description
|
|
162
|
+
state { id name }
|
|
163
|
+
project { id name }
|
|
164
|
+
team { id name }
|
|
165
|
+
assignee { id name email }
|
|
166
|
+
}
|
|
167
|
+
}`;
|
|
168
|
+
|
|
169
|
+
return this.requestGraphQL(query, { id: issueId }, 'issue');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private getBaseUrl(): string {
|
|
173
|
+
return this.config.baseUrl.replace(/\/$/, '');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private getAuthHeader(): string {
|
|
177
|
+
if (!this.config.apiKey) {
|
|
178
|
+
throw new Error('LINEAR_API_KEY is required');
|
|
179
|
+
}
|
|
180
|
+
return `Bearer ${this.config.apiKey}`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private async requestGraphQL(
|
|
184
|
+
query: string,
|
|
185
|
+
variables?: Record<string, any>,
|
|
186
|
+
rootField?: string
|
|
187
|
+
): Promise<RequestResult> {
|
|
188
|
+
const start = Date.now();
|
|
189
|
+
const url = this.getBaseUrl();
|
|
190
|
+
|
|
191
|
+
const res = await fetch(url, {
|
|
192
|
+
method: 'POST',
|
|
193
|
+
headers: {
|
|
194
|
+
Authorization: this.getAuthHeader(),
|
|
195
|
+
'Content-Type': 'application/json',
|
|
196
|
+
'User-Agent': 'CoWork-Linear-Connector/0.1.0',
|
|
197
|
+
},
|
|
198
|
+
body: JSON.stringify({ query, variables }),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const durationMs = Date.now() - start;
|
|
202
|
+
const vendorRequestId = res.headers.get('x-request-id') || undefined;
|
|
203
|
+
|
|
204
|
+
if (!res.ok) {
|
|
205
|
+
const message = await res.text();
|
|
206
|
+
throw new Error(message || `Linear API error (${res.status})`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const payload = (await res.json()) as GraphQLResponse;
|
|
210
|
+
if (payload.errors && payload.errors.length > 0) {
|
|
211
|
+
const message = payload.errors.map((err) => err.message || 'GraphQL error').join('; ');
|
|
212
|
+
throw new Error(message);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const extracted = rootField ? payload.data?.[rootField] : payload.data;
|
|
216
|
+
const pageInfo = extracted?.pageInfo;
|
|
217
|
+
const nextCursor = pageInfo?.hasNextPage ? pageInfo.endCursor : undefined;
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
data: extracted,
|
|
221
|
+
meta: {
|
|
222
|
+
durationMs,
|
|
223
|
+
vendorRequestId,
|
|
224
|
+
baseUrl: this.config.baseUrl,
|
|
225
|
+
},
|
|
226
|
+
nextCursor,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ==================== MCP Stdio Server ====================
|
|
232
|
+
|
|
233
|
+
type ToolProvider = {
|
|
234
|
+
getTools(): MCPTool[];
|
|
235
|
+
executeTool(name: string, args: Record<string, any>): Promise<any>;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
class StdioMCPServer {
|
|
239
|
+
private initialized = false;
|
|
240
|
+
private rl: readline.Interface | null = null;
|
|
241
|
+
|
|
242
|
+
constructor(
|
|
243
|
+
private toolProvider: ToolProvider,
|
|
244
|
+
private serverInfo: MCPServerInfo
|
|
245
|
+
) {}
|
|
246
|
+
|
|
247
|
+
start(): void {
|
|
248
|
+
this.rl = readline.createInterface({
|
|
249
|
+
input: process.stdin,
|
|
250
|
+
output: process.stdout,
|
|
251
|
+
terminal: false,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
this.rl.on('line', (line) => this.handleLine(line));
|
|
255
|
+
this.rl.on('close', () => this.stop());
|
|
256
|
+
|
|
257
|
+
process.on('SIGINT', () => this.stop());
|
|
258
|
+
process.on('SIGTERM', () => this.stop());
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
stop(): void {
|
|
262
|
+
if (this.rl) {
|
|
263
|
+
this.rl.close();
|
|
264
|
+
this.rl = null;
|
|
265
|
+
}
|
|
266
|
+
process.exit(0);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private handleLine(line: string): void {
|
|
270
|
+
const trimmed = line.trim();
|
|
271
|
+
if (!trimmed) return;
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
const message = JSON.parse(trimmed);
|
|
275
|
+
this.handleMessage(message);
|
|
276
|
+
} catch {
|
|
277
|
+
this.sendError(0, MCP_ERROR_CODES.PARSE_ERROR, 'Parse error');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private async handleMessage(message: any): Promise<void> {
|
|
282
|
+
if ('id' in message && message.id !== null) {
|
|
283
|
+
await this.handleRequest(message as JSONRPCRequest);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if ('method' in message) {
|
|
288
|
+
await this.handleNotification(message as JSONRPCNotification);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private async handleRequest(request: JSONRPCRequest): Promise<void> {
|
|
293
|
+
const { id, method, params } = request;
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
let result: any;
|
|
297
|
+
|
|
298
|
+
switch (method) {
|
|
299
|
+
case MCP_METHODS.INITIALIZE:
|
|
300
|
+
result = this.handleInitialize(params);
|
|
301
|
+
break;
|
|
302
|
+
case MCP_METHODS.TOOLS_LIST:
|
|
303
|
+
this.requireInitialized();
|
|
304
|
+
result = this.handleToolsList();
|
|
305
|
+
break;
|
|
306
|
+
case MCP_METHODS.TOOLS_CALL:
|
|
307
|
+
this.requireInitialized();
|
|
308
|
+
result = await this.handleToolsCall(params);
|
|
309
|
+
break;
|
|
310
|
+
case MCP_METHODS.SHUTDOWN:
|
|
311
|
+
result = this.handleShutdown();
|
|
312
|
+
break;
|
|
313
|
+
default:
|
|
314
|
+
throw this.createError(MCP_ERROR_CODES.METHOD_NOT_FOUND, `Method not found: ${method}`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
this.sendResult(id, result);
|
|
318
|
+
} catch (error: any) {
|
|
319
|
+
if (error.code !== undefined) {
|
|
320
|
+
this.sendError(id, error.code, error.message, error.data);
|
|
321
|
+
} else {
|
|
322
|
+
this.sendError(id, MCP_ERROR_CODES.INTERNAL_ERROR, error?.message || 'Internal error');
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private async handleNotification(notification: JSONRPCNotification): Promise<void> {
|
|
328
|
+
const { method } = notification;
|
|
329
|
+
|
|
330
|
+
if (method === MCP_METHODS.INITIALIZED) {
|
|
331
|
+
this.initialized = true;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private handleInitialize(_params: any): {
|
|
336
|
+
protocolVersion: string;
|
|
337
|
+
capabilities: MCPServerInfo['capabilities'];
|
|
338
|
+
serverInfo: MCPServerInfo;
|
|
339
|
+
} {
|
|
340
|
+
if (this.initialized) {
|
|
341
|
+
throw this.createError(MCP_ERROR_CODES.INVALID_REQUEST, 'Already initialized');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
346
|
+
capabilities: this.serverInfo.capabilities,
|
|
347
|
+
serverInfo: this.serverInfo,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private handleToolsList(): { tools: MCPTool[] } {
|
|
352
|
+
return { tools: this.toolProvider.getTools() };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private async handleToolsCall(params: any): Promise<any> {
|
|
356
|
+
const { name, arguments: args } = params || {};
|
|
357
|
+
if (!name) {
|
|
358
|
+
throw this.createError(MCP_ERROR_CODES.INVALID_PARAMS, 'Tool name is required');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
const result = await this.toolProvider.executeTool(name, args || {});
|
|
363
|
+
|
|
364
|
+
if (typeof result === 'string') {
|
|
365
|
+
return { content: [{ type: 'text', text: result }] };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (result && typeof result === 'object') {
|
|
369
|
+
if (result.content && Array.isArray(result.content)) {
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
372
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return { content: [{ type: 'text', text: String(result) }] };
|
|
376
|
+
} catch (error: any) {
|
|
377
|
+
return {
|
|
378
|
+
content: [{ type: 'text', text: `Error: ${error?.message || 'Tool failed'}` }],
|
|
379
|
+
isError: true,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private handleShutdown(): Record<string, never> {
|
|
385
|
+
setImmediate(() => this.stop());
|
|
386
|
+
return {};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
private sendResult(id: JSONRPCId, result: any): void {
|
|
390
|
+
const response: JSONRPCResponse = { jsonrpc: '2.0', id, result };
|
|
391
|
+
this.sendMessage(response);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private sendError(id: JSONRPCId, code: number, message: string, data?: any): void {
|
|
395
|
+
const response: JSONRPCResponse = {
|
|
396
|
+
jsonrpc: '2.0',
|
|
397
|
+
id,
|
|
398
|
+
error: { code, message, data },
|
|
399
|
+
};
|
|
400
|
+
this.sendMessage(response);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private sendMessage(message: JSONRPCResponse | JSONRPCNotification): void {
|
|
404
|
+
process.stdout.write(JSON.stringify(message) + '\n');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private requireInitialized(): void {
|
|
408
|
+
if (!this.initialized) {
|
|
409
|
+
throw this.createError(MCP_ERROR_CODES.SERVER_NOT_INITIALIZED, 'Server not initialized');
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
private createError(code: number, message: string, data?: any): { code: number; message: string; data?: any } {
|
|
414
|
+
return { code, message, data };
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ==================== Tool Definitions ====================
|
|
419
|
+
|
|
420
|
+
const CONNECTOR_PREFIX = 'linear';
|
|
421
|
+
const DEFAULT_BASE_URL = 'https://api.linear.app/graphql';
|
|
422
|
+
|
|
423
|
+
const tools: MCPTool[] = [
|
|
424
|
+
{
|
|
425
|
+
name: `${CONNECTOR_PREFIX}.health`,
|
|
426
|
+
description: 'Check connector health and authentication status',
|
|
427
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
name: `${CONNECTOR_PREFIX}.list_projects`,
|
|
431
|
+
description: 'List Linear projects',
|
|
432
|
+
inputSchema: {
|
|
433
|
+
type: 'object',
|
|
434
|
+
properties: {
|
|
435
|
+
limit: { type: 'number', description: 'Max projects to return' },
|
|
436
|
+
cursor: { type: 'string', description: 'Pagination cursor' },
|
|
437
|
+
},
|
|
438
|
+
additionalProperties: false,
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
name: `${CONNECTOR_PREFIX}.search_issues`,
|
|
443
|
+
description: 'Search issues by title',
|
|
444
|
+
inputSchema: {
|
|
445
|
+
type: 'object',
|
|
446
|
+
properties: {
|
|
447
|
+
query: { type: 'string', description: 'Search text for issue titles' },
|
|
448
|
+
projectId: { type: 'string', description: 'Filter by project id' },
|
|
449
|
+
teamId: { type: 'string', description: 'Filter by team id' },
|
|
450
|
+
limit: { type: 'number', description: 'Max issues to return' },
|
|
451
|
+
cursor: { type: 'string', description: 'Pagination cursor' },
|
|
452
|
+
},
|
|
453
|
+
required: ['query'],
|
|
454
|
+
additionalProperties: false,
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
name: `${CONNECTOR_PREFIX}.get_issue`,
|
|
459
|
+
description: 'Fetch an issue by id',
|
|
460
|
+
inputSchema: {
|
|
461
|
+
type: 'object',
|
|
462
|
+
properties: {
|
|
463
|
+
id: { type: 'string', description: 'Issue id' },
|
|
464
|
+
},
|
|
465
|
+
required: ['id'],
|
|
466
|
+
additionalProperties: false,
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
];
|
|
470
|
+
|
|
471
|
+
const config: LinearConfig = {
|
|
472
|
+
baseUrl: process.env.LINEAR_BASE_URL || DEFAULT_BASE_URL,
|
|
473
|
+
apiKey: process.env.LINEAR_API_KEY,
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
const client = new LinearClient(config);
|
|
477
|
+
|
|
478
|
+
const handlers: Record<string, (args: Record<string, any>) => Promise<any>> = {
|
|
479
|
+
[`${CONNECTOR_PREFIX}.health`]: async () => buildEnvelope(await client.health()),
|
|
480
|
+
[`${CONNECTOR_PREFIX}.list_projects`]: async (args) =>
|
|
481
|
+
buildEnvelope(await client.listProjects(args.limit, args.cursor)),
|
|
482
|
+
[`${CONNECTOR_PREFIX}.search_issues`]: async (args) =>
|
|
483
|
+
buildEnvelope(await client.searchIssues(args.query, args.limit, args.cursor, args.projectId, args.teamId)),
|
|
484
|
+
[`${CONNECTOR_PREFIX}.get_issue`]: async (args) => buildEnvelope(await client.getIssue(args.id)),
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
const toolProvider: ToolProvider = {
|
|
488
|
+
getTools: () => tools,
|
|
489
|
+
executeTool: async (name, args) => {
|
|
490
|
+
const handler = handlers[name];
|
|
491
|
+
if (!handler) {
|
|
492
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
493
|
+
}
|
|
494
|
+
return handler(args);
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const serverInfo: MCPServerInfo = {
|
|
499
|
+
name: 'Linear Connector',
|
|
500
|
+
version: '0.1.0',
|
|
501
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
502
|
+
capabilities: {
|
|
503
|
+
tools: { listChanged: false },
|
|
504
|
+
},
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
const server = new StdioMCPServer(toolProvider, serverInfo);
|
|
508
|
+
server.start();
|
|
509
|
+
|
|
510
|
+
function buildEnvelope(result: RequestResult): any {
|
|
511
|
+
return {
|
|
512
|
+
ok: true,
|
|
513
|
+
data: result.data,
|
|
514
|
+
meta: {
|
|
515
|
+
durationMs: result.meta.durationMs,
|
|
516
|
+
vendorRequestId: result.meta.vendorRequestId,
|
|
517
|
+
baseUrl: result.meta.baseUrl,
|
|
518
|
+
},
|
|
519
|
+
nextCursor: result.nextCursor,
|
|
520
|
+
warnings: [],
|
|
521
|
+
};
|
|
522
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Okta MCP Connector (MVP)
|
|
2
|
+
|
|
3
|
+
This connector exposes Okta Users API endpoints to CoWork OS through MCP tools.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- `OKTA_BASE_URL` (e.g., `https://your-org.okta.com`)
|
|
8
|
+
- `OKTA_API_TOKEN`
|
|
9
|
+
|
|
10
|
+
## Build & Run
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install
|
|
14
|
+
npm run build
|
|
15
|
+
npm start
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Tools
|
|
19
|
+
|
|
20
|
+
- `okta.health`
|
|
21
|
+
- `okta.list_users`
|
|
22
|
+
- `okta.get_user`
|
|
23
|
+
- `okta.create_user`
|
|
24
|
+
- `okta.update_user`
|