session-collab-mcp 2.1.0 → 2.3.0

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.
@@ -4,11 +4,28 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
+ // src/db/migrations.ts
8
+ import { readFileSync, readdirSync } from "fs";
9
+ import { join } from "path";
10
+ var VERSIONED_MIGRATION_PATTERN = /^\d{4}_.+\.sql$/;
11
+ function listMigrationFiles(migrationsDir) {
12
+ return readdirSync(migrationsDir).filter((file) => VERSIONED_MIGRATION_PATTERN.test(file)).sort((left, right) => left.localeCompare(right, "en"));
13
+ }
14
+ function loadMigrationsFromDir(migrationsDir) {
15
+ return listMigrationFiles(migrationsDir).map(
16
+ (file) => readFileSync(join(migrationsDir, file), "utf-8")
17
+ );
18
+ }
19
+ function splitMigrationStatements(migration) {
20
+ const withoutCommentLines = migration.split("\n").filter((line) => !line.trim().startsWith("--")).join("\n");
21
+ return withoutCommentLines.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
22
+ }
23
+
7
24
  // src/db/sqlite-adapter.ts
8
25
  import Database from "better-sqlite3";
9
26
  import { randomUUID } from "crypto";
10
27
  import { existsSync, mkdirSync } from "fs";
11
- import { dirname, join } from "path";
28
+ import { dirname, join as join2 } from "path";
12
29
  import { homedir } from "os";
