session-collab-mcp 0.4.7 → 0.5.2
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/LICENSE +21 -0
- package/README.md +254 -0
- package/migrations/0004_symbols.sql +18 -0
- package/migrations/0005_references.sql +19 -0
- package/migrations/0006_composite_indexes.sql +14 -0
- package/package.json +10 -1
- package/src/cli.ts +3 -0
- package/src/constants.ts +154 -19
- package/src/db/__tests__/queries.test.ts +799 -0
- package/src/db/__tests__/test-helper.ts +216 -0
- package/src/db/queries.ts +376 -43
- package/src/db/sqlite-adapter.ts +6 -6
- package/src/db/types.ts +60 -0
- package/src/mcp/schemas.ts +200 -0
- package/src/mcp/server.ts +16 -1
- package/src/mcp/tools/claim.ts +231 -83
- package/src/mcp/tools/decision.ts +26 -13
- package/src/mcp/tools/lsp.ts +686 -0
- package/src/mcp/tools/message.ts +28 -14
- package/src/mcp/tools/session.ts +82 -42
package/src/mcp/tools/message.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { DatabaseAdapter } from '../../db/sqlite-adapter.js';
|
|
|
4
4
|
import type { McpTool, McpToolResult } from '../protocol';
|
|
5
5
|
import { createToolResult } from '../protocol';
|
|
6
6
|
import { sendMessage, listMessages, getSession } from '../../db/queries';
|
|
7
|
+
import { validateInput, messageSendSchema, messageListSchema } from '../schemas';
|
|
7
8
|
|
|
8
9
|
export const messageTools: McpTool[] = [
|
|
9
10
|
{
|
|
@@ -59,12 +60,17 @@ export async function handleMessageTool(
|
|
|
59
60
|
): Promise<McpToolResult> {
|
|
60
61
|
switch (name) {
|
|
61
62
|
case 'collab_message_send': {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
const validation = validateInput(messageSendSchema, args);
|
|
64
|
+
if (!validation.success) {
|
|
65
|
+
return createToolResult(
|
|
66
|
+
JSON.stringify({ error: 'INVALID_INPUT', message: validation.error }),
|
|
67
|
+
true
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const input = validation.data;
|
|
65
71
|
|
|
66
72
|
// Verify sender session
|
|
67
|
-
const fromSession = await getSession(db,
|
|
73
|
+
const fromSession = await getSession(db, input.from_session_id);
|
|
68
74
|
if (!fromSession || fromSession.status !== 'active') {
|
|
69
75
|
return createToolResult(
|
|
70
76
|
JSON.stringify({
|
|
@@ -76,8 +82,8 @@ export async function handleMessageTool(
|
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
// Verify target session if specified
|
|
79
|
-
if (
|
|
80
|
-
const toSession = await getSession(db,
|
|
85
|
+
if (input.to_session_id) {
|
|
86
|
+
const toSession = await getSession(db, input.to_session_id);
|
|
81
87
|
if (!toSession || toSession.status !== 'active') {
|
|
82
88
|
return createToolResult(
|
|
83
89
|
JSON.stringify({
|
|
@@ -90,28 +96,36 @@ export async function handleMessageTool(
|
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
const message = await sendMessage(db, {
|
|
93
|
-
from_session_id:
|
|
94
|
-
to_session_id:
|
|
95
|
-
content,
|
|
99
|
+
from_session_id: input.from_session_id,
|
|
100
|
+
to_session_id: input.to_session_id,
|
|
101
|
+
content: input.content,
|
|
96
102
|
});
|
|
97
103
|
|
|
98
104
|
return createToolResult(
|
|
99
105
|
JSON.stringify({
|
|
100
106
|
success: true,
|
|
101
107
|
message_id: message.id,
|
|
102
|
-
sent_to:
|
|
108
|
+
sent_to: input.to_session_id ?? 'all sessions (broadcast)',
|
|
103
109
|
message: 'Message sent successfully.',
|
|
104
110
|
})
|
|
105
111
|
);
|
|
106
112
|
}
|
|
107
113
|
|
|
108
114
|
case 'collab_message_list': {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
const validation = validateInput(messageListSchema, args);
|
|
116
|
+
if (!validation.success) {
|
|
117
|
+
return createToolResult(
|
|
118
|
+
JSON.stringify({ error: 'INVALID_INPUT', message: validation.error }),
|
|
119
|
+
true
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const input = validation.data;
|
|
123
|
+
|
|
124
|
+
const unreadOnly = input.unread_only ?? true;
|
|
125
|
+
const markAsRead = input.mark_as_read ?? true;
|
|
112
126
|
|
|
113
127
|
const messages = await listMessages(db, {
|
|
114
|
-
session_id:
|
|
128
|
+
session_id: input.session_id,
|
|
115
129
|
unread_only: unreadOnly,
|
|
116
130
|
mark_as_read: markAsRead,
|
|
117
131
|
});
|
package/src/mcp/tools/session.ts
CHANGED
|
@@ -14,8 +14,17 @@ import {
|
|
|
14
14
|
cleanupStaleSessions,
|
|
15
15
|
listClaims,
|
|
16
16
|
} from '../../db/queries';
|
|
17
|
-
import type { TodoItem, SessionConfig
|
|
17
|
+
import type { TodoItem, SessionConfig } from '../../db/types';
|
|
18
18
|
import { DEFAULT_SESSION_CONFIG } from '../../db/types';
|
|
19
|
+
import {
|
|
20
|
+
validateInput,
|
|
21
|
+
sessionStartSchema,
|
|
22
|
+
sessionEndSchema,
|
|
23
|
+
sessionListSchema,
|
|
24
|
+
sessionHeartbeatSchema,
|
|
25
|
+
statusUpdateSchema,
|
|
26
|
+
configSchema,
|
|
27
|
+
} from '../schemas';
|
|
19
28
|
|
|
20
29
|
export const sessionTools: McpTool[] = [
|
|
21
30
|
{
|
|
@@ -177,17 +186,26 @@ export async function handleSessionTool(
|
|
|
177
186
|
): Promise<McpToolResult> {
|
|
178
187
|
switch (name) {
|
|
179
188
|
case 'collab_session_start': {
|
|
189
|
+
const validation = validateInput(sessionStartSchema, args);
|
|
190
|
+
if (!validation.success) {
|
|
191
|
+
return createToolResult(
|
|
192
|
+
JSON.stringify({ error: 'INVALID_INPUT', message: validation.error }),
|
|
193
|
+
true
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
const input = validation.data;
|
|
197
|
+
|
|
180
198
|
// Cleanup stale sessions first
|
|
181
199
|
await cleanupStaleSessions(db, 30);
|
|
182
200
|
|
|
183
201
|
const session = await createSession(db, {
|
|
184
|
-
name:
|
|
185
|
-
project_root:
|
|
186
|
-
machine_id:
|
|
202
|
+
name: input.name,
|
|
203
|
+
project_root: input.project_root,
|
|
204
|
+
machine_id: input.machine_id,
|
|
187
205
|
user_id: userId,
|
|
188
206
|
});
|
|
189
207
|
|
|
190
|
-
const activeSessions = await listSessions(db, { project_root:
|
|
208
|
+
const activeSessions = await listSessions(db, { project_root: input.project_root, user_id: userId });
|
|
191
209
|
|
|
192
210
|
return createToolResult(
|
|
193
211
|
JSON.stringify(
|
|
@@ -208,10 +226,16 @@ export async function handleSessionTool(
|
|
|
208
226
|
}
|
|
209
227
|
|
|
210
228
|
case 'collab_session_end': {
|
|
211
|
-
const
|
|
212
|
-
|
|
229
|
+
const validation = validateInput(sessionEndSchema, args);
|
|
230
|
+
if (!validation.success) {
|
|
231
|
+
return createToolResult(
|
|
232
|
+
JSON.stringify({ error: 'INVALID_INPUT', message: validation.error }),
|
|
233
|
+
true
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
const input = validation.data;
|
|
213
237
|
|
|
214
|
-
const session = await getSession(db,
|
|
238
|
+
const session = await getSession(db, input.session_id);
|
|
215
239
|
if (!session) {
|
|
216
240
|
return createToolResult(
|
|
217
241
|
JSON.stringify({ error: 'SESSION_NOT_FOUND', message: 'Session not found' }),
|
|
@@ -219,21 +243,30 @@ export async function handleSessionTool(
|
|
|
219
243
|
);
|
|
220
244
|
}
|
|
221
245
|
|
|
222
|
-
await endSession(db,
|
|
246
|
+
await endSession(db, input.session_id, input.release_claims);
|
|
223
247
|
|
|
224
248
|
return createToolResult(
|
|
225
249
|
JSON.stringify({
|
|
226
250
|
success: true,
|
|
227
|
-
message: `Session ended. All claims marked as ${
|
|
251
|
+
message: `Session ended. All claims marked as ${input.release_claims === 'complete' ? 'completed' : 'abandoned'}.`,
|
|
228
252
|
})
|
|
229
253
|
);
|
|
230
254
|
}
|
|
231
255
|
|
|
232
256
|
case 'collab_session_list': {
|
|
257
|
+
const validation = validateInput(sessionListSchema, args);
|
|
258
|
+
if (!validation.success) {
|
|
259
|
+
return createToolResult(
|
|
260
|
+
JSON.stringify({ error: 'INVALID_INPUT', message: validation.error }),
|
|
261
|
+
true
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
const input = validation.data;
|
|
265
|
+
|
|
233
266
|
// Do not filter by user_id - collaboration tool should show all sessions
|
|
234
267
|
const sessions = await listSessions(db, {
|
|
235
|
-
include_inactive:
|
|
236
|
-
project_root:
|
|
268
|
+
include_inactive: input.include_inactive,
|
|
269
|
+
project_root: input.project_root,
|
|
237
270
|
});
|
|
238
271
|
|
|
239
272
|
// Get active claims count for each session and include status info
|
|
@@ -278,13 +311,18 @@ export async function handleSessionTool(
|
|
|
278
311
|
}
|
|
279
312
|
|
|
280
313
|
case 'collab_session_heartbeat': {
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
314
|
+
const validation = validateInput(sessionHeartbeatSchema, args);
|
|
315
|
+
if (!validation.success) {
|
|
316
|
+
return createToolResult(
|
|
317
|
+
JSON.stringify({ error: 'INVALID_INPUT', message: validation.error }),
|
|
318
|
+
true
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
const input = validation.data;
|
|
284
322
|
|
|
285
|
-
const updated = await updateSessionHeartbeat(db,
|
|
286
|
-
current_task:
|
|
287
|
-
todos,
|
|
323
|
+
const updated = await updateSessionHeartbeat(db, input.session_id, {
|
|
324
|
+
current_task: input.current_task,
|
|
325
|
+
todos: input.todos as TodoItem[] | undefined,
|
|
288
326
|
});
|
|
289
327
|
|
|
290
328
|
if (!updated) {
|
|
@@ -301,18 +339,24 @@ export async function handleSessionTool(
|
|
|
301
339
|
JSON.stringify({
|
|
302
340
|
success: true,
|
|
303
341
|
message: 'Heartbeat updated',
|
|
304
|
-
status_synced: !!(
|
|
342
|
+
status_synced: !!(input.current_task || input.todos),
|
|
305
343
|
})
|
|
306
344
|
);
|
|
307
345
|
}
|
|
308
346
|
|
|
309
347
|
case 'collab_status_update': {
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
348
|
+
const validation = validateInput(statusUpdateSchema, args);
|
|
349
|
+
if (!validation.success) {
|
|
350
|
+
return createToolResult(
|
|
351
|
+
JSON.stringify({ error: 'INVALID_INPUT', message: validation.error }),
|
|
352
|
+
true
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
const input = validation.data;
|
|
356
|
+
const todos = input.todos as TodoItem[] | undefined;
|
|
313
357
|
|
|
314
358
|
// Validate session exists
|
|
315
|
-
const session = await getSession(db,
|
|
359
|
+
const session = await getSession(db, input.session_id);
|
|
316
360
|
if (!session || session.status !== 'active') {
|
|
317
361
|
return createToolResult(
|
|
318
362
|
JSON.stringify({
|
|
@@ -323,8 +367,8 @@ export async function handleSessionTool(
|
|
|
323
367
|
);
|
|
324
368
|
}
|
|
325
369
|
|
|
326
|
-
await updateSessionStatus(db,
|
|
327
|
-
current_task:
|
|
370
|
+
await updateSessionStatus(db, input.session_id, {
|
|
371
|
+
current_task: input.current_task,
|
|
328
372
|
todos,
|
|
329
373
|
});
|
|
330
374
|
|
|
@@ -343,28 +387,24 @@ export async function handleSessionTool(
|
|
|
343
387
|
JSON.stringify({
|
|
344
388
|
success: true,
|
|
345
389
|
message: 'Status updated successfully.',
|
|
346
|
-
current_task:
|
|
390
|
+
current_task: input.current_task ?? null,
|
|
347
391
|
progress,
|
|
348
392
|
})
|
|
349
393
|
);
|
|
350
394
|
}
|
|
351
395
|
|
|
352
396
|
case 'collab_config': {
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
// Validate session_id input
|
|
356
|
-
if (!sessionId || typeof sessionId !== 'string') {
|
|
397
|
+
const validation = validateInput(configSchema, args);
|
|
398
|
+
if (!validation.success) {
|
|
357
399
|
return createToolResult(
|
|
358
|
-
JSON.stringify({
|
|
359
|
-
error: 'INVALID_INPUT',
|
|
360
|
-
message: 'session_id is required',
|
|
361
|
-
}),
|
|
400
|
+
JSON.stringify({ error: 'INVALID_INPUT', message: validation.error }),
|
|
362
401
|
true
|
|
363
402
|
);
|
|
364
403
|
}
|
|
404
|
+
const input = validation.data;
|
|
365
405
|
|
|
366
406
|
// Validate session exists
|
|
367
|
-
const session = await getSession(db,
|
|
407
|
+
const session = await getSession(db, input.session_id);
|
|
368
408
|
if (!session || session.status !== 'active') {
|
|
369
409
|
return createToolResult(
|
|
370
410
|
JSON.stringify({
|
|
@@ -387,17 +427,17 @@ export async function handleSessionTool(
|
|
|
387
427
|
|
|
388
428
|
// Update with new values
|
|
389
429
|
const newConfig: SessionConfig = {
|
|
390
|
-
mode:
|
|
391
|
-
allow_release_others:
|
|
392
|
-
?
|
|
430
|
+
mode: input.mode ?? currentConfig.mode,
|
|
431
|
+
allow_release_others: input.allow_release_others !== undefined
|
|
432
|
+
? input.allow_release_others
|
|
393
433
|
: currentConfig.allow_release_others,
|
|
394
|
-
auto_release_stale:
|
|
395
|
-
?
|
|
434
|
+
auto_release_stale: input.auto_release_stale !== undefined
|
|
435
|
+
? input.auto_release_stale
|
|
396
436
|
: currentConfig.auto_release_stale,
|
|
397
|
-
stale_threshold_hours:
|
|
437
|
+
stale_threshold_hours: input.stale_threshold_hours ?? currentConfig.stale_threshold_hours,
|
|
398
438
|
};
|
|
399
439
|
|
|
400
|
-
await updateSessionConfig(db,
|
|
440
|
+
await updateSessionConfig(db, input.session_id, newConfig);
|
|
401
441
|
|
|
402
442
|
return createToolResult(
|
|
403
443
|
JSON.stringify({
|