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/db/sqlite-adapter.ts
CHANGED
|
@@ -39,8 +39,7 @@ class SqlitePreparedStatement implements PreparedStatement {
|
|
|
39
39
|
|
|
40
40
|
constructor(
|
|
41
41
|
private db: Database.Database,
|
|
42
|
-
private sql: string
|
|
43
|
-
private onWrite?: () => void
|
|
42
|
+
private sql: string
|
|
44
43
|
) {}
|
|
45
44
|
|
|
46
45
|
bind(...values: unknown[]): PreparedStatement {
|
|
@@ -66,8 +65,8 @@ class SqlitePreparedStatement implements PreparedStatement {
|
|
|
66
65
|
async run(): Promise<{ meta: { changes: number } }> {
|
|
67
66
|
const stmt = this.db.prepare(this.sql);
|
|
68
67
|
const result = stmt.run(...this.bindings);
|
|
69
|
-
//
|
|
70
|
-
|
|
68
|
+
// Note: wal_autocheckpoint handles periodic checkpoints automatically
|
|
69
|
+
// No need to checkpoint after every write - reduces I/O overhead
|
|
71
70
|
return {
|
|
72
71
|
meta: { changes: result.changes },
|
|
73
72
|
};
|
|
@@ -104,12 +103,13 @@ class SqliteDatabase implements DatabaseAdapter {
|
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
// Force checkpoint to make changes visible to other processes
|
|
106
|
+
// Only called after batch operations, not after individual writes
|
|
107
107
|
checkpoint(): void {
|
|
108
108
|
this.db.pragma('wal_checkpoint(PASSIVE)');
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
prepare(sql: string): PreparedStatement {
|
|
112
|
-
return new SqlitePreparedStatement(this.db, sql
|
|
112
|
+
return new SqlitePreparedStatement(this.db, sql);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
async batch(statements: PreparedStatement[]): Promise<QueryResult<unknown>[]> {
|
|
@@ -124,7 +124,7 @@ class SqliteDatabase implements DatabaseAdapter {
|
|
|
124
124
|
});
|
|
125
125
|
});
|
|
126
126
|
const results = transaction();
|
|
127
|
-
// Checkpoint after batch write
|
|
127
|
+
// Checkpoint after batch write to ensure visibility to other processes
|
|
128
128
|
this.checkpoint();
|
|
129
129
|
return results;
|
|
130
130
|
}
|
package/src/db/types.ts
CHANGED
|
@@ -120,6 +120,62 @@ export interface ClaimFile {
|
|
|
120
120
|
is_pattern: number; // 0 or 1, SQLite boolean
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
// Symbol types for fine-grained conflict detection
|
|
124
|
+
export type SymbolType = 'function' | 'class' | 'method' | 'variable' | 'block' | 'other';
|
|
125
|
+
|
|
126
|
+
export interface ClaimSymbol {
|
|
127
|
+
id: number;
|
|
128
|
+
claim_id: string;
|
|
129
|
+
file_path: string;
|
|
130
|
+
symbol_name: string;
|
|
131
|
+
symbol_type: SymbolType;
|
|
132
|
+
created_at: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Input format for claiming symbols
|
|
136
|
+
export interface SymbolClaim {
|
|
137
|
+
file: string;
|
|
138
|
+
symbols: string[];
|
|
139
|
+
symbol_type?: SymbolType;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Symbol reference for impact tracking
|
|
143
|
+
export interface SymbolReference {
|
|
144
|
+
id: number;
|
|
145
|
+
source_file: string;
|
|
146
|
+
source_symbol: string;
|
|
147
|
+
ref_file: string;
|
|
148
|
+
ref_line: number | null;
|
|
149
|
+
ref_context: string | null;
|
|
150
|
+
session_id: string;
|
|
151
|
+
created_at: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Input format for storing references
|
|
155
|
+
export interface ReferenceInput {
|
|
156
|
+
source_file: string;
|
|
157
|
+
source_symbol: string;
|
|
158
|
+
references: Array<{
|
|
159
|
+
file: string;
|
|
160
|
+
line: number;
|
|
161
|
+
context?: string;
|
|
162
|
+
}>;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Impact analysis result
|
|
166
|
+
export interface ImpactInfo {
|
|
167
|
+
symbol: string;
|
|
168
|
+
file: string;
|
|
169
|
+
affected_claims: Array<{
|
|
170
|
+
claim_id: string;
|
|
171
|
+
session_name: string | null;
|
|
172
|
+
intent: string;
|
|
173
|
+
affected_symbols: string[];
|
|
174
|
+
}>;
|
|
175
|
+
reference_count: number;
|
|
176
|
+
affected_files: string[];
|
|
177
|
+
}
|
|
178
|
+
|
|
123
179
|
export interface Message {
|
|
124
180
|
id: string;
|
|
125
181
|
from_session_id: string;
|
|
@@ -152,4 +208,8 @@ export interface ConflictInfo {
|
|
|
152
208
|
intent: string;
|
|
153
209
|
scope: ClaimScope;
|
|
154
210
|
created_at: string;
|
|
211
|
+
// Symbol-level conflict info (optional)
|
|
212
|
+
symbol_name?: string;
|
|
213
|
+
symbol_type?: SymbolType;
|
|
214
|
+
conflict_level: 'file' | 'symbol';
|
|
155
215
|
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// Zod schemas for MCP tool input validation
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
// Common schemas
|
|
5
|
+
export const sessionIdSchema = z.string().min(1, 'session_id is required');
|
|
6
|
+
export const claimIdSchema = z.string().min(1, 'claim_id is required');
|
|
7
|
+
export const filePathSchema = z.string().min(1);
|
|
8
|
+
export const filesArraySchema = z.array(filePathSchema).min(1, 'At least one file is required');
|
|
9
|
+
|
|
10
|
+
// Symbol claim schema
|
|
11
|
+
export const symbolClaimSchema = z.object({
|
|
12
|
+
file: z.string().min(1),
|
|
13
|
+
symbols: z.array(z.string().min(1)).min(1),
|
|
14
|
+
symbol_type: z.enum(['function', 'class', 'method', 'variable', 'block', 'other']).optional(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const symbolClaimsArraySchema = z.array(symbolClaimSchema);
|
|
18
|
+
|
|
19
|
+
// Claim scope schema
|
|
20
|
+
export const claimScopeSchema = z.enum(['small', 'medium', 'large']).default('medium');
|
|
21
|
+
|
|
22
|
+
// Claim status schema
|
|
23
|
+
export const claimStatusSchema = z.enum(['completed', 'abandoned']);
|
|
24
|
+
|
|
25
|
+
// Session tools input schemas
|
|
26
|
+
export const sessionStartSchema = z.object({
|
|
27
|
+
project_root: z.string().min(1, 'project_root is required'),
|
|
28
|
+
name: z.string().optional(),
|
|
29
|
+
machine_id: z.string().optional(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const sessionEndSchema = z.object({
|
|
33
|
+
session_id: sessionIdSchema,
|
|
34
|
+
release_claims: z.enum(['complete', 'abandon']).default('abandon'),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export const sessionListSchema = z.object({
|
|
38
|
+
include_inactive: z.boolean().optional(),
|
|
39
|
+
project_root: z.string().optional(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const sessionHeartbeatSchema = z.object({
|
|
43
|
+
session_id: sessionIdSchema,
|
|
44
|
+
current_task: z.string().optional(),
|
|
45
|
+
todos: z.array(z.object({
|
|
46
|
+
content: z.string(),
|
|
47
|
+
status: z.enum(['pending', 'in_progress', 'completed']),
|
|
48
|
+
})).optional(),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export const statusUpdateSchema = z.object({
|
|
52
|
+
session_id: sessionIdSchema,
|
|
53
|
+
current_task: z.string().optional(),
|
|
54
|
+
todos: z.array(z.object({
|
|
55
|
+
content: z.string(),
|
|
56
|
+
status: z.enum(['pending', 'in_progress', 'completed']),
|
|
57
|
+
})).optional(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const configSchema = z.object({
|
|
61
|
+
session_id: sessionIdSchema,
|
|
62
|
+
mode: z.enum(['strict', 'smart', 'bypass']).optional(),
|
|
63
|
+
allow_release_others: z.boolean().optional(),
|
|
64
|
+
auto_release_stale: z.boolean().optional(),
|
|
65
|
+
stale_threshold_hours: z.number().min(0).optional(),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Claim tools input schemas
|
|
69
|
+
export const claimCreateSchema = z.object({
|
|
70
|
+
session_id: sessionIdSchema,
|
|
71
|
+
files: z.array(filePathSchema).optional(),
|
|
72
|
+
symbols: symbolClaimsArraySchema.optional(),
|
|
73
|
+
intent: z.string().min(1, 'intent is required'),
|
|
74
|
+
scope: claimScopeSchema.optional(),
|
|
75
|
+
}).refine(
|
|
76
|
+
(data) => (data.files && data.files.length > 0) || (data.symbols && data.symbols.length > 0),
|
|
77
|
+
{ message: 'Either files or symbols must be provided' }
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
export const claimCheckSchema = z.object({
|
|
81
|
+
files: filesArraySchema,
|
|
82
|
+
symbols: symbolClaimsArraySchema.optional(),
|
|
83
|
+
session_id: z.string().optional(),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export const claimReleaseSchema = z.object({
|
|
87
|
+
session_id: sessionIdSchema,
|
|
88
|
+
claim_id: claimIdSchema,
|
|
89
|
+
status: claimStatusSchema,
|
|
90
|
+
summary: z.string().optional(),
|
|
91
|
+
force: z.boolean().optional(),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export const claimListSchema = z.object({
|
|
95
|
+
session_id: z.string().optional(),
|
|
96
|
+
status: z.enum(['active', 'completed', 'abandoned', 'all']).optional(),
|
|
97
|
+
project_root: z.string().optional(),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Message tools input schemas
|
|
101
|
+
export const messageSendSchema = z.object({
|
|
102
|
+
from_session_id: sessionIdSchema,
|
|
103
|
+
to_session_id: z.string().optional(),
|
|
104
|
+
content: z.string().min(1, 'content is required'),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
export const messageListSchema = z.object({
|
|
108
|
+
session_id: sessionIdSchema,
|
|
109
|
+
unread_only: z.boolean().optional(),
|
|
110
|
+
mark_as_read: z.boolean().optional(),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Decision tools input schemas
|
|
114
|
+
export const decisionAddSchema = z.object({
|
|
115
|
+
session_id: sessionIdSchema,
|
|
116
|
+
category: z.enum(['architecture', 'naming', 'api', 'database', 'ui', 'other']).optional(),
|
|
117
|
+
title: z.string().min(1, 'title is required'),
|
|
118
|
+
description: z.string().min(1, 'description is required'),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export const decisionListSchema = z.object({
|
|
122
|
+
category: z.enum(['architecture', 'naming', 'api', 'database', 'ui', 'other']).optional(),
|
|
123
|
+
limit: z.number().min(1).max(100).optional(),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// LSP tools input schemas
|
|
127
|
+
// Using z.ZodType to properly type recursive schema
|
|
128
|
+
type LspSymbol = {
|
|
129
|
+
name: string;
|
|
130
|
+
kind: number;
|
|
131
|
+
range?: { start: { line: number; character: number }; end: { line: number; character: number } };
|
|
132
|
+
children?: LspSymbol[];
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const lspSymbolSchema: z.ZodType<LspSymbol> = z.object({
|
|
136
|
+
name: z.string(),
|
|
137
|
+
kind: z.number(),
|
|
138
|
+
range: z.object({
|
|
139
|
+
start: z.object({ line: z.number(), character: z.number() }),
|
|
140
|
+
end: z.object({ line: z.number(), character: z.number() }),
|
|
141
|
+
}).optional(),
|
|
142
|
+
children: z.lazy(() => z.array(lspSymbolSchema)).optional(),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
export const analyzeSymbolsSchema = z.object({
|
|
146
|
+
session_id: sessionIdSchema,
|
|
147
|
+
files: z.array(z.object({
|
|
148
|
+
file: z.string(),
|
|
149
|
+
symbols: z.array(lspSymbolSchema),
|
|
150
|
+
})),
|
|
151
|
+
check_symbols: z.array(z.string()).optional(),
|
|
152
|
+
references: z.array(z.object({
|
|
153
|
+
symbol: z.string(),
|
|
154
|
+
file: z.string(),
|
|
155
|
+
references: z.array(z.object({
|
|
156
|
+
file: z.string(),
|
|
157
|
+
line: z.number(),
|
|
158
|
+
context: z.string().optional(),
|
|
159
|
+
})),
|
|
160
|
+
})).optional(),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
export const validateSymbolsSchema = z.object({
|
|
164
|
+
file: z.string().min(1),
|
|
165
|
+
symbols: z.array(z.string().min(1)),
|
|
166
|
+
lsp_symbols: z.array(lspSymbolSchema),
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
export const storeReferencesSchema = z.object({
|
|
170
|
+
session_id: sessionIdSchema,
|
|
171
|
+
references: z.array(z.object({
|
|
172
|
+
source_file: z.string(),
|
|
173
|
+
source_symbol: z.string(),
|
|
174
|
+
references: z.array(z.object({
|
|
175
|
+
file: z.string(),
|
|
176
|
+
line: z.number(),
|
|
177
|
+
context: z.string().optional(),
|
|
178
|
+
})),
|
|
179
|
+
})),
|
|
180
|
+
clear_existing: z.boolean().optional(),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
export const impactAnalysisSchema = z.object({
|
|
184
|
+
session_id: sessionIdSchema,
|
|
185
|
+
file: z.string().min(1),
|
|
186
|
+
symbol: z.string().min(1),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Helper function to validate and return parsed data or error result
|
|
190
|
+
export function validateInput<T>(
|
|
191
|
+
schema: z.ZodSchema<T>,
|
|
192
|
+
data: unknown
|
|
193
|
+
): { success: true; data: T } | { success: false; error: string } {
|
|
194
|
+
const result = schema.safeParse(data);
|
|
195
|
+
if (result.success) {
|
|
196
|
+
return { success: true, data: result.data };
|
|
197
|
+
}
|
|
198
|
+
const errors = result.error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ');
|
|
199
|
+
return { success: false, error: errors };
|
|
200
|
+
}
|
package/src/mcp/server.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { sessionTools, handleSessionTool } from './tools/session';
|
|
|
19
19
|
import { claimTools, handleClaimTool } from './tools/claim';
|
|
20
20
|
import { messageTools, handleMessageTool } from './tools/message';
|
|
21
21
|
import { decisionTools, handleDecisionTool } from './tools/decision';
|
|
22
|
+
import { lspTools, handleLspTool } from './tools/lsp';
|
|
22
23
|
import type { AuthContext } from '../auth/types';
|
|
23
24
|
import { VERSION, SERVER_NAME, SERVER_INSTRUCTIONS } from '../constants.js';
|
|
24
25
|
|
|
@@ -32,7 +33,7 @@ const CAPABILITIES: McpCapabilities = {
|
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
// Combine all tools
|
|
35
|
-
const ALL_TOOLS: McpTool[] = [...sessionTools, ...claimTools, ...messageTools, ...decisionTools];
|
|
36
|
+
const ALL_TOOLS: McpTool[] = [...sessionTools, ...claimTools, ...messageTools, ...decisionTools, ...lspTools];
|
|
36
37
|
|
|
37
38
|
export class McpServer {
|
|
38
39
|
private authContext?: AuthContext;
|
|
@@ -105,6 +106,13 @@ export class McpServer {
|
|
|
105
106
|
result = await handleMessageTool(this.db, name, args);
|
|
106
107
|
} else if (name.startsWith('collab_decision_')) {
|
|
107
108
|
result = await handleDecisionTool(this.db, name, args);
|
|
109
|
+
} else if (
|
|
110
|
+
name === 'collab_analyze_symbols' ||
|
|
111
|
+
name === 'collab_validate_symbols' ||
|
|
112
|
+
name === 'collab_store_references' ||
|
|
113
|
+
name === 'collab_impact_analysis'
|
|
114
|
+
) {
|
|
115
|
+
result = await handleLspTool(this.db, name, args);
|
|
108
116
|
} else {
|
|
109
117
|
result = createToolResult(`Unknown tool: ${name}`, true);
|
|
110
118
|
}
|
|
@@ -148,6 +156,13 @@ export async function handleMcpRequest(
|
|
|
148
156
|
return await handleMessageTool(db, name, args);
|
|
149
157
|
} else if (name.startsWith('collab_decision_')) {
|
|
150
158
|
return await handleDecisionTool(db, name, args);
|
|
159
|
+
} else if (
|
|
160
|
+
name === 'collab_analyze_symbols' ||
|
|
161
|
+
name === 'collab_validate_symbols' ||
|
|
162
|
+
name === 'collab_store_references' ||
|
|
163
|
+
name === 'collab_impact_analysis'
|
|
164
|
+
) {
|
|
165
|
+
return await handleLspTool(db, name, args);
|
|
151
166
|
} else {
|
|
152
167
|
return createToolResult(`Unknown tool: ${name}`, true);
|
|
153
168
|
}
|