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,543 @@
|
|
|
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
|
+
// ==================== Zendesk Client ====================
|
|
77
|
+
|
|
78
|
+
type ZendeskConfig = {
|
|
79
|
+
baseUrl: string;
|
|
80
|
+
accessToken?: string;
|
|
81
|
+
email?: string;
|
|
82
|
+
apiToken?: string;
|
|
83
|
+
clientId?: string;
|
|
84
|
+
clientSecret?: string;
|
|
85
|
+
refreshToken?: string;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
type RequestMeta = {
|
|
89
|
+
durationMs: number;
|
|
90
|
+
vendorRequestId?: string;
|
|
91
|
+
baseUrl: string;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
type RequestResult = {
|
|
95
|
+
data: any;
|
|
96
|
+
meta: RequestMeta;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
class ZendeskClient {
|
|
100
|
+
constructor(private config: ZendeskConfig) {}
|
|
101
|
+
|
|
102
|
+
async health(): Promise<RequestResult> {
|
|
103
|
+
return this.requestJson('GET', 'api/v2/users/me.json');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async searchTickets(query: string): Promise<RequestResult> {
|
|
107
|
+
const params = new URLSearchParams({ query });
|
|
108
|
+
return this.requestJson('GET', `api/v2/search.json?${params.toString()}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async getTicket(ticketId: string): Promise<RequestResult> {
|
|
112
|
+
return this.requestJson('GET', `api/v2/tickets/${encodeURIComponent(ticketId)}.json`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async createTicket(payload: Record<string, any>): Promise<RequestResult> {
|
|
116
|
+
return this.requestJson('POST', 'api/v2/tickets.json', payload);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async updateTicket(ticketId: string, payload: Record<string, any>): Promise<RequestResult> {
|
|
120
|
+
return this.requestJson('PUT', `api/v2/tickets/${encodeURIComponent(ticketId)}.json`, payload);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private getBaseUrl(): string {
|
|
124
|
+
if (!this.config.baseUrl) {
|
|
125
|
+
throw new Error('ZENDESK_BASE_URL or ZENDESK_SUBDOMAIN is required');
|
|
126
|
+
}
|
|
127
|
+
return this.config.baseUrl.replace(/\/$/, '');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private canRefresh(): boolean {
|
|
131
|
+
return Boolean(this.config.clientId && this.config.clientSecret && this.config.refreshToken);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private async ensureAccessToken(): Promise<string> {
|
|
135
|
+
if (this.config.accessToken) {
|
|
136
|
+
return this.config.accessToken;
|
|
137
|
+
}
|
|
138
|
+
if (!this.canRefresh()) {
|
|
139
|
+
throw new Error('Missing Zendesk credentials (ZENDESK_ACCESS_TOKEN or ZENDESK_EMAIL + ZENDESK_API_TOKEN)');
|
|
140
|
+
}
|
|
141
|
+
await this.refreshAccessToken();
|
|
142
|
+
if (!this.config.accessToken) {
|
|
143
|
+
throw new Error('Failed to refresh Zendesk access token');
|
|
144
|
+
}
|
|
145
|
+
return this.config.accessToken;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private async getAuthHeader(): Promise<string> {
|
|
149
|
+
if (this.config.accessToken || this.canRefresh()) {
|
|
150
|
+
const token = await this.ensureAccessToken();
|
|
151
|
+
return `Bearer ${token}`;
|
|
152
|
+
}
|
|
153
|
+
if (this.config.email && this.config.apiToken) {
|
|
154
|
+
const basic = Buffer.from(`${this.config.email}/token:${this.config.apiToken}`).toString('base64');
|
|
155
|
+
return `Basic ${basic}`;
|
|
156
|
+
}
|
|
157
|
+
throw new Error('Missing Zendesk credentials (ZENDESK_ACCESS_TOKEN or ZENDESK_EMAIL + ZENDESK_API_TOKEN)');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private async requestJson(method: string, path: string, body?: any): Promise<RequestResult> {
|
|
161
|
+
const start = Date.now();
|
|
162
|
+
const url = `${this.getBaseUrl()}/${path.replace(/^\//, '')}`;
|
|
163
|
+
|
|
164
|
+
const res = await fetch(url, {
|
|
165
|
+
method,
|
|
166
|
+
headers: {
|
|
167
|
+
Authorization: await this.getAuthHeader(),
|
|
168
|
+
'Content-Type': 'application/json',
|
|
169
|
+
'User-Agent': 'CoWork-Zendesk-Connector/0.1.0',
|
|
170
|
+
},
|
|
171
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const durationMs = Date.now() - start;
|
|
175
|
+
const vendorRequestId = res.headers.get('x-request-id') || undefined;
|
|
176
|
+
|
|
177
|
+
if (res.status === 401 && this.canRefresh()) {
|
|
178
|
+
await this.refreshAccessToken();
|
|
179
|
+
return this.requestJson(method, path, body);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!res.ok) {
|
|
183
|
+
const message = await res.text();
|
|
184
|
+
throw new Error(message || `Zendesk API error (${res.status})`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let data: any = null;
|
|
188
|
+
if (res.status !== 204) {
|
|
189
|
+
data = await res.json();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
data,
|
|
194
|
+
meta: {
|
|
195
|
+
durationMs,
|
|
196
|
+
vendorRequestId,
|
|
197
|
+
baseUrl: this.config.baseUrl,
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private async refreshAccessToken(): Promise<void> {
|
|
203
|
+
if (!this.canRefresh()) {
|
|
204
|
+
throw new Error('Missing Zendesk refresh credentials');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const res = await fetch(`${this.getBaseUrl()}/oauth/tokens`, {
|
|
208
|
+
method: 'POST',
|
|
209
|
+
headers: { 'Content-Type': 'application/json' },
|
|
210
|
+
body: JSON.stringify({
|
|
211
|
+
grant_type: 'refresh_token',
|
|
212
|
+
client_id: this.config.clientId,
|
|
213
|
+
client_secret: this.config.clientSecret,
|
|
214
|
+
refresh_token: this.config.refreshToken,
|
|
215
|
+
}),
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (!res.ok) {
|
|
219
|
+
const text = await res.text();
|
|
220
|
+
throw new Error(`Zendesk OAuth refresh failed: ${text}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const data = await res.json();
|
|
224
|
+
if (!data.access_token) {
|
|
225
|
+
throw new Error('Zendesk OAuth refresh returned no access_token');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.config.accessToken = data.access_token;
|
|
229
|
+
if (data.refresh_token) {
|
|
230
|
+
this.config.refreshToken = data.refresh_token;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ==================== MCP Stdio Server ====================
|
|
236
|
+
|
|
237
|
+
type ToolProvider = {
|
|
238
|
+
getTools(): MCPTool[];
|
|
239
|
+
executeTool(name: string, args: Record<string, any>): Promise<any>;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
class StdioMCPServer {
|
|
243
|
+
private initialized = false;
|
|
244
|
+
private rl: readline.Interface | null = null;
|
|
245
|
+
|
|
246
|
+
constructor(
|
|
247
|
+
private toolProvider: ToolProvider,
|
|
248
|
+
private serverInfo: MCPServerInfo
|
|
249
|
+
) {}
|
|
250
|
+
|
|
251
|
+
start(): void {
|
|
252
|
+
this.rl = readline.createInterface({
|
|
253
|
+
input: process.stdin,
|
|
254
|
+
output: process.stdout,
|
|
255
|
+
terminal: false,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
this.rl.on('line', (line) => this.handleLine(line));
|
|
259
|
+
this.rl.on('close', () => this.stop());
|
|
260
|
+
|
|
261
|
+
process.on('SIGINT', () => this.stop());
|
|
262
|
+
process.on('SIGTERM', () => this.stop());
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
stop(): void {
|
|
266
|
+
if (this.rl) {
|
|
267
|
+
this.rl.close();
|
|
268
|
+
this.rl = null;
|
|
269
|
+
}
|
|
270
|
+
process.exit(0);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private handleLine(line: string): void {
|
|
274
|
+
const trimmed = line.trim();
|
|
275
|
+
if (!trimmed) return;
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const message = JSON.parse(trimmed);
|
|
279
|
+
this.handleMessage(message);
|
|
280
|
+
} catch {
|
|
281
|
+
this.sendError(0, MCP_ERROR_CODES.PARSE_ERROR, 'Parse error');
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private async handleMessage(message: any): Promise<void> {
|
|
286
|
+
if ('id' in message && message.id !== null) {
|
|
287
|
+
await this.handleRequest(message as JSONRPCRequest);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if ('method' in message) {
|
|
292
|
+
await this.handleNotification(message as JSONRPCNotification);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private async handleRequest(request: JSONRPCRequest): Promise<void> {
|
|
297
|
+
const { id, method, params } = request;
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
let result: any;
|
|
301
|
+
|
|
302
|
+
switch (method) {
|
|
303
|
+
case MCP_METHODS.INITIALIZE:
|
|
304
|
+
result = this.handleInitialize(params);
|
|
305
|
+
break;
|
|
306
|
+
case MCP_METHODS.TOOLS_LIST:
|
|
307
|
+
this.requireInitialized();
|
|
308
|
+
result = this.handleToolsList();
|
|
309
|
+
break;
|
|
310
|
+
case MCP_METHODS.TOOLS_CALL:
|
|
311
|
+
this.requireInitialized();
|
|
312
|
+
result = await this.handleToolsCall(params);
|
|
313
|
+
break;
|
|
314
|
+
case MCP_METHODS.SHUTDOWN:
|
|
315
|
+
result = this.handleShutdown();
|
|
316
|
+
break;
|
|
317
|
+
default:
|
|
318
|
+
throw this.createError(MCP_ERROR_CODES.METHOD_NOT_FOUND, `Method not found: ${method}`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
this.sendResult(id, result);
|
|
322
|
+
} catch (error: any) {
|
|
323
|
+
if (error.code !== undefined) {
|
|
324
|
+
this.sendError(id, error.code, error.message, error.data);
|
|
325
|
+
} else {
|
|
326
|
+
this.sendError(id, MCP_ERROR_CODES.INTERNAL_ERROR, error?.message || 'Internal error');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private async handleNotification(notification: JSONRPCNotification): Promise<void> {
|
|
332
|
+
const { method } = notification;
|
|
333
|
+
|
|
334
|
+
if (method === MCP_METHODS.INITIALIZED) {
|
|
335
|
+
this.initialized = true;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private handleInitialize(_params: any): {
|
|
340
|
+
protocolVersion: string;
|
|
341
|
+
capabilities: MCPServerInfo['capabilities'];
|
|
342
|
+
serverInfo: MCPServerInfo;
|
|
343
|
+
} {
|
|
344
|
+
if (this.initialized) {
|
|
345
|
+
throw this.createError(MCP_ERROR_CODES.INVALID_REQUEST, 'Already initialized');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
350
|
+
capabilities: this.serverInfo.capabilities,
|
|
351
|
+
serverInfo: this.serverInfo,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private handleToolsList(): { tools: MCPTool[] } {
|
|
356
|
+
return { tools: this.toolProvider.getTools() };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private async handleToolsCall(params: any): Promise<any> {
|
|
360
|
+
const { name, arguments: args } = params || {};
|
|
361
|
+
if (!name) {
|
|
362
|
+
throw this.createError(MCP_ERROR_CODES.INVALID_PARAMS, 'Tool name is required');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
const result = await this.toolProvider.executeTool(name, args || {});
|
|
367
|
+
|
|
368
|
+
if (typeof result === 'string') {
|
|
369
|
+
return { content: [{ type: 'text', text: result }] };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (result && typeof result === 'object') {
|
|
373
|
+
if (result.content && Array.isArray(result.content)) {
|
|
374
|
+
return result;
|
|
375
|
+
}
|
|
376
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return { content: [{ type: 'text', text: String(result) }] };
|
|
380
|
+
} catch (error: any) {
|
|
381
|
+
return {
|
|
382
|
+
content: [{ type: 'text', text: `Error: ${error?.message || 'Tool failed'}` }],
|
|
383
|
+
isError: true,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private handleShutdown(): Record<string, never> {
|
|
389
|
+
setImmediate(() => this.stop());
|
|
390
|
+
return {};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private sendResult(id: JSONRPCId, result: any): void {
|
|
394
|
+
const response: JSONRPCResponse = { jsonrpc: '2.0', id, result };
|
|
395
|
+
this.sendMessage(response);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private sendError(id: JSONRPCId, code: number, message: string, data?: any): void {
|
|
399
|
+
const response: JSONRPCResponse = {
|
|
400
|
+
jsonrpc: '2.0',
|
|
401
|
+
id,
|
|
402
|
+
error: { code, message, data },
|
|
403
|
+
};
|
|
404
|
+
this.sendMessage(response);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private sendMessage(message: JSONRPCResponse | JSONRPCNotification): void {
|
|
408
|
+
process.stdout.write(JSON.stringify(message) + '\n');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private requireInitialized(): void {
|
|
412
|
+
if (!this.initialized) {
|
|
413
|
+
throw this.createError(MCP_ERROR_CODES.SERVER_NOT_INITIALIZED, 'Server not initialized');
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private createError(code: number, message: string, data?: any): { code: number; message: string; data?: any } {
|
|
418
|
+
return { code, message, data };
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ==================== Tool Definitions ====================
|
|
423
|
+
|
|
424
|
+
const CONNECTOR_PREFIX = 'zendesk';
|
|
425
|
+
|
|
426
|
+
const baseUrl = process.env.ZENDESK_BASE_URL
|
|
427
|
+
? process.env.ZENDESK_BASE_URL
|
|
428
|
+
: process.env.ZENDESK_SUBDOMAIN
|
|
429
|
+
? `https://${process.env.ZENDESK_SUBDOMAIN}.zendesk.com`
|
|
430
|
+
: '';
|
|
431
|
+
|
|
432
|
+
const config: ZendeskConfig = {
|
|
433
|
+
baseUrl,
|
|
434
|
+
accessToken: process.env.ZENDESK_ACCESS_TOKEN,
|
|
435
|
+
email: process.env.ZENDESK_EMAIL,
|
|
436
|
+
apiToken: process.env.ZENDESK_API_TOKEN,
|
|
437
|
+
clientId: process.env.ZENDESK_CLIENT_ID,
|
|
438
|
+
clientSecret: process.env.ZENDESK_CLIENT_SECRET,
|
|
439
|
+
refreshToken: process.env.ZENDESK_REFRESH_TOKEN,
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const client = new ZendeskClient(config);
|
|
443
|
+
|
|
444
|
+
const tools: MCPTool[] = [
|
|
445
|
+
{
|
|
446
|
+
name: `${CONNECTOR_PREFIX}.health`,
|
|
447
|
+
description: 'Check connector health and authentication status',
|
|
448
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
name: `${CONNECTOR_PREFIX}.search_tickets`,
|
|
452
|
+
description: 'Search Zendesk tickets with a query string',
|
|
453
|
+
inputSchema: {
|
|
454
|
+
type: 'object',
|
|
455
|
+
properties: {
|
|
456
|
+
query: { type: 'string', description: 'Search query (Zendesk search syntax)' },
|
|
457
|
+
},
|
|
458
|
+
required: ['query'],
|
|
459
|
+
additionalProperties: false,
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
name: `${CONNECTOR_PREFIX}.get_ticket`,
|
|
464
|
+
description: 'Fetch a ticket by ID',
|
|
465
|
+
inputSchema: {
|
|
466
|
+
type: 'object',
|
|
467
|
+
properties: {
|
|
468
|
+
id: { type: 'string', description: 'Ticket ID' },
|
|
469
|
+
},
|
|
470
|
+
required: ['id'],
|
|
471
|
+
additionalProperties: false,
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
name: `${CONNECTOR_PREFIX}.create_ticket`,
|
|
476
|
+
description: 'Create a new ticket',
|
|
477
|
+
inputSchema: {
|
|
478
|
+
type: 'object',
|
|
479
|
+
properties: {
|
|
480
|
+
ticket: { type: 'object', description: 'Zendesk ticket payload (ticket object)' },
|
|
481
|
+
},
|
|
482
|
+
required: ['ticket'],
|
|
483
|
+
additionalProperties: false,
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
name: `${CONNECTOR_PREFIX}.update_ticket`,
|
|
488
|
+
description: 'Update a ticket',
|
|
489
|
+
inputSchema: {
|
|
490
|
+
type: 'object',
|
|
491
|
+
properties: {
|
|
492
|
+
id: { type: 'string', description: 'Ticket ID' },
|
|
493
|
+
ticket: { type: 'object', description: 'Zendesk ticket payload (ticket object)' },
|
|
494
|
+
},
|
|
495
|
+
required: ['id', 'ticket'],
|
|
496
|
+
additionalProperties: false,
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
];
|
|
500
|
+
|
|
501
|
+
const handlers: Record<string, (args: Record<string, any>) => Promise<any>> = {
|
|
502
|
+
[`${CONNECTOR_PREFIX}.health`]: async () => buildEnvelope(await client.health()),
|
|
503
|
+
[`${CONNECTOR_PREFIX}.search_tickets`]: async (args) => buildEnvelope(await client.searchTickets(args.query)),
|
|
504
|
+
[`${CONNECTOR_PREFIX}.get_ticket`]: async (args) => buildEnvelope(await client.getTicket(args.id)),
|
|
505
|
+
[`${CONNECTOR_PREFIX}.create_ticket`]: async (args) => buildEnvelope(await client.createTicket({ ticket: args.ticket })),
|
|
506
|
+
[`${CONNECTOR_PREFIX}.update_ticket`]: async (args) => buildEnvelope(await client.updateTicket(args.id, { ticket: args.ticket })),
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const toolProvider: ToolProvider = {
|
|
510
|
+
getTools: () => tools,
|
|
511
|
+
executeTool: async (name, args) => {
|
|
512
|
+
const handler = handlers[name];
|
|
513
|
+
if (!handler) {
|
|
514
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
515
|
+
}
|
|
516
|
+
return handler(args);
|
|
517
|
+
},
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
const serverInfo: MCPServerInfo = {
|
|
521
|
+
name: 'Zendesk Connector',
|
|
522
|
+
version: '0.1.0',
|
|
523
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
524
|
+
capabilities: {
|
|
525
|
+
tools: { listChanged: false },
|
|
526
|
+
},
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
const server = new StdioMCPServer(toolProvider, serverInfo);
|
|
530
|
+
server.start();
|
|
531
|
+
|
|
532
|
+
function buildEnvelope(result: RequestResult): any {
|
|
533
|
+
return {
|
|
534
|
+
ok: true,
|
|
535
|
+
data: result.data,
|
|
536
|
+
meta: {
|
|
537
|
+
durationMs: result.meta.durationMs,
|
|
538
|
+
vendorRequestId: result.meta.vendorRequestId,
|
|
539
|
+
baseUrl: result.meta.baseUrl,
|
|
540
|
+
},
|
|
541
|
+
warnings: [],
|
|
542
|
+
};
|
|
543
|
+
}
|
|
@@ -48,6 +48,9 @@ const MemoryService_1 = require("../memory/MemoryService");
|
|
|
48
48
|
// Memory management constants
|
|
49
49
|
const MAX_CACHED_EXECUTORS = 10; // Maximum number of completed task executors to keep in memory
|
|
50
50
|
const EXECUTOR_CACHE_TTL_MS = 30 * 60 * 1000; // 30 minutes - time before completed executors are cleaned up
|
|
51
|
+
// Activity throttling constants
|
|
52
|
+
const ACTIVITY_THROTTLE_WINDOW_MS = 2000; // 2 seconds - window for deduping similar activities
|
|
53
|
+
const THROTTLED_ACTIVITY_TYPES = new Set(['tool_call', 'file_created', 'file_modified', 'file_deleted']);
|
|
51
54
|
/**
|
|
52
55
|
* AgentDaemon is the core orchestrator that manages task execution
|
|
53
56
|
* It coordinates between the database, task executors, and UI
|
|
@@ -58,6 +61,8 @@ class AgentDaemon extends events_1.EventEmitter {
|
|
|
58
61
|
this.dbManager = dbManager;
|
|
59
62
|
this.activeTasks = new Map();
|
|
60
63
|
this.pendingApprovals = new Map();
|
|
64
|
+
// Activity throttle: Map<taskId:eventType, lastTimestamp>
|
|
65
|
+
this.activityThrottle = new Map();
|
|
61
66
|
const db = dbManager.getDatabase();
|
|
62
67
|
this.taskRepo = new repositories_1.TaskRepository(db);
|
|
63
68
|
this.eventRepo = new repositories_1.TaskEventRepository(db);
|
|
@@ -485,6 +490,26 @@ class AgentDaemon extends events_1.EventEmitter {
|
|
|
485
490
|
const task = this.taskRepo.findById(taskId);
|
|
486
491
|
if (!task)
|
|
487
492
|
return;
|
|
493
|
+
// Throttle high-frequency activity types to reduce database writes
|
|
494
|
+
if (THROTTLED_ACTIVITY_TYPES.has(type)) {
|
|
495
|
+
const throttleKey = `${taskId}:${type}`;
|
|
496
|
+
const now = Date.now();
|
|
497
|
+
const lastTime = this.activityThrottle.get(throttleKey);
|
|
498
|
+
if (lastTime && (now - lastTime) < ACTIVITY_THROTTLE_WINDOW_MS) {
|
|
499
|
+
// Skip this activity - too soon after the last one of the same type
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
this.activityThrottle.set(throttleKey, now);
|
|
503
|
+
// Clean up old throttle entries periodically (keep map from growing unbounded)
|
|
504
|
+
if (this.activityThrottle.size > 1000) {
|
|
505
|
+
const cutoff = now - ACTIVITY_THROTTLE_WINDOW_MS * 10;
|
|
506
|
+
for (const [key, time] of this.activityThrottle) {
|
|
507
|
+
if (time < cutoff) {
|
|
508
|
+
this.activityThrottle.delete(key);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
488
513
|
const activity = this.buildActivityFromEvent(task, type, payload);
|
|
489
514
|
if (!activity)
|
|
490
515
|
return;
|