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,588 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const readline = __importStar(require("readline"));
|
|
37
|
+
const PROTOCOL_VERSION = '2024-11-05';
|
|
38
|
+
const MCP_METHODS = {
|
|
39
|
+
INITIALIZE: 'initialize',
|
|
40
|
+
INITIALIZED: 'notifications/initialized',
|
|
41
|
+
SHUTDOWN: 'shutdown',
|
|
42
|
+
TOOLS_LIST: 'tools/list',
|
|
43
|
+
TOOLS_CALL: 'tools/call',
|
|
44
|
+
};
|
|
45
|
+
const MCP_ERROR_CODES = {
|
|
46
|
+
PARSE_ERROR: -32700,
|
|
47
|
+
INVALID_REQUEST: -32600,
|
|
48
|
+
METHOD_NOT_FOUND: -32601,
|
|
49
|
+
INVALID_PARAMS: -32602,
|
|
50
|
+
INTERNAL_ERROR: -32603,
|
|
51
|
+
SERVER_NOT_INITIALIZED: -32002,
|
|
52
|
+
};
|
|
53
|
+
class JiraClient {
|
|
54
|
+
constructor(config) {
|
|
55
|
+
this.config = config;
|
|
56
|
+
}
|
|
57
|
+
async health() {
|
|
58
|
+
return this.requestJson('GET', 'myself');
|
|
59
|
+
}
|
|
60
|
+
async listProjects(startAt, maxResults) {
|
|
61
|
+
const params = new URLSearchParams();
|
|
62
|
+
if (startAt !== undefined)
|
|
63
|
+
params.set('startAt', String(startAt));
|
|
64
|
+
if (maxResults !== undefined)
|
|
65
|
+
params.set('maxResults', String(maxResults));
|
|
66
|
+
const query = params.toString();
|
|
67
|
+
return this.requestJson('GET', `project/search${query ? `?${query}` : ''}`);
|
|
68
|
+
}
|
|
69
|
+
async getIssue(issueIdOrKey, fields) {
|
|
70
|
+
const params = new URLSearchParams();
|
|
71
|
+
if (fields && fields.length > 0)
|
|
72
|
+
params.set('fields', fields.join(','));
|
|
73
|
+
const query = params.toString();
|
|
74
|
+
return this.requestJson('GET', `issue/${encodeURIComponent(issueIdOrKey)}${query ? `?${query}` : ''}`);
|
|
75
|
+
}
|
|
76
|
+
async searchIssues(jql, startAt, maxResults, fields) {
|
|
77
|
+
const payload = { jql };
|
|
78
|
+
if (startAt !== undefined)
|
|
79
|
+
payload.startAt = startAt;
|
|
80
|
+
if (maxResults !== undefined)
|
|
81
|
+
payload.maxResults = maxResults;
|
|
82
|
+
if (fields && fields.length > 0)
|
|
83
|
+
payload.fields = fields;
|
|
84
|
+
return this.requestJson('POST', 'search', payload);
|
|
85
|
+
}
|
|
86
|
+
async createIssue(payload) {
|
|
87
|
+
return this.requestJson('POST', 'issue', payload);
|
|
88
|
+
}
|
|
89
|
+
async updateIssue(issueIdOrKey, payload) {
|
|
90
|
+
return this.requestJson('PUT', `issue/${encodeURIComponent(issueIdOrKey)}`, payload);
|
|
91
|
+
}
|
|
92
|
+
getBaseUrl() {
|
|
93
|
+
if (!this.config.baseUrl) {
|
|
94
|
+
throw new Error('JIRA_BASE_URL is required');
|
|
95
|
+
}
|
|
96
|
+
return `${this.config.baseUrl.replace(/\/$/, '')}/rest/api/${this.config.apiVersion}`;
|
|
97
|
+
}
|
|
98
|
+
canRefresh() {
|
|
99
|
+
return Boolean(this.config.clientId && this.config.clientSecret && this.config.refreshToken);
|
|
100
|
+
}
|
|
101
|
+
async ensureAccessToken() {
|
|
102
|
+
if (this.config.accessToken) {
|
|
103
|
+
return this.config.accessToken;
|
|
104
|
+
}
|
|
105
|
+
if (!this.canRefresh()) {
|
|
106
|
+
throw new Error('Missing Jira credentials (provide JIRA_ACCESS_TOKEN or JIRA_EMAIL + JIRA_API_TOKEN)');
|
|
107
|
+
}
|
|
108
|
+
await this.refreshAccessToken();
|
|
109
|
+
if (!this.config.accessToken) {
|
|
110
|
+
throw new Error('Failed to refresh Jira access token');
|
|
111
|
+
}
|
|
112
|
+
return this.config.accessToken;
|
|
113
|
+
}
|
|
114
|
+
async getAuthHeader() {
|
|
115
|
+
if (this.config.accessToken || this.canRefresh()) {
|
|
116
|
+
const token = await this.ensureAccessToken();
|
|
117
|
+
return `Bearer ${token}`;
|
|
118
|
+
}
|
|
119
|
+
if (this.config.email && this.config.apiToken) {
|
|
120
|
+
const basic = Buffer.from(`${this.config.email}:${this.config.apiToken}`).toString('base64');
|
|
121
|
+
return `Basic ${basic}`;
|
|
122
|
+
}
|
|
123
|
+
throw new Error('Missing Jira credentials (provide JIRA_ACCESS_TOKEN or JIRA_EMAIL + JIRA_API_TOKEN)');
|
|
124
|
+
}
|
|
125
|
+
async requestJson(method, path, body) {
|
|
126
|
+
const start = Date.now();
|
|
127
|
+
const url = `${this.getBaseUrl()}/${path.replace(/^\//, '')}`;
|
|
128
|
+
const res = await fetch(url, {
|
|
129
|
+
method,
|
|
130
|
+
headers: {
|
|
131
|
+
Authorization: await this.getAuthHeader(),
|
|
132
|
+
'Content-Type': 'application/json',
|
|
133
|
+
'User-Agent': 'CoWork-Jira-Connector/0.1.0',
|
|
134
|
+
},
|
|
135
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
136
|
+
});
|
|
137
|
+
const durationMs = Date.now() - start;
|
|
138
|
+
const rateLimit = parseRateLimit(res.headers);
|
|
139
|
+
const vendorRequestId = res.headers.get('x-arequestid') || res.headers.get('x-request-id') || undefined;
|
|
140
|
+
if (res.status === 401 && this.canRefresh()) {
|
|
141
|
+
await this.refreshAccessToken();
|
|
142
|
+
return this.requestJson(method, path, body);
|
|
143
|
+
}
|
|
144
|
+
if (!res.ok) {
|
|
145
|
+
const message = await this.extractErrorMessage(res);
|
|
146
|
+
throw new Error(message);
|
|
147
|
+
}
|
|
148
|
+
let data = null;
|
|
149
|
+
if (res.status !== 204) {
|
|
150
|
+
data = await res.json();
|
|
151
|
+
}
|
|
152
|
+
const nextCursor = deriveNextCursor(data);
|
|
153
|
+
return {
|
|
154
|
+
data,
|
|
155
|
+
meta: {
|
|
156
|
+
durationMs,
|
|
157
|
+
rateLimit,
|
|
158
|
+
vendorRequestId,
|
|
159
|
+
apiVersion: this.config.apiVersion,
|
|
160
|
+
baseUrl: this.config.baseUrl,
|
|
161
|
+
},
|
|
162
|
+
nextCursor,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
async refreshAccessToken() {
|
|
166
|
+
if (!this.canRefresh()) {
|
|
167
|
+
throw new Error('Missing Jira refresh credentials');
|
|
168
|
+
}
|
|
169
|
+
const res = await fetch('https://auth.atlassian.com/oauth/token', {
|
|
170
|
+
method: 'POST',
|
|
171
|
+
headers: { 'Content-Type': 'application/json' },
|
|
172
|
+
body: JSON.stringify({
|
|
173
|
+
grant_type: 'refresh_token',
|
|
174
|
+
client_id: this.config.clientId,
|
|
175
|
+
client_secret: this.config.clientSecret,
|
|
176
|
+
refresh_token: this.config.refreshToken,
|
|
177
|
+
}),
|
|
178
|
+
});
|
|
179
|
+
if (!res.ok) {
|
|
180
|
+
const text = await res.text();
|
|
181
|
+
throw new Error(`Jira OAuth refresh failed: ${text}`);
|
|
182
|
+
}
|
|
183
|
+
const data = await res.json();
|
|
184
|
+
if (!data.access_token) {
|
|
185
|
+
throw new Error('Jira OAuth refresh returned no access_token');
|
|
186
|
+
}
|
|
187
|
+
this.config.accessToken = data.access_token;
|
|
188
|
+
}
|
|
189
|
+
async extractErrorMessage(res) {
|
|
190
|
+
const text = await res.text();
|
|
191
|
+
if (!text) {
|
|
192
|
+
return `Jira API error (status ${res.status})`;
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const parsed = JSON.parse(text);
|
|
196
|
+
if (parsed.errorMessages && Array.isArray(parsed.errorMessages) && parsed.errorMessages.length > 0) {
|
|
197
|
+
return parsed.errorMessages.join('; ');
|
|
198
|
+
}
|
|
199
|
+
if (parsed.errors && typeof parsed.errors === 'object') {
|
|
200
|
+
const messages = Object.entries(parsed.errors).map(([field, msg]) => `${field}: ${msg}`);
|
|
201
|
+
return messages.join('; ');
|
|
202
|
+
}
|
|
203
|
+
if (parsed.message) {
|
|
204
|
+
return parsed.message;
|
|
205
|
+
}
|
|
206
|
+
return JSON.stringify(parsed);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return text;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function parseRateLimit(headers) {
|
|
214
|
+
const limit = numberHeader(headers.get('x-ratelimit-limit'));
|
|
215
|
+
const remaining = numberHeader(headers.get('x-ratelimit-remaining'));
|
|
216
|
+
const reset = headers.get('x-ratelimit-reset');
|
|
217
|
+
if (limit === undefined || remaining === undefined) {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
limit,
|
|
222
|
+
remaining,
|
|
223
|
+
resetAt: reset || undefined,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function numberHeader(value) {
|
|
227
|
+
if (!value)
|
|
228
|
+
return undefined;
|
|
229
|
+
const num = Number(value);
|
|
230
|
+
return Number.isFinite(num) ? num : undefined;
|
|
231
|
+
}
|
|
232
|
+
function deriveNextCursor(data) {
|
|
233
|
+
if (!data || typeof data !== 'object')
|
|
234
|
+
return undefined;
|
|
235
|
+
if (typeof data.startAt === 'number' && typeof data.maxResults === 'number' && typeof data.total === 'number') {
|
|
236
|
+
const next = data.startAt + data.maxResults;
|
|
237
|
+
if (next < data.total) {
|
|
238
|
+
return String(next);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
class StdioMCPServer {
|
|
244
|
+
constructor(toolProvider, serverInfo) {
|
|
245
|
+
this.toolProvider = toolProvider;
|
|
246
|
+
this.serverInfo = serverInfo;
|
|
247
|
+
this.initialized = false;
|
|
248
|
+
this.rl = null;
|
|
249
|
+
}
|
|
250
|
+
start() {
|
|
251
|
+
this.rl = readline.createInterface({
|
|
252
|
+
input: process.stdin,
|
|
253
|
+
output: process.stdout,
|
|
254
|
+
terminal: false,
|
|
255
|
+
});
|
|
256
|
+
this.rl.on('line', (line) => this.handleLine(line));
|
|
257
|
+
this.rl.on('close', () => this.stop());
|
|
258
|
+
process.on('SIGINT', () => this.stop());
|
|
259
|
+
process.on('SIGTERM', () => this.stop());
|
|
260
|
+
}
|
|
261
|
+
stop() {
|
|
262
|
+
if (this.rl) {
|
|
263
|
+
this.rl.close();
|
|
264
|
+
this.rl = null;
|
|
265
|
+
}
|
|
266
|
+
process.exit(0);
|
|
267
|
+
}
|
|
268
|
+
handleLine(line) {
|
|
269
|
+
const trimmed = line.trim();
|
|
270
|
+
if (!trimmed)
|
|
271
|
+
return;
|
|
272
|
+
try {
|
|
273
|
+
const message = JSON.parse(trimmed);
|
|
274
|
+
this.handleMessage(message);
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
this.sendError(0, MCP_ERROR_CODES.PARSE_ERROR, 'Parse error');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async handleMessage(message) {
|
|
281
|
+
if ('id' in message && message.id !== null) {
|
|
282
|
+
await this.handleRequest(message);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if ('method' in message) {
|
|
286
|
+
await this.handleNotification(message);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
async handleRequest(request) {
|
|
290
|
+
const { id, method, params } = request;
|
|
291
|
+
try {
|
|
292
|
+
let result;
|
|
293
|
+
switch (method) {
|
|
294
|
+
case MCP_METHODS.INITIALIZE:
|
|
295
|
+
result = this.handleInitialize(params);
|
|
296
|
+
break;
|
|
297
|
+
case MCP_METHODS.TOOLS_LIST:
|
|
298
|
+
this.requireInitialized();
|
|
299
|
+
result = this.handleToolsList();
|
|
300
|
+
break;
|
|
301
|
+
case MCP_METHODS.TOOLS_CALL:
|
|
302
|
+
this.requireInitialized();
|
|
303
|
+
result = await this.handleToolsCall(params);
|
|
304
|
+
break;
|
|
305
|
+
case MCP_METHODS.SHUTDOWN:
|
|
306
|
+
result = this.handleShutdown();
|
|
307
|
+
break;
|
|
308
|
+
default:
|
|
309
|
+
throw this.createError(MCP_ERROR_CODES.METHOD_NOT_FOUND, `Method not found: ${method}`);
|
|
310
|
+
}
|
|
311
|
+
this.sendResult(id, result);
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
if (error.code !== undefined) {
|
|
315
|
+
this.sendError(id, error.code, error.message, error.data);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
this.sendError(id, MCP_ERROR_CODES.INTERNAL_ERROR, error?.message || 'Internal error');
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async handleNotification(notification) {
|
|
323
|
+
const { method } = notification;
|
|
324
|
+
if (method === MCP_METHODS.INITIALIZED) {
|
|
325
|
+
this.initialized = true;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
handleInitialize(_params) {
|
|
329
|
+
if (this.initialized) {
|
|
330
|
+
throw this.createError(MCP_ERROR_CODES.INVALID_REQUEST, 'Already initialized');
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
334
|
+
capabilities: this.serverInfo.capabilities,
|
|
335
|
+
serverInfo: this.serverInfo,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
handleToolsList() {
|
|
339
|
+
return { tools: this.toolProvider.getTools() };
|
|
340
|
+
}
|
|
341
|
+
async handleToolsCall(params) {
|
|
342
|
+
const { name, arguments: args } = params || {};
|
|
343
|
+
if (!name) {
|
|
344
|
+
throw this.createError(MCP_ERROR_CODES.INVALID_PARAMS, 'Tool name is required');
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
const result = await this.toolProvider.executeTool(name, args || {});
|
|
348
|
+
if (typeof result === 'string') {
|
|
349
|
+
return { content: [{ type: 'text', text: result }] };
|
|
350
|
+
}
|
|
351
|
+
if (result && typeof result === 'object') {
|
|
352
|
+
if (result.content && Array.isArray(result.content)) {
|
|
353
|
+
return result;
|
|
354
|
+
}
|
|
355
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
356
|
+
}
|
|
357
|
+
return { content: [{ type: 'text', text: String(result) }] };
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
return {
|
|
361
|
+
content: [{ type: 'text', text: `Error: ${error?.message || 'Tool failed'}` }],
|
|
362
|
+
isError: true,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
handleShutdown() {
|
|
367
|
+
setImmediate(() => this.stop());
|
|
368
|
+
return {};
|
|
369
|
+
}
|
|
370
|
+
sendResult(id, result) {
|
|
371
|
+
const response = { jsonrpc: '2.0', id, result };
|
|
372
|
+
this.sendMessage(response);
|
|
373
|
+
}
|
|
374
|
+
sendError(id, code, message, data) {
|
|
375
|
+
const response = {
|
|
376
|
+
jsonrpc: '2.0',
|
|
377
|
+
id,
|
|
378
|
+
error: { code, message, data },
|
|
379
|
+
};
|
|
380
|
+
this.sendMessage(response);
|
|
381
|
+
}
|
|
382
|
+
sendMessage(message) {
|
|
383
|
+
process.stdout.write(JSON.stringify(message) + '\n');
|
|
384
|
+
}
|
|
385
|
+
requireInitialized() {
|
|
386
|
+
if (!this.initialized) {
|
|
387
|
+
throw this.createError(MCP_ERROR_CODES.SERVER_NOT_INITIALIZED, 'Server not initialized');
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
createError(code, message, data) {
|
|
391
|
+
return { code, message, data };
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// ==================== Tool Definitions ====================
|
|
395
|
+
const CONNECTOR_PREFIX = 'jira';
|
|
396
|
+
const DEFAULT_API_VERSION = '3';
|
|
397
|
+
const tools = [
|
|
398
|
+
{
|
|
399
|
+
name: `${CONNECTOR_PREFIX}.health`,
|
|
400
|
+
description: 'Check connector health and authentication status',
|
|
401
|
+
inputSchema: {
|
|
402
|
+
type: 'object',
|
|
403
|
+
properties: {
|
|
404
|
+
requestId: { type: 'string', description: 'Optional request id for tracing' },
|
|
405
|
+
},
|
|
406
|
+
additionalProperties: false,
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
name: `${CONNECTOR_PREFIX}.list_projects`,
|
|
411
|
+
description: 'List Jira projects',
|
|
412
|
+
inputSchema: {
|
|
413
|
+
type: 'object',
|
|
414
|
+
properties: {
|
|
415
|
+
limit: { type: 'number', description: 'Max results per page' },
|
|
416
|
+
cursor: { type: 'string', description: 'Pagination cursor (startAt number)' },
|
|
417
|
+
requestId: { type: 'string', description: 'Optional request id for tracing' },
|
|
418
|
+
},
|
|
419
|
+
additionalProperties: false,
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
name: `${CONNECTOR_PREFIX}.get_issue`,
|
|
424
|
+
description: 'Fetch a Jira issue by id or key',
|
|
425
|
+
inputSchema: {
|
|
426
|
+
type: 'object',
|
|
427
|
+
properties: {
|
|
428
|
+
id: { type: 'string', description: 'Issue id or key (e.g., PROJ-123)' },
|
|
429
|
+
fields: {
|
|
430
|
+
type: 'array',
|
|
431
|
+
description: 'Optional list of fields to include',
|
|
432
|
+
items: { type: 'string' },
|
|
433
|
+
},
|
|
434
|
+
requestId: { type: 'string', description: 'Optional request id for tracing' },
|
|
435
|
+
},
|
|
436
|
+
required: ['id'],
|
|
437
|
+
additionalProperties: false,
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
name: `${CONNECTOR_PREFIX}.search_issues`,
|
|
442
|
+
description: 'Run a JQL query',
|
|
443
|
+
inputSchema: {
|
|
444
|
+
type: 'object',
|
|
445
|
+
properties: {
|
|
446
|
+
jql: { type: 'string', description: 'JQL query to execute' },
|
|
447
|
+
fields: {
|
|
448
|
+
type: 'array',
|
|
449
|
+
description: 'Optional list of fields to include',
|
|
450
|
+
items: { type: 'string' },
|
|
451
|
+
},
|
|
452
|
+
limit: { type: 'number', description: 'Max results per page' },
|
|
453
|
+
cursor: { type: 'string', description: 'Pagination cursor (startAt number)' },
|
|
454
|
+
requestId: { type: 'string', description: 'Optional request id for tracing' },
|
|
455
|
+
},
|
|
456
|
+
required: ['jql'],
|
|
457
|
+
additionalProperties: false,
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: `${CONNECTOR_PREFIX}.create_issue`,
|
|
462
|
+
description: 'Create a Jira issue',
|
|
463
|
+
inputSchema: {
|
|
464
|
+
type: 'object',
|
|
465
|
+
properties: {
|
|
466
|
+
projectKey: { type: 'string', description: 'Project key (e.g., PROJ)' },
|
|
467
|
+
issueType: { type: 'string', description: 'Issue type (e.g., Task, Bug)' },
|
|
468
|
+
fields: { type: 'object', description: 'Additional Jira fields to include' },
|
|
469
|
+
idempotencyKey: { type: 'string', description: 'Optional idempotency key (best-effort)' },
|
|
470
|
+
requestId: { type: 'string', description: 'Optional request id for tracing' },
|
|
471
|
+
},
|
|
472
|
+
required: ['projectKey', 'issueType', 'fields'],
|
|
473
|
+
additionalProperties: false,
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
name: `${CONNECTOR_PREFIX}.update_issue`,
|
|
478
|
+
description: 'Update a Jira issue',
|
|
479
|
+
inputSchema: {
|
|
480
|
+
type: 'object',
|
|
481
|
+
properties: {
|
|
482
|
+
id: { type: 'string', description: 'Issue id or key (e.g., PROJ-123)' },
|
|
483
|
+
fields: { type: 'object', description: 'Field map for update' },
|
|
484
|
+
idempotencyKey: { type: 'string', description: 'Optional idempotency key (best-effort)' },
|
|
485
|
+
requestId: { type: 'string', description: 'Optional request id for tracing' },
|
|
486
|
+
},
|
|
487
|
+
required: ['id', 'fields'],
|
|
488
|
+
additionalProperties: false,
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
];
|
|
492
|
+
const config = {
|
|
493
|
+
baseUrl: process.env.JIRA_BASE_URL,
|
|
494
|
+
apiVersion: process.env.JIRA_API_VERSION || DEFAULT_API_VERSION,
|
|
495
|
+
accessToken: process.env.JIRA_ACCESS_TOKEN,
|
|
496
|
+
email: process.env.JIRA_EMAIL,
|
|
497
|
+
apiToken: process.env.JIRA_API_TOKEN,
|
|
498
|
+
clientId: process.env.JIRA_CLIENT_ID,
|
|
499
|
+
clientSecret: process.env.JIRA_CLIENT_SECRET,
|
|
500
|
+
refreshToken: process.env.JIRA_REFRESH_TOKEN,
|
|
501
|
+
};
|
|
502
|
+
const client = new JiraClient(config);
|
|
503
|
+
const handlers = {
|
|
504
|
+
[`${CONNECTOR_PREFIX}.health`]: async (args) => {
|
|
505
|
+
const result = await client.health();
|
|
506
|
+
return buildEnvelope(result, args.requestId);
|
|
507
|
+
},
|
|
508
|
+
[`${CONNECTOR_PREFIX}.list_projects`]: async (args) => {
|
|
509
|
+
const startAt = parseCursor(args.cursor);
|
|
510
|
+
const result = await client.listProjects(startAt, args.limit);
|
|
511
|
+
return buildEnvelope(result, args.requestId);
|
|
512
|
+
},
|
|
513
|
+
[`${CONNECTOR_PREFIX}.get_issue`]: async (args) => {
|
|
514
|
+
const result = await client.getIssue(args.id, args.fields);
|
|
515
|
+
return buildEnvelope(result, args.requestId);
|
|
516
|
+
},
|
|
517
|
+
[`${CONNECTOR_PREFIX}.search_issues`]: async (args) => {
|
|
518
|
+
const startAt = parseCursor(args.cursor);
|
|
519
|
+
const result = await client.searchIssues(args.jql, startAt, args.limit, args.fields);
|
|
520
|
+
return buildEnvelope(result, args.requestId);
|
|
521
|
+
},
|
|
522
|
+
[`${CONNECTOR_PREFIX}.create_issue`]: async (args) => {
|
|
523
|
+
const payload = buildCreatePayload(args.projectKey, args.issueType, args.fields);
|
|
524
|
+
const warnings = args.idempotencyKey
|
|
525
|
+
? ['Jira does not natively support idempotency keys; handled best-effort.']
|
|
526
|
+
: [];
|
|
527
|
+
const result = await client.createIssue(payload);
|
|
528
|
+
return buildEnvelope(result, args.requestId, warnings);
|
|
529
|
+
},
|
|
530
|
+
[`${CONNECTOR_PREFIX}.update_issue`]: async (args) => {
|
|
531
|
+
const warnings = args.idempotencyKey
|
|
532
|
+
? ['Jira does not natively support idempotency keys; handled best-effort.']
|
|
533
|
+
: [];
|
|
534
|
+
const result = await client.updateIssue(args.id, { fields: args.fields });
|
|
535
|
+
return buildEnvelope(result, args.requestId, warnings);
|
|
536
|
+
},
|
|
537
|
+
};
|
|
538
|
+
const toolProvider = {
|
|
539
|
+
getTools: () => tools,
|
|
540
|
+
executeTool: async (name, args) => {
|
|
541
|
+
const handler = handlers[name];
|
|
542
|
+
if (!handler) {
|
|
543
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
544
|
+
}
|
|
545
|
+
return handler(args);
|
|
546
|
+
},
|
|
547
|
+
};
|
|
548
|
+
const serverInfo = {
|
|
549
|
+
name: 'Jira Connector',
|
|
550
|
+
version: '0.1.0',
|
|
551
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
552
|
+
capabilities: {
|
|
553
|
+
tools: { listChanged: false },
|
|
554
|
+
},
|
|
555
|
+
};
|
|
556
|
+
const server = new StdioMCPServer(toolProvider, serverInfo);
|
|
557
|
+
server.start();
|
|
558
|
+
function buildEnvelope(result, requestId, warnings = []) {
|
|
559
|
+
return {
|
|
560
|
+
ok: true,
|
|
561
|
+
data: result.data,
|
|
562
|
+
meta: {
|
|
563
|
+
requestId,
|
|
564
|
+
durationMs: result.meta.durationMs,
|
|
565
|
+
rateLimit: result.meta.rateLimit,
|
|
566
|
+
vendorRequestId: result.meta.vendorRequestId,
|
|
567
|
+
apiVersion: result.meta.apiVersion,
|
|
568
|
+
baseUrl: result.meta.baseUrl,
|
|
569
|
+
},
|
|
570
|
+
nextCursor: result.nextCursor,
|
|
571
|
+
warnings,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function parseCursor(cursor) {
|
|
575
|
+
if (!cursor)
|
|
576
|
+
return undefined;
|
|
577
|
+
const value = Number(cursor);
|
|
578
|
+
return Number.isFinite(value) ? value : undefined;
|
|
579
|
+
}
|
|
580
|
+
function buildCreatePayload(projectKey, issueType, fields) {
|
|
581
|
+
return {
|
|
582
|
+
fields: {
|
|
583
|
+
...fields,
|
|
584
|
+
project: { key: projectKey },
|
|
585
|
+
issuetype: { name: issueType },
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cowork-jira-mcp",
|
|
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
|
+
}
|