13
30
  if (typeof globalThis.crypto === "undefined") {
14
31
  globalThis.crypto = {
@@ -93,7 +110,7 @@ var SqliteDatabase = class {
93
110
  // Handles upgrades gracefully by ignoring "already exists" and "duplicate column" errors
94
111
  initSchema(migrations) {
95
112
  for (const migration of migrations) {
96
- const statements = migration.split(";").map((s) => s.trim()).filter((s) => s.length > 0 && !s.startsWith("--"));
113
+ const statements = splitMigrationStatements(migration);
97
114
  for (const stmt of statements) {
98
115
  try {
99
116
  this.runSql(stmt);
@@ -116,8 +133,8 @@ var SqliteDatabase = class {
116
133
  }
117
134
  };
118
135
  function getDefaultDbPath() {
119
- const dataDir = join(homedir(), ".claude", "session-collab");
120
- return join(dataDir, "collab.db");
136
+ const dataDir = join2(homedir(), ".claude", "session-collab");
137
+ return join2(dataDir, "collab.db");
121
138
  }
122
139
  function createLocalDatabase(dbPath) {
123
140
  const path = dbPath ?? getDefaultDbPath();
@@ -125,13 +142,13 @@ function createLocalDatabase(dbPath) {
125
142
  }
126
143
 
127
144
  // src/constants.ts
128
- import { readFileSync } from "fs";
129
- import { join as join2, dirname as dirname2 } from "path";
145
+ import { readFileSync as readFileSync2 } from "fs";
146
+ import { join as join3, dirname as dirname2 } from "path";
130
147
  import { fileURLToPath } from "url";
131
148
  function getVersion() {
132
149
  try {
133
150
  const __dirname2 = dirname2(fileURLToPath(import.meta.url));
134
- const pkg = JSON.parse(readFileSync(join2(__dirname2, "..", "package.json"), "utf-8"));
151
+ const pkg = JSON.parse(readFileSync2(join3(__dirname2, "..", "package.json"), "utf-8"));
135
152
  return pkg.version;
136
153
  } catch {
137
154
  return "0.0.0";
@@ -144,12 +161,13 @@ var SERVER_INSTRUCTIONS = `
144
161
 
145
162
  Coordinate sessions and persist context across conversations.
146
163
 
147
- ## 10 Core Tools
164
+ ## Core Tools
148
165
 
149
- ### Session (5 tools)
166
+ ### Session
150
167
  - \`collab_session_start\`: Start session with project_root
151
168
  - \`collab_session_end\`: End session, release claims
152
- - \`collab_session_list\`: List active sessions
169
+ - \`collab_session_list\`: List active sessions and their active claim summaries
170
+ - \`collab_session_update\`: Update heartbeat, current task, todos, and progress
153
171
  - \`collab_config\`: Configure session behavior
154
172
  - \`collab_status\`: Get session status and summary
155
173
 
@@ -170,10 +188,11 @@ Coordinate sessions and persist context across conversations.
170
188
 
171
189
  1. **On start**: \`collab_session_start\` with project_root
172
190
  2. **Before editing**: \`collab_claim\` action="check"
173
- 3. **For changes**: \`collab_claim\` action="create"
191
+ 3. **For changes**: \`collab_claim\` action="create" (blocked by default if another active session conflicts)
174
192
  4. **Save context**: \`collab_memory_save\` for important findings
175
- 5. **When done**: \`collab_claim\` action="release"
176
- 6. **On end**: \`collab_session_end\`
193
+ 5. **While working**: \`collab_session_update\` with current_task/todos
194
+ 6. **When done**: \`collab_claim\` action="release"
195
+ 7. **On end**: \`collab_session_end\`
177
196
 
178
197
  ## Memory Categories
179
198
 
@@ -4239,6 +4258,27 @@ var JsonRpcRequestSchema = external_exports.object({
4239
4258
  method: external_exports.string(),
4240
4259
  params: external_exports.record(external_exports.unknown()).optional()
4241
4260
  });
4261
+ var MCP_ERROR_CODES = {
4262
+ PARSE_ERROR: -32700,
4263
+ INVALID_REQUEST: -32600,
4264
+ METHOD_NOT_FOUND: -32601,
4265
+ INVALID_PARAMS: -32602,
4266
+ INTERNAL_ERROR: -32603
4267
+ };
4268
+ function createSuccessResponse(id, result) {
4269
+ return {
4270
+ jsonrpc: "2.0",
4271
+ id,
4272
+ result
4273
+ };
4274
+ }
4275
+ function createErrorResponse(id, code, message, data) {
4276
+ return {
4277
+ jsonrpc: "2.0",
4278
+ id,
4279
+ error: { code, message, data }
4280
+ };
4281
+ }
4242
4282
  function createToolResult(text, isError = false) {
4243
4283
  return {
4244
4284
  content: [{ type: "text", text }],
@@ -4296,6 +4336,39 @@ async function listSessions(db, params = {}) {
4296
4336
  const result = await db.prepare(query).bind(...bindings).all();
4297
4337
  return result.results;
4298
4338
  }
4339
+ async function updateSessionHeartbeat(db, id, statusUpdate) {
4340
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4341
+ let progress = null;
4342
+ let todosJson = null;
4343
+ if (statusUpdate?.todos) {
4344
+ const total = statusUpdate.todos.length;
4345
+ const completed = statusUpdate.todos.filter((t) => t.status === "completed").length;
4346
+ progress = {
4347
+ completed,
4348
+ total,
4349
+ percentage: total > 0 ? Math.round(completed / total * 100) : 0
4350
+ };
4351
+ todosJson = JSON.stringify(statusUpdate.todos);
4352
+ }
4353
+ let query = "UPDATE sessions SET last_heartbeat = ?";
4354
+ const bindings = [now];
4355
+ if (statusUpdate?.current_task !== void 0) {
4356
+ query += ", current_task = ?";
4357
+ bindings.push(statusUpdate.current_task);
4358
+ }
4359
+ if (progress) {
4360
+ query += ", progress = ?";
4361
+ bindings.push(JSON.stringify(progress));
4362
+ }
4363
+ if (todosJson) {
4364
+ query += ", todos = ?";
4365
+ bindings.push(todosJson);
4366
+ }
4367
+ query += " WHERE id = ? AND status = 'active'";
4368
+ bindings.push(id);
4369
+ const result = await db.prepare(query).bind(...bindings).run();
4370
+ return result.meta.changes > 0;
4371
+ }
4299
4372
  async function endSession(db, id, release_claims = "abandon") {
4300
4373
  const claimStatus = release_claims === "complete" ? "completed" : "abandoned";
4301
4374
  await db.prepare("UPDATE claims SET status = ?, updated_at = ? WHERE session_id = ? AND status = 'active'").bind(claimStatus, (/* @__PURE__ */ new Date()).toISOString(), id).run();
@@ -4960,7 +5033,8 @@ var claimCreateSchema = external_exports.object({
4960
5033
  symbols: symbolClaimsArraySchema.optional(),
4961
5034
  intent: external_exports.string().min(1, "intent is required"),
4962
5035
  scope: claimScopeSchema.optional(),
4963
- priority: external_exports.number().min(0).max(100).optional()
5036
+ priority: external_exports.number().min(0).max(100).optional(),
5037
+ allow_conflicts: external_exports.boolean().optional()
4964
5038
  }).refine(
4965
5039
  (data) => data.files && data.files.length > 0 || data.symbols && data.symbols.length > 0,
4966
5040
  { message: "Either files or symbols must be provided" }
@@ -4974,7 +5048,7 @@ var claimUpdatePrioritySchema = external_exports.object({
4974
5048
  var claimCheckSchema = external_exports.object({
4975
5049
  files: filesArraySchema,
4976
5050
  symbols: symbolClaimsArraySchema.optional(),
4977
- session_id: external_exports.string().optional(),
5051
+ session_id: sessionIdSchema,
4978
5052
  exclude_self: external_exports.boolean().optional()
4979
5053
  });
4980
5054
  var claimReleaseSchema = external_exports.object({
@@ -5066,6 +5140,14 @@ function validationError(message) {
5066
5140
  }
5067
5141
 
5068
5142
  // src/mcp/tools/session.ts
5143
+ function parseJsonField(value) {
5144
+ if (!value) return null;
5145
+ try {
5146
+ return JSON.parse(value);
5147
+ } catch {
5148
+ return null;
5149
+ }
5150
+ }
5069
5151
  var sessionTools = [
5070
5152
  {
5071
5153
  name: "collab_session_start",
@@ -5121,6 +5203,39 @@ var sessionTools = [
5121
5203
  }
5122
5204
  }
5123
5205
  },
5206
+ {
5207
+ name: "collab_session_update",
5208
+ description: "Update session heartbeat, current task, todos, and progress.",
5209
+ inputSchema: {
5210
+ type: "object",
5211
+ properties: {
5212
+ session_id: {
5213
+ type: "string",
5214
+ description: "Session ID to update"
5215
+ },
5216
+ current_task: {
5217
+ type: "string",
5218
+ description: "Current work summary"
5219
+ },
5220
+ todos: {
5221
+ type: "array",
5222
+ items: {
5223
+ type: "object",
5224
+ properties: {
5225
+ content: { type: "string" },
5226
+ status: {
5227
+ type: "string",
5228
+ enum: ["pending", "in_progress", "completed"]
5229
+ }
5230
+ },
5231
+ required: ["content", "status"]
5232
+ },
5233
+ description: "Current todo state for progress reporting"
5234
+ }
5235
+ },
5236
+ required: ["session_id"]
5237
+ }
5238
+ },
5124
5239
  {
5125
5240
  name: "collab_config",
5126
5241
  description: "Configure session behavior.",
@@ -5270,7 +5385,17 @@ Decisions:
5270
5385
  id: session.id,
5271
5386
  name: session.name,
5272
5387
  status: session.status,
5388
+ current_task: session.current_task,
5389
+ progress: parseJsonField(session.progress),
5390
+ todos: parseJsonField(session.todos),
5273
5391
  active_claims: claims.length,
5392
+ claims: claims.map((c) => ({
5393
+ id: c.id,
5394
+ files: c.files,
5395
+ intent: c.intent,
5396
+ priority: getPriorityLevel(c.priority),
5397
+ created_at: c.created_at
5398
+ })),
5274
5399
  last_heartbeat: session.last_heartbeat
5275
5400
  };
5276
5401
  })
@@ -5280,6 +5405,34 @@ Decisions:
5280
5405
  total: sessionsWithClaims.length
5281
5406
  });
5282
5407
  }
5408
+ case "collab_session_update": {
5409
+ const validation = validateInput(statusUpdateSchema, args);
5410
+ if (!validation.success) {
5411
+ return validationError(validation.error);
5412
+ }
5413
+ const input = validation.data;
5414
+ const sessionResult = await validateActiveSession(db, input.session_id);
5415
+ if (!sessionResult.valid) {
5416
+ return sessionResult.error;
5417
+ }
5418
+ const updated = await updateSessionHeartbeat(db, input.session_id, {
5419
+ current_task: input.current_task,
5420
+ todos: input.todos
5421
+ });
5422
+ if (!updated) {
5423
+ return errorResponse(ERROR_CODES.SESSION_NOT_FOUND, "Session not found");
5424
+ }
5425
+ const session = await getSession(db, input.session_id);
5426
+ return successResponse({
5427
+ success: true,
5428
+ session_id: input.session_id,
5429
+ current_task: session?.current_task ?? null,
5430
+ progress: parseJsonField(session?.progress ?? null),
5431
+ todos: parseJsonField(session?.todos ?? null),
5432
+ last_heartbeat: session?.last_heartbeat ?? null,
5433
+ message: "Session updated."
5434
+ });
5435
+ }
5283
5436
  case "collab_config": {
5284
5437
  const validation = validateInput(configSchema, args);
5285
5438
  if (!validation.success) {
@@ -5419,6 +5572,10 @@ var claimTools = [
5419
5572
  type: "boolean",
5420
5573
  description: "Force release even if not owner (for release action)"
5421
5574
  },
5575
+ allow_conflicts: {
5576
+ type: "boolean",
5577
+ description: "Explicitly create a claim even when conflicts are detected (for create action)"
5578
+ },
5422
5579
  summary: {
5423
5580
  type: "string",
5424
5581
  description: "Release summary (for release action)"
@@ -5454,6 +5611,21 @@ async function handleClaimTool(db, name, args) {
5454
5611
  }
5455
5612
  const symbolFiles = (input.symbols ?? []).map((symbol) => symbol.file);
5456
5613
  const files = Array.from(/* @__PURE__ */ new Set([...input.files ?? [], ...symbolFiles]));
5614
+ const conflicts = await checkConflicts(db, files, input.session_id, input.symbols);
5615
+ if (conflicts.length > 0 && !input.allow_conflicts) {
5616
+ return successResponse({
5617
+ success: false,
5618
+ status: "blocked_by_conflicts",
5619
+ files,
5620
+ symbols: input.symbols ?? [],
5621
+ conflicts: conflicts.map((c) => ({
5622
+ session_name: c.session_name,
5623
+ file: c.file_path,
5624
+ intent: c.intent
5625
+ })),
5626
+ message: `Claim not created. ${conflicts.length} conflict(s) detected. Coordinate before proceeding.`
5627
+ });
5628
+ }
5457
5629
  const { claim } = await createClaim(db, {
5458
5630
  session_id: input.session_id,
5459
5631
  files,
@@ -5478,7 +5650,6 @@ Files: ${files.join(", ")}`,
5478
5650
  related_claim_id: claim.id,
5479
5651
  metadata: { claim_id: claim.id, files }
5480
5652
  });
5481
- const conflicts = await checkConflicts(db, files, input.session_id, input.symbols);
5482
5653
  if (conflicts.length > 0) {
5483
5654
  return successResponse({
5484
5655
  success: true,
@@ -5958,7 +6129,76 @@ async function handleProtectionTool(db, name, args) {
5958
6129
  }
5959
6130
 
5960
6131
  // src/mcp/server.ts
6132
+ var SERVER_INFO = {
6133
+ name: SERVER_NAME,
6134
+ version: VERSION
6135
+ };
6136
+ var CAPABILITIES = {
6137
+ tools: {}
6138
+ };
5961
6139
  var ALL_TOOLS = [...sessionTools, ...claimTools, ...memoryTools, ...protectionTools];
6140
+ var McpServer = class {
6141
+ constructor(db, authContext) {
6142
+ this.db = db;
6143
+ this.authContext = authContext;
6144
+ }
6145
+ authContext;
6146
+ async handleRequest(request) {
6147
+ const { method, params, id } = request;
6148
+ try {
6149
+ switch (method) {
6150
+ case "initialize":
6151
+ return await this.handleInitialize(id);
6152
+ case "notifications/initialized":
6153
+ return createSuccessResponse(id, {});
6154
+ case "tools/list":
6155
+ return await this.handleToolsList(id);
6156
+ case "tools/call":
6157
+ return await this.handleToolCall(id, params);
6158
+ case "ping":
6159
+ return createSuccessResponse(id, {});
6160
+ default:
6161
+ return createErrorResponse(id, MCP_ERROR_CODES.METHOD_NOT_FOUND, `Method not found: ${method}`);
6162
+ }
6163
+ } catch (error) {
6164
+ const message = error instanceof Error ? error.message : "Unknown error";
6165
+ return createErrorResponse(id, MCP_ERROR_CODES.INTERNAL_ERROR, message);
6166
+ }
6167
+ }
6168
+ async handleInitialize(id) {
6169
+ return createSuccessResponse(id, {
6170
+ protocolVersion: "2024-11-05",
6171
+ serverInfo: SERVER_INFO,
6172
+ capabilities: CAPABILITIES,
6173
+ instructions: SERVER_INSTRUCTIONS
6174
+ });
6175
+ }
6176
+ async handleToolsList(id) {
6177
+ return createSuccessResponse(id, { tools: ALL_TOOLS });
6178
+ }
6179
+ async handleToolCall(id, params) {
6180
+ const { name, arguments: args = {} } = params;
6181
+ let result;
6182
+ const userId = this.authContext?.userId !== "legacy" ? this.authContext?.userId : void 0;
6183
+ try {
6184
+ if (name.startsWith("collab_session_") || name === "collab_config" || name === "collab_status") {
6185
+ result = await handleSessionTool(this.db, name, args, userId);
6186
+ } else if (name === "collab_claim") {
6187
+ result = await handleClaimTool(this.db, name, args);
6188
+ } else if (name.startsWith("collab_memory_")) {
6189
+ result = await handleMemoryTool(this.db, name, args);
6190
+ } else if (name === "collab_protect") {
6191
+ result = await handleProtectionTool(this.db, name, args);
6192
+ } else {
6193
+ result = createToolResult(`Unknown tool: ${name}`, true);
6194
+ }
6195
+ } catch (error) {
6196
+ const message = error instanceof Error ? error.message : "Tool execution failed";
6197
+ result = createToolResult(message, true);
6198
+ }
6199
+ return createSuccessResponse(id, result);
6200
+ }
6201
+ };
5962
6202
  function getMcpTools() {
5963
6203
  return ALL_TOOLS;
5964
6204
  }
@@ -5982,12 +6222,16 @@ async function handleMcpRequest(db, name, args) {
5982
6222
  }
5983
6223
 
5984
6224
  export {
6225
+ loadMigrationsFromDir,
5985
6226
  getDefaultDbPath,
5986
6227
  createLocalDatabase,
6228
+ JsonRpcRequestSchema,
6229
+ generateId,
5987
6230
  VERSION,
5988
6231
  SERVER_NAME,
5989
6232
  SERVER_INSTRUCTIONS,
6233
+ McpServer,
5990
6234
  getMcpTools,
5991
6235
  handleMcpRequest
5992
6236
  };
5993
- //# sourceMappingURL=chunk-V4APNPXF.js.map
6237
+ //# sourceMappingURL=chunk-TFGXE3EI.js.map