@mtaap/mcp 0.2.12 → 0.2.13
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 +27 -0
- package/dist/cli.js +46 -26
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +392 -1
- package/dist/index.js +52 -25
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.js +2656 -0
- package/dist/server.js.map +1 -0
- package/package.json +5 -2
package/dist/server.js
ADDED
|
@@ -0,0 +1,2656 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/server.ts
|
|
27
|
+
var import_express = __toESM(require("express"));
|
|
28
|
+
var import_node_crypto = require("crypto");
|
|
29
|
+
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
30
|
+
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
31
|
+
|
|
32
|
+
// src/index.ts
|
|
33
|
+
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
34
|
+
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
35
|
+
|
|
36
|
+
// package.json
|
|
37
|
+
var package_default = {
|
|
38
|
+
name: "@mtaap/mcp",
|
|
39
|
+
version: "0.2.12",
|
|
40
|
+
description: "Model Context Protocol (MCP) server for AI agents to interact with Collab - the multi-tenant collaborative agent development platform",
|
|
41
|
+
mcpName: "collab",
|
|
42
|
+
scripts: {
|
|
43
|
+
build: "tsup"
|
|
44
|
+
},
|
|
45
|
+
main: "./dist/index.js",
|
|
46
|
+
types: "./dist/index.d.ts",
|
|
47
|
+
bin: {
|
|
48
|
+
"collab-mcp": "./dist/cli.js",
|
|
49
|
+
"collab-mcp-server": "./dist/server.js"
|
|
50
|
+
},
|
|
51
|
+
publishConfig: {
|
|
52
|
+
access: "public"
|
|
53
|
+
},
|
|
54
|
+
exports: {
|
|
55
|
+
".": {
|
|
56
|
+
types: "./dist/index.d.ts",
|
|
57
|
+
require: "./dist/index.js"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
files: [
|
|
61
|
+
"dist"
|
|
62
|
+
],
|
|
63
|
+
keywords: [
|
|
64
|
+
"mcp",
|
|
65
|
+
"model-context-protocol",
|
|
66
|
+
"ai",
|
|
67
|
+
"agent",
|
|
68
|
+
"collaboration",
|
|
69
|
+
"task-management",
|
|
70
|
+
"claude",
|
|
71
|
+
"anthropic"
|
|
72
|
+
],
|
|
73
|
+
license: "Proprietary",
|
|
74
|
+
author: "MTAAP Contributors",
|
|
75
|
+
engines: {
|
|
76
|
+
node: ">=18.18.0"
|
|
77
|
+
},
|
|
78
|
+
dependencies: {
|
|
79
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
80
|
+
express: "^5.0.1",
|
|
81
|
+
zod: "^4.3.5"
|
|
82
|
+
},
|
|
83
|
+
devDependencies: {
|
|
84
|
+
"@mtaap/config-typescript": "workspace:*",
|
|
85
|
+
"@mtaap/core": "workspace:*",
|
|
86
|
+
"@types/express": "^5.0.0",
|
|
87
|
+
"@types/node": "^22.0.0",
|
|
88
|
+
tsup: "^8.5.1",
|
|
89
|
+
typescript: "^5.4.0"
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// src/version.ts
|
|
94
|
+
var VERSION = package_default.version;
|
|
95
|
+
|
|
96
|
+
// src/index.ts
|
|
97
|
+
var import_zod3 = require("zod");
|
|
98
|
+
|
|
99
|
+
// ../../packages/core/dist/constants/enums.js
|
|
100
|
+
var TaskState;
|
|
101
|
+
(function(TaskState2) {
|
|
102
|
+
TaskState2["DRAFT"] = "DRAFT";
|
|
103
|
+
TaskState2["TODO"] = "TODO";
|
|
104
|
+
TaskState2["BACKLOG"] = "BACKLOG";
|
|
105
|
+
TaskState2["READY"] = "READY";
|
|
106
|
+
TaskState2["IN_PROGRESS"] = "IN_PROGRESS";
|
|
107
|
+
TaskState2["REVIEW"] = "REVIEW";
|
|
108
|
+
TaskState2["DONE"] = "DONE";
|
|
109
|
+
})(TaskState || (TaskState = {}));
|
|
110
|
+
var VerificationStatus;
|
|
111
|
+
(function(VerificationStatus2) {
|
|
112
|
+
VerificationStatus2["PENDING"] = "PENDING";
|
|
113
|
+
VerificationStatus2["PASSED"] = "PASSED";
|
|
114
|
+
VerificationStatus2["NEEDS_REVISION"] = "NEEDS_REVISION";
|
|
115
|
+
})(VerificationStatus || (VerificationStatus = {}));
|
|
116
|
+
var UserRole;
|
|
117
|
+
(function(UserRole2) {
|
|
118
|
+
UserRole2["ADMIN"] = "ADMIN";
|
|
119
|
+
UserRole2["MEMBER"] = "MEMBER";
|
|
120
|
+
})(UserRole || (UserRole = {}));
|
|
121
|
+
var ProjectType;
|
|
122
|
+
(function(ProjectType2) {
|
|
123
|
+
ProjectType2["TEAM"] = "TEAM";
|
|
124
|
+
ProjectType2["PERSONAL"] = "PERSONAL";
|
|
125
|
+
})(ProjectType || (ProjectType = {}));
|
|
126
|
+
var ProjectOrigin;
|
|
127
|
+
(function(ProjectOrigin2) {
|
|
128
|
+
ProjectOrigin2["CREATED"] = "CREATED";
|
|
129
|
+
ProjectOrigin2["PROMOTED"] = "PROMOTED";
|
|
130
|
+
})(ProjectOrigin || (ProjectOrigin = {}));
|
|
131
|
+
var TaskPriority;
|
|
132
|
+
(function(TaskPriority2) {
|
|
133
|
+
TaskPriority2["LOW"] = "LOW";
|
|
134
|
+
TaskPriority2["MEDIUM"] = "MEDIUM";
|
|
135
|
+
TaskPriority2["HIGH"] = "HIGH";
|
|
136
|
+
TaskPriority2["CRITICAL"] = "CRITICAL";
|
|
137
|
+
})(TaskPriority || (TaskPriority = {}));
|
|
138
|
+
var DeploymentMode;
|
|
139
|
+
(function(DeploymentMode2) {
|
|
140
|
+
DeploymentMode2["SAAS"] = "saas";
|
|
141
|
+
DeploymentMode2["ONPREM"] = "onprem";
|
|
142
|
+
})(DeploymentMode || (DeploymentMode = {}));
|
|
143
|
+
var ErrorType;
|
|
144
|
+
(function(ErrorType3) {
|
|
145
|
+
ErrorType3["BUILD_FAILURE"] = "BUILD_FAILURE";
|
|
146
|
+
ErrorType3["TEST_FAILURE"] = "TEST_FAILURE";
|
|
147
|
+
ErrorType3["CONFLICT"] = "CONFLICT";
|
|
148
|
+
ErrorType3["AUTH_ERROR"] = "AUTH_ERROR";
|
|
149
|
+
ErrorType3["OTHER"] = "OTHER";
|
|
150
|
+
})(ErrorType || (ErrorType = {}));
|
|
151
|
+
var PRStatus;
|
|
152
|
+
(function(PRStatus2) {
|
|
153
|
+
PRStatus2["OPEN"] = "OPEN";
|
|
154
|
+
PRStatus2["CLOSED"] = "CLOSED";
|
|
155
|
+
PRStatus2["MERGED"] = "MERGED";
|
|
156
|
+
PRStatus2["DELETED"] = "DELETED";
|
|
157
|
+
})(PRStatus || (PRStatus = {}));
|
|
158
|
+
var PricingTier;
|
|
159
|
+
(function(PricingTier2) {
|
|
160
|
+
PricingTier2["FREE"] = "FREE";
|
|
161
|
+
PricingTier2["PRO"] = "PRO";
|
|
162
|
+
PricingTier2["ENTERPRISE"] = "ENTERPRISE";
|
|
163
|
+
})(PricingTier || (PricingTier = {}));
|
|
164
|
+
var ApiKeyPermission;
|
|
165
|
+
(function(ApiKeyPermission2) {
|
|
166
|
+
ApiKeyPermission2["READ"] = "READ";
|
|
167
|
+
ApiKeyPermission2["WRITE"] = "WRITE";
|
|
168
|
+
ApiKeyPermission2["ADMIN"] = "ADMIN";
|
|
169
|
+
})(ApiKeyPermission || (ApiKeyPermission = {}));
|
|
170
|
+
var WebSocketEventType;
|
|
171
|
+
(function(WebSocketEventType2) {
|
|
172
|
+
WebSocketEventType2["TASK_ASSIGNED"] = "task.assigned";
|
|
173
|
+
WebSocketEventType2["TASK_ABANDONED"] = "task.abandoned";
|
|
174
|
+
WebSocketEventType2["TASK_PROGRESS"] = "task.progress";
|
|
175
|
+
WebSocketEventType2["TASK_PR_CREATED"] = "task.pr_created";
|
|
176
|
+
WebSocketEventType2["TASK_REVIEW_REQUESTED"] = "task.review_requested";
|
|
177
|
+
WebSocketEventType2["TASK_COMPLETED"] = "task.completed";
|
|
178
|
+
WebSocketEventType2["TASK_ERROR"] = "task.error";
|
|
179
|
+
WebSocketEventType2["TASK_STATE_CHANGED"] = "task.state_changed";
|
|
180
|
+
WebSocketEventType2["TASK_UPDATED"] = "task.updated";
|
|
181
|
+
WebSocketEventType2["TASK_DELETED"] = "task.deleted";
|
|
182
|
+
WebSocketEventType2["MEMBER_JOINED"] = "member.joined";
|
|
183
|
+
})(WebSocketEventType || (WebSocketEventType = {}));
|
|
184
|
+
var AuthProvider;
|
|
185
|
+
(function(AuthProvider2) {
|
|
186
|
+
AuthProvider2["CREDENTIALS"] = "credentials";
|
|
187
|
+
AuthProvider2["LDAP"] = "ldap";
|
|
188
|
+
AuthProvider2["SSO"] = "sso";
|
|
189
|
+
})(AuthProvider || (AuthProvider = {}));
|
|
190
|
+
var SubscriptionStatus;
|
|
191
|
+
(function(SubscriptionStatus2) {
|
|
192
|
+
SubscriptionStatus2["ACTIVE"] = "ACTIVE";
|
|
193
|
+
SubscriptionStatus2["INACTIVE"] = "INACTIVE";
|
|
194
|
+
SubscriptionStatus2["PAST_DUE"] = "PAST_DUE";
|
|
195
|
+
SubscriptionStatus2["CANCELED"] = "CANCELED";
|
|
196
|
+
SubscriptionStatus2["INCOMPLETE"] = "INCOMPLETE";
|
|
197
|
+
})(SubscriptionStatus || (SubscriptionStatus = {}));
|
|
198
|
+
var EventType;
|
|
199
|
+
(function(EventType2) {
|
|
200
|
+
EventType2["AUTH"] = "AUTH";
|
|
201
|
+
EventType2["AUTHORIZATION"] = "AUTHORIZATION";
|
|
202
|
+
EventType2["ACCESS"] = "ACCESS";
|
|
203
|
+
EventType2["MODIFICATION"] = "MODIFICATION";
|
|
204
|
+
})(EventType || (EventType = {}));
|
|
205
|
+
|
|
206
|
+
// ../../packages/core/dist/constants/state-machine.js
|
|
207
|
+
var VALID_TRANSITIONS = {
|
|
208
|
+
[TaskState.DRAFT]: [TaskState.TODO],
|
|
209
|
+
[TaskState.TODO]: [TaskState.DRAFT, TaskState.IN_PROGRESS],
|
|
210
|
+
// Deprecated: BACKLOG transitions to DRAFT (its replacement)
|
|
211
|
+
[TaskState.BACKLOG]: [TaskState.DRAFT, TaskState.TODO],
|
|
212
|
+
// Deprecated: READY transitions to TODO (its replacement)
|
|
213
|
+
[TaskState.READY]: [TaskState.TODO, TaskState.IN_PROGRESS],
|
|
214
|
+
[TaskState.IN_PROGRESS]: [
|
|
215
|
+
TaskState.DRAFT,
|
|
216
|
+
TaskState.TODO,
|
|
217
|
+
TaskState.REVIEW
|
|
218
|
+
],
|
|
219
|
+
[TaskState.REVIEW]: [
|
|
220
|
+
TaskState.DRAFT,
|
|
221
|
+
TaskState.TODO,
|
|
222
|
+
TaskState.IN_PROGRESS,
|
|
223
|
+
TaskState.DONE
|
|
224
|
+
],
|
|
225
|
+
[TaskState.DONE]: []
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// ../../packages/core/dist/config/deployment.js
|
|
229
|
+
var config = {
|
|
230
|
+
deploymentMode: process.env.DEPLOYMENT_MODE || "saas"
|
|
231
|
+
};
|
|
232
|
+
var isSaas = config.deploymentMode === "saas";
|
|
233
|
+
var isOnPrem = config.deploymentMode === "onprem";
|
|
234
|
+
|
|
235
|
+
// ../../packages/core/dist/version.js
|
|
236
|
+
var VERSION2 = "0.1.0";
|
|
237
|
+
|
|
238
|
+
// ../../packages/core/dist/config/index.js
|
|
239
|
+
var DEPLOYMENT_MODE = process.env.DEPLOYMENT_MODE || "saas";
|
|
240
|
+
var config2 = {
|
|
241
|
+
version: VERSION2,
|
|
242
|
+
deploymentMode: DEPLOYMENT_MODE,
|
|
243
|
+
billing: {
|
|
244
|
+
enabled: DEPLOYMENT_MODE === "saas",
|
|
245
|
+
revenuecat: {
|
|
246
|
+
publicKey: process.env.REVENUECAT_PUBLIC_API_KEY,
|
|
247
|
+
stripeKey: process.env.STRIPE_SECRET_KEY
|
|
248
|
+
// Required by RevenueCat Web Billing
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
licensing: {
|
|
252
|
+
enabled: DEPLOYMENT_MODE === "onprem",
|
|
253
|
+
licenseKey: process.env.LICENSE_KEY
|
|
254
|
+
},
|
|
255
|
+
auth: {
|
|
256
|
+
credentials: true,
|
|
257
|
+
ldap: process.env.LDAP_ENABLED === "true"
|
|
258
|
+
},
|
|
259
|
+
git: {
|
|
260
|
+
deleteMergedBranches: process.env.DELETE_MERGED_BRANCHES !== "false",
|
|
261
|
+
enforceConventionalCommits: process.env.ENFORCE_CONVENTIONAL_COMMITS === "true",
|
|
262
|
+
defaultBaseBranch: process.env.DEFAULT_BASE_BRANCH || "develop"
|
|
263
|
+
},
|
|
264
|
+
pricing: {
|
|
265
|
+
maxPersonalProjects: {
|
|
266
|
+
FREE: 2,
|
|
267
|
+
PRO: 5,
|
|
268
|
+
ENTERPRISE: 10
|
|
269
|
+
},
|
|
270
|
+
maxCollaboratorsPerProject: {
|
|
271
|
+
FREE: 3,
|
|
272
|
+
PRO: -1,
|
|
273
|
+
ENTERPRISE: -1
|
|
274
|
+
},
|
|
275
|
+
maxProjectsPerOrg: {
|
|
276
|
+
FREE: 5,
|
|
277
|
+
PRO: -1,
|
|
278
|
+
ENTERPRISE: -1
|
|
279
|
+
},
|
|
280
|
+
maxSeats: {
|
|
281
|
+
FREE: 3,
|
|
282
|
+
PRO: -1,
|
|
283
|
+
ENTERPRISE: -1
|
|
284
|
+
},
|
|
285
|
+
// Default seats when subscription doesn't specify an explicit seat count
|
|
286
|
+
defaultSeats: {
|
|
287
|
+
FREE: 3,
|
|
288
|
+
PRO: 10,
|
|
289
|
+
ENTERPRISE: 999999
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
features: {
|
|
293
|
+
stripe: {
|
|
294
|
+
enabled: DEPLOYMENT_MODE === "saas"
|
|
295
|
+
},
|
|
296
|
+
gitlab: {
|
|
297
|
+
enabled: true
|
|
298
|
+
},
|
|
299
|
+
ldap: {
|
|
300
|
+
enabled: process.env.LDAP_ENABLED === "true"
|
|
301
|
+
},
|
|
302
|
+
auditLogs: {
|
|
303
|
+
enabled: process.env.AUDIT_LOGS_ENABLED === "true"
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
api: {
|
|
307
|
+
apiKey: {
|
|
308
|
+
defaultExpiryDays: 90,
|
|
309
|
+
prefix: "usr_"
|
|
310
|
+
},
|
|
311
|
+
rateLimit: {
|
|
312
|
+
requestsPerMinute: 100,
|
|
313
|
+
requestsPerHour: 1e3
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
limits: {
|
|
317
|
+
projectNameMaxLength: 100,
|
|
318
|
+
taskDescriptionMaxLength: 5e3,
|
|
319
|
+
notesMaxLength: 500,
|
|
320
|
+
conventionsNotesMaxLength: 500,
|
|
321
|
+
recentCompletedTasksLimit: 10
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
var DEFAULT_SEAT_LIMITS = {
|
|
325
|
+
[PricingTier.FREE]: config2.pricing.defaultSeats.FREE,
|
|
326
|
+
[PricingTier.PRO]: config2.pricing.defaultSeats.PRO,
|
|
327
|
+
[PricingTier.ENTERPRISE]: config2.pricing.defaultSeats.ENTERPRISE
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// ../../packages/core/dist/types/index.js
|
|
331
|
+
var import_zod = require("zod");
|
|
332
|
+
var UserIdSchema = import_zod.z.string().regex(/^usr_[a-zA-Z0-9]+$/);
|
|
333
|
+
var UserSchema = import_zod.z.object({
|
|
334
|
+
id: UserIdSchema,
|
|
335
|
+
email: import_zod.z.string().email(),
|
|
336
|
+
name: import_zod.z.string().min(1).max(255),
|
|
337
|
+
role: import_zod.z.nativeEnum(UserRole),
|
|
338
|
+
organizationId: import_zod.z.string().optional(),
|
|
339
|
+
lastActiveAt: import_zod.z.coerce.date().optional(),
|
|
340
|
+
createdAt: import_zod.z.coerce.date()
|
|
341
|
+
});
|
|
342
|
+
var OrganizationUserSchema = import_zod.z.object({
|
|
343
|
+
id: import_zod.z.number().int(),
|
|
344
|
+
userId: UserIdSchema,
|
|
345
|
+
organizationId: import_zod.z.string(),
|
|
346
|
+
roleId: import_zod.z.nativeEnum(UserRole).optional(),
|
|
347
|
+
user: UserSchema.pick({ id: true, email: true, name: true, role: true }),
|
|
348
|
+
createdAt: import_zod.z.coerce.date()
|
|
349
|
+
});
|
|
350
|
+
var OrganizationIdSchema = import_zod.z.string().regex(/^org_[a-zA-Z0-9]+$/);
|
|
351
|
+
var OrganizationSchema = import_zod.z.object({
|
|
352
|
+
id: OrganizationIdSchema,
|
|
353
|
+
name: import_zod.z.string().min(1).max(255),
|
|
354
|
+
slug: import_zod.z.string().min(1).max(100).regex(/^[a-z0-9-]+$/),
|
|
355
|
+
logoUrl: import_zod.z.string().url().nullable(),
|
|
356
|
+
accentColor: import_zod.z.string().regex(/^#[0-9A-Fa-f]{6}$/).nullable(),
|
|
357
|
+
tenantName: import_zod.z.string().nullable(),
|
|
358
|
+
pricingTier: import_zod.z.nativeEnum(PricingTier),
|
|
359
|
+
createdAt: import_zod.z.coerce.date()
|
|
360
|
+
});
|
|
361
|
+
var OrganizationSettingsSchema = import_zod.z.object({
|
|
362
|
+
organizationId: OrganizationIdSchema,
|
|
363
|
+
ldapEnabled: import_zod.z.boolean(),
|
|
364
|
+
ldapUrl: import_zod.z.string().url().nullable(),
|
|
365
|
+
ldapBindDN: import_zod.z.string().nullable(),
|
|
366
|
+
ldapSearchBase: import_zod.z.string().nullable(),
|
|
367
|
+
deleteMergedBranches: import_zod.z.boolean(),
|
|
368
|
+
enforceConventionalCommits: import_zod.z.boolean(),
|
|
369
|
+
maxPersonalProjectsPerUser: import_zod.z.number().int().min(0)
|
|
370
|
+
});
|
|
371
|
+
var TagSchema = import_zod.z.object({
|
|
372
|
+
id: import_zod.z.string(),
|
|
373
|
+
name: import_zod.z.string().min(1).max(50),
|
|
374
|
+
organizationId: OrganizationIdSchema,
|
|
375
|
+
createdAt: import_zod.z.coerce.date()
|
|
376
|
+
});
|
|
377
|
+
var ProjectIdSchema = import_zod.z.string().regex(/^prj_[a-zA-Z0-9]+$/);
|
|
378
|
+
var ProjectSchema = import_zod.z.object({
|
|
379
|
+
id: ProjectIdSchema,
|
|
380
|
+
name: import_zod.z.string().min(1).max(100),
|
|
381
|
+
description: import_zod.z.string().max(500).nullable(),
|
|
382
|
+
type: import_zod.z.nativeEnum(ProjectType),
|
|
383
|
+
origin: import_zod.z.nativeEnum(ProjectOrigin),
|
|
384
|
+
organizationId: OrganizationIdSchema,
|
|
385
|
+
ownerId: UserIdSchema.nullable(),
|
|
386
|
+
repositoryUrl: import_zod.z.string().url(),
|
|
387
|
+
baseBranch: import_zod.z.string().default("develop"),
|
|
388
|
+
tags: import_zod.z.array(import_zod.z.string()),
|
|
389
|
+
createdAt: import_zod.z.coerce.date(),
|
|
390
|
+
updatedAt: import_zod.z.coerce.date()
|
|
391
|
+
});
|
|
392
|
+
var EpicIdSchema = import_zod.z.string().regex(/^epc_[a-zA-Z0-9]+$/);
|
|
393
|
+
var EpicSchema = import_zod.z.object({
|
|
394
|
+
id: EpicIdSchema,
|
|
395
|
+
projectId: ProjectIdSchema,
|
|
396
|
+
name: import_zod.z.string().min(1).max(200),
|
|
397
|
+
description: import_zod.z.string().nullable(),
|
|
398
|
+
createdAt: import_zod.z.coerce.date(),
|
|
399
|
+
updatedAt: import_zod.z.coerce.date()
|
|
400
|
+
});
|
|
401
|
+
var TaskIdSchema = import_zod.z.string().regex(/^tsk_[a-zA-Z0-9]+$/);
|
|
402
|
+
var TaskSchema = import_zod.z.object({
|
|
403
|
+
id: TaskIdSchema,
|
|
404
|
+
projectId: ProjectIdSchema,
|
|
405
|
+
epicId: EpicIdSchema.nullable(),
|
|
406
|
+
title: import_zod.z.string().min(1).max(200),
|
|
407
|
+
description: import_zod.z.string().max(5e3),
|
|
408
|
+
state: import_zod.z.nativeEnum(TaskState),
|
|
409
|
+
priority: import_zod.z.nativeEnum(TaskPriority),
|
|
410
|
+
assigneeId: UserIdSchema.nullable(),
|
|
411
|
+
createdBy: UserIdSchema.nullable(),
|
|
412
|
+
assignedAt: import_zod.z.coerce.date().nullable(),
|
|
413
|
+
startedAt: import_zod.z.coerce.date().nullable(),
|
|
414
|
+
completedAt: import_zod.z.coerce.date().nullable(),
|
|
415
|
+
branchName: import_zod.z.string().nullable(),
|
|
416
|
+
pullRequestUrl: import_zod.z.string().url().nullable(),
|
|
417
|
+
pullRequestNumber: import_zod.z.number().int().nullable(),
|
|
418
|
+
pullRequestStatus: import_zod.z.nativeEnum(PRStatus).nullable(),
|
|
419
|
+
errorType: import_zod.z.nativeEnum(ErrorType).nullable(),
|
|
420
|
+
errorMessage: import_zod.z.string().max(1e3).nullable(),
|
|
421
|
+
createdAt: import_zod.z.coerce.date(),
|
|
422
|
+
updatedAt: import_zod.z.coerce.date()
|
|
423
|
+
});
|
|
424
|
+
var AcceptanceCriterionSchema = import_zod.z.object({
|
|
425
|
+
id: import_zod.z.string(),
|
|
426
|
+
taskId: TaskIdSchema,
|
|
427
|
+
description: import_zod.z.string().min(1).max(500),
|
|
428
|
+
completed: import_zod.z.boolean(),
|
|
429
|
+
completedAt: import_zod.z.coerce.date().nullable(),
|
|
430
|
+
order: import_zod.z.number().int(),
|
|
431
|
+
createdAt: import_zod.z.coerce.date()
|
|
432
|
+
});
|
|
433
|
+
var ProgressUpdateSchema = import_zod.z.object({
|
|
434
|
+
id: import_zod.z.string(),
|
|
435
|
+
taskId: TaskIdSchema,
|
|
436
|
+
userId: UserIdSchema,
|
|
437
|
+
message: import_zod.z.string().max(2e3),
|
|
438
|
+
checkpoints: import_zod.z.array(import_zod.z.string()).optional(),
|
|
439
|
+
createdAt: import_zod.z.coerce.date()
|
|
440
|
+
});
|
|
441
|
+
var TaskNoteSchema = import_zod.z.object({
|
|
442
|
+
id: import_zod.z.string(),
|
|
443
|
+
taskId: TaskIdSchema,
|
|
444
|
+
userId: UserIdSchema,
|
|
445
|
+
content: import_zod.z.string().max(500),
|
|
446
|
+
createdAt: import_zod.z.coerce.date()
|
|
447
|
+
});
|
|
448
|
+
var ApiKeyIdSchema = import_zod.z.string().regex(/^key_[a-zA-Z0-9]+$/);
|
|
449
|
+
var ApiKeySchema = import_zod.z.object({
|
|
450
|
+
id: ApiKeyIdSchema,
|
|
451
|
+
userId: UserIdSchema,
|
|
452
|
+
name: import_zod.z.string().min(1).max(100),
|
|
453
|
+
keyHash: import_zod.z.string(),
|
|
454
|
+
permissions: import_zod.z.nativeEnum(ApiKeyPermission),
|
|
455
|
+
lastUsedAt: import_zod.z.coerce.date().nullable(),
|
|
456
|
+
expiresAt: import_zod.z.coerce.date().nullable(),
|
|
457
|
+
revoked: import_zod.z.boolean(),
|
|
458
|
+
createdAt: import_zod.z.coerce.date()
|
|
459
|
+
});
|
|
460
|
+
var SubscriptionIdSchema = import_zod.z.string();
|
|
461
|
+
var SubscriptionSchema = import_zod.z.object({
|
|
462
|
+
id: SubscriptionIdSchema,
|
|
463
|
+
organizationId: OrganizationIdSchema,
|
|
464
|
+
pricingTier: import_zod.z.nativeEnum(PricingTier),
|
|
465
|
+
seats: import_zod.z.number().int().min(0),
|
|
466
|
+
stripeSubscriptionId: import_zod.z.string().nullable(),
|
|
467
|
+
stripeCustomerId: import_zod.z.string().nullable(),
|
|
468
|
+
status: import_zod.z.enum(["active", "past_due", "canceled", "incomplete"]),
|
|
469
|
+
currentPeriodStart: import_zod.z.coerce.date(),
|
|
470
|
+
currentPeriodEnd: import_zod.z.coerce.date(),
|
|
471
|
+
createdAt: import_zod.z.coerce.date()
|
|
472
|
+
});
|
|
473
|
+
var ProjectCollaboratorSchema = import_zod.z.object({
|
|
474
|
+
id: import_zod.z.string(),
|
|
475
|
+
projectId: ProjectIdSchema,
|
|
476
|
+
userId: UserIdSchema,
|
|
477
|
+
addedBy: UserIdSchema,
|
|
478
|
+
createdAt: import_zod.z.coerce.date()
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// ../../packages/core/dist/validation/index.js
|
|
482
|
+
var import_zod2 = require("zod");
|
|
483
|
+
var ListProjectsInputSchema = import_zod2.z.object({
|
|
484
|
+
workspaceType: import_zod2.z.preprocess((val) => typeof val === "string" ? val.toUpperCase() : val, import_zod2.z.enum(["TEAM", "PERSONAL", "ALL"]).optional())
|
|
485
|
+
});
|
|
486
|
+
var ListTasksInputSchema = import_zod2.z.object({
|
|
487
|
+
projectId: import_zod2.z.string().optional(),
|
|
488
|
+
state: import_zod2.z.nativeEnum(TaskState).optional(),
|
|
489
|
+
assigneeId: import_zod2.z.string().optional(),
|
|
490
|
+
includeArchived: import_zod2.z.boolean().optional()
|
|
491
|
+
});
|
|
492
|
+
var cuidOrPrefixedId = import_zod2.z.string().regex(/^([a-z0-9]+|[a-z]+_[a-zA-Z0-9]+)$/);
|
|
493
|
+
var gitBranchName = import_zod2.z.string().min(1).max(100).regex(/^[a-zA-Z0-9][-a-zA-Z0-9._/]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$/, "Branch name must start and end with alphanumeric character").refine((val) => !val.includes("..") && !val.includes("@{") && !val.includes("//") && !val.endsWith(".lock") && !val.includes("~") && !val.includes("^") && !val.includes(":") && !val.includes("?") && !val.includes("*") && !val.includes("[") && !val.includes("\\") && !val.includes(" ") && !val.includes(";") && !val.includes("&") && !val.includes("|") && !val.includes("$") && !val.includes("`") && !val.includes("'") && !val.includes('"') && !val.includes("<") && !val.includes(">") && !val.includes("(") && !val.includes(")"), "Invalid branch name: contains forbidden characters or sequences");
|
|
494
|
+
var GetTaskInputSchema = import_zod2.z.object({
|
|
495
|
+
taskId: cuidOrPrefixedId
|
|
496
|
+
});
|
|
497
|
+
var AssignTaskInputSchema = import_zod2.z.object({
|
|
498
|
+
projectId: cuidOrPrefixedId,
|
|
499
|
+
taskId: cuidOrPrefixedId,
|
|
500
|
+
expectedState: import_zod2.z.nativeEnum(TaskState).default(TaskState.TODO)
|
|
501
|
+
});
|
|
502
|
+
var UpdateProgressInputSchema = import_zod2.z.object({
|
|
503
|
+
taskId: cuidOrPrefixedId,
|
|
504
|
+
statusMessage: import_zod2.z.string().max(1e3).optional(),
|
|
505
|
+
completedCheckpointIds: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
506
|
+
currentCheckpointIndex: import_zod2.z.number().int().optional()
|
|
507
|
+
});
|
|
508
|
+
var CompleteTaskInputSchema = import_zod2.z.object({
|
|
509
|
+
projectId: cuidOrPrefixedId,
|
|
510
|
+
taskId: cuidOrPrefixedId,
|
|
511
|
+
pullRequestTitle: import_zod2.z.string().min(1).max(300).optional(),
|
|
512
|
+
pullRequestBody: import_zod2.z.string().max(1e4).optional()
|
|
513
|
+
});
|
|
514
|
+
var ReportErrorInputSchema = import_zod2.z.object({
|
|
515
|
+
taskId: cuidOrPrefixedId,
|
|
516
|
+
errorType: import_zod2.z.nativeEnum(ErrorType),
|
|
517
|
+
errorMessage: import_zod2.z.string().min(1).max(1e3),
|
|
518
|
+
context: import_zod2.z.string().max(2e3).optional()
|
|
519
|
+
});
|
|
520
|
+
var GetProjectContextInputSchema = import_zod2.z.object({
|
|
521
|
+
projectId: cuidOrPrefixedId
|
|
522
|
+
});
|
|
523
|
+
var AddNoteInputSchema = import_zod2.z.object({
|
|
524
|
+
taskId: cuidOrPrefixedId,
|
|
525
|
+
content: import_zod2.z.string().min(1).max(500)
|
|
526
|
+
});
|
|
527
|
+
var AbandonTaskInputSchema = import_zod2.z.object({
|
|
528
|
+
projectId: cuidOrPrefixedId,
|
|
529
|
+
taskId: cuidOrPrefixedId,
|
|
530
|
+
deleteBranch: import_zod2.z.boolean().optional()
|
|
531
|
+
});
|
|
532
|
+
var RequestChangesInputSchema = import_zod2.z.object({
|
|
533
|
+
projectId: cuidOrPrefixedId,
|
|
534
|
+
taskId: cuidOrPrefixedId,
|
|
535
|
+
reviewComments: import_zod2.z.string().min(1).max(5e3),
|
|
536
|
+
requestedChanges: import_zod2.z.array(import_zod2.z.string().min(1).max(500)).optional()
|
|
537
|
+
});
|
|
538
|
+
var ApproveTaskInputSchema = import_zod2.z.object({
|
|
539
|
+
projectId: cuidOrPrefixedId,
|
|
540
|
+
taskId: cuidOrPrefixedId,
|
|
541
|
+
reviewComments: import_zod2.z.string().max(2e3).optional()
|
|
542
|
+
});
|
|
543
|
+
var ArchiveTaskInputSchema = import_zod2.z.object({
|
|
544
|
+
projectId: cuidOrPrefixedId,
|
|
545
|
+
taskId: cuidOrPrefixedId
|
|
546
|
+
});
|
|
547
|
+
var UnarchiveTaskInputSchema = import_zod2.z.object({
|
|
548
|
+
projectId: cuidOrPrefixedId,
|
|
549
|
+
taskId: cuidOrPrefixedId
|
|
550
|
+
});
|
|
551
|
+
var CreatePersonalProjectInputSchema = import_zod2.z.object({
|
|
552
|
+
name: import_zod2.z.string().min(1).max(100),
|
|
553
|
+
description: import_zod2.z.string().max(500).optional(),
|
|
554
|
+
repositoryUrl: import_zod2.z.string().url()
|
|
555
|
+
});
|
|
556
|
+
var CheckActiveTaskInputSchema = import_zod2.z.object({});
|
|
557
|
+
var CreateTaskMCPInputSchema = import_zod2.z.object({
|
|
558
|
+
projectId: cuidOrPrefixedId,
|
|
559
|
+
epicId: cuidOrPrefixedId.nullable().optional(),
|
|
560
|
+
title: import_zod2.z.string().min(1).max(200),
|
|
561
|
+
description: import_zod2.z.string().max(5e3),
|
|
562
|
+
priority: import_zod2.z.nativeEnum(TaskPriority).default(TaskPriority.MEDIUM),
|
|
563
|
+
acceptanceCriteria: import_zod2.z.array(import_zod2.z.object({
|
|
564
|
+
description: import_zod2.z.string().min(1).max(500)
|
|
565
|
+
})).min(1)
|
|
566
|
+
});
|
|
567
|
+
var CreateOrganizationInputSchema = import_zod2.z.object({
|
|
568
|
+
name: import_zod2.z.string().min(1).max(255),
|
|
569
|
+
slug: import_zod2.z.string().min(1).max(100).regex(/^[a-z0-9-]+$/).optional()
|
|
570
|
+
});
|
|
571
|
+
var UpdateOrganizationInputSchema = import_zod2.z.object({
|
|
572
|
+
organizationId: cuidOrPrefixedId,
|
|
573
|
+
name: import_zod2.z.string().min(1).max(255).optional(),
|
|
574
|
+
logoUrl: import_zod2.z.string().url().nullable().optional(),
|
|
575
|
+
accentColor: import_zod2.z.string().regex(/^#[0-9A-Fa-f]{6}$/, "Invalid hex color format. Expected #RRGGBB").nullable().optional(),
|
|
576
|
+
tenantName: import_zod2.z.string().max(255).nullable().optional()
|
|
577
|
+
});
|
|
578
|
+
var CreateProjectInputSchema = import_zod2.z.object({
|
|
579
|
+
name: import_zod2.z.string().min(1).max(100),
|
|
580
|
+
description: import_zod2.z.string().max(500).optional(),
|
|
581
|
+
type: import_zod2.z.nativeEnum(ProjectType),
|
|
582
|
+
repositoryUrl: import_zod2.z.string().url(),
|
|
583
|
+
baseBranch: import_zod2.z.string().default("develop").optional(),
|
|
584
|
+
tags: import_zod2.z.array(import_zod2.z.string()).default([])
|
|
585
|
+
});
|
|
586
|
+
var UpdateProjectInputSchema = import_zod2.z.object({
|
|
587
|
+
projectId: import_zod2.z.string().min(1).optional(),
|
|
588
|
+
name: import_zod2.z.string().min(1).max(100).optional(),
|
|
589
|
+
description: import_zod2.z.string().max(500).optional(),
|
|
590
|
+
repositoryUrl: import_zod2.z.string().url().optional(),
|
|
591
|
+
baseBranch: import_zod2.z.string().optional(),
|
|
592
|
+
tags: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
593
|
+
allowMemberArchive: import_zod2.z.boolean().optional()
|
|
594
|
+
});
|
|
595
|
+
var CreateEpicInputSchema = import_zod2.z.object({
|
|
596
|
+
projectId: cuidOrPrefixedId,
|
|
597
|
+
name: import_zod2.z.string().min(1).max(200),
|
|
598
|
+
description: import_zod2.z.string().max(2e3).optional()
|
|
599
|
+
});
|
|
600
|
+
var CreateTaskInputSchema = import_zod2.z.object({
|
|
601
|
+
projectId: import_zod2.z.string().min(1),
|
|
602
|
+
epicId: import_zod2.z.string().min(1).nullable().optional(),
|
|
603
|
+
title: import_zod2.z.string().min(1).max(200),
|
|
604
|
+
description: import_zod2.z.string().max(5e3),
|
|
605
|
+
priority: import_zod2.z.nativeEnum(TaskPriority).default(TaskPriority.MEDIUM),
|
|
606
|
+
acceptanceCriteria: import_zod2.z.array(import_zod2.z.object({
|
|
607
|
+
description: import_zod2.z.string().min(1).max(500)
|
|
608
|
+
})).min(1)
|
|
609
|
+
});
|
|
610
|
+
var UpdateTaskInputSchema = import_zod2.z.object({
|
|
611
|
+
taskId: import_zod2.z.string().min(1),
|
|
612
|
+
title: import_zod2.z.string().min(1).max(200).optional(),
|
|
613
|
+
description: import_zod2.z.string().max(5e3).optional(),
|
|
614
|
+
priority: import_zod2.z.nativeEnum(TaskPriority).optional(),
|
|
615
|
+
state: import_zod2.z.nativeEnum(TaskState).optional(),
|
|
616
|
+
assigneeId: import_zod2.z.string().nullable().optional(),
|
|
617
|
+
acceptanceCriteria: import_zod2.z.array(import_zod2.z.object({
|
|
618
|
+
id: import_zod2.z.string().optional(),
|
|
619
|
+
description: import_zod2.z.string().min(1).max(500),
|
|
620
|
+
completed: import_zod2.z.boolean().optional()
|
|
621
|
+
})).optional()
|
|
622
|
+
});
|
|
623
|
+
var AssignTaskWebappInputSchema = import_zod2.z.object({
|
|
624
|
+
taskId: import_zod2.z.string().min(1),
|
|
625
|
+
userId: import_zod2.z.string().min(1)
|
|
626
|
+
});
|
|
627
|
+
var CreateTagInputSchema = import_zod2.z.object({
|
|
628
|
+
organizationId: cuidOrPrefixedId,
|
|
629
|
+
name: import_zod2.z.string().min(1).max(50).regex(/^[a-zA-Z0-9\s-]+$/)
|
|
630
|
+
});
|
|
631
|
+
var UpdateTagInputSchema = import_zod2.z.object({
|
|
632
|
+
name: import_zod2.z.string().min(1).max(50).regex(/^[a-zA-Z0-9\s-]+$/)
|
|
633
|
+
});
|
|
634
|
+
var UpdateOrganizationSettingsInputSchema = import_zod2.z.object({
|
|
635
|
+
organizationId: cuidOrPrefixedId,
|
|
636
|
+
ldapEnabled: import_zod2.z.boolean().optional(),
|
|
637
|
+
ldapUrl: import_zod2.z.string().url().nullable().optional(),
|
|
638
|
+
ldapBindDN: import_zod2.z.string().nullable().optional(),
|
|
639
|
+
ldapSearchBase: import_zod2.z.string().nullable().optional(),
|
|
640
|
+
deleteMergedBranches: import_zod2.z.boolean().optional(),
|
|
641
|
+
enforceConventionalCommits: import_zod2.z.boolean().optional(),
|
|
642
|
+
maxPersonalProjectsPerUser: import_zod2.z.number().int().min(0).optional(),
|
|
643
|
+
logoUrl: import_zod2.z.string().url().nullable().optional(),
|
|
644
|
+
accentColor: import_zod2.z.string().regex(/^#[0-9A-Fa-f]{6}$/, "Invalid hex color format. Expected #RRGGBB").nullable().optional(),
|
|
645
|
+
tenantName: import_zod2.z.string().max(255).nullable().optional()
|
|
646
|
+
});
|
|
647
|
+
var InviteUserInputSchema = import_zod2.z.object({
|
|
648
|
+
organizationId: cuidOrPrefixedId,
|
|
649
|
+
email: import_zod2.z.string().email(),
|
|
650
|
+
role: import_zod2.z.nativeEnum(UserRole).default(UserRole.MEMBER),
|
|
651
|
+
tags: import_zod2.z.array(import_zod2.z.string()).default([])
|
|
652
|
+
});
|
|
653
|
+
var AssignUserTagsInputSchema = import_zod2.z.object({
|
|
654
|
+
userId: cuidOrPrefixedId,
|
|
655
|
+
tags: import_zod2.z.array(import_zod2.z.string()).min(0)
|
|
656
|
+
});
|
|
657
|
+
var InviteCollaboratorInputSchema = import_zod2.z.object({
|
|
658
|
+
projectId: cuidOrPrefixedId,
|
|
659
|
+
email: import_zod2.z.string().email()
|
|
660
|
+
});
|
|
661
|
+
var PublishProjectInputSchema = import_zod2.z.object({
|
|
662
|
+
projectId: cuidOrPrefixedId,
|
|
663
|
+
transferOwnership: import_zod2.z.boolean().default(false),
|
|
664
|
+
tags: import_zod2.z.array(import_zod2.z.string()).min(1)
|
|
665
|
+
});
|
|
666
|
+
var GenerateApiKeyInputSchema = import_zod2.z.object({
|
|
667
|
+
name: import_zod2.z.string().min(1).max(100),
|
|
668
|
+
expiresInDays: import_zod2.z.number().int().min(1).max(365).default(90),
|
|
669
|
+
permissions: import_zod2.z.nativeEnum(ApiKeyPermission).default(ApiKeyPermission.WRITE)
|
|
670
|
+
});
|
|
671
|
+
var RevokeApiKeyInputSchema = import_zod2.z.object({
|
|
672
|
+
keyId: cuidOrPrefixedId
|
|
673
|
+
});
|
|
674
|
+
var LoginInputSchema = import_zod2.z.object({
|
|
675
|
+
email: import_zod2.z.string().email(),
|
|
676
|
+
password: import_zod2.z.string().min(8).max(255)
|
|
677
|
+
});
|
|
678
|
+
var RegisterInputSchema = import_zod2.z.object({
|
|
679
|
+
email: import_zod2.z.string().email(),
|
|
680
|
+
password: import_zod2.z.string().min(8).max(255),
|
|
681
|
+
name: import_zod2.z.string().min(1).max(255),
|
|
682
|
+
organizationSlug: import_zod2.z.string().min(1).max(100).regex(/^[a-z0-9-]+$/).optional()
|
|
683
|
+
});
|
|
684
|
+
var VerifyTaskInputSchema = import_zod2.z.object({
|
|
685
|
+
projectId: cuidOrPrefixedId,
|
|
686
|
+
taskId: cuidOrPrefixedId,
|
|
687
|
+
approved: import_zod2.z.boolean(),
|
|
688
|
+
feedback: import_zod2.z.string().max(5e3).optional()
|
|
689
|
+
});
|
|
690
|
+
var GetTaskPromptInputSchema = import_zod2.z.object({
|
|
691
|
+
projectId: cuidOrPrefixedId,
|
|
692
|
+
taskId: cuidOrPrefixedId
|
|
693
|
+
});
|
|
694
|
+
var UpdateTaskMCPInputSchema = import_zod2.z.object({
|
|
695
|
+
projectId: cuidOrPrefixedId,
|
|
696
|
+
taskId: cuidOrPrefixedId,
|
|
697
|
+
title: import_zod2.z.string().min(1).max(200).optional(),
|
|
698
|
+
description: import_zod2.z.string().max(5e3).optional(),
|
|
699
|
+
priority: import_zod2.z.nativeEnum(TaskPriority).optional(),
|
|
700
|
+
acceptanceCriteria: import_zod2.z.array(import_zod2.z.object({
|
|
701
|
+
id: import_zod2.z.string().optional(),
|
|
702
|
+
description: import_zod2.z.string().min(1).max(500)
|
|
703
|
+
})).optional()
|
|
704
|
+
});
|
|
705
|
+
var ReportBranchInputSchema = import_zod2.z.object({
|
|
706
|
+
projectId: cuidOrPrefixedId,
|
|
707
|
+
taskId: cuidOrPrefixedId,
|
|
708
|
+
branchName: gitBranchName
|
|
709
|
+
});
|
|
710
|
+
var ReportPRInputSchema = import_zod2.z.object({
|
|
711
|
+
projectId: cuidOrPrefixedId,
|
|
712
|
+
taskId: cuidOrPrefixedId,
|
|
713
|
+
pullRequestUrl: import_zod2.z.string().url(),
|
|
714
|
+
pullRequestNumber: import_zod2.z.number().int().positive()
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
// ../../packages/core/dist/logging/metrics.js
|
|
718
|
+
var metrics = /* @__PURE__ */ new Map();
|
|
719
|
+
function labelsToKey(labels) {
|
|
720
|
+
if (!labels || Object.keys(labels).length === 0)
|
|
721
|
+
return "";
|
|
722
|
+
return Object.entries(labels).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}="${v}"`).join(",");
|
|
723
|
+
}
|
|
724
|
+
function createCounter(name, help) {
|
|
725
|
+
const data = {
|
|
726
|
+
name,
|
|
727
|
+
type: "counter",
|
|
728
|
+
help,
|
|
729
|
+
values: /* @__PURE__ */ new Map()
|
|
730
|
+
};
|
|
731
|
+
metrics.set(name, data);
|
|
732
|
+
return {
|
|
733
|
+
inc(labels, value = 1) {
|
|
734
|
+
const key = labelsToKey(labels);
|
|
735
|
+
const current = data.values.get(key) || 0;
|
|
736
|
+
data.values.set(key, current + value);
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
function createGauge(name, help) {
|
|
741
|
+
const data = {
|
|
742
|
+
name,
|
|
743
|
+
type: "gauge",
|
|
744
|
+
help,
|
|
745
|
+
values: /* @__PURE__ */ new Map()
|
|
746
|
+
};
|
|
747
|
+
metrics.set(name, data);
|
|
748
|
+
return {
|
|
749
|
+
set(labels, value) {
|
|
750
|
+
const key = labelsToKey(labels);
|
|
751
|
+
data.values.set(key, value);
|
|
752
|
+
},
|
|
753
|
+
inc(labels, value = 1) {
|
|
754
|
+
const key = labelsToKey(labels);
|
|
755
|
+
const current = data.values.get(key) || 0;
|
|
756
|
+
data.values.set(key, current + value);
|
|
757
|
+
},
|
|
758
|
+
dec(labels, value = 1) {
|
|
759
|
+
const key = labelsToKey(labels);
|
|
760
|
+
const current = data.values.get(key) || 0;
|
|
761
|
+
data.values.set(key, current - value);
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
function createHistogram(name, help, buckets = [5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]) {
|
|
766
|
+
const data = {
|
|
767
|
+
name,
|
|
768
|
+
type: "histogram",
|
|
769
|
+
help,
|
|
770
|
+
values: /* @__PURE__ */ new Map(),
|
|
771
|
+
buckets
|
|
772
|
+
};
|
|
773
|
+
metrics.set(name, data);
|
|
774
|
+
return {
|
|
775
|
+
observe(labels, value) {
|
|
776
|
+
const baseKey = labelsToKey(labels);
|
|
777
|
+
const sumKey = `${baseKey}|sum`;
|
|
778
|
+
const countKey = `${baseKey}|count`;
|
|
779
|
+
data.values.set(sumKey, (data.values.get(sumKey) || 0) + value);
|
|
780
|
+
data.values.set(countKey, (data.values.get(countKey) || 0) + 1);
|
|
781
|
+
for (const bucket of buckets) {
|
|
782
|
+
const bucketKey = `${baseKey}|le="${bucket}"`;
|
|
783
|
+
if (value <= bucket) {
|
|
784
|
+
data.values.set(bucketKey, (data.values.get(bucketKey) || 0) + 1);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
const infKey = `${baseKey}|le="+Inf"`;
|
|
788
|
+
data.values.set(infKey, (data.values.get(infKey) || 0) + 1);
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
var httpRequestsTotal = createCounter("mtaap_http_requests_total", "Total number of HTTP requests");
|
|
793
|
+
var httpRequestDuration = createHistogram("mtaap_http_request_duration_seconds", "HTTP request duration in seconds");
|
|
794
|
+
var activeUsers = createGauge("mtaap_active_users", "Number of active users");
|
|
795
|
+
var tasksTotal = createCounter("mtaap_tasks_total", "Total number of tasks by state");
|
|
796
|
+
var taskStateChanges = createCounter("mtaap_task_state_changes_total", "Total number of task state changes");
|
|
797
|
+
var httpErrorsTotal = createCounter("mtaap_http_errors_total", "Total number of HTTP errors");
|
|
798
|
+
var httpActiveConnections = createGauge("mtaap_http_active_connections", "Number of active HTTP connections");
|
|
799
|
+
var newSignupsTotal = createCounter("mtaap_new_signups_total", "Total number of new user signups");
|
|
800
|
+
var loginSuccessTotal = createCounter("mtaap_login_success_total", "Total number of successful logins");
|
|
801
|
+
var loginFailureTotal = createCounter("mtaap_login_failure_total", "Total number of failed logins");
|
|
802
|
+
var dbConnectionPoolActive = createGauge("mtaap_db_connection_pool_active", "Number of active database connections");
|
|
803
|
+
var dbConnectionPoolIdle = createGauge("mtaap_db_connection_pool_idle", "Number of idle database connections");
|
|
804
|
+
var dbConnectionPoolMax = createGauge("mtaap_db_connection_pool_max", "Maximum number of database connections");
|
|
805
|
+
var dbQueryDuration = createHistogram("mtaap_db_query_duration_seconds", "Database query duration in seconds");
|
|
806
|
+
var dbSlowQueriesTotal = createCounter("mtaap_db_slow_queries_total", "Total number of slow database queries (>1s)");
|
|
807
|
+
var dbErrorsTotal = createCounter("mtaap_db_errors_total", "Total number of database errors");
|
|
808
|
+
var tasksCreatedTotal = createCounter("mtaap_tasks_created_total", "Total number of tasks created");
|
|
809
|
+
var tasksAssignedTotal = createCounter("mtaap_tasks_assigned_total", "Total number of tasks assigned");
|
|
810
|
+
var tasksCompletedTotal = createCounter("mtaap_tasks_completed_total", "Total number of tasks completed");
|
|
811
|
+
var tasksByState = createGauge("mtaap_tasks_by_state", "Number of tasks by state");
|
|
812
|
+
|
|
813
|
+
// ../../packages/core/dist/logging/performance-monitor.js
|
|
814
|
+
var MAX_SAMPLES = 1e3;
|
|
815
|
+
var ALERT_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
816
|
+
var DEFAULT_THRESHOLDS = {
|
|
817
|
+
api: {
|
|
818
|
+
p50: 100,
|
|
819
|
+
p95: 500,
|
|
820
|
+
p99: 1e3
|
|
821
|
+
},
|
|
822
|
+
db: {
|
|
823
|
+
p95: 100,
|
|
824
|
+
p99: 500
|
|
825
|
+
},
|
|
826
|
+
webvitals: {
|
|
827
|
+
FCP: 2e3,
|
|
828
|
+
LCP: 2500,
|
|
829
|
+
FID: 100,
|
|
830
|
+
CLS: 0.1
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
function cloneThresholds() {
|
|
834
|
+
return Object.fromEntries(Object.entries(DEFAULT_THRESHOLDS).map(([category, metrics2]) => [
|
|
835
|
+
category,
|
|
836
|
+
{ ...metrics2 }
|
|
837
|
+
]));
|
|
838
|
+
}
|
|
839
|
+
function labelsToKey2(labels) {
|
|
840
|
+
if (!labels || Object.keys(labels).length === 0)
|
|
841
|
+
return "";
|
|
842
|
+
return Object.entries(labels).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}="${value}"`).join(",");
|
|
843
|
+
}
|
|
844
|
+
function percentile(sortedValues, percentileValue) {
|
|
845
|
+
if (sortedValues.length === 0)
|
|
846
|
+
return 0;
|
|
847
|
+
const rank = Math.ceil(percentileValue / 100 * sortedValues.length);
|
|
848
|
+
const index = Math.min(Math.max(rank - 1, 0), sortedValues.length - 1);
|
|
849
|
+
return sortedValues[index];
|
|
850
|
+
}
|
|
851
|
+
var CircularBuffer = class {
|
|
852
|
+
capacity;
|
|
853
|
+
values = [];
|
|
854
|
+
index = 0;
|
|
855
|
+
size = 0;
|
|
856
|
+
constructor(capacity) {
|
|
857
|
+
this.capacity = capacity;
|
|
858
|
+
}
|
|
859
|
+
add(value) {
|
|
860
|
+
if (this.size < this.capacity) {
|
|
861
|
+
this.values.push(value);
|
|
862
|
+
this.size += 1;
|
|
863
|
+
this.index = this.size % this.capacity;
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
this.values[this.index] = value;
|
|
867
|
+
this.index = (this.index + 1) % this.capacity;
|
|
868
|
+
}
|
|
869
|
+
getValues() {
|
|
870
|
+
if (this.size < this.capacity) {
|
|
871
|
+
return this.values.slice(0, this.size);
|
|
872
|
+
}
|
|
873
|
+
return this.values.slice();
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
var PerformanceMonitor = class {
|
|
877
|
+
samples = /* @__PURE__ */ new Map();
|
|
878
|
+
thresholds = cloneThresholds();
|
|
879
|
+
alertCallback;
|
|
880
|
+
lastAlertTimestamps = /* @__PURE__ */ new Map();
|
|
881
|
+
recordTiming(category, name, durationMs, labels) {
|
|
882
|
+
if (!Number.isFinite(durationMs))
|
|
883
|
+
return;
|
|
884
|
+
const categoryMap = this.getCategoryMap(category);
|
|
885
|
+
const nameMap = this.getNameMap(categoryMap, name);
|
|
886
|
+
const labelKey = labelsToKey2(labels);
|
|
887
|
+
const buffer = nameMap.get(labelKey) ?? new CircularBuffer(MAX_SAMPLES);
|
|
888
|
+
buffer.add(durationMs);
|
|
889
|
+
nameMap.set(labelKey, buffer);
|
|
890
|
+
}
|
|
891
|
+
getPercentiles(category, name) {
|
|
892
|
+
const samples = this.collectSamples(category, name);
|
|
893
|
+
if (samples.length === 0) {
|
|
894
|
+
return { p50: 0, p95: 0, p99: 0 };
|
|
895
|
+
}
|
|
896
|
+
const sorted = [...samples].sort((a, b) => a - b);
|
|
897
|
+
return {
|
|
898
|
+
p50: percentile(sorted, 50),
|
|
899
|
+
p95: percentile(sorted, 95),
|
|
900
|
+
p99: percentile(sorted, 99)
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
checkThresholds() {
|
|
904
|
+
const alerts = [];
|
|
905
|
+
const now = Date.now();
|
|
906
|
+
const callbackAlerts = [];
|
|
907
|
+
for (const [category, nameMap] of this.samples) {
|
|
908
|
+
const thresholds = this.thresholds[category];
|
|
909
|
+
if (!thresholds)
|
|
910
|
+
continue;
|
|
911
|
+
for (const name of nameMap.keys()) {
|
|
912
|
+
const percentiles = this.getPercentiles(category, name);
|
|
913
|
+
for (const [metric, threshold] of Object.entries(thresholds)) {
|
|
914
|
+
const value = this.resolveMetricValue(metric, name, percentiles);
|
|
915
|
+
if (value === void 0 || value <= threshold)
|
|
916
|
+
continue;
|
|
917
|
+
const alert = {
|
|
918
|
+
category,
|
|
919
|
+
name,
|
|
920
|
+
metric,
|
|
921
|
+
value,
|
|
922
|
+
threshold
|
|
923
|
+
};
|
|
924
|
+
alerts.push(alert);
|
|
925
|
+
const alertKey = `${category}|${name}|${metric}`;
|
|
926
|
+
const lastAlertTime = this.lastAlertTimestamps.get(alertKey);
|
|
927
|
+
if (!lastAlertTime || now - lastAlertTime >= ALERT_COOLDOWN_MS) {
|
|
928
|
+
this.lastAlertTimestamps.set(alertKey, now);
|
|
929
|
+
callbackAlerts.push(alert);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
if (callbackAlerts.length > 0 && this.alertCallback) {
|
|
935
|
+
this.alertCallback(callbackAlerts);
|
|
936
|
+
}
|
|
937
|
+
return alerts;
|
|
938
|
+
}
|
|
939
|
+
setAlertCallback(callback) {
|
|
940
|
+
this.alertCallback = callback;
|
|
941
|
+
}
|
|
942
|
+
setThreshold(category, metric, value) {
|
|
943
|
+
if (!this.thresholds[category]) {
|
|
944
|
+
this.thresholds[category] = {};
|
|
945
|
+
}
|
|
946
|
+
this.thresholds[category][metric] = value;
|
|
947
|
+
}
|
|
948
|
+
collectSamples(category, name) {
|
|
949
|
+
const categoryMap = this.samples.get(category);
|
|
950
|
+
if (!categoryMap)
|
|
951
|
+
return [];
|
|
952
|
+
const entries = name ? [[name, categoryMap.get(name)]] : Array.from(categoryMap.entries());
|
|
953
|
+
const samples = [];
|
|
954
|
+
for (const [, labelMap] of entries) {
|
|
955
|
+
if (!labelMap)
|
|
956
|
+
continue;
|
|
957
|
+
for (const buffer of labelMap.values()) {
|
|
958
|
+
samples.push(...buffer.getValues());
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
return samples;
|
|
962
|
+
}
|
|
963
|
+
resolveMetricValue(metric, name, percentiles) {
|
|
964
|
+
if (metric === "p50")
|
|
965
|
+
return percentiles.p50;
|
|
966
|
+
if (metric === "p95")
|
|
967
|
+
return percentiles.p95;
|
|
968
|
+
if (metric === "p99")
|
|
969
|
+
return percentiles.p99;
|
|
970
|
+
if (metric === name)
|
|
971
|
+
return percentiles.p95;
|
|
972
|
+
return void 0;
|
|
973
|
+
}
|
|
974
|
+
getCategoryMap(category) {
|
|
975
|
+
const existing = this.samples.get(category);
|
|
976
|
+
if (existing)
|
|
977
|
+
return existing;
|
|
978
|
+
const created = /* @__PURE__ */ new Map();
|
|
979
|
+
this.samples.set(category, created);
|
|
980
|
+
return created;
|
|
981
|
+
}
|
|
982
|
+
getNameMap(categoryMap, name) {
|
|
983
|
+
const existing = categoryMap.get(name);
|
|
984
|
+
if (existing)
|
|
985
|
+
return existing;
|
|
986
|
+
const created = /* @__PURE__ */ new Map();
|
|
987
|
+
categoryMap.set(name, created);
|
|
988
|
+
return created;
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
var defaultMonitor = new PerformanceMonitor();
|
|
992
|
+
|
|
993
|
+
// ../../packages/core/dist/logging/error-tracker.js
|
|
994
|
+
var NoOpErrorTracker = class {
|
|
995
|
+
captureError(error, context) {
|
|
996
|
+
console.error("Error captured:", error.message, context);
|
|
997
|
+
}
|
|
998
|
+
captureException(error, context) {
|
|
999
|
+
console.error("Exception captured:", error, context);
|
|
1000
|
+
}
|
|
1001
|
+
captureMessage(message, level, context) {
|
|
1002
|
+
console[level === "warning" ? "warn" : level](`Message captured [${level}]:`, message, context);
|
|
1003
|
+
}
|
|
1004
|
+
setUser(user) {
|
|
1005
|
+
console.log("User set:", user);
|
|
1006
|
+
}
|
|
1007
|
+
clearUser() {
|
|
1008
|
+
console.log("User cleared");
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
var errorTrackerInstance = new NoOpErrorTracker();
|
|
1012
|
+
|
|
1013
|
+
// src/api-client.ts
|
|
1014
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
1015
|
+
function sanitizeForLogging(str) {
|
|
1016
|
+
let sanitized = str.replace(/\bcollab_[a-zA-Z0-9_-]+\b/gi, "[REDACTED_API_KEY]");
|
|
1017
|
+
sanitized = sanitized.replace(/\bBearer\s+[a-zA-Z0-9._-]+\b/gi, "Bearer [REDACTED]");
|
|
1018
|
+
sanitized = sanitized.replace(/([?&](api_?key|token|auth|key|secret)=)[^&\s]+/gi, "$1[REDACTED]");
|
|
1019
|
+
return sanitized;
|
|
1020
|
+
}
|
|
1021
|
+
var ApiError = class extends Error {
|
|
1022
|
+
constructor(message, code, status, details) {
|
|
1023
|
+
super(message);
|
|
1024
|
+
this.code = code;
|
|
1025
|
+
this.status = status;
|
|
1026
|
+
this.details = details;
|
|
1027
|
+
this.name = "ApiError";
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
var MCPApiClient = class {
|
|
1031
|
+
baseUrl;
|
|
1032
|
+
apiKey;
|
|
1033
|
+
timeout;
|
|
1034
|
+
debug;
|
|
1035
|
+
authContext = null;
|
|
1036
|
+
constructor(config3) {
|
|
1037
|
+
this.baseUrl = config3.baseUrl.replace(/\/$/, "");
|
|
1038
|
+
this.apiKey = config3.apiKey;
|
|
1039
|
+
this.timeout = config3.timeout ?? DEFAULT_TIMEOUT;
|
|
1040
|
+
this.debug = config3.debug ?? false;
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Make an HTTP request to the API
|
|
1044
|
+
*/
|
|
1045
|
+
async request(method, path, body) {
|
|
1046
|
+
const url = `${this.baseUrl}${path}`;
|
|
1047
|
+
const controller = new AbortController();
|
|
1048
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
1049
|
+
if (this.debug) {
|
|
1050
|
+
console.error(`[mcp-api] ${method} ${sanitizeForLogging(path)}`);
|
|
1051
|
+
}
|
|
1052
|
+
try {
|
|
1053
|
+
const response = await fetch(url, {
|
|
1054
|
+
method,
|
|
1055
|
+
headers: {
|
|
1056
|
+
"Content-Type": "application/json",
|
|
1057
|
+
"X-API-Key": this.apiKey
|
|
1058
|
+
},
|
|
1059
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
1060
|
+
signal: controller.signal
|
|
1061
|
+
});
|
|
1062
|
+
clearTimeout(timeoutId);
|
|
1063
|
+
const data = await response.json();
|
|
1064
|
+
if (!response.ok) {
|
|
1065
|
+
throw new ApiError(
|
|
1066
|
+
data.error || "API request failed",
|
|
1067
|
+
data.code || "UNKNOWN_ERROR",
|
|
1068
|
+
response.status,
|
|
1069
|
+
data.details
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
return data;
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
clearTimeout(timeoutId);
|
|
1075
|
+
if (error instanceof ApiError) {
|
|
1076
|
+
throw error;
|
|
1077
|
+
}
|
|
1078
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1079
|
+
throw new ApiError(
|
|
1080
|
+
"Request timed out",
|
|
1081
|
+
"TIMEOUT",
|
|
1082
|
+
408
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
const rawMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1086
|
+
throw new ApiError(
|
|
1087
|
+
sanitizeForLogging(rawMessage),
|
|
1088
|
+
"NETWORK_ERROR",
|
|
1089
|
+
0
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Authenticate and get user context
|
|
1095
|
+
*/
|
|
1096
|
+
async authenticate() {
|
|
1097
|
+
const context = await this.request("GET", "/api/mcp/auth");
|
|
1098
|
+
this.authContext = context;
|
|
1099
|
+
return context;
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Get cached auth context or authenticate
|
|
1103
|
+
*/
|
|
1104
|
+
async getAuthContext() {
|
|
1105
|
+
if (this.authContext) {
|
|
1106
|
+
return this.authContext;
|
|
1107
|
+
}
|
|
1108
|
+
return this.authenticate();
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* List accessible projects
|
|
1112
|
+
*/
|
|
1113
|
+
async listProjects(workspaceType) {
|
|
1114
|
+
const type = workspaceType || "ALL";
|
|
1115
|
+
return this.request(
|
|
1116
|
+
"GET",
|
|
1117
|
+
`/api/mcp/projects?workspaceType=${encodeURIComponent(type)}`
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Get single project details
|
|
1122
|
+
*/
|
|
1123
|
+
async getProject(projectId) {
|
|
1124
|
+
return this.request("GET", `/api/mcp/projects/${projectId}`);
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Get project context (README, stack, conventions)
|
|
1128
|
+
*/
|
|
1129
|
+
async getProjectContext(projectId) {
|
|
1130
|
+
return this.request(
|
|
1131
|
+
"GET",
|
|
1132
|
+
`/api/mcp/projects/${projectId}/context`
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Create a personal project
|
|
1137
|
+
*/
|
|
1138
|
+
async createPersonalProject(name, description, repositoryUrl) {
|
|
1139
|
+
return this.request(
|
|
1140
|
+
"POST",
|
|
1141
|
+
"/api/mcp/projects/personal",
|
|
1142
|
+
{ name, description, repositoryUrl }
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Create a task in a project
|
|
1147
|
+
*/
|
|
1148
|
+
async createTask(input) {
|
|
1149
|
+
return this.request("POST", "/api/mcp/tasks", input);
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* List tasks with optional filters
|
|
1153
|
+
*/
|
|
1154
|
+
async listTasks(filters = {}) {
|
|
1155
|
+
const params = new URLSearchParams();
|
|
1156
|
+
if (filters.projectId) params.set("projectId", filters.projectId);
|
|
1157
|
+
if (filters.state) params.set("state", filters.state);
|
|
1158
|
+
if (filters.assigneeId) params.set("assigneeId", filters.assigneeId);
|
|
1159
|
+
if (filters.includeArchived) params.set("includeArchived", "true");
|
|
1160
|
+
const queryString = params.toString();
|
|
1161
|
+
const path = queryString ? `/api/mcp/tasks?${queryString}` : "/api/mcp/tasks";
|
|
1162
|
+
return this.request("GET", path);
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Get full task details
|
|
1166
|
+
*/
|
|
1167
|
+
async getTask(taskId) {
|
|
1168
|
+
return this.request("GET", `/api/mcp/tasks/${taskId}`);
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Assign task to current user and create branch
|
|
1172
|
+
*/
|
|
1173
|
+
async assignTask(taskId, projectId, expectedState = TaskState.TODO) {
|
|
1174
|
+
return this.request("POST", `/api/mcp/tasks/${taskId}/assign`, {
|
|
1175
|
+
projectId,
|
|
1176
|
+
expectedState
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Update task progress
|
|
1181
|
+
*/
|
|
1182
|
+
async updateProgress(taskId, data) {
|
|
1183
|
+
return this.request("POST", `/api/mcp/tasks/${taskId}/progress`, data);
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Complete task and prepare for PR creation.
|
|
1187
|
+
* Returns PR suggestions for the agent to use when creating the PR locally.
|
|
1188
|
+
*/
|
|
1189
|
+
async completeTask(taskId, projectId, pullRequestTitle, pullRequestBody) {
|
|
1190
|
+
return this.request("POST", `/api/mcp/tasks/${taskId}/complete`, {
|
|
1191
|
+
projectId,
|
|
1192
|
+
pullRequestTitle,
|
|
1193
|
+
pullRequestBody
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Abandon task and optionally delete branch
|
|
1198
|
+
*/
|
|
1199
|
+
async abandonTask(taskId, projectId, deleteBranch = false) {
|
|
1200
|
+
return this.request("POST", `/api/mcp/tasks/${taskId}/abandon`, {
|
|
1201
|
+
projectId,
|
|
1202
|
+
deleteBranch
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Archive a task (soft delete)
|
|
1207
|
+
*/
|
|
1208
|
+
async archiveTask(taskId, projectId) {
|
|
1209
|
+
return this.request("POST", `/api/mcp/tasks/${taskId}/archive`, {
|
|
1210
|
+
projectId
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Unarchive a task (restore)
|
|
1215
|
+
*/
|
|
1216
|
+
async unarchiveTask(taskId, projectId) {
|
|
1217
|
+
return this.request("DELETE", `/api/mcp/tasks/${taskId}/archive`, {
|
|
1218
|
+
projectId
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Report task error
|
|
1223
|
+
*/
|
|
1224
|
+
async reportError(taskId, errorType, errorMessage, context) {
|
|
1225
|
+
return this.request("POST", `/api/mcp/tasks/${taskId}/error`, {
|
|
1226
|
+
errorType,
|
|
1227
|
+
errorMessage,
|
|
1228
|
+
context
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Add note to task
|
|
1233
|
+
*/
|
|
1234
|
+
async addNote(taskId, content) {
|
|
1235
|
+
return this.request("POST", `/api/mcp/tasks/${taskId}/notes`, { content });
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Request changes on a task in review
|
|
1239
|
+
*/
|
|
1240
|
+
async requestChanges(taskId, projectId, reviewComments, requestedChanges) {
|
|
1241
|
+
return this.request("POST", `/api/mcp/tasks/${taskId}/request-changes`, {
|
|
1242
|
+
projectId,
|
|
1243
|
+
reviewComments,
|
|
1244
|
+
requestedChanges
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Approve a task in review and mark as DONE
|
|
1249
|
+
*/
|
|
1250
|
+
async approveTask(taskId, projectId, reviewComments) {
|
|
1251
|
+
return this.request("POST", `/api/mcp/tasks/${taskId}/approve`, {
|
|
1252
|
+
projectId,
|
|
1253
|
+
reviewComments
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Report branch created by agent
|
|
1258
|
+
*/
|
|
1259
|
+
async reportBranch(taskId, projectId, branchName) {
|
|
1260
|
+
return this.request("POST", `/api/mcp/tasks/${taskId}/branch`, {
|
|
1261
|
+
projectId,
|
|
1262
|
+
branchName
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Report PR created by agent
|
|
1267
|
+
*/
|
|
1268
|
+
async reportPR(taskId, projectId, pullRequestUrl, pullRequestNumber) {
|
|
1269
|
+
return this.request("POST", `/api/mcp/tasks/${taskId}/pr`, {
|
|
1270
|
+
projectId,
|
|
1271
|
+
pullRequestUrl,
|
|
1272
|
+
pullRequestNumber
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Verify a DRAFT task to move it to TODO state
|
|
1277
|
+
*/
|
|
1278
|
+
async verifyTask(taskId, projectId, approved, feedback) {
|
|
1279
|
+
return this.request("POST", `/api/mcp/tasks/${taskId}/verify`, {
|
|
1280
|
+
projectId,
|
|
1281
|
+
approved,
|
|
1282
|
+
feedback
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Get state-appropriate prompt for a task
|
|
1287
|
+
*/
|
|
1288
|
+
async getTaskPrompt(taskId, projectId) {
|
|
1289
|
+
return this.request(
|
|
1290
|
+
"GET",
|
|
1291
|
+
`/api/mcp/tasks/${taskId}/prompt?projectId=${encodeURIComponent(projectId)}`
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Update task details (DRAFT/TODO states only)
|
|
1296
|
+
*/
|
|
1297
|
+
async updateTask(taskId, projectId, data) {
|
|
1298
|
+
return this.request("PATCH", `/api/mcp/tasks/${taskId}`, {
|
|
1299
|
+
projectId,
|
|
1300
|
+
...data
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
};
|
|
1304
|
+
|
|
1305
|
+
// src/permissions.ts
|
|
1306
|
+
var PERMISSION_RANK = {
|
|
1307
|
+
READ: 1,
|
|
1308
|
+
WRITE: 2,
|
|
1309
|
+
ADMIN: 3
|
|
1310
|
+
};
|
|
1311
|
+
function assertApiKeyPermission(apiKey, required, toolName) {
|
|
1312
|
+
const actualRank = PERMISSION_RANK[apiKey.permissions] ?? 0;
|
|
1313
|
+
const requiredRank = PERMISSION_RANK[required] ?? 0;
|
|
1314
|
+
if (actualRank >= requiredRank) {
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
console.warn("API key permission violation", {
|
|
1318
|
+
keyId: apiKey.id,
|
|
1319
|
+
requiredPermission: required,
|
|
1320
|
+
actualPermission: apiKey.permissions,
|
|
1321
|
+
tool: toolName
|
|
1322
|
+
});
|
|
1323
|
+
const error = new Error(
|
|
1324
|
+
`API key lacks required permissions (required: ${required})`
|
|
1325
|
+
);
|
|
1326
|
+
error.status = 403;
|
|
1327
|
+
throw error;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// src/index.ts
|
|
1331
|
+
var COLLAB_SERVER_INSTRUCTIONS = `Collab - AI-assisted software development task management platform.
|
|
1332
|
+
|
|
1333
|
+
TOOL CATEGORIES:
|
|
1334
|
+
|
|
1335
|
+
1. Project Discovery (READ):
|
|
1336
|
+
- list_projects, get_project_context, get_version
|
|
1337
|
+
|
|
1338
|
+
2. Task Management (READ/WRITE):
|
|
1339
|
+
- list_tasks, get_task, create_task, update_task, archive_task, unarchive_task
|
|
1340
|
+
|
|
1341
|
+
3. Task Verification (WRITE):
|
|
1342
|
+
- verify_task, get_task_prompt
|
|
1343
|
+
|
|
1344
|
+
4. Task Execution (WRITE):
|
|
1345
|
+
- assign_task, update_progress, add_note, complete_task, abandon_task, report_error
|
|
1346
|
+
|
|
1347
|
+
5. Git Operations (WRITE):
|
|
1348
|
+
- report_branch, report_pr
|
|
1349
|
+
|
|
1350
|
+
6. Code Review (WRITE):
|
|
1351
|
+
- request_changes, approve_task
|
|
1352
|
+
|
|
1353
|
+
7. Session Management (READ):
|
|
1354
|
+
- check_active_task
|
|
1355
|
+
|
|
1356
|
+
WORKFLOWS:
|
|
1357
|
+
|
|
1358
|
+
Task Creation & Verification:
|
|
1359
|
+
create_task -> get_task_prompt (DRAFT) -> verify_task(approved=true) -> task moves to TODO
|
|
1360
|
+
|
|
1361
|
+
Standard Task Workflow:
|
|
1362
|
+
list_projects -> get_project_context -> list_tasks(state=TODO) -> get_task -> get_task_prompt (TODO)
|
|
1363
|
+
-> assign_task (returns suggested branch name and worktree path)
|
|
1364
|
+
-> git worktree add <worktreePath> -b <branchName> <baseBranch>
|
|
1365
|
+
-> cd <worktreePath>
|
|
1366
|
+
-> [update_progress...]
|
|
1367
|
+
-> git push -u origin <branchName>
|
|
1368
|
+
-> report_branch (tell server about the branch)
|
|
1369
|
+
-> complete_task (returns PR suggestions)
|
|
1370
|
+
-> gh pr create (local gh command)
|
|
1371
|
+
-> report_pr (tell server about the PR)
|
|
1372
|
+
-> git worktree remove <worktreePath>
|
|
1373
|
+
|
|
1374
|
+
Resume Workflow:
|
|
1375
|
+
check_active_task -> (if active) get_task -> get_task_prompt (IN_PROGRESS) -> update_progress -> complete_task
|
|
1376
|
+
|
|
1377
|
+
Review Workflow:
|
|
1378
|
+
list_tasks(state=REVIEW) -> get_task -> approve_task OR request_changes
|
|
1379
|
+
|
|
1380
|
+
Task Editing:
|
|
1381
|
+
update_task (DRAFT/TODO only) -> if was TODO, reverts to DRAFT -> verify_task again
|
|
1382
|
+
|
|
1383
|
+
Error Recovery:
|
|
1384
|
+
report_error -> abandon_task -> list_tasks -> assign_task (retry or pick different task)
|
|
1385
|
+
(abandon_task returns IN_PROGRESS tasks to TODO state)
|
|
1386
|
+
|
|
1387
|
+
GIT OPERATIONS NOTE:
|
|
1388
|
+
The agent handles all git operations locally using git worktrees for isolation.
|
|
1389
|
+
After assign_task returns a suggested branch name and worktree path:
|
|
1390
|
+
1. Create worktree: git worktree add <worktreePath> -b <branchName> <baseBranch>
|
|
1391
|
+
2. Work in worktree directory: cd <worktreePath>
|
|
1392
|
+
3. After completing work, push and call report_branch
|
|
1393
|
+
4. After complete_task, create PR with gh and call report_pr
|
|
1394
|
+
5. Clean up worktree: git worktree remove <worktreePath>
|
|
1395
|
+
Worktrees enable parallel task execution without git conflicts.
|
|
1396
|
+
|
|
1397
|
+
TASK STATE FLOW:
|
|
1398
|
+
DRAFT -> TODO -> IN_PROGRESS -> REVIEW -> DONE
|
|
1399
|
+
(verify_task: DRAFT -> TODO)
|
|
1400
|
+
(update_task on TODO: reverts to DRAFT)
|
|
1401
|
+
(request_changes: REVIEW -> IN_PROGRESS)
|
|
1402
|
+
(abandon_task: IN_PROGRESS -> TODO)
|
|
1403
|
+
|
|
1404
|
+
CONSTRAINTS:
|
|
1405
|
+
- DRAFT tasks must be verified before assignment
|
|
1406
|
+
- verify_task requires programmatic validation (title 10+ chars, description 50+ chars, criteria 20+ chars each)
|
|
1407
|
+
- update_task only works on DRAFT and TODO states
|
|
1408
|
+
- assign_task is atomic - fails if already claimed
|
|
1409
|
+
- Only TODO tasks can be assigned
|
|
1410
|
+
- complete_task requires IN_PROGRESS state
|
|
1411
|
+
- request_changes/approve_task require REVIEW state
|
|
1412
|
+
- Always check_active_task before starting new work
|
|
1413
|
+
- Call update_progress frequently to checkpoint
|
|
1414
|
+
- Agent must have git/gh CLI configured for local git operations`;
|
|
1415
|
+
function initializeMCPServer(apiClient, authContext) {
|
|
1416
|
+
const server = new import_mcp.McpServer(
|
|
1417
|
+
{
|
|
1418
|
+
name: "collab",
|
|
1419
|
+
version: VERSION
|
|
1420
|
+
},
|
|
1421
|
+
{
|
|
1422
|
+
instructions: COLLAB_SERVER_INSTRUCTIONS
|
|
1423
|
+
}
|
|
1424
|
+
);
|
|
1425
|
+
const mockApiKey = {
|
|
1426
|
+
permissions: authContext.permissions.includes("ADMIN") ? ApiKeyPermission.ADMIN : authContext.permissions.includes("WRITE") ? ApiKeyPermission.WRITE : ApiKeyPermission.READ
|
|
1427
|
+
};
|
|
1428
|
+
server.registerTool(
|
|
1429
|
+
"list_projects",
|
|
1430
|
+
{
|
|
1431
|
+
description: "Discover all accessible projects. Use first to find project IDs. Filter by TEAM, PERSONAL, or ALL workspaces.",
|
|
1432
|
+
inputSchema: {
|
|
1433
|
+
workspaceType: import_zod3.z.enum(["TEAM", "PERSONAL", "ALL"]).optional().describe("Filter by workspace type")
|
|
1434
|
+
}
|
|
1435
|
+
},
|
|
1436
|
+
async (args) => {
|
|
1437
|
+
assertApiKeyPermission(
|
|
1438
|
+
mockApiKey,
|
|
1439
|
+
ApiKeyPermission.READ,
|
|
1440
|
+
"list_projects"
|
|
1441
|
+
);
|
|
1442
|
+
const validated = ListProjectsInputSchema.parse(args);
|
|
1443
|
+
try {
|
|
1444
|
+
const projects = await apiClient.listProjects(validated.workspaceType);
|
|
1445
|
+
return {
|
|
1446
|
+
content: [
|
|
1447
|
+
{
|
|
1448
|
+
type: "text",
|
|
1449
|
+
text: JSON.stringify(projects, null, 2)
|
|
1450
|
+
}
|
|
1451
|
+
]
|
|
1452
|
+
};
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
return handleApiError(error);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
);
|
|
1458
|
+
server.registerTool(
|
|
1459
|
+
"list_tasks",
|
|
1460
|
+
{
|
|
1461
|
+
description: "Query tasks with filters. Use state=TODO for assignable tasks, state=REVIEW for pending reviews.",
|
|
1462
|
+
inputSchema: {
|
|
1463
|
+
projectId: import_zod3.z.string().optional().describe("Filter by project ID"),
|
|
1464
|
+
state: import_zod3.z.nativeEnum(TaskState).optional().describe("Filter by task state"),
|
|
1465
|
+
assigneeId: import_zod3.z.string().optional().describe("Filter by assignee ID"),
|
|
1466
|
+
includeArchived: import_zod3.z.boolean().optional().describe("Include archived tasks (default: false)")
|
|
1467
|
+
}
|
|
1468
|
+
},
|
|
1469
|
+
async (args) => {
|
|
1470
|
+
assertApiKeyPermission(mockApiKey, ApiKeyPermission.READ, "list_tasks");
|
|
1471
|
+
const validated = ListTasksInputSchema.parse(args);
|
|
1472
|
+
try {
|
|
1473
|
+
const tasks = await apiClient.listTasks({
|
|
1474
|
+
projectId: validated.projectId,
|
|
1475
|
+
state: validated.state,
|
|
1476
|
+
assigneeId: validated.assigneeId,
|
|
1477
|
+
includeArchived: validated.includeArchived
|
|
1478
|
+
});
|
|
1479
|
+
return {
|
|
1480
|
+
content: [
|
|
1481
|
+
{
|
|
1482
|
+
type: "text",
|
|
1483
|
+
text: JSON.stringify(tasks, null, 2)
|
|
1484
|
+
}
|
|
1485
|
+
]
|
|
1486
|
+
};
|
|
1487
|
+
} catch (error) {
|
|
1488
|
+
return handleApiError(error);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
);
|
|
1492
|
+
server.registerTool(
|
|
1493
|
+
"get_task",
|
|
1494
|
+
{
|
|
1495
|
+
description: "Get complete task details with acceptance criteria and notes. Call before assign_task to understand requirements.",
|
|
1496
|
+
inputSchema: {
|
|
1497
|
+
taskId: import_zod3.z.string().describe("The task ID to retrieve")
|
|
1498
|
+
}
|
|
1499
|
+
},
|
|
1500
|
+
async (args) => {
|
|
1501
|
+
assertApiKeyPermission(mockApiKey, ApiKeyPermission.READ, "get_task");
|
|
1502
|
+
const validated = GetTaskInputSchema.parse(args);
|
|
1503
|
+
try {
|
|
1504
|
+
const task = await apiClient.getTask(validated.taskId);
|
|
1505
|
+
return {
|
|
1506
|
+
content: [
|
|
1507
|
+
{
|
|
1508
|
+
type: "text",
|
|
1509
|
+
text: JSON.stringify(task, null, 2)
|
|
1510
|
+
}
|
|
1511
|
+
]
|
|
1512
|
+
};
|
|
1513
|
+
} catch (error) {
|
|
1514
|
+
return handleApiError(error);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
);
|
|
1518
|
+
server.registerTool(
|
|
1519
|
+
"assign_task",
|
|
1520
|
+
{
|
|
1521
|
+
description: "Atomically claim a task. Race-safe - fails if already assigned. Task must be TODO. Returns suggested branch name and worktree path for isolated parallel development.",
|
|
1522
|
+
inputSchema: {
|
|
1523
|
+
projectId: import_zod3.z.string().describe("The project ID"),
|
|
1524
|
+
taskId: import_zod3.z.string().describe("The task ID to assign"),
|
|
1525
|
+
expectedState: import_zod3.z.nativeEnum(TaskState).optional().describe("Expected task state (default: TODO)")
|
|
1526
|
+
}
|
|
1527
|
+
},
|
|
1528
|
+
async (args) => {
|
|
1529
|
+
assertApiKeyPermission(
|
|
1530
|
+
mockApiKey,
|
|
1531
|
+
ApiKeyPermission.WRITE,
|
|
1532
|
+
"assign_task"
|
|
1533
|
+
);
|
|
1534
|
+
const validated = AssignTaskInputSchema.parse(args);
|
|
1535
|
+
try {
|
|
1536
|
+
const result = await apiClient.assignTask(
|
|
1537
|
+
validated.taskId,
|
|
1538
|
+
validated.projectId,
|
|
1539
|
+
validated.expectedState
|
|
1540
|
+
);
|
|
1541
|
+
return {
|
|
1542
|
+
content: [
|
|
1543
|
+
{
|
|
1544
|
+
type: "text",
|
|
1545
|
+
text: JSON.stringify(result, null, 2)
|
|
1546
|
+
}
|
|
1547
|
+
]
|
|
1548
|
+
};
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
return handleApiError(error);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
);
|
|
1554
|
+
server.registerTool(
|
|
1555
|
+
"update_progress",
|
|
1556
|
+
{
|
|
1557
|
+
description: "Report progress and checkpoint work. Call frequently during execution. Marks acceptance criteria complete.",
|
|
1558
|
+
inputSchema: {
|
|
1559
|
+
taskId: import_zod3.z.string().describe("The task ID to update"),
|
|
1560
|
+
statusMessage: import_zod3.z.string().optional().describe("Status message (max 1000 chars)"),
|
|
1561
|
+
completedCheckpointIds: import_zod3.z.array(import_zod3.z.string()).optional().describe("Array of completed checkpoint IDs"),
|
|
1562
|
+
currentCheckpointIndex: import_zod3.z.number().optional().describe("Current checkpoint index")
|
|
1563
|
+
}
|
|
1564
|
+
},
|
|
1565
|
+
async (args) => {
|
|
1566
|
+
assertApiKeyPermission(
|
|
1567
|
+
mockApiKey,
|
|
1568
|
+
ApiKeyPermission.WRITE,
|
|
1569
|
+
"update_progress"
|
|
1570
|
+
);
|
|
1571
|
+
const validated = UpdateProgressInputSchema.parse(args);
|
|
1572
|
+
try {
|
|
1573
|
+
const result = await apiClient.updateProgress(validated.taskId, {
|
|
1574
|
+
statusMessage: validated.statusMessage,
|
|
1575
|
+
completedCheckpointIds: validated.completedCheckpointIds,
|
|
1576
|
+
currentCheckpointIndex: validated.currentCheckpointIndex
|
|
1577
|
+
});
|
|
1578
|
+
return {
|
|
1579
|
+
content: [
|
|
1580
|
+
{
|
|
1581
|
+
type: "text",
|
|
1582
|
+
text: JSON.stringify(result, null, 2)
|
|
1583
|
+
}
|
|
1584
|
+
]
|
|
1585
|
+
};
|
|
1586
|
+
} catch (error) {
|
|
1587
|
+
return handleApiError(error);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
);
|
|
1591
|
+
server.registerTool(
|
|
1592
|
+
"complete_task",
|
|
1593
|
+
{
|
|
1594
|
+
description: "Prepare task for PR creation. Returns PR suggestions. After creating PR locally, call report_pr to transition to REVIEW. Requires IN_PROGRESS state.",
|
|
1595
|
+
inputSchema: {
|
|
1596
|
+
projectId: import_zod3.z.string().describe("The project ID"),
|
|
1597
|
+
taskId: import_zod3.z.string().describe("The task ID to complete"),
|
|
1598
|
+
pullRequestTitle: import_zod3.z.string().optional().describe("PR title (max 300 chars)"),
|
|
1599
|
+
pullRequestBody: import_zod3.z.string().optional().describe("PR body/description (max 10000 chars)")
|
|
1600
|
+
}
|
|
1601
|
+
},
|
|
1602
|
+
async (args) => {
|
|
1603
|
+
assertApiKeyPermission(
|
|
1604
|
+
mockApiKey,
|
|
1605
|
+
ApiKeyPermission.WRITE,
|
|
1606
|
+
"complete_task"
|
|
1607
|
+
);
|
|
1608
|
+
const validated = CompleteTaskInputSchema.parse(args);
|
|
1609
|
+
try {
|
|
1610
|
+
const result = await apiClient.completeTask(
|
|
1611
|
+
validated.taskId,
|
|
1612
|
+
validated.projectId,
|
|
1613
|
+
validated.pullRequestTitle,
|
|
1614
|
+
validated.pullRequestBody
|
|
1615
|
+
);
|
|
1616
|
+
return {
|
|
1617
|
+
content: [
|
|
1618
|
+
{
|
|
1619
|
+
type: "text",
|
|
1620
|
+
text: JSON.stringify(result, null, 2)
|
|
1621
|
+
}
|
|
1622
|
+
]
|
|
1623
|
+
};
|
|
1624
|
+
} catch (error) {
|
|
1625
|
+
return handleApiError(error);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
);
|
|
1629
|
+
server.registerTool(
|
|
1630
|
+
"check_active_task",
|
|
1631
|
+
{
|
|
1632
|
+
description: "Check for resumable work from previous session. Always call before starting new work."
|
|
1633
|
+
},
|
|
1634
|
+
async () => {
|
|
1635
|
+
assertApiKeyPermission(
|
|
1636
|
+
mockApiKey,
|
|
1637
|
+
ApiKeyPermission.READ,
|
|
1638
|
+
"check_active_task"
|
|
1639
|
+
);
|
|
1640
|
+
const result = await checkActiveTask();
|
|
1641
|
+
return {
|
|
1642
|
+
content: [
|
|
1643
|
+
{
|
|
1644
|
+
type: "text",
|
|
1645
|
+
text: JSON.stringify(result, null, 2)
|
|
1646
|
+
}
|
|
1647
|
+
]
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
);
|
|
1651
|
+
server.registerTool(
|
|
1652
|
+
"report_error",
|
|
1653
|
+
{
|
|
1654
|
+
description: "Report unrecoverable errors (BUILD_FAILURE, TEST_FAILURE, CONFLICT, AUTH_ERROR). Consider abandon_task after.",
|
|
1655
|
+
inputSchema: {
|
|
1656
|
+
taskId: import_zod3.z.string().describe("The task ID"),
|
|
1657
|
+
errorType: import_zod3.z.nativeEnum(ErrorType).describe("Error type: BUILD_FAILURE, TEST_FAILURE, CONFLICT, AUTH_ERROR, OTHER"),
|
|
1658
|
+
errorMessage: import_zod3.z.string().describe("Error message (max 1000 chars)"),
|
|
1659
|
+
context: import_zod3.z.string().optional().describe("Additional context (max 2000 chars)")
|
|
1660
|
+
}
|
|
1661
|
+
},
|
|
1662
|
+
async (args) => {
|
|
1663
|
+
assertApiKeyPermission(
|
|
1664
|
+
mockApiKey,
|
|
1665
|
+
ApiKeyPermission.WRITE,
|
|
1666
|
+
"report_error"
|
|
1667
|
+
);
|
|
1668
|
+
const validated = ReportErrorInputSchema.parse(args);
|
|
1669
|
+
try {
|
|
1670
|
+
const result = await apiClient.reportError(
|
|
1671
|
+
validated.taskId,
|
|
1672
|
+
validated.errorType,
|
|
1673
|
+
validated.errorMessage,
|
|
1674
|
+
validated.context
|
|
1675
|
+
);
|
|
1676
|
+
return {
|
|
1677
|
+
content: [
|
|
1678
|
+
{
|
|
1679
|
+
type: "text",
|
|
1680
|
+
text: JSON.stringify(result, null, 2)
|
|
1681
|
+
}
|
|
1682
|
+
]
|
|
1683
|
+
};
|
|
1684
|
+
} catch (error) {
|
|
1685
|
+
return handleApiError(error);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
);
|
|
1689
|
+
server.registerTool(
|
|
1690
|
+
"get_project_context",
|
|
1691
|
+
{
|
|
1692
|
+
description: "Load project README, tech stack, and coding conventions. Call after selecting project.",
|
|
1693
|
+
inputSchema: {
|
|
1694
|
+
projectId: import_zod3.z.string().describe("The project ID")
|
|
1695
|
+
}
|
|
1696
|
+
},
|
|
1697
|
+
async (args) => {
|
|
1698
|
+
assertApiKeyPermission(
|
|
1699
|
+
mockApiKey,
|
|
1700
|
+
ApiKeyPermission.READ,
|
|
1701
|
+
"get_project_context"
|
|
1702
|
+
);
|
|
1703
|
+
const validated = GetProjectContextInputSchema.parse(args);
|
|
1704
|
+
try {
|
|
1705
|
+
const context = await apiClient.getProjectContext(validated.projectId);
|
|
1706
|
+
return {
|
|
1707
|
+
content: [
|
|
1708
|
+
{
|
|
1709
|
+
type: "text",
|
|
1710
|
+
text: JSON.stringify(context, null, 2)
|
|
1711
|
+
}
|
|
1712
|
+
]
|
|
1713
|
+
};
|
|
1714
|
+
} catch (error) {
|
|
1715
|
+
return handleApiError(error);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
);
|
|
1719
|
+
server.registerTool(
|
|
1720
|
+
"add_note",
|
|
1721
|
+
{
|
|
1722
|
+
description: "Document implementation decisions. Notes persist for future reference and handoff.",
|
|
1723
|
+
inputSchema: {
|
|
1724
|
+
taskId: import_zod3.z.string().describe("The task ID"),
|
|
1725
|
+
content: import_zod3.z.string().describe("Note content (max 500 chars)")
|
|
1726
|
+
}
|
|
1727
|
+
},
|
|
1728
|
+
async (args) => {
|
|
1729
|
+
assertApiKeyPermission(mockApiKey, ApiKeyPermission.WRITE, "add_note");
|
|
1730
|
+
const validated = AddNoteInputSchema.parse(args);
|
|
1731
|
+
try {
|
|
1732
|
+
const result = await apiClient.addNote(
|
|
1733
|
+
validated.taskId,
|
|
1734
|
+
validated.content
|
|
1735
|
+
);
|
|
1736
|
+
return {
|
|
1737
|
+
content: [
|
|
1738
|
+
{
|
|
1739
|
+
type: "text",
|
|
1740
|
+
text: JSON.stringify(result, null, 2)
|
|
1741
|
+
}
|
|
1742
|
+
]
|
|
1743
|
+
};
|
|
1744
|
+
} catch (error) {
|
|
1745
|
+
return handleApiError(error);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
);
|
|
1749
|
+
server.registerTool(
|
|
1750
|
+
"abandon_task",
|
|
1751
|
+
{
|
|
1752
|
+
description: "Release task assignment and optionally clean up branch. Task returns to TODO. Use after errors.",
|
|
1753
|
+
inputSchema: {
|
|
1754
|
+
projectId: import_zod3.z.string().describe("The project ID"),
|
|
1755
|
+
taskId: import_zod3.z.string().describe("The task ID to abandon"),
|
|
1756
|
+
deleteBranch: import_zod3.z.boolean().optional().describe("Whether to delete the associated branch")
|
|
1757
|
+
}
|
|
1758
|
+
},
|
|
1759
|
+
async (args) => {
|
|
1760
|
+
assertApiKeyPermission(
|
|
1761
|
+
mockApiKey,
|
|
1762
|
+
ApiKeyPermission.WRITE,
|
|
1763
|
+
"abandon_task"
|
|
1764
|
+
);
|
|
1765
|
+
const validated = AbandonTaskInputSchema.parse(args);
|
|
1766
|
+
try {
|
|
1767
|
+
const result = await apiClient.abandonTask(
|
|
1768
|
+
validated.taskId,
|
|
1769
|
+
validated.projectId,
|
|
1770
|
+
validated.deleteBranch
|
|
1771
|
+
);
|
|
1772
|
+
return {
|
|
1773
|
+
content: [
|
|
1774
|
+
{
|
|
1775
|
+
type: "text",
|
|
1776
|
+
text: JSON.stringify(result, null, 2)
|
|
1777
|
+
}
|
|
1778
|
+
]
|
|
1779
|
+
};
|
|
1780
|
+
} catch (error) {
|
|
1781
|
+
return handleApiError(error);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
);
|
|
1785
|
+
server.registerTool(
|
|
1786
|
+
"report_branch",
|
|
1787
|
+
{
|
|
1788
|
+
description: "Report a branch created by the agent. Call after using git to create and push a branch. Task must be IN_PROGRESS.",
|
|
1789
|
+
inputSchema: {
|
|
1790
|
+
projectId: import_zod3.z.string().describe("The project ID"),
|
|
1791
|
+
taskId: import_zod3.z.string().describe("The task ID"),
|
|
1792
|
+
branchName: import_zod3.z.string().describe("Name of the branch created (e.g., feature/TASK-123-fix-login)")
|
|
1793
|
+
}
|
|
1794
|
+
},
|
|
1795
|
+
async (args) => {
|
|
1796
|
+
assertApiKeyPermission(
|
|
1797
|
+
mockApiKey,
|
|
1798
|
+
ApiKeyPermission.WRITE,
|
|
1799
|
+
"report_branch"
|
|
1800
|
+
);
|
|
1801
|
+
const validated = ReportBranchInputSchema.parse(args);
|
|
1802
|
+
try {
|
|
1803
|
+
const result = await apiClient.reportBranch(
|
|
1804
|
+
validated.taskId,
|
|
1805
|
+
validated.projectId,
|
|
1806
|
+
validated.branchName
|
|
1807
|
+
);
|
|
1808
|
+
return {
|
|
1809
|
+
content: [
|
|
1810
|
+
{
|
|
1811
|
+
type: "text",
|
|
1812
|
+
text: JSON.stringify(result, null, 2)
|
|
1813
|
+
}
|
|
1814
|
+
]
|
|
1815
|
+
};
|
|
1816
|
+
} catch (error) {
|
|
1817
|
+
return handleApiError(error);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
);
|
|
1821
|
+
server.registerTool(
|
|
1822
|
+
"report_pr",
|
|
1823
|
+
{
|
|
1824
|
+
description: "Report a PR created by the agent. Call after using gh pr create. Transitions task to REVIEW state.",
|
|
1825
|
+
inputSchema: {
|
|
1826
|
+
projectId: import_zod3.z.string().describe("The project ID"),
|
|
1827
|
+
taskId: import_zod3.z.string().describe("The task ID"),
|
|
1828
|
+
pullRequestUrl: import_zod3.z.string().describe("Full URL of the created PR (e.g., https://github.com/owner/repo/pull/123)"),
|
|
1829
|
+
pullRequestNumber: import_zod3.z.number().describe("PR number (e.g., 123)")
|
|
1830
|
+
}
|
|
1831
|
+
},
|
|
1832
|
+
async (args) => {
|
|
1833
|
+
assertApiKeyPermission(
|
|
1834
|
+
mockApiKey,
|
|
1835
|
+
ApiKeyPermission.WRITE,
|
|
1836
|
+
"report_pr"
|
|
1837
|
+
);
|
|
1838
|
+
const validated = ReportPRInputSchema.parse(args);
|
|
1839
|
+
try {
|
|
1840
|
+
const result = await apiClient.reportPR(
|
|
1841
|
+
validated.taskId,
|
|
1842
|
+
validated.projectId,
|
|
1843
|
+
validated.pullRequestUrl,
|
|
1844
|
+
validated.pullRequestNumber
|
|
1845
|
+
);
|
|
1846
|
+
return {
|
|
1847
|
+
content: [
|
|
1848
|
+
{
|
|
1849
|
+
type: "text",
|
|
1850
|
+
text: JSON.stringify(result, null, 2)
|
|
1851
|
+
}
|
|
1852
|
+
]
|
|
1853
|
+
};
|
|
1854
|
+
} catch (error) {
|
|
1855
|
+
return handleApiError(error);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
);
|
|
1859
|
+
server.registerTool(
|
|
1860
|
+
"archive_task",
|
|
1861
|
+
{
|
|
1862
|
+
description: "Soft-delete a task. Hidden but restorable via unarchive_task.",
|
|
1863
|
+
inputSchema: {
|
|
1864
|
+
projectId: import_zod3.z.string().describe("The project ID"),
|
|
1865
|
+
taskId: import_zod3.z.string().describe("The task ID to archive")
|
|
1866
|
+
}
|
|
1867
|
+
},
|
|
1868
|
+
async (args) => {
|
|
1869
|
+
assertApiKeyPermission(
|
|
1870
|
+
mockApiKey,
|
|
1871
|
+
ApiKeyPermission.WRITE,
|
|
1872
|
+
"archive_task"
|
|
1873
|
+
);
|
|
1874
|
+
const validated = ArchiveTaskInputSchema.parse(args);
|
|
1875
|
+
try {
|
|
1876
|
+
const result = await apiClient.archiveTask(
|
|
1877
|
+
validated.taskId,
|
|
1878
|
+
validated.projectId
|
|
1879
|
+
);
|
|
1880
|
+
return {
|
|
1881
|
+
content: [
|
|
1882
|
+
{
|
|
1883
|
+
type: "text",
|
|
1884
|
+
text: JSON.stringify(result, null, 2)
|
|
1885
|
+
}
|
|
1886
|
+
]
|
|
1887
|
+
};
|
|
1888
|
+
} catch (error) {
|
|
1889
|
+
return handleApiError(error);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
);
|
|
1893
|
+
server.registerTool(
|
|
1894
|
+
"unarchive_task",
|
|
1895
|
+
{
|
|
1896
|
+
description: "Restore previously archived task to original state.",
|
|
1897
|
+
inputSchema: {
|
|
1898
|
+
projectId: import_zod3.z.string().describe("The project ID"),
|
|
1899
|
+
taskId: import_zod3.z.string().describe("The task ID to restore")
|
|
1900
|
+
}
|
|
1901
|
+
},
|
|
1902
|
+
async (args) => {
|
|
1903
|
+
assertApiKeyPermission(
|
|
1904
|
+
mockApiKey,
|
|
1905
|
+
ApiKeyPermission.WRITE,
|
|
1906
|
+
"unarchive_task"
|
|
1907
|
+
);
|
|
1908
|
+
const validated = UnarchiveTaskInputSchema.parse(args);
|
|
1909
|
+
try {
|
|
1910
|
+
const result = await apiClient.unarchiveTask(
|
|
1911
|
+
validated.taskId,
|
|
1912
|
+
validated.projectId
|
|
1913
|
+
);
|
|
1914
|
+
return {
|
|
1915
|
+
content: [
|
|
1916
|
+
{
|
|
1917
|
+
type: "text",
|
|
1918
|
+
text: JSON.stringify(result, null, 2)
|
|
1919
|
+
}
|
|
1920
|
+
]
|
|
1921
|
+
};
|
|
1922
|
+
} catch (error) {
|
|
1923
|
+
return handleApiError(error);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
);
|
|
1927
|
+
server.registerTool(
|
|
1928
|
+
"create_personal_project",
|
|
1929
|
+
{
|
|
1930
|
+
description: "Create new project in personal workspace linked to GitHub repository.",
|
|
1931
|
+
inputSchema: {
|
|
1932
|
+
name: import_zod3.z.string().describe("Project name (max 100 chars)"),
|
|
1933
|
+
description: import_zod3.z.string().optional().describe("Project description (max 500 chars)"),
|
|
1934
|
+
repositoryUrl: import_zod3.z.string().describe("GitHub repository URL")
|
|
1935
|
+
}
|
|
1936
|
+
},
|
|
1937
|
+
async (args) => {
|
|
1938
|
+
assertApiKeyPermission(
|
|
1939
|
+
mockApiKey,
|
|
1940
|
+
ApiKeyPermission.WRITE,
|
|
1941
|
+
"create_personal_project"
|
|
1942
|
+
);
|
|
1943
|
+
const validated = CreatePersonalProjectInputSchema.parse(args);
|
|
1944
|
+
try {
|
|
1945
|
+
const result = await apiClient.createPersonalProject(
|
|
1946
|
+
validated.name,
|
|
1947
|
+
validated.description,
|
|
1948
|
+
validated.repositoryUrl
|
|
1949
|
+
);
|
|
1950
|
+
return {
|
|
1951
|
+
content: [
|
|
1952
|
+
{
|
|
1953
|
+
type: "text",
|
|
1954
|
+
text: JSON.stringify(result, null, 2)
|
|
1955
|
+
}
|
|
1956
|
+
]
|
|
1957
|
+
};
|
|
1958
|
+
} catch (error) {
|
|
1959
|
+
return handleApiError(error);
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
);
|
|
1963
|
+
server.registerTool(
|
|
1964
|
+
"create_task",
|
|
1965
|
+
{
|
|
1966
|
+
description: "Create task with title, description, acceptance criteria. Starts in DRAFT. Priority: LOW/MEDIUM/HIGH/CRITICAL.",
|
|
1967
|
+
inputSchema: {
|
|
1968
|
+
projectId: import_zod3.z.string().describe("The project ID to create the task in"),
|
|
1969
|
+
epicId: import_zod3.z.string().nullable().optional().describe("Optional epic ID to associate the task with"),
|
|
1970
|
+
title: import_zod3.z.string().describe("Task title (max 200 chars)"),
|
|
1971
|
+
description: import_zod3.z.string().describe("Task description (max 5000 chars)"),
|
|
1972
|
+
priority: import_zod3.z.nativeEnum(TaskPriority).optional().describe("Task priority: LOW, MEDIUM, HIGH, CRITICAL (default: MEDIUM)"),
|
|
1973
|
+
acceptanceCriteria: import_zod3.z.array(
|
|
1974
|
+
import_zod3.z.object({
|
|
1975
|
+
description: import_zod3.z.string().describe("Acceptance criterion description (max 500 chars)")
|
|
1976
|
+
})
|
|
1977
|
+
).describe("Array of acceptance criteria (at least 1 required)")
|
|
1978
|
+
}
|
|
1979
|
+
},
|
|
1980
|
+
async (args) => {
|
|
1981
|
+
assertApiKeyPermission(
|
|
1982
|
+
mockApiKey,
|
|
1983
|
+
ApiKeyPermission.WRITE,
|
|
1984
|
+
"create_task"
|
|
1985
|
+
);
|
|
1986
|
+
const validated = CreateTaskMCPInputSchema.parse(args);
|
|
1987
|
+
try {
|
|
1988
|
+
const result = await apiClient.createTask({
|
|
1989
|
+
projectId: validated.projectId,
|
|
1990
|
+
epicId: validated.epicId,
|
|
1991
|
+
title: validated.title,
|
|
1992
|
+
description: validated.description,
|
|
1993
|
+
priority: validated.priority,
|
|
1994
|
+
acceptanceCriteria: validated.acceptanceCriteria
|
|
1995
|
+
});
|
|
1996
|
+
return {
|
|
1997
|
+
content: [
|
|
1998
|
+
{
|
|
1999
|
+
type: "text",
|
|
2000
|
+
text: JSON.stringify(result, null, 2)
|
|
2001
|
+
}
|
|
2002
|
+
]
|
|
2003
|
+
};
|
|
2004
|
+
} catch (error) {
|
|
2005
|
+
return handleApiError(error);
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
);
|
|
2009
|
+
server.registerTool(
|
|
2010
|
+
"request_changes",
|
|
2011
|
+
{
|
|
2012
|
+
description: "Return task from REVIEW to IN_PROGRESS with feedback. Original assignee addresses changes.",
|
|
2013
|
+
inputSchema: {
|
|
2014
|
+
projectId: import_zod3.z.string().describe("The project ID"),
|
|
2015
|
+
taskId: import_zod3.z.string().describe("The task ID to review"),
|
|
2016
|
+
reviewComments: import_zod3.z.string().describe("Review comments explaining requested changes (max 5000 chars)"),
|
|
2017
|
+
requestedChanges: import_zod3.z.array(import_zod3.z.string()).optional().describe("List of specific changes requested")
|
|
2018
|
+
}
|
|
2019
|
+
},
|
|
2020
|
+
async (args) => {
|
|
2021
|
+
assertApiKeyPermission(
|
|
2022
|
+
mockApiKey,
|
|
2023
|
+
ApiKeyPermission.WRITE,
|
|
2024
|
+
"request_changes"
|
|
2025
|
+
);
|
|
2026
|
+
const validated = RequestChangesInputSchema.parse(args);
|
|
2027
|
+
try {
|
|
2028
|
+
const result = await apiClient.requestChanges(
|
|
2029
|
+
validated.taskId,
|
|
2030
|
+
validated.projectId,
|
|
2031
|
+
validated.reviewComments,
|
|
2032
|
+
validated.requestedChanges
|
|
2033
|
+
);
|
|
2034
|
+
return {
|
|
2035
|
+
content: [
|
|
2036
|
+
{
|
|
2037
|
+
type: "text",
|
|
2038
|
+
text: JSON.stringify(result, null, 2)
|
|
2039
|
+
}
|
|
2040
|
+
]
|
|
2041
|
+
};
|
|
2042
|
+
} catch (error) {
|
|
2043
|
+
return handleApiError(error);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
);
|
|
2047
|
+
server.registerTool(
|
|
2048
|
+
"approve_task",
|
|
2049
|
+
{
|
|
2050
|
+
description: "Approve completed work and mark DONE. Only for REVIEW state tasks.",
|
|
2051
|
+
inputSchema: {
|
|
2052
|
+
projectId: import_zod3.z.string().describe("The project ID"),
|
|
2053
|
+
taskId: import_zod3.z.string().describe("The task ID to approve"),
|
|
2054
|
+
reviewComments: import_zod3.z.string().optional().describe("Optional approval comments (max 2000 chars)")
|
|
2055
|
+
}
|
|
2056
|
+
},
|
|
2057
|
+
async (args) => {
|
|
2058
|
+
assertApiKeyPermission(
|
|
2059
|
+
mockApiKey,
|
|
2060
|
+
ApiKeyPermission.WRITE,
|
|
2061
|
+
"approve_task"
|
|
2062
|
+
);
|
|
2063
|
+
const validated = ApproveTaskInputSchema.parse(args);
|
|
2064
|
+
try {
|
|
2065
|
+
const result = await apiClient.approveTask(
|
|
2066
|
+
validated.taskId,
|
|
2067
|
+
validated.projectId,
|
|
2068
|
+
validated.reviewComments
|
|
2069
|
+
);
|
|
2070
|
+
return {
|
|
2071
|
+
content: [
|
|
2072
|
+
{
|
|
2073
|
+
type: "text",
|
|
2074
|
+
text: JSON.stringify(result, null, 2)
|
|
2075
|
+
}
|
|
2076
|
+
]
|
|
2077
|
+
};
|
|
2078
|
+
} catch (error) {
|
|
2079
|
+
return handleApiError(error);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
);
|
|
2083
|
+
server.registerTool(
|
|
2084
|
+
"verify_task",
|
|
2085
|
+
{
|
|
2086
|
+
description: "Verify a DRAFT task and move it to TODO state. Requires task to pass programmatic validation (title 10+ chars, description 50+ chars, each criterion 20+ chars). If approved=false, stores feedback with NEEDS_REVISION status.",
|
|
2087
|
+
inputSchema: {
|
|
2088
|
+
projectId: import_zod3.z.string().describe("The project ID"),
|
|
2089
|
+
taskId: import_zod3.z.string().describe("The task ID to verify"),
|
|
2090
|
+
approved: import_zod3.z.boolean().describe("Whether to approve the task"),
|
|
2091
|
+
feedback: import_zod3.z.string().optional().describe("Feedback for the task (required if not approved)")
|
|
2092
|
+
}
|
|
2093
|
+
},
|
|
2094
|
+
async (args) => {
|
|
2095
|
+
assertApiKeyPermission(mockApiKey, ApiKeyPermission.WRITE, "verify_task");
|
|
2096
|
+
const validated = VerifyTaskInputSchema.parse(args);
|
|
2097
|
+
try {
|
|
2098
|
+
const result = await apiClient.verifyTask(
|
|
2099
|
+
validated.taskId,
|
|
2100
|
+
validated.projectId,
|
|
2101
|
+
validated.approved,
|
|
2102
|
+
validated.feedback
|
|
2103
|
+
);
|
|
2104
|
+
return {
|
|
2105
|
+
content: [
|
|
2106
|
+
{
|
|
2107
|
+
type: "text",
|
|
2108
|
+
text: JSON.stringify(result, null, 2)
|
|
2109
|
+
}
|
|
2110
|
+
]
|
|
2111
|
+
};
|
|
2112
|
+
} catch (error) {
|
|
2113
|
+
return handleApiError(error);
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
);
|
|
2117
|
+
server.registerTool(
|
|
2118
|
+
"get_task_prompt",
|
|
2119
|
+
{
|
|
2120
|
+
description: "Get state-appropriate prompt for a task. Returns verify prompt for DRAFT, assignment prompt for TODO, or continue prompt for IN_PROGRESS tasks.",
|
|
2121
|
+
inputSchema: {
|
|
2122
|
+
projectId: import_zod3.z.string().describe("The project ID"),
|
|
2123
|
+
taskId: import_zod3.z.string().describe("The task ID")
|
|
2124
|
+
}
|
|
2125
|
+
},
|
|
2126
|
+
async (args) => {
|
|
2127
|
+
assertApiKeyPermission(
|
|
2128
|
+
mockApiKey,
|
|
2129
|
+
ApiKeyPermission.READ,
|
|
2130
|
+
"get_task_prompt"
|
|
2131
|
+
);
|
|
2132
|
+
const validated = GetTaskPromptInputSchema.parse(args);
|
|
2133
|
+
try {
|
|
2134
|
+
const result = await apiClient.getTaskPrompt(
|
|
2135
|
+
validated.taskId,
|
|
2136
|
+
validated.projectId
|
|
2137
|
+
);
|
|
2138
|
+
return {
|
|
2139
|
+
content: [
|
|
2140
|
+
{
|
|
2141
|
+
type: "text",
|
|
2142
|
+
text: JSON.stringify(result, null, 2)
|
|
2143
|
+
}
|
|
2144
|
+
]
|
|
2145
|
+
};
|
|
2146
|
+
} catch (error) {
|
|
2147
|
+
return handleApiError(error);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
);
|
|
2151
|
+
server.registerTool(
|
|
2152
|
+
"update_task",
|
|
2153
|
+
{
|
|
2154
|
+
description: "Update task details (title, description, priority, acceptanceCriteria). Only works for DRAFT and TODO states. If task is in TODO state, it reverts to DRAFT and requires re-verification.",
|
|
2155
|
+
inputSchema: {
|
|
2156
|
+
projectId: import_zod3.z.string().describe("The project ID"),
|
|
2157
|
+
taskId: import_zod3.z.string().describe("The task ID to update"),
|
|
2158
|
+
title: import_zod3.z.string().optional().describe("New task title"),
|
|
2159
|
+
description: import_zod3.z.string().optional().describe("New task description"),
|
|
2160
|
+
priority: import_zod3.z.nativeEnum(TaskPriority).optional().describe("New task priority"),
|
|
2161
|
+
acceptanceCriteria: import_zod3.z.array(
|
|
2162
|
+
import_zod3.z.object({
|
|
2163
|
+
id: import_zod3.z.string().optional().describe("Existing criterion ID (for updates)"),
|
|
2164
|
+
description: import_zod3.z.string().describe("Criterion description")
|
|
2165
|
+
})
|
|
2166
|
+
).optional().describe("New acceptance criteria (replaces existing)")
|
|
2167
|
+
}
|
|
2168
|
+
},
|
|
2169
|
+
async (args) => {
|
|
2170
|
+
assertApiKeyPermission(mockApiKey, ApiKeyPermission.WRITE, "update_task");
|
|
2171
|
+
const validated = UpdateTaskMCPInputSchema.parse(args);
|
|
2172
|
+
try {
|
|
2173
|
+
const result = await apiClient.updateTask(
|
|
2174
|
+
validated.taskId,
|
|
2175
|
+
validated.projectId,
|
|
2176
|
+
{
|
|
2177
|
+
title: validated.title,
|
|
2178
|
+
description: validated.description,
|
|
2179
|
+
priority: validated.priority,
|
|
2180
|
+
acceptanceCriteria: validated.acceptanceCriteria
|
|
2181
|
+
}
|
|
2182
|
+
);
|
|
2183
|
+
return {
|
|
2184
|
+
content: [
|
|
2185
|
+
{
|
|
2186
|
+
type: "text",
|
|
2187
|
+
text: JSON.stringify(result, null, 2)
|
|
2188
|
+
}
|
|
2189
|
+
]
|
|
2190
|
+
};
|
|
2191
|
+
} catch (error) {
|
|
2192
|
+
return handleApiError(error);
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
);
|
|
2196
|
+
server.registerTool(
|
|
2197
|
+
"get_version",
|
|
2198
|
+
{
|
|
2199
|
+
description: "Check MCP server version and connectivity."
|
|
2200
|
+
},
|
|
2201
|
+
async () => {
|
|
2202
|
+
assertApiKeyPermission(mockApiKey, ApiKeyPermission.READ, "get_version");
|
|
2203
|
+
return {
|
|
2204
|
+
content: [
|
|
2205
|
+
{
|
|
2206
|
+
type: "text",
|
|
2207
|
+
text: JSON.stringify(
|
|
2208
|
+
{
|
|
2209
|
+
version: VERSION,
|
|
2210
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2211
|
+
},
|
|
2212
|
+
null,
|
|
2213
|
+
2
|
|
2214
|
+
)
|
|
2215
|
+
}
|
|
2216
|
+
]
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
);
|
|
2220
|
+
return server;
|
|
2221
|
+
}
|
|
2222
|
+
function handleApiError(error) {
|
|
2223
|
+
if (error instanceof ApiError) {
|
|
2224
|
+
return {
|
|
2225
|
+
content: [
|
|
2226
|
+
{
|
|
2227
|
+
type: "text",
|
|
2228
|
+
text: JSON.stringify(
|
|
2229
|
+
{
|
|
2230
|
+
error: error.message,
|
|
2231
|
+
code: error.code,
|
|
2232
|
+
status: error.status,
|
|
2233
|
+
details: error.details
|
|
2234
|
+
},
|
|
2235
|
+
null,
|
|
2236
|
+
2
|
|
2237
|
+
)
|
|
2238
|
+
}
|
|
2239
|
+
],
|
|
2240
|
+
isError: true
|
|
2241
|
+
};
|
|
2242
|
+
}
|
|
2243
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2244
|
+
return {
|
|
2245
|
+
content: [
|
|
2246
|
+
{
|
|
2247
|
+
type: "text",
|
|
2248
|
+
text: JSON.stringify({ error: message }, null, 2)
|
|
2249
|
+
}
|
|
2250
|
+
],
|
|
2251
|
+
isError: true
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
var ActiveTaskSchema = import_zod3.z.object({
|
|
2255
|
+
taskId: import_zod3.z.string().min(1),
|
|
2256
|
+
projectId: import_zod3.z.string().min(1),
|
|
2257
|
+
branchName: import_zod3.z.string().optional(),
|
|
2258
|
+
worktreePath: import_zod3.z.string().optional(),
|
|
2259
|
+
startedAt: import_zod3.z.string().optional()
|
|
2260
|
+
});
|
|
2261
|
+
async function checkActiveTask() {
|
|
2262
|
+
const fs = await import("fs");
|
|
2263
|
+
const path = await import("path");
|
|
2264
|
+
const cwd = process.cwd();
|
|
2265
|
+
const collabDir = path.join(cwd, ".collab");
|
|
2266
|
+
const activeTaskPath = path.join(collabDir, "active-task.json");
|
|
2267
|
+
try {
|
|
2268
|
+
const resolvedPath = path.resolve(activeTaskPath);
|
|
2269
|
+
const resolvedCollabDir = path.resolve(collabDir);
|
|
2270
|
+
if (!resolvedPath.startsWith(resolvedCollabDir + path.sep) && resolvedPath !== resolvedCollabDir) {
|
|
2271
|
+
return {
|
|
2272
|
+
hasActiveTask: false,
|
|
2273
|
+
task: null,
|
|
2274
|
+
error: "Invalid active task path"
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
const stats = await fs.promises.lstat(activeTaskPath);
|
|
2278
|
+
if (stats.isSymbolicLink()) {
|
|
2279
|
+
return {
|
|
2280
|
+
hasActiveTask: false,
|
|
2281
|
+
task: null,
|
|
2282
|
+
error: "Symlinks not allowed for active task file"
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
if (!stats.isFile()) {
|
|
2286
|
+
return {
|
|
2287
|
+
hasActiveTask: false,
|
|
2288
|
+
task: null
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
const content = await fs.promises.readFile(activeTaskPath, "utf-8");
|
|
2292
|
+
let parsed;
|
|
2293
|
+
try {
|
|
2294
|
+
parsed = JSON.parse(content);
|
|
2295
|
+
} catch {
|
|
2296
|
+
return {
|
|
2297
|
+
hasActiveTask: false,
|
|
2298
|
+
task: null,
|
|
2299
|
+
error: "Invalid JSON in active task file"
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
const validationResult = ActiveTaskSchema.safeParse(parsed);
|
|
2303
|
+
if (!validationResult.success) {
|
|
2304
|
+
return {
|
|
2305
|
+
hasActiveTask: false,
|
|
2306
|
+
task: null,
|
|
2307
|
+
error: "Active task file has invalid structure"
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
return {
|
|
2311
|
+
hasActiveTask: true,
|
|
2312
|
+
task: validationResult.data
|
|
2313
|
+
};
|
|
2314
|
+
} catch (err) {
|
|
2315
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
2316
|
+
return {
|
|
2317
|
+
hasActiveTask: false,
|
|
2318
|
+
task: null
|
|
2319
|
+
};
|
|
2320
|
+
}
|
|
2321
|
+
return {
|
|
2322
|
+
hasActiveTask: false,
|
|
2323
|
+
task: null,
|
|
2324
|
+
error: "Failed to read active task file"
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
// src/auth.ts
|
|
2330
|
+
function createAuthMiddleware(baseUrl) {
|
|
2331
|
+
return async (req, res, next) => {
|
|
2332
|
+
const apiKey = req.headers["x-api-key"];
|
|
2333
|
+
if (!apiKey || typeof apiKey !== "string") {
|
|
2334
|
+
res.status(401).json({
|
|
2335
|
+
jsonrpc: "2.0",
|
|
2336
|
+
error: {
|
|
2337
|
+
code: -32001,
|
|
2338
|
+
message: "Missing or invalid X-API-Key header"
|
|
2339
|
+
},
|
|
2340
|
+
id: null
|
|
2341
|
+
});
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
if (!apiKey.startsWith("collab_")) {
|
|
2345
|
+
res.status(401).json({
|
|
2346
|
+
jsonrpc: "2.0",
|
|
2347
|
+
error: {
|
|
2348
|
+
code: -32001,
|
|
2349
|
+
message: "Invalid API key format"
|
|
2350
|
+
},
|
|
2351
|
+
id: null
|
|
2352
|
+
});
|
|
2353
|
+
return;
|
|
2354
|
+
}
|
|
2355
|
+
try {
|
|
2356
|
+
const apiClient = new MCPApiClient({
|
|
2357
|
+
baseUrl,
|
|
2358
|
+
apiKey,
|
|
2359
|
+
timeout: 3e4
|
|
2360
|
+
});
|
|
2361
|
+
const authContext = await apiClient.authenticate();
|
|
2362
|
+
req.apiClient = apiClient;
|
|
2363
|
+
req.authContext = authContext;
|
|
2364
|
+
next();
|
|
2365
|
+
} catch (error) {
|
|
2366
|
+
if (error instanceof ApiError) {
|
|
2367
|
+
const statusCode = error.status || 401;
|
|
2368
|
+
const errorCode = statusCode === 401 ? -32001 : statusCode === 403 ? -32002 : -32e3;
|
|
2369
|
+
res.status(statusCode).json({
|
|
2370
|
+
jsonrpc: "2.0",
|
|
2371
|
+
error: {
|
|
2372
|
+
code: errorCode,
|
|
2373
|
+
message: error.message
|
|
2374
|
+
},
|
|
2375
|
+
id: null
|
|
2376
|
+
});
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
console.error("[collab-mcp-server] Authentication error:", error);
|
|
2380
|
+
res.status(500).json({
|
|
2381
|
+
jsonrpc: "2.0",
|
|
2382
|
+
error: {
|
|
2383
|
+
code: -32e3,
|
|
2384
|
+
message: "Internal authentication error"
|
|
2385
|
+
},
|
|
2386
|
+
id: null
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
// src/server.ts
|
|
2393
|
+
var DEFAULT_PORT = 3001;
|
|
2394
|
+
var sessions = /* @__PURE__ */ new Map();
|
|
2395
|
+
var SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
2396
|
+
var SESSION_CLEANUP_INTERVAL_MS = 60 * 1e3;
|
|
2397
|
+
var cleanupIntervalId = null;
|
|
2398
|
+
function startSessionCleanup() {
|
|
2399
|
+
cleanupIntervalId = setInterval(() => {
|
|
2400
|
+
const now = Date.now();
|
|
2401
|
+
let cleanedCount = 0;
|
|
2402
|
+
for (const [id, session] of sessions.entries()) {
|
|
2403
|
+
if (now - session.lastAccessedAt.getTime() > SESSION_TIMEOUT_MS) {
|
|
2404
|
+
console.error(`[collab-mcp-server] Session timed out: ${id}`);
|
|
2405
|
+
session.transport.close();
|
|
2406
|
+
sessions.delete(id);
|
|
2407
|
+
cleanedCount++;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
if (cleanedCount > 0) {
|
|
2411
|
+
console.error(`[collab-mcp-server] Cleaned up ${cleanedCount} expired session(s)`);
|
|
2412
|
+
}
|
|
2413
|
+
}, SESSION_CLEANUP_INTERVAL_MS);
|
|
2414
|
+
}
|
|
2415
|
+
function getSessionIdHeader(req) {
|
|
2416
|
+
const header = req.headers["mcp-session-id"];
|
|
2417
|
+
if (Array.isArray(header)) {
|
|
2418
|
+
return header[0];
|
|
2419
|
+
}
|
|
2420
|
+
return header;
|
|
2421
|
+
}
|
|
2422
|
+
async function createHTTPServer() {
|
|
2423
|
+
const port = parseInt(process.env.PORT || String(DEFAULT_PORT), 10);
|
|
2424
|
+
const baseUrl = process.env.COLLAB_BASE_URL;
|
|
2425
|
+
if (!baseUrl) {
|
|
2426
|
+
console.error("[collab-mcp-server] Error: COLLAB_BASE_URL environment variable is required");
|
|
2427
|
+
console.error("\nRequired environment variables:");
|
|
2428
|
+
console.error(" COLLAB_BASE_URL Collab webapp URL (e.g., https://collab.mtaap.de)");
|
|
2429
|
+
console.error(" PORT Server port (default: 3001)");
|
|
2430
|
+
console.error("\nClients must provide X-API-Key header with their Collab API key.");
|
|
2431
|
+
process.exit(1);
|
|
2432
|
+
}
|
|
2433
|
+
try {
|
|
2434
|
+
new URL(baseUrl);
|
|
2435
|
+
} catch {
|
|
2436
|
+
console.error(`[collab-mcp-server] Error: COLLAB_BASE_URL is not a valid URL: ${baseUrl}`);
|
|
2437
|
+
process.exit(1);
|
|
2438
|
+
}
|
|
2439
|
+
const app = (0, import_express.default)();
|
|
2440
|
+
app.use(import_express.default.json());
|
|
2441
|
+
app.get("/mcp/health", (_req, res) => {
|
|
2442
|
+
res.json({
|
|
2443
|
+
status: "ok",
|
|
2444
|
+
version: VERSION,
|
|
2445
|
+
activeSessions: sessions.size,
|
|
2446
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2447
|
+
});
|
|
2448
|
+
});
|
|
2449
|
+
const authMiddleware = createAuthMiddleware(baseUrl);
|
|
2450
|
+
app.post("/mcp", authMiddleware, async (req, res) => {
|
|
2451
|
+
const authReq = req;
|
|
2452
|
+
const sessionId = getSessionIdHeader(req);
|
|
2453
|
+
let sessionData;
|
|
2454
|
+
if (sessionId) {
|
|
2455
|
+
sessionData = sessions.get(sessionId);
|
|
2456
|
+
if (!sessionData) {
|
|
2457
|
+
res.status(400).json({
|
|
2458
|
+
jsonrpc: "2.0",
|
|
2459
|
+
error: {
|
|
2460
|
+
code: -32e3,
|
|
2461
|
+
message: "Invalid or expired session"
|
|
2462
|
+
},
|
|
2463
|
+
id: null
|
|
2464
|
+
});
|
|
2465
|
+
return;
|
|
2466
|
+
}
|
|
2467
|
+
sessionData.lastAccessedAt = /* @__PURE__ */ new Date();
|
|
2468
|
+
} else if ((0, import_types.isInitializeRequest)(req.body)) {
|
|
2469
|
+
const now = /* @__PURE__ */ new Date();
|
|
2470
|
+
const transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
2471
|
+
sessionIdGenerator: () => (0, import_node_crypto.randomUUID)(),
|
|
2472
|
+
onsessioninitialized: (id) => {
|
|
2473
|
+
sessions.set(id, {
|
|
2474
|
+
transport,
|
|
2475
|
+
apiClient: authReq.apiClient,
|
|
2476
|
+
authContext: authReq.authContext,
|
|
2477
|
+
createdAt: now,
|
|
2478
|
+
lastAccessedAt: now
|
|
2479
|
+
});
|
|
2480
|
+
console.error(`[collab-mcp-server] Session initialized: ${id} (user: ${authReq.authContext.userEmail})`);
|
|
2481
|
+
},
|
|
2482
|
+
onsessionclosed: (id) => {
|
|
2483
|
+
sessions.delete(id);
|
|
2484
|
+
console.error(`[collab-mcp-server] Session closed: ${id}`);
|
|
2485
|
+
}
|
|
2486
|
+
});
|
|
2487
|
+
transport.onclose = () => {
|
|
2488
|
+
if (transport.sessionId) {
|
|
2489
|
+
sessions.delete(transport.sessionId);
|
|
2490
|
+
console.error(`[collab-mcp-server] Transport closed for session: ${transport.sessionId}`);
|
|
2491
|
+
}
|
|
2492
|
+
};
|
|
2493
|
+
const server = initializeMCPServer(authReq.apiClient, authReq.authContext);
|
|
2494
|
+
await server.connect(transport);
|
|
2495
|
+
sessionData = {
|
|
2496
|
+
transport,
|
|
2497
|
+
apiClient: authReq.apiClient,
|
|
2498
|
+
authContext: authReq.authContext,
|
|
2499
|
+
createdAt: now,
|
|
2500
|
+
lastAccessedAt: now
|
|
2501
|
+
};
|
|
2502
|
+
} else {
|
|
2503
|
+
res.status(400).json({
|
|
2504
|
+
jsonrpc: "2.0",
|
|
2505
|
+
error: {
|
|
2506
|
+
code: -32e3,
|
|
2507
|
+
message: "No session ID provided and request is not an initialize request"
|
|
2508
|
+
},
|
|
2509
|
+
id: null
|
|
2510
|
+
});
|
|
2511
|
+
return;
|
|
2512
|
+
}
|
|
2513
|
+
await sessionData.transport.handleRequest(req, res, req.body);
|
|
2514
|
+
});
|
|
2515
|
+
app.get("/mcp", authMiddleware, async (req, res) => {
|
|
2516
|
+
const sessionId = getSessionIdHeader(req);
|
|
2517
|
+
if (!sessionId) {
|
|
2518
|
+
res.status(400).json({
|
|
2519
|
+
jsonrpc: "2.0",
|
|
2520
|
+
error: {
|
|
2521
|
+
code: -32e3,
|
|
2522
|
+
message: "mcp-session-id header is required for SSE stream"
|
|
2523
|
+
},
|
|
2524
|
+
id: null
|
|
2525
|
+
});
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2528
|
+
const sessionData = sessions.get(sessionId);
|
|
2529
|
+
if (!sessionData) {
|
|
2530
|
+
res.status(400).json({
|
|
2531
|
+
jsonrpc: "2.0",
|
|
2532
|
+
error: {
|
|
2533
|
+
code: -32e3,
|
|
2534
|
+
message: "Invalid or expired session"
|
|
2535
|
+
},
|
|
2536
|
+
id: null
|
|
2537
|
+
});
|
|
2538
|
+
return;
|
|
2539
|
+
}
|
|
2540
|
+
sessionData.lastAccessedAt = /* @__PURE__ */ new Date();
|
|
2541
|
+
await sessionData.transport.handleRequest(req, res);
|
|
2542
|
+
});
|
|
2543
|
+
app.delete("/mcp", authMiddleware, async (req, res) => {
|
|
2544
|
+
const sessionId = getSessionIdHeader(req);
|
|
2545
|
+
if (!sessionId) {
|
|
2546
|
+
res.status(400).json({
|
|
2547
|
+
jsonrpc: "2.0",
|
|
2548
|
+
error: {
|
|
2549
|
+
code: -32e3,
|
|
2550
|
+
message: "mcp-session-id header is required for session termination"
|
|
2551
|
+
},
|
|
2552
|
+
id: null
|
|
2553
|
+
});
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2556
|
+
const sessionData = sessions.get(sessionId);
|
|
2557
|
+
if (!sessionData) {
|
|
2558
|
+
res.status(400).json({
|
|
2559
|
+
jsonrpc: "2.0",
|
|
2560
|
+
error: {
|
|
2561
|
+
code: -32e3,
|
|
2562
|
+
message: "Invalid or expired session"
|
|
2563
|
+
},
|
|
2564
|
+
id: null
|
|
2565
|
+
});
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
await sessionData.transport.handleRequest(req, res);
|
|
2569
|
+
});
|
|
2570
|
+
startSessionCleanup();
|
|
2571
|
+
app.listen(port, () => {
|
|
2572
|
+
console.error(`[collab-mcp-server] Collab MCP HTTP Server v${VERSION}`);
|
|
2573
|
+
console.error(`[collab-mcp-server] Listening on http://0.0.0.0:${port}`);
|
|
2574
|
+
console.error(`[collab-mcp-server] MCP endpoint: POST/GET/DELETE /mcp`);
|
|
2575
|
+
console.error(`[collab-mcp-server] Health check: GET /mcp/health`);
|
|
2576
|
+
console.error(`[collab-mcp-server] API URL: ${baseUrl}`);
|
|
2577
|
+
console.error(`[collab-mcp-server] Session timeout: ${SESSION_TIMEOUT_MS / 1e3 / 60} minutes`);
|
|
2578
|
+
});
|
|
2579
|
+
}
|
|
2580
|
+
function handleCliFlags() {
|
|
2581
|
+
const args = process.argv.slice(2);
|
|
2582
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
2583
|
+
console.log(`collab-mcp-server v${VERSION}`);
|
|
2584
|
+
process.exit(0);
|
|
2585
|
+
}
|
|
2586
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2587
|
+
console.log(`collab-mcp-server v${VERSION}
|
|
2588
|
+
|
|
2589
|
+
Collab MCP HTTP Server - Streamable HTTP transport for remote MCP connections
|
|
2590
|
+
|
|
2591
|
+
Usage:
|
|
2592
|
+
collab-mcp-server [options]
|
|
2593
|
+
|
|
2594
|
+
Options:
|
|
2595
|
+
-v, --version Show version number
|
|
2596
|
+
-h, --help Show this help message
|
|
2597
|
+
|
|
2598
|
+
Environment Variables:
|
|
2599
|
+
COLLAB_BASE_URL Collab webapp URL (required)
|
|
2600
|
+
PORT Server port (default: 3001)
|
|
2601
|
+
|
|
2602
|
+
Client Authentication:
|
|
2603
|
+
Clients must provide an X-API-Key header with their Collab API key.
|
|
2604
|
+
Each session is authenticated independently.
|
|
2605
|
+
|
|
2606
|
+
Example deployment with Docker:
|
|
2607
|
+
docker run -e COLLAB_BASE_URL=https://collab.mtaap.de -p 3001:3001 mtaap/mcp-server
|
|
2608
|
+
|
|
2609
|
+
Example client configuration (Claude Desktop):
|
|
2610
|
+
{
|
|
2611
|
+
"mcpServers": {
|
|
2612
|
+
"collab-remote": {
|
|
2613
|
+
"url": "https://your-server.com/mcp",
|
|
2614
|
+
"headers": {
|
|
2615
|
+
"X-API-Key": "collab_your_api_key_here"
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
}`);
|
|
2620
|
+
process.exit(0);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
function setupShutdownHandlers() {
|
|
2624
|
+
const shutdown = (signal) => {
|
|
2625
|
+
console.error(`[collab-mcp-server] Received ${signal}, shutting down...`);
|
|
2626
|
+
if (cleanupIntervalId) {
|
|
2627
|
+
clearInterval(cleanupIntervalId);
|
|
2628
|
+
cleanupIntervalId = null;
|
|
2629
|
+
}
|
|
2630
|
+
for (const [sessionId, sessionData] of sessions.entries()) {
|
|
2631
|
+
console.error(`[collab-mcp-server] Closing session: ${sessionId}`);
|
|
2632
|
+
sessionData.transport.close();
|
|
2633
|
+
}
|
|
2634
|
+
sessions.clear();
|
|
2635
|
+
process.exit(0);
|
|
2636
|
+
};
|
|
2637
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
2638
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
2639
|
+
process.on("uncaughtException", (error) => {
|
|
2640
|
+
console.error("[collab-mcp-server] Uncaught exception:", error.message);
|
|
2641
|
+
process.exit(1);
|
|
2642
|
+
});
|
|
2643
|
+
process.on("unhandledRejection", (reason) => {
|
|
2644
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
2645
|
+
console.error("[collab-mcp-server] Unhandled rejection:", message);
|
|
2646
|
+
process.exit(1);
|
|
2647
|
+
});
|
|
2648
|
+
}
|
|
2649
|
+
handleCliFlags();
|
|
2650
|
+
setupShutdownHandlers();
|
|
2651
|
+
console.error("[collab-mcp-server] Starting Collab MCP HTTP Server...");
|
|
2652
|
+
createHTTPServer().catch((error) => {
|
|
2653
|
+
console.error("[collab-mcp-server] Failed to start server:", error.message);
|
|
2654
|
+
process.exit(1);
|
|
2655
|
+
});
|
|
2656
|
+
//# sourceMappingURL=server.js.map
|