@mtaap/mcp 0.4.1 → 0.5.1
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 +3 -3
- package/dist/apps/activity.html +159 -0
- package/dist/apps/agent-sessions.html +159 -0
- package/dist/apps/kanban.html +159 -0
- package/dist/apps/project-overview.html +159 -0
- package/dist/apps/task-details.html +159 -0
- package/dist/cli.js +681 -108
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +24 -25
- package/dist/index.js +681 -108
- package/dist/index.js.map +1 -1
- package/dist/server.js +735 -124
- package/dist/server.js.map +1 -1
- package/package.json +21 -8
package/dist/server.js
CHANGED
|
@@ -36,11 +36,13 @@ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
|
36
36
|
// package.json
|
|
37
37
|
var package_default = {
|
|
38
38
|
name: "@mtaap/mcp",
|
|
39
|
-
version: "0.
|
|
39
|
+
version: "0.5.1",
|
|
40
40
|
description: "Model Context Protocol (MCP) server for AI agents to interact with Collab - the multi-tenant collaborative agent development platform",
|
|
41
41
|
mcpName: "collab",
|
|
42
42
|
scripts: {
|
|
43
|
-
build: "tsup"
|
|
43
|
+
build: "pnpm build:ui && tsup",
|
|
44
|
+
"build:ui": "node scripts/build-ui.mjs",
|
|
45
|
+
"dev:ui": "vite build --watch"
|
|
44
46
|
},
|
|
45
47
|
main: "./dist/index.js",
|
|
46
48
|
types: "./dist/index.d.ts",
|
|
@@ -78,6 +80,7 @@ var package_default = {
|
|
|
78
80
|
},
|
|
79
81
|
dependencies: {
|
|
80
82
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
83
|
+
"@modelcontextprotocol/ext-apps": "^1.0.1",
|
|
81
84
|
express: "^5.0.1",
|
|
82
85
|
zod: "^4.3.5"
|
|
83
86
|
},
|
|
@@ -86,8 +89,18 @@ var package_default = {
|
|
|
86
89
|
"@mtaap/core": "workspace:*",
|
|
87
90
|
"@types/express": "^5.0.0",
|
|
88
91
|
"@types/node": "^22.0.0",
|
|
92
|
+
"@types/react": "^19.0.0",
|
|
93
|
+
"@types/react-dom": "^19.0.0",
|
|
94
|
+
"@vitejs/plugin-react": "^4.4.0",
|
|
95
|
+
autoprefixer: "^10.4.21",
|
|
96
|
+
postcss: "^8.5.3",
|
|
97
|
+
react: "^19.0.0",
|
|
98
|
+
"react-dom": "^19.0.0",
|
|
99
|
+
tailwindcss: "^3.4.17",
|
|
89
100
|
tsup: "^8.5.1",
|
|
90
|
-
typescript: "^5.4.0"
|
|
101
|
+
typescript: "^5.4.0",
|
|
102
|
+
vite: "^6.3.2",
|
|
103
|
+
"vite-plugin-singlefile": "^2.0.3"
|
|
91
104
|
}
|
|
92
105
|
};
|
|
93
106
|
|
|
@@ -95,7 +108,7 @@ var package_default = {
|
|
|
95
108
|
var VERSION = package_default.version;
|
|
96
109
|
|
|
97
110
|
// src/index.ts
|
|
98
|
-
var
|
|
111
|
+
var import_zod9 = require("zod");
|
|
99
112
|
|
|
100
113
|
// ../../packages/core/dist/constants/enums.js
|
|
101
114
|
var TaskState;
|
|
@@ -181,6 +194,10 @@ var WebSocketEventType;
|
|
|
181
194
|
WebSocketEventType2["TASK_UPDATED"] = "task.updated";
|
|
182
195
|
WebSocketEventType2["TASK_DELETED"] = "task.deleted";
|
|
183
196
|
WebSocketEventType2["MEMBER_JOINED"] = "member.joined";
|
|
197
|
+
WebSocketEventType2["AGENT_STARTED"] = "agent.started";
|
|
198
|
+
WebSocketEventType2["AGENT_OUTPUT"] = "agent.output";
|
|
199
|
+
WebSocketEventType2["AGENT_STOPPED"] = "agent.stopped";
|
|
200
|
+
WebSocketEventType2["AGENT_ERROR"] = "agent.error";
|
|
184
201
|
})(WebSocketEventType || (WebSocketEventType = {}));
|
|
185
202
|
var SubscriptionStatus;
|
|
186
203
|
(function(SubscriptionStatus2) {
|
|
@@ -203,6 +220,31 @@ var CreatedVia;
|
|
|
203
220
|
CreatedVia2["API_KEY"] = "API_KEY";
|
|
204
221
|
CreatedVia2["OAUTH"] = "OAUTH";
|
|
205
222
|
})(CreatedVia || (CreatedVia = {}));
|
|
223
|
+
var AgentModel;
|
|
224
|
+
(function(AgentModel2) {
|
|
225
|
+
AgentModel2["OPUS"] = "OPUS";
|
|
226
|
+
AgentModel2["SONNET"] = "SONNET";
|
|
227
|
+
AgentModel2["HAIKU"] = "HAIKU";
|
|
228
|
+
})(AgentModel || (AgentModel = {}));
|
|
229
|
+
var AgentModelMode;
|
|
230
|
+
(function(AgentModelMode2) {
|
|
231
|
+
AgentModelMode2["DEFAULT"] = "DEFAULT";
|
|
232
|
+
AgentModelMode2["PRESET"] = "PRESET";
|
|
233
|
+
AgentModelMode2["CUSTOM"] = "CUSTOM";
|
|
234
|
+
})(AgentModelMode || (AgentModelMode = {}));
|
|
235
|
+
var AgentCLIType;
|
|
236
|
+
(function(AgentCLIType2) {
|
|
237
|
+
AgentCLIType2["CLAUDE_CODE"] = "CLAUDE_CODE";
|
|
238
|
+
AgentCLIType2["OPENCODE"] = "OPENCODE";
|
|
239
|
+
})(AgentCLIType || (AgentCLIType = {}));
|
|
240
|
+
var AgentSessionStatus;
|
|
241
|
+
(function(AgentSessionStatus2) {
|
|
242
|
+
AgentSessionStatus2["STARTING"] = "STARTING";
|
|
243
|
+
AgentSessionStatus2["RUNNING"] = "RUNNING";
|
|
244
|
+
AgentSessionStatus2["STOPPING"] = "STOPPING";
|
|
245
|
+
AgentSessionStatus2["STOPPED"] = "STOPPED";
|
|
246
|
+
AgentSessionStatus2["ERROR"] = "ERROR";
|
|
247
|
+
})(AgentSessionStatus || (AgentSessionStatus = {}));
|
|
206
248
|
|
|
207
249
|
// ../../packages/core/dist/constants/state-machine.js
|
|
208
250
|
var VALID_TRANSITIONS = {
|
|
@@ -291,9 +333,9 @@ var isOnPrem = config.deploymentMode === "onprem";
|
|
|
291
333
|
|
|
292
334
|
// ../../packages/core/dist/versions.js
|
|
293
335
|
var VERSIONS = {
|
|
294
|
-
core: "0.
|
|
295
|
-
web: "0.
|
|
296
|
-
mcp: "0.
|
|
336
|
+
core: "0.4.0",
|
|
337
|
+
web: "0.4.0",
|
|
338
|
+
mcp: "0.5.0"
|
|
297
339
|
};
|
|
298
340
|
var VERSION2 = VERSIONS.core;
|
|
299
341
|
|
|
@@ -374,6 +416,18 @@ var config2 = {
|
|
|
374
416
|
requestsPerHour: 1e3
|
|
375
417
|
}
|
|
376
418
|
},
|
|
419
|
+
agent: {
|
|
420
|
+
defaultModel: "SONNET",
|
|
421
|
+
sessionTimeoutMs: 30 * 60 * 1e3,
|
|
422
|
+
// 30 minutes
|
|
423
|
+
maxSessionDurationMs: 4 * 60 * 60 * 1e3,
|
|
424
|
+
// 4 hours
|
|
425
|
+
maxConcurrentSessions: {
|
|
426
|
+
FREE: 1,
|
|
427
|
+
PRO: 5,
|
|
428
|
+
ENTERPRISE: -1
|
|
429
|
+
}
|
|
430
|
+
},
|
|
377
431
|
limits: {
|
|
378
432
|
projectNameMaxLength: 100,
|
|
379
433
|
taskDescriptionMaxLength: 5e3,
|
|
@@ -781,7 +835,8 @@ var UpdateProjectInputSchema = import_zod3.z.object({
|
|
|
781
835
|
repositoryUrl: import_zod3.z.string().url().optional(),
|
|
782
836
|
baseBranch: import_zod3.z.string().optional(),
|
|
783
837
|
tags: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
784
|
-
allowMemberArchive: import_zod3.z.boolean().optional()
|
|
838
|
+
allowMemberArchive: import_zod3.z.boolean().optional(),
|
|
839
|
+
localRepoPath: import_zod3.z.string().max(500).optional()
|
|
785
840
|
});
|
|
786
841
|
var CreateEpicInputSchema = import_zod3.z.object({
|
|
787
842
|
projectId: cuidOrPrefixedId,
|
|
@@ -998,6 +1053,10 @@ var dbSlowQueriesTotal = createCounter("mtaap_db_slow_queries_total", "Total num
|
|
|
998
1053
|
var tasksCreatedTotal = createCounter("mtaap_tasks_created_total", "Total number of tasks created");
|
|
999
1054
|
var tasksAssignedTotal = createCounter("mtaap_tasks_assigned_total", "Total number of tasks assigned");
|
|
1000
1055
|
var tasksCompletedTotal = createCounter("mtaap_tasks_completed_total", "Total number of tasks completed");
|
|
1056
|
+
var agentSessionsTotal = createCounter("mtaap_agent_sessions_total", "Total number of agent sessions started");
|
|
1057
|
+
var agentErrorsTotal = createCounter("mtaap_agent_errors_total", "Total number of agent errors");
|
|
1058
|
+
var agentSessionDuration = createHistogram("mtaap_agent_session_duration_seconds", "Agent session duration in seconds", [1, 5, 15, 30, 60, 120, 300, 600, 1800, 3600]);
|
|
1059
|
+
var agentSessionsActive = createGauge("mtaap_agent_sessions_active", "Number of currently active agent sessions");
|
|
1001
1060
|
|
|
1002
1061
|
// ../../packages/core/dist/logging/performance-monitor.js
|
|
1003
1062
|
var MAX_SAMPLES = 1e3;
|
|
@@ -1201,6 +1260,9 @@ var errorTrackerInstance = new NoOpErrorTracker();
|
|
|
1201
1260
|
|
|
1202
1261
|
// src/api-client.ts
|
|
1203
1262
|
var DEFAULT_TIMEOUT = 3e4;
|
|
1263
|
+
var DEFAULT_CACHE_TTL = 3e4;
|
|
1264
|
+
var MAX_RETRIES = 2;
|
|
1265
|
+
var INITIAL_RETRY_DELAY = 500;
|
|
1204
1266
|
function sanitizeForLogging(str) {
|
|
1205
1267
|
let sanitized = str.replace(/\bcollab_[a-zA-Z0-9_-]+\b/gi, "[REDACTED_API_KEY]");
|
|
1206
1268
|
sanitized = sanitized.replace(/\bBearer\s+[a-zA-Z0-9._-]+\b/gi, "Bearer [REDACTED]");
|
|
@@ -1215,7 +1277,17 @@ var ApiError = class extends Error {
|
|
|
1215
1277
|
this.details = details;
|
|
1216
1278
|
this.name = "ApiError";
|
|
1217
1279
|
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Whether this error is transient and the request can be retried.
|
|
1282
|
+
* Retries on network errors (status 0), timeouts (408), and server errors (5xx).
|
|
1283
|
+
*/
|
|
1284
|
+
get isRetryable() {
|
|
1285
|
+
return this.status === 0 || this.status === 408 || this.status >= 500;
|
|
1286
|
+
}
|
|
1218
1287
|
};
|
|
1288
|
+
function sleep(ms) {
|
|
1289
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1290
|
+
}
|
|
1219
1291
|
var MCPApiClient = class {
|
|
1220
1292
|
baseUrl;
|
|
1221
1293
|
apiKey;
|
|
@@ -1223,6 +1295,7 @@ var MCPApiClient = class {
|
|
|
1223
1295
|
timeout;
|
|
1224
1296
|
debug;
|
|
1225
1297
|
authContext = null;
|
|
1298
|
+
cache = /* @__PURE__ */ new Map();
|
|
1226
1299
|
constructor(config3) {
|
|
1227
1300
|
if (!config3.apiKey && !config3.oauthToken) {
|
|
1228
1301
|
throw new Error("Either apiKey or oauthToken must be provided");
|
|
@@ -1234,14 +1307,54 @@ var MCPApiClient = class {
|
|
|
1234
1307
|
this.debug = config3.debug ?? false;
|
|
1235
1308
|
}
|
|
1236
1309
|
/**
|
|
1237
|
-
*
|
|
1310
|
+
* Get a cached value if it exists and hasn't expired.
|
|
1311
|
+
*/
|
|
1312
|
+
getCached(key) {
|
|
1313
|
+
const entry = this.cache.get(key);
|
|
1314
|
+
if (!entry) return void 0;
|
|
1315
|
+
if (Date.now() > entry.expiresAt) {
|
|
1316
|
+
this.cache.delete(key);
|
|
1317
|
+
return void 0;
|
|
1318
|
+
}
|
|
1319
|
+
return entry.data;
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Store a value in cache with a TTL.
|
|
1238
1323
|
*/
|
|
1239
|
-
|
|
1240
|
-
|
|
1324
|
+
setCache(key, data, ttl = DEFAULT_CACHE_TTL) {
|
|
1325
|
+
this.cache.set(key, { data, expiresAt: Date.now() + ttl });
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Make an HTTP request to the API with automatic retry on transient failures.
|
|
1329
|
+
*/
|
|
1330
|
+
async request(method, path2, body) {
|
|
1331
|
+
let lastError;
|
|
1332
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
1333
|
+
try {
|
|
1334
|
+
return await this.requestOnce(method, path2, body);
|
|
1335
|
+
} catch (error) {
|
|
1336
|
+
if (!(error instanceof ApiError) || !error.isRetryable || attempt === MAX_RETRIES) {
|
|
1337
|
+
throw error;
|
|
1338
|
+
}
|
|
1339
|
+
lastError = error;
|
|
1340
|
+
const delay = INITIAL_RETRY_DELAY * Math.pow(2, attempt);
|
|
1341
|
+
if (this.debug) {
|
|
1342
|
+
console.error(`[mcp-api] Retry ${attempt + 1}/${MAX_RETRIES} after ${delay}ms (${lastError.code})`);
|
|
1343
|
+
}
|
|
1344
|
+
await sleep(delay);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
throw lastError;
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Execute a single HTTP request to the API.
|
|
1351
|
+
*/
|
|
1352
|
+
async requestOnce(method, path2, body) {
|
|
1353
|
+
const url = `${this.baseUrl}${path2}`;
|
|
1241
1354
|
const controller = new AbortController();
|
|
1242
1355
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
1243
1356
|
if (this.debug) {
|
|
1244
|
-
console.error(`[mcp-api] ${method} ${sanitizeForLogging(
|
|
1357
|
+
console.error(`[mcp-api] ${method} ${sanitizeForLogging(path2)}`);
|
|
1245
1358
|
}
|
|
1246
1359
|
try {
|
|
1247
1360
|
const headers = {
|
|
@@ -1317,14 +1430,19 @@ ${data.hint ? `Hint: ${data.hint}` : ""}`,
|
|
|
1317
1430
|
return this.authenticate();
|
|
1318
1431
|
}
|
|
1319
1432
|
/**
|
|
1320
|
-
* List accessible projects
|
|
1433
|
+
* List accessible projects (cached for 30s)
|
|
1321
1434
|
*/
|
|
1322
1435
|
async listProjects(workspaceType) {
|
|
1323
1436
|
const type = workspaceType || "ALL";
|
|
1324
|
-
|
|
1437
|
+
const cacheKey = `listProjects:${type}`;
|
|
1438
|
+
const cached = this.getCached(cacheKey);
|
|
1439
|
+
if (cached) return cached;
|
|
1440
|
+
const result = await this.request(
|
|
1325
1441
|
"GET",
|
|
1326
1442
|
`/api/mcp/projects?workspaceType=${encodeURIComponent(type)}`
|
|
1327
1443
|
);
|
|
1444
|
+
this.setCache(cacheKey, result);
|
|
1445
|
+
return result;
|
|
1328
1446
|
}
|
|
1329
1447
|
/**
|
|
1330
1448
|
* Get single project details
|
|
@@ -1333,13 +1451,18 @@ ${data.hint ? `Hint: ${data.hint}` : ""}`,
|
|
|
1333
1451
|
return this.request("GET", `/api/mcp/projects/${projectId}`);
|
|
1334
1452
|
}
|
|
1335
1453
|
/**
|
|
1336
|
-
* Get project context (README, stack, conventions)
|
|
1454
|
+
* Get project context (README, stack, conventions) (cached for 60s)
|
|
1337
1455
|
*/
|
|
1338
1456
|
async getProjectContext(projectId) {
|
|
1339
|
-
|
|
1457
|
+
const cacheKey = `projectContext:${projectId}`;
|
|
1458
|
+
const cached = this.getCached(cacheKey);
|
|
1459
|
+
if (cached) return cached;
|
|
1460
|
+
const result = await this.request(
|
|
1340
1461
|
"GET",
|
|
1341
1462
|
`/api/mcp/projects/${projectId}/context`
|
|
1342
1463
|
);
|
|
1464
|
+
this.setCache(cacheKey, result, 6e4);
|
|
1465
|
+
return result;
|
|
1343
1466
|
}
|
|
1344
1467
|
/**
|
|
1345
1468
|
* Create a personal project
|
|
@@ -1367,8 +1490,8 @@ ${data.hint ? `Hint: ${data.hint}` : ""}`,
|
|
|
1367
1490
|
if (filters.assigneeId) params.set("assigneeId", filters.assigneeId);
|
|
1368
1491
|
if (filters.includeArchived) params.set("includeArchived", "true");
|
|
1369
1492
|
const queryString = params.toString();
|
|
1370
|
-
const
|
|
1371
|
-
return this.request("GET",
|
|
1493
|
+
const path2 = queryString ? `/api/mcp/tasks?${queryString}` : "/api/mcp/tasks";
|
|
1494
|
+
return this.request("GET", path2);
|
|
1372
1495
|
}
|
|
1373
1496
|
/**
|
|
1374
1497
|
* Get full task details
|
|
@@ -1501,7 +1624,8 @@ ${data.hint ? `Hint: ${data.hint}` : ""}`,
|
|
|
1501
1624
|
);
|
|
1502
1625
|
}
|
|
1503
1626
|
/**
|
|
1504
|
-
* Update task details (DRAFT/TODO states only)
|
|
1627
|
+
* Update task details (DRAFT/TODO states only).
|
|
1628
|
+
* Task stays in its current state.
|
|
1505
1629
|
*/
|
|
1506
1630
|
async updateTask(taskId, projectId, data) {
|
|
1507
1631
|
return this.request("PATCH", `/api/mcp/tasks/${taskId}`, {
|
|
@@ -1536,6 +1660,454 @@ function assertApiKeyPermission(apiKey, required, toolName) {
|
|
|
1536
1660
|
throw error;
|
|
1537
1661
|
}
|
|
1538
1662
|
|
|
1663
|
+
// src/apps/task-details.ts
|
|
1664
|
+
var import_server2 = require("@modelcontextprotocol/ext-apps/server");
|
|
1665
|
+
var import_zod4 = require("zod");
|
|
1666
|
+
|
|
1667
|
+
// src/apps/helpers.ts
|
|
1668
|
+
var import_server = require("@modelcontextprotocol/ext-apps/server");
|
|
1669
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
1670
|
+
var import_node_path = __toESM(require("path"));
|
|
1671
|
+
var import_node_url = require("url");
|
|
1672
|
+
var import_meta = {};
|
|
1673
|
+
var getDirname = () => {
|
|
1674
|
+
try {
|
|
1675
|
+
return import_node_path.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
1676
|
+
} catch {
|
|
1677
|
+
return __dirname;
|
|
1678
|
+
}
|
|
1679
|
+
};
|
|
1680
|
+
function getAppHtmlPath(htmlFileName) {
|
|
1681
|
+
return import_node_path.default.join(getDirname(), "apps", htmlFileName);
|
|
1682
|
+
}
|
|
1683
|
+
function registerAppHtmlResource(server, config3) {
|
|
1684
|
+
(0, import_server.registerAppResource)(
|
|
1685
|
+
server,
|
|
1686
|
+
config3.name,
|
|
1687
|
+
config3.uri,
|
|
1688
|
+
{
|
|
1689
|
+
mimeType: import_server.RESOURCE_MIME_TYPE,
|
|
1690
|
+
description: config3.description
|
|
1691
|
+
},
|
|
1692
|
+
async () => {
|
|
1693
|
+
const htmlPath = getAppHtmlPath(config3.htmlFileName);
|
|
1694
|
+
try {
|
|
1695
|
+
const html = await import_promises.default.readFile(htmlPath, "utf-8");
|
|
1696
|
+
return {
|
|
1697
|
+
contents: [
|
|
1698
|
+
{
|
|
1699
|
+
uri: config3.uri,
|
|
1700
|
+
mimeType: import_server.RESOURCE_MIME_TYPE,
|
|
1701
|
+
text: html
|
|
1702
|
+
}
|
|
1703
|
+
]
|
|
1704
|
+
};
|
|
1705
|
+
} catch {
|
|
1706
|
+
return {
|
|
1707
|
+
contents: [
|
|
1708
|
+
{
|
|
1709
|
+
uri: config3.uri,
|
|
1710
|
+
mimeType: import_server.RESOURCE_MIME_TYPE,
|
|
1711
|
+
text: `<!DOCTYPE html><html><body><p>${config3.fallbackLabel} UI not available. Build the UI apps first.</p></body></html>`
|
|
1712
|
+
}
|
|
1713
|
+
]
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
// src/apps/task-details.ts
|
|
1721
|
+
var RESOURCE_URI = "ui://collab/task-details.html";
|
|
1722
|
+
function registerTaskDetailsApp(server, apiClient, _authContext) {
|
|
1723
|
+
(0, import_server2.registerAppTool)(
|
|
1724
|
+
server,
|
|
1725
|
+
"view_task_details",
|
|
1726
|
+
{
|
|
1727
|
+
title: "Task Details",
|
|
1728
|
+
description: "View interactive task details with acceptance criteria, notes, and progress. Opens a rich UI panel.",
|
|
1729
|
+
inputSchema: {
|
|
1730
|
+
taskId: import_zod4.z.string().describe("The task ID to view"),
|
|
1731
|
+
projectId: import_zod4.z.string().describe("The project ID")
|
|
1732
|
+
},
|
|
1733
|
+
_meta: { ui: { resourceUri: RESOURCE_URI } }
|
|
1734
|
+
},
|
|
1735
|
+
async ({ taskId, projectId }) => {
|
|
1736
|
+
try {
|
|
1737
|
+
const task = await apiClient.getTask(taskId);
|
|
1738
|
+
let project = null;
|
|
1739
|
+
try {
|
|
1740
|
+
project = await apiClient.getProject(projectId);
|
|
1741
|
+
} catch {
|
|
1742
|
+
}
|
|
1743
|
+
return {
|
|
1744
|
+
content: [
|
|
1745
|
+
{
|
|
1746
|
+
type: "text",
|
|
1747
|
+
text: `Viewing task: ${task.title}`
|
|
1748
|
+
}
|
|
1749
|
+
],
|
|
1750
|
+
structuredContent: {
|
|
1751
|
+
task,
|
|
1752
|
+
project
|
|
1753
|
+
}
|
|
1754
|
+
};
|
|
1755
|
+
} catch (error) {
|
|
1756
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1757
|
+
return {
|
|
1758
|
+
content: [{ type: "text", text: `Error loading task: ${message}` }],
|
|
1759
|
+
isError: true
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
);
|
|
1764
|
+
registerAppHtmlResource(server, {
|
|
1765
|
+
name: "Task Details UI",
|
|
1766
|
+
uri: RESOURCE_URI,
|
|
1767
|
+
description: "Interactive task details view with acceptance criteria and notes",
|
|
1768
|
+
htmlFileName: "task-details.html",
|
|
1769
|
+
fallbackLabel: "Task Details"
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
// src/apps/kanban.ts
|
|
1774
|
+
var import_server3 = require("@modelcontextprotocol/ext-apps/server");
|
|
1775
|
+
var import_zod5 = require("zod");
|
|
1776
|
+
var RESOURCE_URI2 = "ui://collab/kanban.html";
|
|
1777
|
+
function registerKanbanApp(server, apiClient, _authContext) {
|
|
1778
|
+
(0, import_server3.registerAppTool)(
|
|
1779
|
+
server,
|
|
1780
|
+
"view_kanban_board",
|
|
1781
|
+
{
|
|
1782
|
+
title: "Kanban Board",
|
|
1783
|
+
description: "View interactive drag-and-drop Kanban board for a project. Drag tasks between columns to change their state.",
|
|
1784
|
+
inputSchema: {
|
|
1785
|
+
projectId: import_zod5.z.string().describe("The project ID to display")
|
|
1786
|
+
},
|
|
1787
|
+
_meta: { ui: { resourceUri: RESOURCE_URI2 } }
|
|
1788
|
+
},
|
|
1789
|
+
async ({ projectId }) => {
|
|
1790
|
+
try {
|
|
1791
|
+
const [project, tasks] = await Promise.all([
|
|
1792
|
+
apiClient.getProject(projectId),
|
|
1793
|
+
apiClient.listTasks({ projectId })
|
|
1794
|
+
]);
|
|
1795
|
+
return {
|
|
1796
|
+
content: [
|
|
1797
|
+
{
|
|
1798
|
+
type: "text",
|
|
1799
|
+
text: `Kanban board for ${project.name} (${tasks.length} tasks)`
|
|
1800
|
+
}
|
|
1801
|
+
],
|
|
1802
|
+
structuredContent: {
|
|
1803
|
+
project,
|
|
1804
|
+
tasks
|
|
1805
|
+
}
|
|
1806
|
+
};
|
|
1807
|
+
} catch (error) {
|
|
1808
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1809
|
+
return {
|
|
1810
|
+
content: [{ type: "text", text: `Error loading kanban: ${message}` }],
|
|
1811
|
+
isError: true
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
);
|
|
1816
|
+
registerAppHtmlResource(server, {
|
|
1817
|
+
name: "Kanban Board UI",
|
|
1818
|
+
uri: RESOURCE_URI2,
|
|
1819
|
+
description: "Interactive drag-and-drop Kanban board for task management",
|
|
1820
|
+
htmlFileName: "kanban.html",
|
|
1821
|
+
fallbackLabel: "Kanban"
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
// src/apps/activity.ts
|
|
1826
|
+
var import_server4 = require("@modelcontextprotocol/ext-apps/server");
|
|
1827
|
+
var import_zod6 = require("zod");
|
|
1828
|
+
var RESOURCE_URI3 = "ui://collab/activity.html";
|
|
1829
|
+
function registerActivityApp(server, apiClient, _authContext) {
|
|
1830
|
+
(0, import_server4.registerAppTool)(
|
|
1831
|
+
server,
|
|
1832
|
+
"view_activity",
|
|
1833
|
+
{
|
|
1834
|
+
title: "User Activity",
|
|
1835
|
+
description: "View who is working on what across the team. Shows IN_PROGRESS and REVIEW tasks per user.",
|
|
1836
|
+
inputSchema: {
|
|
1837
|
+
projectId: import_zod6.z.string().optional().describe("Optional project ID to filter by")
|
|
1838
|
+
},
|
|
1839
|
+
_meta: { ui: { resourceUri: RESOURCE_URI3 } }
|
|
1840
|
+
},
|
|
1841
|
+
async ({ projectId }) => {
|
|
1842
|
+
try {
|
|
1843
|
+
const [inProgressTasks, reviewTasks] = await Promise.all([
|
|
1844
|
+
apiClient.listTasks({
|
|
1845
|
+
projectId,
|
|
1846
|
+
state: TaskState.IN_PROGRESS
|
|
1847
|
+
}),
|
|
1848
|
+
apiClient.listTasks({
|
|
1849
|
+
projectId,
|
|
1850
|
+
state: TaskState.REVIEW
|
|
1851
|
+
})
|
|
1852
|
+
]);
|
|
1853
|
+
const userMap = /* @__PURE__ */ new Map();
|
|
1854
|
+
for (const task of inProgressTasks) {
|
|
1855
|
+
if (task.assigneeId) {
|
|
1856
|
+
if (!userMap.has(task.assigneeId)) {
|
|
1857
|
+
userMap.set(task.assigneeId, {
|
|
1858
|
+
id: task.assigneeId,
|
|
1859
|
+
name: task.assigneeName || "Unknown",
|
|
1860
|
+
email: task.assigneeEmail || "",
|
|
1861
|
+
activeTasks: [],
|
|
1862
|
+
reviewTasks: []
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
userMap.get(task.assigneeId).activeTasks.push(task);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
for (const task of reviewTasks) {
|
|
1869
|
+
if (task.assigneeId) {
|
|
1870
|
+
if (!userMap.has(task.assigneeId)) {
|
|
1871
|
+
userMap.set(task.assigneeId, {
|
|
1872
|
+
id: task.assigneeId,
|
|
1873
|
+
name: task.assigneeName || "Unknown",
|
|
1874
|
+
email: task.assigneeEmail || "",
|
|
1875
|
+
activeTasks: [],
|
|
1876
|
+
reviewTasks: []
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
userMap.get(task.assigneeId).reviewTasks.push(task);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
const users = Array.from(userMap.values());
|
|
1883
|
+
return {
|
|
1884
|
+
content: [
|
|
1885
|
+
{
|
|
1886
|
+
type: "text",
|
|
1887
|
+
text: `Activity: ${users.length} users with active work`
|
|
1888
|
+
}
|
|
1889
|
+
],
|
|
1890
|
+
structuredContent: {
|
|
1891
|
+
users,
|
|
1892
|
+
projectId
|
|
1893
|
+
}
|
|
1894
|
+
};
|
|
1895
|
+
} catch (error) {
|
|
1896
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1897
|
+
return {
|
|
1898
|
+
content: [
|
|
1899
|
+
{ type: "text", text: `Error loading activity: ${message}` }
|
|
1900
|
+
],
|
|
1901
|
+
isError: true
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
);
|
|
1906
|
+
registerAppHtmlResource(server, {
|
|
1907
|
+
name: "User Activity UI",
|
|
1908
|
+
uri: RESOURCE_URI3,
|
|
1909
|
+
description: "Dashboard showing team member activity and workload",
|
|
1910
|
+
htmlFileName: "activity.html",
|
|
1911
|
+
fallbackLabel: "Activity"
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
// src/apps/project-overview.ts
|
|
1916
|
+
var import_server5 = require("@modelcontextprotocol/ext-apps/server");
|
|
1917
|
+
var import_zod7 = require("zod");
|
|
1918
|
+
var RESOURCE_URI4 = "ui://collab/project-overview.html";
|
|
1919
|
+
function registerProjectOverviewApp(server, apiClient, _authContext) {
|
|
1920
|
+
(0, import_server5.registerAppTool)(
|
|
1921
|
+
server,
|
|
1922
|
+
"view_project_overview",
|
|
1923
|
+
{
|
|
1924
|
+
title: "Project Overview",
|
|
1925
|
+
description: "View project health dashboard with task metrics, state distribution, and conventions.",
|
|
1926
|
+
inputSchema: {
|
|
1927
|
+
projectId: import_zod7.z.string().describe("The project ID to display")
|
|
1928
|
+
},
|
|
1929
|
+
_meta: { ui: { resourceUri: RESOURCE_URI4 } }
|
|
1930
|
+
},
|
|
1931
|
+
async ({ projectId }) => {
|
|
1932
|
+
try {
|
|
1933
|
+
const [project, context, tasks] = await Promise.all([
|
|
1934
|
+
apiClient.getProject(projectId),
|
|
1935
|
+
apiClient.getProjectContext(projectId).catch(() => null),
|
|
1936
|
+
apiClient.listTasks({ projectId })
|
|
1937
|
+
]);
|
|
1938
|
+
const activeTasks = tasks.filter((t) => !t.isArchived);
|
|
1939
|
+
const inProgress = activeTasks.filter(
|
|
1940
|
+
(t) => t.state === TaskState.IN_PROGRESS
|
|
1941
|
+
).length;
|
|
1942
|
+
const done = activeTasks.filter(
|
|
1943
|
+
(t) => t.state === TaskState.DONE
|
|
1944
|
+
).length;
|
|
1945
|
+
const blocked = activeTasks.filter((t) => t.errorType).length;
|
|
1946
|
+
const tasksByState = {
|
|
1947
|
+
DRAFT: 0,
|
|
1948
|
+
TODO: 0,
|
|
1949
|
+
IN_PROGRESS: 0,
|
|
1950
|
+
REVIEW: 0,
|
|
1951
|
+
DONE: 0
|
|
1952
|
+
};
|
|
1953
|
+
for (const task of activeTasks) {
|
|
1954
|
+
tasksByState[task.state]++;
|
|
1955
|
+
}
|
|
1956
|
+
const tasksByPriority = {
|
|
1957
|
+
LOW: 0,
|
|
1958
|
+
MEDIUM: 0,
|
|
1959
|
+
HIGH: 0,
|
|
1960
|
+
CRITICAL: 0
|
|
1961
|
+
};
|
|
1962
|
+
for (const task of activeTasks) {
|
|
1963
|
+
tasksByPriority[task.priority]++;
|
|
1964
|
+
}
|
|
1965
|
+
const recentCompleted = activeTasks.filter((t) => t.state === TaskState.DONE).sort(
|
|
1966
|
+
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
|
1967
|
+
).slice(0, 5);
|
|
1968
|
+
return {
|
|
1969
|
+
content: [
|
|
1970
|
+
{
|
|
1971
|
+
type: "text",
|
|
1972
|
+
text: `Project overview for ${project.name}: ${activeTasks.length} tasks`
|
|
1973
|
+
}
|
|
1974
|
+
],
|
|
1975
|
+
structuredContent: {
|
|
1976
|
+
project,
|
|
1977
|
+
context,
|
|
1978
|
+
metrics: {
|
|
1979
|
+
totalTasks: activeTasks.length,
|
|
1980
|
+
inProgress,
|
|
1981
|
+
blocked,
|
|
1982
|
+
done
|
|
1983
|
+
},
|
|
1984
|
+
tasksByState,
|
|
1985
|
+
tasksByPriority,
|
|
1986
|
+
recentCompleted
|
|
1987
|
+
}
|
|
1988
|
+
};
|
|
1989
|
+
} catch (error) {
|
|
1990
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1991
|
+
return {
|
|
1992
|
+
content: [
|
|
1993
|
+
{ type: "text", text: `Error loading project overview: ${message}` }
|
|
1994
|
+
],
|
|
1995
|
+
isError: true
|
|
1996
|
+
};
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
);
|
|
2000
|
+
registerAppHtmlResource(server, {
|
|
2001
|
+
name: "Project Overview UI",
|
|
2002
|
+
uri: RESOURCE_URI4,
|
|
2003
|
+
description: "Project health dashboard with metrics and visualizations",
|
|
2004
|
+
htmlFileName: "project-overview.html",
|
|
2005
|
+
fallbackLabel: "Project Overview"
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
// src/apps/agent-sessions.ts
|
|
2010
|
+
var import_server6 = require("@modelcontextprotocol/ext-apps/server");
|
|
2011
|
+
var import_zod8 = require("zod");
|
|
2012
|
+
var RESOURCE_URI5 = "ui://collab/agent-sessions.html";
|
|
2013
|
+
function registerAgentSessionsApp(server, apiClient, authContext) {
|
|
2014
|
+
(0, import_server6.registerAppTool)(
|
|
2015
|
+
server,
|
|
2016
|
+
"view_agent_sessions",
|
|
2017
|
+
{
|
|
2018
|
+
title: "Agent Sessions",
|
|
2019
|
+
description: "View active AI agent sessions working on tasks. Monitor real-time progress and session status.",
|
|
2020
|
+
inputSchema: {
|
|
2021
|
+
projectId: import_zod8.z.string().optional().describe("Optional project ID to filter by")
|
|
2022
|
+
},
|
|
2023
|
+
_meta: { ui: { resourceUri: RESOURCE_URI5 } }
|
|
2024
|
+
},
|
|
2025
|
+
async ({ projectId }) => {
|
|
2026
|
+
try {
|
|
2027
|
+
const [inProgressTasks, reviewTasks] = await Promise.all([
|
|
2028
|
+
apiClient.listTasks({
|
|
2029
|
+
projectId,
|
|
2030
|
+
state: TaskState.IN_PROGRESS
|
|
2031
|
+
}),
|
|
2032
|
+
apiClient.listTasks({
|
|
2033
|
+
projectId,
|
|
2034
|
+
state: TaskState.REVIEW
|
|
2035
|
+
})
|
|
2036
|
+
]);
|
|
2037
|
+
const sessions2 = [];
|
|
2038
|
+
for (const task of inProgressTasks) {
|
|
2039
|
+
if (task.assigneeId) {
|
|
2040
|
+
const totalCriteria = task.acceptanceCriteria?.length || 0;
|
|
2041
|
+
const completedCriteria = task.acceptanceCriteria?.filter((c) => c.completed).length || 0;
|
|
2042
|
+
const progress = totalCriteria > 0 ? Math.round(completedCriteria / totalCriteria * 100) : 0;
|
|
2043
|
+
const latestUpdate = task.progressUpdates?.[task.progressUpdates.length - 1];
|
|
2044
|
+
sessions2.push({
|
|
2045
|
+
id: `session-${task.id}`,
|
|
2046
|
+
taskId: task.id,
|
|
2047
|
+
taskTitle: task.title,
|
|
2048
|
+
agentName: task.assigneeName || "Agent",
|
|
2049
|
+
status: "RUNNING",
|
|
2050
|
+
startedAt: task.updatedAt,
|
|
2051
|
+
lastCheckpoint: latestUpdate?.timestamp,
|
|
2052
|
+
currentStep: latestUpdate?.statusMessage,
|
|
2053
|
+
progress
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
for (const task of reviewTasks) {
|
|
2058
|
+
if (task.assigneeId) {
|
|
2059
|
+
sessions2.push({
|
|
2060
|
+
id: `session-${task.id}`,
|
|
2061
|
+
taskId: task.id,
|
|
2062
|
+
taskTitle: task.title,
|
|
2063
|
+
agentName: task.assigneeName || "Agent",
|
|
2064
|
+
status: "STOPPED",
|
|
2065
|
+
startedAt: task.updatedAt,
|
|
2066
|
+
progress: 100
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
return {
|
|
2071
|
+
content: [
|
|
2072
|
+
{
|
|
2073
|
+
type: "text",
|
|
2074
|
+
text: `Agent sessions: ${sessions2.length} active`
|
|
2075
|
+
}
|
|
2076
|
+
],
|
|
2077
|
+
structuredContent: {
|
|
2078
|
+
sessions: sessions2,
|
|
2079
|
+
projectId
|
|
2080
|
+
}
|
|
2081
|
+
};
|
|
2082
|
+
} catch (error) {
|
|
2083
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2084
|
+
return {
|
|
2085
|
+
content: [
|
|
2086
|
+
{ type: "text", text: `Error loading agent sessions: ${message}` }
|
|
2087
|
+
],
|
|
2088
|
+
isError: true
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
);
|
|
2093
|
+
registerAppHtmlResource(server, {
|
|
2094
|
+
name: "Agent Sessions UI",
|
|
2095
|
+
uri: RESOURCE_URI5,
|
|
2096
|
+
description: "Real-time view of AI agent sessions and their progress",
|
|
2097
|
+
htmlFileName: "agent-sessions.html",
|
|
2098
|
+
fallbackLabel: "Agent Sessions"
|
|
2099
|
+
});
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
// src/apps/index.ts
|
|
2103
|
+
function registerMCPApps(server, apiClient, authContext) {
|
|
2104
|
+
registerTaskDetailsApp(server, apiClient, authContext);
|
|
2105
|
+
registerKanbanApp(server, apiClient, authContext);
|
|
2106
|
+
registerActivityApp(server, apiClient, authContext);
|
|
2107
|
+
registerProjectOverviewApp(server, apiClient, authContext);
|
|
2108
|
+
registerAgentSessionsApp(server, apiClient, authContext);
|
|
2109
|
+
}
|
|
2110
|
+
|
|
1539
2111
|
// src/index.ts
|
|
1540
2112
|
var COLLAB_SERVER_INSTRUCTIONS = `Collab - AI-assisted software development task management platform.
|
|
1541
2113
|
|
|
@@ -1587,7 +2159,7 @@ Review Workflow:
|
|
|
1587
2159
|
list_tasks(state=REVIEW) -> get_task -> approve_task OR request_changes
|
|
1588
2160
|
|
|
1589
2161
|
Task Editing:
|
|
1590
|
-
update_task (DRAFT/TODO only) ->
|
|
2162
|
+
update_task (DRAFT/TODO only) -> task stays in current state
|
|
1591
2163
|
|
|
1592
2164
|
Error Recovery:
|
|
1593
2165
|
report_error -> abandon_task -> list_tasks -> assign_task (retry or pick different task)
|
|
@@ -1606,7 +2178,7 @@ GIT OPERATIONS NOTE:
|
|
|
1606
2178
|
TASK STATE FLOW:
|
|
1607
2179
|
DRAFT -> TODO -> IN_PROGRESS -> REVIEW -> DONE
|
|
1608
2180
|
(verify_task: DRAFT -> TODO)
|
|
1609
|
-
(update_task
|
|
2181
|
+
(update_task: DRAFT/TODO only, state unchanged)
|
|
1610
2182
|
(request_changes: REVIEW -> IN_PROGRESS)
|
|
1611
2183
|
(abandon_task: IN_PROGRESS -> TODO)
|
|
1612
2184
|
|
|
@@ -1639,7 +2211,7 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
1639
2211
|
{
|
|
1640
2212
|
description: "Discover all accessible projects. Use first to find project IDs. Filter by TEAM, PERSONAL, or ALL workspaces.",
|
|
1641
2213
|
inputSchema: {
|
|
1642
|
-
workspaceType:
|
|
2214
|
+
workspaceType: import_zod9.z.enum(["TEAM", "PERSONAL", "ALL"]).optional().describe("Filter by workspace type")
|
|
1643
2215
|
}
|
|
1644
2216
|
},
|
|
1645
2217
|
async (args) => {
|
|
@@ -1669,10 +2241,10 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
1669
2241
|
{
|
|
1670
2242
|
description: "Query tasks with filters. Use state=TODO for assignable tasks, state=REVIEW for pending reviews.",
|
|
1671
2243
|
inputSchema: {
|
|
1672
|
-
projectId:
|
|
1673
|
-
state:
|
|
1674
|
-
assigneeId:
|
|
1675
|
-
includeArchived:
|
|
2244
|
+
projectId: import_zod9.z.string().optional().describe("Filter by project ID"),
|
|
2245
|
+
state: import_zod9.z.nativeEnum(TaskState).optional().describe("Filter by task state"),
|
|
2246
|
+
assigneeId: import_zod9.z.string().optional().describe("Filter by assignee ID"),
|
|
2247
|
+
includeArchived: import_zod9.z.boolean().optional().describe("Include archived tasks (default: false)")
|
|
1676
2248
|
}
|
|
1677
2249
|
},
|
|
1678
2250
|
async (args) => {
|
|
@@ -1703,7 +2275,7 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
1703
2275
|
{
|
|
1704
2276
|
description: "Get complete task details with acceptance criteria and notes. Call before assign_task to understand requirements.",
|
|
1705
2277
|
inputSchema: {
|
|
1706
|
-
taskId:
|
|
2278
|
+
taskId: import_zod9.z.string().describe("The task ID to retrieve")
|
|
1707
2279
|
}
|
|
1708
2280
|
},
|
|
1709
2281
|
async (args) => {
|
|
@@ -1729,9 +2301,9 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
1729
2301
|
{
|
|
1730
2302
|
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.",
|
|
1731
2303
|
inputSchema: {
|
|
1732
|
-
projectId:
|
|
1733
|
-
taskId:
|
|
1734
|
-
expectedState:
|
|
2304
|
+
projectId: import_zod9.z.string().describe("The project ID"),
|
|
2305
|
+
taskId: import_zod9.z.string().describe("The task ID to assign"),
|
|
2306
|
+
expectedState: import_zod9.z.nativeEnum(TaskState).optional().describe("Expected task state (default: TODO)")
|
|
1735
2307
|
}
|
|
1736
2308
|
},
|
|
1737
2309
|
async (args) => {
|
|
@@ -1765,10 +2337,10 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
1765
2337
|
{
|
|
1766
2338
|
description: "Report progress and checkpoint work. Call frequently during execution. Marks acceptance criteria complete.",
|
|
1767
2339
|
inputSchema: {
|
|
1768
|
-
taskId:
|
|
1769
|
-
statusMessage:
|
|
1770
|
-
completedCheckpointIds:
|
|
1771
|
-
currentCheckpointIndex:
|
|
2340
|
+
taskId: import_zod9.z.string().describe("The task ID to update"),
|
|
2341
|
+
statusMessage: import_zod9.z.string().optional().describe("Status message (max 1000 chars)"),
|
|
2342
|
+
completedCheckpointIds: import_zod9.z.array(import_zod9.z.string()).optional().describe("Array of completed checkpoint IDs"),
|
|
2343
|
+
currentCheckpointIndex: import_zod9.z.number().optional().describe("Current checkpoint index")
|
|
1772
2344
|
}
|
|
1773
2345
|
},
|
|
1774
2346
|
async (args) => {
|
|
@@ -1802,10 +2374,10 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
1802
2374
|
{
|
|
1803
2375
|
description: "Prepare task for PR creation. Returns PR suggestions. After creating PR locally, call report_pr to transition to REVIEW. Requires IN_PROGRESS state.",
|
|
1804
2376
|
inputSchema: {
|
|
1805
|
-
projectId:
|
|
1806
|
-
taskId:
|
|
1807
|
-
pullRequestTitle:
|
|
1808
|
-
pullRequestBody:
|
|
2377
|
+
projectId: import_zod9.z.string().describe("The project ID"),
|
|
2378
|
+
taskId: import_zod9.z.string().describe("The task ID to complete"),
|
|
2379
|
+
pullRequestTitle: import_zod9.z.string().optional().describe("PR title (max 300 chars)"),
|
|
2380
|
+
pullRequestBody: import_zod9.z.string().optional().describe("PR body/description (max 10000 chars)")
|
|
1809
2381
|
}
|
|
1810
2382
|
},
|
|
1811
2383
|
async (args) => {
|
|
@@ -1862,10 +2434,10 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
1862
2434
|
{
|
|
1863
2435
|
description: "Report unrecoverable errors (BUILD_FAILURE, TEST_FAILURE, CONFLICT, AUTH_ERROR). Consider abandon_task after.",
|
|
1864
2436
|
inputSchema: {
|
|
1865
|
-
taskId:
|
|
1866
|
-
errorType:
|
|
1867
|
-
errorMessage:
|
|
1868
|
-
context:
|
|
2437
|
+
taskId: import_zod9.z.string().describe("The task ID"),
|
|
2438
|
+
errorType: import_zod9.z.nativeEnum(ErrorType).describe("Error type: BUILD_FAILURE, TEST_FAILURE, CONFLICT, AUTH_ERROR, OTHER"),
|
|
2439
|
+
errorMessage: import_zod9.z.string().describe("Error message (max 1000 chars)"),
|
|
2440
|
+
context: import_zod9.z.string().optional().describe("Additional context (max 2000 chars)")
|
|
1869
2441
|
}
|
|
1870
2442
|
},
|
|
1871
2443
|
async (args) => {
|
|
@@ -1900,7 +2472,7 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
1900
2472
|
{
|
|
1901
2473
|
description: "Load project README, tech stack, and coding conventions. Call after selecting project.",
|
|
1902
2474
|
inputSchema: {
|
|
1903
|
-
projectId:
|
|
2475
|
+
projectId: import_zod9.z.string().describe("The project ID")
|
|
1904
2476
|
}
|
|
1905
2477
|
},
|
|
1906
2478
|
async (args) => {
|
|
@@ -1930,8 +2502,8 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
1930
2502
|
{
|
|
1931
2503
|
description: "Document implementation decisions. Notes persist for future reference and handoff.",
|
|
1932
2504
|
inputSchema: {
|
|
1933
|
-
taskId:
|
|
1934
|
-
content:
|
|
2505
|
+
taskId: import_zod9.z.string().describe("The task ID"),
|
|
2506
|
+
content: import_zod9.z.string().describe("Note content (max 500 chars)")
|
|
1935
2507
|
}
|
|
1936
2508
|
},
|
|
1937
2509
|
async (args) => {
|
|
@@ -1960,9 +2532,9 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
1960
2532
|
{
|
|
1961
2533
|
description: "Release task assignment and optionally clean up branch. Task returns to TODO. Use after errors.",
|
|
1962
2534
|
inputSchema: {
|
|
1963
|
-
projectId:
|
|
1964
|
-
taskId:
|
|
1965
|
-
deleteBranch:
|
|
2535
|
+
projectId: import_zod9.z.string().describe("The project ID"),
|
|
2536
|
+
taskId: import_zod9.z.string().describe("The task ID to abandon"),
|
|
2537
|
+
deleteBranch: import_zod9.z.boolean().optional().describe("Whether to delete the associated branch")
|
|
1966
2538
|
}
|
|
1967
2539
|
},
|
|
1968
2540
|
async (args) => {
|
|
@@ -1996,9 +2568,9 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
1996
2568
|
{
|
|
1997
2569
|
description: "Report a branch created by the agent. Call after using git to create and push a branch. Task must be IN_PROGRESS.",
|
|
1998
2570
|
inputSchema: {
|
|
1999
|
-
projectId:
|
|
2000
|
-
taskId:
|
|
2001
|
-
branchName:
|
|
2571
|
+
projectId: import_zod9.z.string().describe("The project ID"),
|
|
2572
|
+
taskId: import_zod9.z.string().describe("The task ID"),
|
|
2573
|
+
branchName: import_zod9.z.string().describe("Name of the branch created (e.g., feature/TASK-123-fix-login)")
|
|
2002
2574
|
}
|
|
2003
2575
|
},
|
|
2004
2576
|
async (args) => {
|
|
@@ -2032,10 +2604,10 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
2032
2604
|
{
|
|
2033
2605
|
description: "Report a PR created by the agent. Call after using gh pr create. Transitions task to REVIEW state.",
|
|
2034
2606
|
inputSchema: {
|
|
2035
|
-
projectId:
|
|
2036
|
-
taskId:
|
|
2037
|
-
pullRequestUrl:
|
|
2038
|
-
pullRequestNumber:
|
|
2607
|
+
projectId: import_zod9.z.string().describe("The project ID"),
|
|
2608
|
+
taskId: import_zod9.z.string().describe("The task ID"),
|
|
2609
|
+
pullRequestUrl: import_zod9.z.string().describe("Full URL of the created PR (e.g., https://github.com/owner/repo/pull/123)"),
|
|
2610
|
+
pullRequestNumber: import_zod9.z.number().describe("PR number (e.g., 123)")
|
|
2039
2611
|
}
|
|
2040
2612
|
},
|
|
2041
2613
|
async (args) => {
|
|
@@ -2070,8 +2642,8 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
2070
2642
|
{
|
|
2071
2643
|
description: "Soft-delete a task. Hidden but restorable via unarchive_task.",
|
|
2072
2644
|
inputSchema: {
|
|
2073
|
-
projectId:
|
|
2074
|
-
taskId:
|
|
2645
|
+
projectId: import_zod9.z.string().describe("The project ID"),
|
|
2646
|
+
taskId: import_zod9.z.string().describe("The task ID to archive")
|
|
2075
2647
|
}
|
|
2076
2648
|
},
|
|
2077
2649
|
async (args) => {
|
|
@@ -2104,8 +2676,8 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
2104
2676
|
{
|
|
2105
2677
|
description: "Restore previously archived task to original state.",
|
|
2106
2678
|
inputSchema: {
|
|
2107
|
-
projectId:
|
|
2108
|
-
taskId:
|
|
2679
|
+
projectId: import_zod9.z.string().describe("The project ID"),
|
|
2680
|
+
taskId: import_zod9.z.string().describe("The task ID to restore")
|
|
2109
2681
|
}
|
|
2110
2682
|
},
|
|
2111
2683
|
async (args) => {
|
|
@@ -2138,9 +2710,9 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
2138
2710
|
{
|
|
2139
2711
|
description: "Create new project in personal workspace linked to GitHub repository.",
|
|
2140
2712
|
inputSchema: {
|
|
2141
|
-
name:
|
|
2142
|
-
description:
|
|
2143
|
-
repositoryUrl:
|
|
2713
|
+
name: import_zod9.z.string().describe("Project name (max 100 chars)"),
|
|
2714
|
+
description: import_zod9.z.string().optional().describe("Project description (max 500 chars)"),
|
|
2715
|
+
repositoryUrl: import_zod9.z.string().describe("GitHub repository URL")
|
|
2144
2716
|
}
|
|
2145
2717
|
},
|
|
2146
2718
|
async (args) => {
|
|
@@ -2174,14 +2746,14 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
2174
2746
|
{
|
|
2175
2747
|
description: "Create task with title, description, acceptance criteria. Starts in DRAFT. Priority: LOW/MEDIUM/HIGH/CRITICAL.",
|
|
2176
2748
|
inputSchema: {
|
|
2177
|
-
projectId:
|
|
2178
|
-
epicId:
|
|
2179
|
-
title:
|
|
2180
|
-
description:
|
|
2181
|
-
priority:
|
|
2182
|
-
acceptanceCriteria:
|
|
2183
|
-
|
|
2184
|
-
description:
|
|
2749
|
+
projectId: import_zod9.z.string().describe("The project ID to create the task in"),
|
|
2750
|
+
epicId: import_zod9.z.string().nullable().optional().describe("Optional epic ID to associate the task with"),
|
|
2751
|
+
title: import_zod9.z.string().describe("Task title (max 200 chars)"),
|
|
2752
|
+
description: import_zod9.z.string().describe("Task description (max 5000 chars)"),
|
|
2753
|
+
priority: import_zod9.z.nativeEnum(TaskPriority).optional().describe("Task priority: LOW, MEDIUM, HIGH, CRITICAL (default: MEDIUM)"),
|
|
2754
|
+
acceptanceCriteria: import_zod9.z.array(
|
|
2755
|
+
import_zod9.z.object({
|
|
2756
|
+
description: import_zod9.z.string().describe("Acceptance criterion description (max 500 chars)")
|
|
2185
2757
|
})
|
|
2186
2758
|
).describe("Array of acceptance criteria (at least 1 required)")
|
|
2187
2759
|
}
|
|
@@ -2220,10 +2792,10 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
2220
2792
|
{
|
|
2221
2793
|
description: "Return task from REVIEW to IN_PROGRESS with feedback. Original assignee addresses changes.",
|
|
2222
2794
|
inputSchema: {
|
|
2223
|
-
projectId:
|
|
2224
|
-
taskId:
|
|
2225
|
-
reviewComments:
|
|
2226
|
-
requestedChanges:
|
|
2795
|
+
projectId: import_zod9.z.string().describe("The project ID"),
|
|
2796
|
+
taskId: import_zod9.z.string().describe("The task ID to review"),
|
|
2797
|
+
reviewComments: import_zod9.z.string().describe("Review comments explaining requested changes (max 5000 chars)"),
|
|
2798
|
+
requestedChanges: import_zod9.z.array(import_zod9.z.string()).optional().describe("List of specific changes requested")
|
|
2227
2799
|
}
|
|
2228
2800
|
},
|
|
2229
2801
|
async (args) => {
|
|
@@ -2258,9 +2830,9 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
2258
2830
|
{
|
|
2259
2831
|
description: "Approve completed work and mark DONE. Only for REVIEW state tasks.",
|
|
2260
2832
|
inputSchema: {
|
|
2261
|
-
projectId:
|
|
2262
|
-
taskId:
|
|
2263
|
-
reviewComments:
|
|
2833
|
+
projectId: import_zod9.z.string().describe("The project ID"),
|
|
2834
|
+
taskId: import_zod9.z.string().describe("The task ID to approve"),
|
|
2835
|
+
reviewComments: import_zod9.z.string().optional().describe("Optional approval comments (max 2000 chars)")
|
|
2264
2836
|
}
|
|
2265
2837
|
},
|
|
2266
2838
|
async (args) => {
|
|
@@ -2294,10 +2866,10 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
2294
2866
|
{
|
|
2295
2867
|
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.",
|
|
2296
2868
|
inputSchema: {
|
|
2297
|
-
projectId:
|
|
2298
|
-
taskId:
|
|
2299
|
-
approved:
|
|
2300
|
-
feedback:
|
|
2869
|
+
projectId: import_zod9.z.string().describe("The project ID"),
|
|
2870
|
+
taskId: import_zod9.z.string().describe("The task ID to verify"),
|
|
2871
|
+
approved: import_zod9.z.boolean().describe("Whether to approve the task"),
|
|
2872
|
+
feedback: import_zod9.z.string().optional().describe("Feedback for the task (required if not approved)")
|
|
2301
2873
|
}
|
|
2302
2874
|
},
|
|
2303
2875
|
async (args) => {
|
|
@@ -2328,8 +2900,8 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
2328
2900
|
{
|
|
2329
2901
|
description: "Get state-appropriate prompt for a task. Returns verify prompt for DRAFT, assignment prompt for TODO, or continue prompt for IN_PROGRESS tasks.",
|
|
2330
2902
|
inputSchema: {
|
|
2331
|
-
projectId:
|
|
2332
|
-
taskId:
|
|
2903
|
+
projectId: import_zod9.z.string().describe("The project ID"),
|
|
2904
|
+
taskId: import_zod9.z.string().describe("The task ID")
|
|
2333
2905
|
}
|
|
2334
2906
|
},
|
|
2335
2907
|
async (args) => {
|
|
@@ -2360,17 +2932,17 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
2360
2932
|
server.registerTool(
|
|
2361
2933
|
"update_task",
|
|
2362
2934
|
{
|
|
2363
|
-
description: "Update task details (title, description, priority, acceptanceCriteria). Only works for DRAFT and TODO states.
|
|
2935
|
+
description: "Update task details (title, description, priority, acceptanceCriteria). Only works for DRAFT and TODO states. Task stays in its current state.",
|
|
2364
2936
|
inputSchema: {
|
|
2365
|
-
projectId:
|
|
2366
|
-
taskId:
|
|
2367
|
-
title:
|
|
2368
|
-
description:
|
|
2369
|
-
priority:
|
|
2370
|
-
acceptanceCriteria:
|
|
2371
|
-
|
|
2372
|
-
id:
|
|
2373
|
-
description:
|
|
2937
|
+
projectId: import_zod9.z.string().describe("The project ID"),
|
|
2938
|
+
taskId: import_zod9.z.string().describe("The task ID to update"),
|
|
2939
|
+
title: import_zod9.z.string().optional().describe("New task title"),
|
|
2940
|
+
description: import_zod9.z.string().optional().describe("New task description"),
|
|
2941
|
+
priority: import_zod9.z.nativeEnum(TaskPriority).optional().describe("New task priority"),
|
|
2942
|
+
acceptanceCriteria: import_zod9.z.array(
|
|
2943
|
+
import_zod9.z.object({
|
|
2944
|
+
id: import_zod9.z.string().optional().describe("Existing criterion ID (for updates)"),
|
|
2945
|
+
description: import_zod9.z.string().describe("Criterion description")
|
|
2374
2946
|
})
|
|
2375
2947
|
).optional().describe("New acceptance criteria (replaces existing)")
|
|
2376
2948
|
}
|
|
@@ -2426,6 +2998,7 @@ function initializeMCPServer(apiClient, authContext) {
|
|
|
2426
2998
|
};
|
|
2427
2999
|
}
|
|
2428
3000
|
);
|
|
3001
|
+
registerMCPApps(server, apiClient, authContext);
|
|
2429
3002
|
return server;
|
|
2430
3003
|
}
|
|
2431
3004
|
function handleApiError(error) {
|
|
@@ -2460,30 +3033,30 @@ function handleApiError(error) {
|
|
|
2460
3033
|
isError: true
|
|
2461
3034
|
};
|
|
2462
3035
|
}
|
|
2463
|
-
var ActiveTaskSchema =
|
|
2464
|
-
taskId:
|
|
2465
|
-
projectId:
|
|
2466
|
-
branchName:
|
|
2467
|
-
worktreePath:
|
|
2468
|
-
startedAt:
|
|
3036
|
+
var ActiveTaskSchema = import_zod9.z.object({
|
|
3037
|
+
taskId: import_zod9.z.string().min(1),
|
|
3038
|
+
projectId: import_zod9.z.string().min(1),
|
|
3039
|
+
branchName: import_zod9.z.string().optional(),
|
|
3040
|
+
worktreePath: import_zod9.z.string().optional(),
|
|
3041
|
+
startedAt: import_zod9.z.string().optional()
|
|
2469
3042
|
});
|
|
2470
3043
|
async function checkActiveTask() {
|
|
2471
|
-
const
|
|
2472
|
-
const
|
|
3044
|
+
const fs2 = await import("fs");
|
|
3045
|
+
const path2 = await import("path");
|
|
2473
3046
|
const cwd = process.cwd();
|
|
2474
|
-
const collabDir =
|
|
2475
|
-
const activeTaskPath =
|
|
3047
|
+
const collabDir = path2.join(cwd, ".collab");
|
|
3048
|
+
const activeTaskPath = path2.join(collabDir, "active-task.json");
|
|
2476
3049
|
try {
|
|
2477
|
-
const resolvedPath =
|
|
2478
|
-
const resolvedCollabDir =
|
|
2479
|
-
if (!resolvedPath.startsWith(resolvedCollabDir +
|
|
3050
|
+
const resolvedPath = path2.resolve(activeTaskPath);
|
|
3051
|
+
const resolvedCollabDir = path2.resolve(collabDir);
|
|
3052
|
+
if (!resolvedPath.startsWith(resolvedCollabDir + path2.sep) && resolvedPath !== resolvedCollabDir) {
|
|
2480
3053
|
return {
|
|
2481
3054
|
hasActiveTask: false,
|
|
2482
3055
|
task: null,
|
|
2483
3056
|
error: "Invalid active task path"
|
|
2484
3057
|
};
|
|
2485
3058
|
}
|
|
2486
|
-
const stats = await
|
|
3059
|
+
const stats = await fs2.promises.lstat(activeTaskPath);
|
|
2487
3060
|
if (stats.isSymbolicLink()) {
|
|
2488
3061
|
return {
|
|
2489
3062
|
hasActiveTask: false,
|
|
@@ -2497,7 +3070,7 @@ async function checkActiveTask() {
|
|
|
2497
3070
|
task: null
|
|
2498
3071
|
};
|
|
2499
3072
|
}
|
|
2500
|
-
const content = await
|
|
3073
|
+
const content = await fs2.promises.readFile(activeTaskPath, "utf-8");
|
|
2501
3074
|
let parsed;
|
|
2502
3075
|
try {
|
|
2503
3076
|
parsed = JSON.parse(content);
|
|
@@ -2558,6 +3131,12 @@ async function validateOAuthToken(baseUrl, accessToken) {
|
|
|
2558
3131
|
}
|
|
2559
3132
|
return response.json();
|
|
2560
3133
|
}
|
|
3134
|
+
function buildWwwAuthenticateHeader(req) {
|
|
3135
|
+
const protocol = req.headers["x-forwarded-proto"] || req.protocol || "https";
|
|
3136
|
+
const host = req.headers["x-forwarded-host"] || req.headers.host;
|
|
3137
|
+
const resourceMetadataUrl = `${protocol}://${host}/.well-known/oauth-protected-resource`;
|
|
3138
|
+
return `Bearer resource_metadata="${resourceMetadataUrl}"`;
|
|
3139
|
+
}
|
|
2561
3140
|
function createAuthMiddleware(baseUrl) {
|
|
2562
3141
|
return async (req, res, next) => {
|
|
2563
3142
|
const authHeader = req.headers.authorization;
|
|
@@ -2566,7 +3145,7 @@ function createAuthMiddleware(baseUrl) {
|
|
|
2566
3145
|
try {
|
|
2567
3146
|
const validation = await validateOAuthToken(baseUrl, bearerToken);
|
|
2568
3147
|
if (!validation.valid) {
|
|
2569
|
-
res.status(401).json({
|
|
3148
|
+
res.status(401).set("WWW-Authenticate", buildWwwAuthenticateHeader(req)).json({
|
|
2570
3149
|
jsonrpc: "2.0",
|
|
2571
3150
|
error: {
|
|
2572
3151
|
code: -32001,
|
|
@@ -2603,7 +3182,7 @@ function createAuthMiddleware(baseUrl) {
|
|
|
2603
3182
|
return;
|
|
2604
3183
|
} catch (error) {
|
|
2605
3184
|
console.error("[collab-mcp-server] OAuth validation error:", error);
|
|
2606
|
-
res.status(401).json({
|
|
3185
|
+
res.status(401).set("WWW-Authenticate", buildWwwAuthenticateHeader(req)).json({
|
|
2607
3186
|
jsonrpc: "2.0",
|
|
2608
3187
|
error: {
|
|
2609
3188
|
code: -32001,
|
|
@@ -2616,7 +3195,7 @@ function createAuthMiddleware(baseUrl) {
|
|
|
2616
3195
|
}
|
|
2617
3196
|
const apiKey = req.headers["x-api-key"];
|
|
2618
3197
|
if (!apiKey || typeof apiKey !== "string") {
|
|
2619
|
-
res.status(401).json({
|
|
3198
|
+
res.status(401).set("WWW-Authenticate", buildWwwAuthenticateHeader(req)).json({
|
|
2620
3199
|
jsonrpc: "2.0",
|
|
2621
3200
|
error: {
|
|
2622
3201
|
code: -32001,
|
|
@@ -2736,19 +3315,11 @@ async function createHTTPServer() {
|
|
|
2736
3315
|
}
|
|
2737
3316
|
const app = (0, import_express.default)();
|
|
2738
3317
|
app.use(import_express.default.json());
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
version: VERSION,
|
|
2743
|
-
activeSessions: sessions.size,
|
|
2744
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2745
|
-
});
|
|
2746
|
-
});
|
|
2747
|
-
app.get("/mcp/.well-known/oauth-protected-resource", (_req, res) => {
|
|
2748
|
-
const protocol = _req.headers["x-forwarded-proto"] || _req.protocol || "https";
|
|
2749
|
-
const host = _req.headers["x-forwarded-host"] || _req.headers.host;
|
|
3318
|
+
function buildProtectedResourceMetadata(req) {
|
|
3319
|
+
const protocol = req.headers["x-forwarded-proto"] || req.protocol || "https";
|
|
3320
|
+
const host = req.headers["x-forwarded-host"] || req.headers.host;
|
|
2750
3321
|
const resourceUrl = `${protocol}://${host}/mcp`;
|
|
2751
|
-
|
|
3322
|
+
return {
|
|
2752
3323
|
resource: resourceUrl,
|
|
2753
3324
|
authorization_servers: [baseUrl],
|
|
2754
3325
|
scopes_supported: [
|
|
@@ -2758,8 +3329,48 @@ async function createHTTPServer() {
|
|
|
2758
3329
|
],
|
|
2759
3330
|
bearer_methods_supported: ["header"],
|
|
2760
3331
|
resource_documentation: `${baseUrl}/docs/mcp`
|
|
3332
|
+
};
|
|
3333
|
+
}
|
|
3334
|
+
app.use((_req, res, next) => {
|
|
3335
|
+
const origin = _req.headers.origin;
|
|
3336
|
+
const allowedOrigins = [
|
|
3337
|
+
"https://claude.ai",
|
|
3338
|
+
"https://claude.com",
|
|
3339
|
+
"http://localhost:3000",
|
|
3340
|
+
"http://localhost:5173"
|
|
3341
|
+
];
|
|
3342
|
+
if (origin && allowedOrigins.includes(origin)) {
|
|
3343
|
+
res.header("Access-Control-Allow-Origin", origin);
|
|
3344
|
+
}
|
|
3345
|
+
res.header(
|
|
3346
|
+
"Access-Control-Allow-Methods",
|
|
3347
|
+
"GET, POST, DELETE, OPTIONS"
|
|
3348
|
+
);
|
|
3349
|
+
res.header(
|
|
3350
|
+
"Access-Control-Allow-Headers",
|
|
3351
|
+
"Content-Type, Authorization, X-API-Key, mcp-session-id"
|
|
3352
|
+
);
|
|
3353
|
+
res.header("Access-Control-Expose-Headers", "mcp-session-id");
|
|
3354
|
+
if (_req.method === "OPTIONS") {
|
|
3355
|
+
res.status(204).end();
|
|
3356
|
+
return;
|
|
3357
|
+
}
|
|
3358
|
+
next();
|
|
3359
|
+
});
|
|
3360
|
+
app.get("/mcp/health", (_req, res) => {
|
|
3361
|
+
res.json({
|
|
3362
|
+
status: "ok",
|
|
3363
|
+
version: VERSION,
|
|
3364
|
+
activeSessions: sessions.size,
|
|
3365
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2761
3366
|
});
|
|
2762
3367
|
});
|
|
3368
|
+
app.get("/.well-known/oauth-protected-resource", (req, res) => {
|
|
3369
|
+
res.json(buildProtectedResourceMetadata(req));
|
|
3370
|
+
});
|
|
3371
|
+
app.get("/mcp/.well-known/oauth-protected-resource", (req, res) => {
|
|
3372
|
+
res.json(buildProtectedResourceMetadata(req));
|
|
3373
|
+
});
|
|
2763
3374
|
const authMiddleware = createAuthMiddleware(baseUrl);
|
|
2764
3375
|
app.post("/mcp", authMiddleware, async (req, res) => {
|
|
2765
3376
|
const authReq = req;
|
|
@@ -2895,7 +3506,7 @@ async function createHTTPServer() {
|
|
|
2895
3506
|
console.error(`[collab-mcp-server] MCP endpoint: POST/GET/DELETE /mcp`);
|
|
2896
3507
|
console.error(`[collab-mcp-server] Health check: GET /mcp/health`);
|
|
2897
3508
|
console.error(
|
|
2898
|
-
`[collab-mcp-server] OAuth
|
|
3509
|
+
`[collab-mcp-server] OAuth PRM: GET /.well-known/oauth-protected-resource`
|
|
2899
3510
|
);
|
|
2900
3511
|
console.error(`[collab-mcp-server] API URL: ${baseUrl}`);
|
|
2901
3512
|
console.error(
|