@papyruslabsai/seshat-mcp 0.1.0 → 0.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.
- package/dist/index.d.ts +29 -14
- package/dist/index.js +257 -27
- package/dist/loader.d.ts +29 -1
- package/dist/loader.js +119 -0
- package/dist/tools/functors.d.ts +34 -0
- package/dist/tools/functors.js +554 -0
- package/dist/tools/index.d.ts +35 -3
- package/dist/tools/index.js +74 -25
- package/dist/types.d.ts +36 -1
- package/package.json +35 -35
package/dist/index.d.ts
CHANGED
|
@@ -3,22 +3,37 @@
|
|
|
3
3
|
* @papyruslabs/seshat-mcp — Semantic MCP Server
|
|
4
4
|
*
|
|
5
5
|
* Exposes a codebase's 9D JSTF-T coordinate space as queryable MCP tools.
|
|
6
|
-
* Reads .seshat/_bundle.json from
|
|
6
|
+
* Reads .seshat/_bundle.json from project directories.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* get_data_flow — δ dimension: inputs, outputs, mutations
|
|
13
|
-
* find_by_constraint — κ dimension search (AUTH, THROWS, DB_ACCESS, etc.)
|
|
14
|
-
* get_blast_radius — Theorem 9.4: affected set for given entity IDs
|
|
15
|
-
* list_modules — Group entities by layer, module, file, or language
|
|
16
|
-
* get_topology — API topology (routes, plugins, auth, tables)
|
|
8
|
+
* Multi-project mode:
|
|
9
|
+
* Set SESHAT_PROJECTS env var to comma-separated paths or a glob pattern.
|
|
10
|
+
* In multi-project mode, all tools require a `project` parameter.
|
|
11
|
+
* Use `list_projects` to see available projects.
|
|
17
12
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
13
|
+
* Single-project mode (default):
|
|
14
|
+
* When SESHAT_PROJECTS is not set, loads from CWD. No `project` param needed.
|
|
15
|
+
*
|
|
16
|
+
* Tools (8 core + 7 interpretation functors + 1 meta):
|
|
17
|
+
* list_projects — Show loaded projects with entity counts
|
|
18
|
+
* query_entities — Search entities by name, layer, module, language
|
|
19
|
+
* get_entity — Full 9D coordinate dump for one entity
|
|
20
|
+
* get_dependencies — ε edge traversal (callers/callees with depth control)
|
|
21
|
+
* get_data_flow — δ dimension: inputs, outputs, mutations
|
|
22
|
+
* find_by_constraint — κ dimension search (AUTH, THROWS, DB_ACCESS, etc.)
|
|
23
|
+
* get_blast_radius — Theorem 9.4: affected set for given entity IDs
|
|
24
|
+
* list_modules — Group entities by layer, module, file, or language
|
|
25
|
+
* get_topology — API topology (routes, plugins, auth, tables)
|
|
26
|
+
* find_dead_code — Unreachable entities via ε-graph BFS
|
|
27
|
+
* find_layer_violations — ε edges violating architectural layer ordering
|
|
28
|
+
* get_coupling_metrics — Module coupling/cohesion/instability from ε-graph
|
|
29
|
+
* get_auth_matrix — Auth coverage across API-facing entities from κ
|
|
30
|
+
* find_error_gaps — Fallible callees whose callers lack try/catch
|
|
31
|
+
* get_test_coverage — Entities exercised by tests vs uncovered
|
|
32
|
+
* get_optimal_context — Greedy knapsack: max relevance per token for LLM context
|
|
20
33
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
34
|
+
* Usage:
|
|
35
|
+
* npx @papyruslabs/seshat-mcp # single project (CWD)
|
|
36
|
+
* SESHAT_PROJECTS=/path/a,/path/b npx @papyruslabs/seshat-mcp # multi-project
|
|
37
|
+
* SESHAT_PROJECTS="/home/user/projects/*" npx @papyruslabs/seshat-mcp # glob
|
|
23
38
|
*/
|
|
24
39
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -3,37 +3,109 @@
|
|
|
3
3
|
* @papyruslabs/seshat-mcp — Semantic MCP Server
|
|
4
4
|
*
|
|
5
5
|
* Exposes a codebase's 9D JSTF-T coordinate space as queryable MCP tools.
|
|
6
|
-
* Reads .seshat/_bundle.json from
|
|
6
|
+
* Reads .seshat/_bundle.json from project directories.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* get_data_flow — δ dimension: inputs, outputs, mutations
|
|
13
|
-
* find_by_constraint — κ dimension search (AUTH, THROWS, DB_ACCESS, etc.)
|
|
14
|
-
* get_blast_radius — Theorem 9.4: affected set for given entity IDs
|
|
15
|
-
* list_modules — Group entities by layer, module, file, or language
|
|
16
|
-
* get_topology — API topology (routes, plugins, auth, tables)
|
|
8
|
+
* Multi-project mode:
|
|
9
|
+
* Set SESHAT_PROJECTS env var to comma-separated paths or a glob pattern.
|
|
10
|
+
* In multi-project mode, all tools require a `project` parameter.
|
|
11
|
+
* Use `list_projects` to see available projects.
|
|
17
12
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
13
|
+
* Single-project mode (default):
|
|
14
|
+
* When SESHAT_PROJECTS is not set, loads from CWD. No `project` param needed.
|
|
15
|
+
*
|
|
16
|
+
* Tools (8 core + 7 interpretation functors + 1 meta):
|
|
17
|
+
* list_projects — Show loaded projects with entity counts
|
|
18
|
+
* query_entities — Search entities by name, layer, module, language
|
|
19
|
+
* get_entity — Full 9D coordinate dump for one entity
|
|
20
|
+
* get_dependencies — ε edge traversal (callers/callees with depth control)
|
|
21
|
+
* get_data_flow — δ dimension: inputs, outputs, mutations
|
|
22
|
+
* find_by_constraint — κ dimension search (AUTH, THROWS, DB_ACCESS, etc.)
|
|
23
|
+
* get_blast_radius — Theorem 9.4: affected set for given entity IDs
|
|
24
|
+
* list_modules — Group entities by layer, module, file, or language
|
|
25
|
+
* get_topology — API topology (routes, plugins, auth, tables)
|
|
26
|
+
* find_dead_code — Unreachable entities via ε-graph BFS
|
|
27
|
+
* find_layer_violations — ε edges violating architectural layer ordering
|
|
28
|
+
* get_coupling_metrics — Module coupling/cohesion/instability from ε-graph
|
|
29
|
+
* get_auth_matrix — Auth coverage across API-facing entities from κ
|
|
30
|
+
* find_error_gaps — Fallible callees whose callers lack try/catch
|
|
31
|
+
* get_test_coverage — Entities exercised by tests vs uncovered
|
|
32
|
+
* get_optimal_context — Greedy knapsack: max relevance per token for LLM context
|
|
20
33
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
34
|
+
* Usage:
|
|
35
|
+
* npx @papyruslabs/seshat-mcp # single project (CWD)
|
|
36
|
+
* SESHAT_PROJECTS=/path/a,/path/b npx @papyruslabs/seshat-mcp # multi-project
|
|
37
|
+
* SESHAT_PROJECTS="/home/user/projects/*" npx @papyruslabs/seshat-mcp # glob
|
|
23
38
|
*/
|
|
39
|
+
import fs from 'fs';
|
|
40
|
+
import path from 'path';
|
|
24
41
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
25
42
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
26
43
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
27
|
-
import {
|
|
44
|
+
import { MultiLoader } from './loader.js';
|
|
28
45
|
import { initTools, queryEntities, getEntity, getDependencies, getDataFlow, findByConstraint, getBlastRadius, listModules, getTopology, } from './tools/index.js';
|
|
46
|
+
import { findDeadCode, findLayerViolations, getCouplingMetrics, getAuthMatrix, findErrorGaps, getTestCoverage, getOptimalContext, } from './tools/functors.js';
|
|
47
|
+
// ─── Project Discovery ───────────────────────────────────────────
|
|
48
|
+
/**
|
|
49
|
+
* Discover project directories from SESHAT_PROJECTS env var.
|
|
50
|
+
*
|
|
51
|
+
* - Not set → [process.cwd()] (backward compat)
|
|
52
|
+
* - Comma-separated paths → resolve each
|
|
53
|
+
* - Glob pattern (contains *) → scan parent dir for subdirs with .seshat/_bundle.json
|
|
54
|
+
*/
|
|
55
|
+
function discoverProjects() {
|
|
56
|
+
const env = process.env.SESHAT_PROJECTS;
|
|
57
|
+
if (!env)
|
|
58
|
+
return [process.cwd()];
|
|
59
|
+
const trimmed = env.trim();
|
|
60
|
+
// Glob pattern: /path/to/projects/*
|
|
61
|
+
if (trimmed.includes('*')) {
|
|
62
|
+
const globDir = trimmed.replace(/\/?\*$/, '');
|
|
63
|
+
const resolved = path.resolve(globDir);
|
|
64
|
+
if (!fs.existsSync(resolved)) {
|
|
65
|
+
process.stderr.write(`SESHAT_PROJECTS glob dir not found: ${resolved}\n`);
|
|
66
|
+
return [process.cwd()];
|
|
67
|
+
}
|
|
68
|
+
const dirs = [];
|
|
69
|
+
for (const entry of fs.readdirSync(resolved, { withFileTypes: true })) {
|
|
70
|
+
if (!entry.isDirectory())
|
|
71
|
+
continue;
|
|
72
|
+
const candidate = path.join(resolved, entry.name);
|
|
73
|
+
const bundlePath = path.join(candidate, '.seshat', '_bundle.json');
|
|
74
|
+
if (fs.existsSync(bundlePath)) {
|
|
75
|
+
dirs.push(candidate);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (dirs.length === 0) {
|
|
79
|
+
process.stderr.write(`SESHAT_PROJECTS: no dirs with .seshat/_bundle.json found in ${resolved}\n`);
|
|
80
|
+
return [process.cwd()];
|
|
81
|
+
}
|
|
82
|
+
return dirs;
|
|
83
|
+
}
|
|
84
|
+
// Comma-separated paths
|
|
85
|
+
return trimmed.split(',').map(p => path.resolve(p.trim())).filter(p => p.length > 0);
|
|
86
|
+
}
|
|
87
|
+
// ─── Project param definition (injected into all tool schemas) ───
|
|
88
|
+
const projectParam = {
|
|
89
|
+
type: 'string',
|
|
90
|
+
description: 'Project name (required in multi-project mode). Use list_projects to see available projects.',
|
|
91
|
+
};
|
|
29
92
|
// ─── Tool Definitions ─────────────────────────────────────────────
|
|
30
93
|
const TOOLS = [
|
|
94
|
+
{
|
|
95
|
+
name: 'list_projects',
|
|
96
|
+
description: 'List all loaded projects with entity counts, languages, and metadata. Call this first to see what projects are available, then pass the project name to other tools.',
|
|
97
|
+
inputSchema: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
properties: {},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
31
102
|
{
|
|
32
103
|
name: 'query_entities',
|
|
33
104
|
description: 'Search entities in the 9D semantic coordinate space. Filter by name, architectural layer (route/service/repository/component), module, or source language. Returns entity summaries with constraint tags.',
|
|
34
105
|
inputSchema: {
|
|
35
106
|
type: 'object',
|
|
36
107
|
properties: {
|
|
108
|
+
project: projectParam,
|
|
37
109
|
query: {
|
|
38
110
|
type: 'string',
|
|
39
111
|
description: 'Search term — matches against entity name, ID, source file, and module',
|
|
@@ -63,6 +135,7 @@ const TOOLS = [
|
|
|
63
135
|
inputSchema: {
|
|
64
136
|
type: 'object',
|
|
65
137
|
properties: {
|
|
138
|
+
project: projectParam,
|
|
66
139
|
id: {
|
|
67
140
|
type: 'string',
|
|
68
141
|
description: 'Entity ID or name',
|
|
@@ -77,6 +150,7 @@ const TOOLS = [
|
|
|
77
150
|
inputSchema: {
|
|
78
151
|
type: 'object',
|
|
79
152
|
properties: {
|
|
153
|
+
project: projectParam,
|
|
80
154
|
entity_id: {
|
|
81
155
|
type: 'string',
|
|
82
156
|
description: 'Entity ID or name',
|
|
@@ -100,6 +174,7 @@ const TOOLS = [
|
|
|
100
174
|
inputSchema: {
|
|
101
175
|
type: 'object',
|
|
102
176
|
properties: {
|
|
177
|
+
project: projectParam,
|
|
103
178
|
entity_id: {
|
|
104
179
|
type: 'string',
|
|
105
180
|
description: 'Entity ID or name',
|
|
@@ -114,6 +189,7 @@ const TOOLS = [
|
|
|
114
189
|
inputSchema: {
|
|
115
190
|
type: 'object',
|
|
116
191
|
properties: {
|
|
192
|
+
project: projectParam,
|
|
117
193
|
constraint: {
|
|
118
194
|
type: 'string',
|
|
119
195
|
description: 'Constraint tag to search for: AUTH, VALIDATED, PURE, THROWS, DB_ACCESS, NETWORK_IO, IMP, etc.',
|
|
@@ -128,6 +204,7 @@ const TOOLS = [
|
|
|
128
204
|
inputSchema: {
|
|
129
205
|
type: 'object',
|
|
130
206
|
properties: {
|
|
207
|
+
project: projectParam,
|
|
131
208
|
entity_ids: {
|
|
132
209
|
type: 'array',
|
|
133
210
|
items: { type: 'string' },
|
|
@@ -143,6 +220,7 @@ const TOOLS = [
|
|
|
143
220
|
inputSchema: {
|
|
144
221
|
type: 'object',
|
|
145
222
|
properties: {
|
|
223
|
+
project: projectParam,
|
|
146
224
|
group_by: {
|
|
147
225
|
type: 'string',
|
|
148
226
|
enum: ['layer', 'module', 'file', 'language'],
|
|
@@ -156,28 +234,140 @@ const TOOLS = [
|
|
|
156
234
|
description: 'Get the API topology: routes, plugins, auth patterns, and database tables. Built from the χ (context) and ε (edges) dimensions across all entities.',
|
|
157
235
|
inputSchema: {
|
|
158
236
|
type: 'object',
|
|
159
|
-
properties: {
|
|
237
|
+
properties: {
|
|
238
|
+
project: projectParam,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
// ─── Interpretation Functor Tools ────────────────────────────────
|
|
243
|
+
{
|
|
244
|
+
name: 'find_dead_code',
|
|
245
|
+
description: 'Find unreachable entities (dead code candidates). BFS from entry points (routes, exported functions, tests, plugins) through the ε call graph. Entities not reachable from any entry point are flagged.',
|
|
246
|
+
inputSchema: {
|
|
247
|
+
type: 'object',
|
|
248
|
+
properties: {
|
|
249
|
+
project: projectParam,
|
|
250
|
+
include_tests: {
|
|
251
|
+
type: 'boolean',
|
|
252
|
+
description: 'Include test entities in dead code results (default: false)',
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: 'find_layer_violations',
|
|
259
|
+
description: 'Detect architectural layer violations in the ε call graph. Finds backward calls (lower layer calling higher layer, e.g. repository → route) and skip-layer calls (jumping over multiple layers). Uses χ.layer for classification.',
|
|
260
|
+
inputSchema: {
|
|
261
|
+
type: 'object',
|
|
262
|
+
properties: {
|
|
263
|
+
project: projectParam,
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
name: 'get_coupling_metrics',
|
|
269
|
+
description: 'Compute coupling, cohesion, and instability metrics for modules or layers. Analyzes ε edges between and within groups. High coupling + low cohesion = candidates for refactoring.',
|
|
270
|
+
inputSchema: {
|
|
271
|
+
type: 'object',
|
|
272
|
+
properties: {
|
|
273
|
+
project: projectParam,
|
|
274
|
+
group_by: {
|
|
275
|
+
type: 'string',
|
|
276
|
+
enum: ['module', 'layer'],
|
|
277
|
+
description: 'Group entities by module or layer (default: module)',
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: 'get_auth_matrix',
|
|
284
|
+
description: 'Analyze authentication coverage across all API-facing entities. Shows which routes/controllers have auth requirements (from κ.auth) and which don\'t. Detects inconsistencies like DB access without auth.',
|
|
285
|
+
inputSchema: {
|
|
286
|
+
type: 'object',
|
|
287
|
+
properties: {
|
|
288
|
+
project: projectParam,
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
name: 'find_error_gaps',
|
|
294
|
+
description: 'Find error handling gaps: fallible entities (κ.throws, network/db side effects) whose callers lack try/catch (κ.errorHandling). These are crash risk points where exceptions can propagate unhandled.',
|
|
295
|
+
inputSchema: {
|
|
296
|
+
type: 'object',
|
|
297
|
+
properties: {
|
|
298
|
+
project: projectParam,
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: 'get_test_coverage',
|
|
304
|
+
description: 'Compute semantic test coverage: which production entities are exercised by test entities via the ε call graph. Optionally weight uncovered entities by blast radius to prioritize what to test first.',
|
|
305
|
+
inputSchema: {
|
|
306
|
+
type: 'object',
|
|
307
|
+
properties: {
|
|
308
|
+
project: projectParam,
|
|
309
|
+
weight_by_blast_radius: {
|
|
310
|
+
type: 'boolean',
|
|
311
|
+
description: 'Rank uncovered entities by blast radius to prioritize testing (default: false, slower)',
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: 'get_optimal_context',
|
|
318
|
+
description: 'Compute the optimal set of related entities to include in an LLM context window for a target entity. Uses greedy knapsack: maximizes relevance per token. Returns entities ranked by relevance with token estimates.',
|
|
319
|
+
inputSchema: {
|
|
320
|
+
type: 'object',
|
|
321
|
+
properties: {
|
|
322
|
+
project: projectParam,
|
|
323
|
+
target_entity: {
|
|
324
|
+
type: 'string',
|
|
325
|
+
description: 'Entity ID or name to build context around',
|
|
326
|
+
},
|
|
327
|
+
max_tokens: {
|
|
328
|
+
type: 'number',
|
|
329
|
+
description: 'Token budget for the context window (default: 8000)',
|
|
330
|
+
},
|
|
331
|
+
strategy: {
|
|
332
|
+
type: 'string',
|
|
333
|
+
enum: ['bfs', 'blast_radius'],
|
|
334
|
+
description: 'Traversal strategy: bfs (faster, local neighborhood) or blast_radius (full affected set)',
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
required: ['target_entity'],
|
|
160
338
|
},
|
|
161
339
|
},
|
|
162
340
|
];
|
|
163
341
|
// ─── Server Setup ─────────────────────────────────────────────────
|
|
164
342
|
async function main() {
|
|
165
|
-
//
|
|
166
|
-
const
|
|
343
|
+
// Discover and load projects
|
|
344
|
+
const projectDirs = discoverProjects();
|
|
345
|
+
const loader = new MultiLoader(projectDirs);
|
|
167
346
|
try {
|
|
168
347
|
loader.load();
|
|
169
348
|
}
|
|
170
349
|
catch (err) {
|
|
171
|
-
// Server will start but tools will return helpful errors
|
|
172
350
|
process.stderr.write(`Warning: ${err.message}\n`);
|
|
173
351
|
}
|
|
174
352
|
initTools(loader);
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
const
|
|
353
|
+
// Build server name
|
|
354
|
+
const projectNames = loader.getProjectNames();
|
|
355
|
+
const totalEntities = loader.totalEntities();
|
|
356
|
+
const isMulti = loader.isMultiProject();
|
|
357
|
+
let serverLabel;
|
|
358
|
+
if (!loader.isLoaded()) {
|
|
359
|
+
serverLabel = 'seshat-mcp (no projects loaded)';
|
|
360
|
+
}
|
|
361
|
+
else if (isMulti) {
|
|
362
|
+
serverLabel = `seshat-mcp (${projectNames.length} projects, ${totalEntities} entities)`;
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
const manifest = loader.getManifest();
|
|
366
|
+
serverLabel = `seshat-mcp (${manifest?.projectName || projectNames[0] || 'unknown'})`;
|
|
367
|
+
}
|
|
178
368
|
const server = new Server({
|
|
179
|
-
name:
|
|
180
|
-
version: '0.
|
|
369
|
+
name: serverLabel,
|
|
370
|
+
version: '0.3.0',
|
|
181
371
|
}, {
|
|
182
372
|
capabilities: {
|
|
183
373
|
tools: {},
|
|
@@ -196,13 +386,25 @@ async function main() {
|
|
|
196
386
|
content: [{
|
|
197
387
|
type: 'text',
|
|
198
388
|
text: JSON.stringify({
|
|
199
|
-
error: 'No .seshat/_bundle.json
|
|
389
|
+
error: 'No projects loaded. Ensure .seshat/_bundle.json exists or set SESHAT_PROJECTS env var.',
|
|
200
390
|
}, null, 2),
|
|
201
391
|
}],
|
|
202
392
|
};
|
|
203
393
|
}
|
|
204
394
|
let result;
|
|
205
395
|
switch (name) {
|
|
396
|
+
// Meta
|
|
397
|
+
case 'list_projects':
|
|
398
|
+
result = {
|
|
399
|
+
multiProject: isMulti,
|
|
400
|
+
projects: loader.getProjectInfo(),
|
|
401
|
+
totalEntities,
|
|
402
|
+
hint: isMulti
|
|
403
|
+
? 'Pass the project name as the "project" parameter to all other tools.'
|
|
404
|
+
: 'Single project mode — the "project" parameter is optional.',
|
|
405
|
+
};
|
|
406
|
+
break;
|
|
407
|
+
// Core tools
|
|
206
408
|
case 'query_entities':
|
|
207
409
|
result = queryEntities(args);
|
|
208
410
|
break;
|
|
@@ -225,7 +427,29 @@ async function main() {
|
|
|
225
427
|
result = listModules(args);
|
|
226
428
|
break;
|
|
227
429
|
case 'get_topology':
|
|
228
|
-
result = getTopology();
|
|
430
|
+
result = getTopology(args);
|
|
431
|
+
break;
|
|
432
|
+
// Interpretation Functors
|
|
433
|
+
case 'find_dead_code':
|
|
434
|
+
result = findDeadCode(args);
|
|
435
|
+
break;
|
|
436
|
+
case 'find_layer_violations':
|
|
437
|
+
result = findLayerViolations(args);
|
|
438
|
+
break;
|
|
439
|
+
case 'get_coupling_metrics':
|
|
440
|
+
result = getCouplingMetrics(args);
|
|
441
|
+
break;
|
|
442
|
+
case 'get_auth_matrix':
|
|
443
|
+
result = getAuthMatrix(args);
|
|
444
|
+
break;
|
|
445
|
+
case 'find_error_gaps':
|
|
446
|
+
result = findErrorGaps(args);
|
|
447
|
+
break;
|
|
448
|
+
case 'get_test_coverage':
|
|
449
|
+
result = getTestCoverage(args);
|
|
450
|
+
break;
|
|
451
|
+
case 'get_optimal_context':
|
|
452
|
+
result = getOptimalContext(args);
|
|
229
453
|
break;
|
|
230
454
|
default:
|
|
231
455
|
result = { error: `Unknown tool: ${name}` };
|
|
@@ -252,7 +476,13 @@ async function main() {
|
|
|
252
476
|
// Start stdio transport
|
|
253
477
|
const transport = new StdioServerTransport();
|
|
254
478
|
await server.connect(transport);
|
|
255
|
-
|
|
479
|
+
if (isMulti) {
|
|
480
|
+
process.stderr.write(`Seshat MCP server started: ${projectNames.length} projects (${totalEntities} entities) — ${projectNames.join(', ')}\n`);
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
const manifest = loader.getManifest();
|
|
484
|
+
process.stderr.write(`Seshat MCP server started: ${manifest?.projectName || 'unknown'} (${totalEntities} entities)\n`);
|
|
485
|
+
}
|
|
256
486
|
}
|
|
257
487
|
main().catch((err) => {
|
|
258
488
|
process.stderr.write(`Fatal: ${err.message}\n`);
|
package/dist/loader.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Discovers and loads .seshat/_bundle.json from the current working directory.
|
|
5
5
|
* Falls back to checking common alternative locations.
|
|
6
6
|
*/
|
|
7
|
-
import type { JstfEntity, Topology, Manifest } from './types.js';
|
|
7
|
+
import type { JstfEntity, Topology, Manifest, ProjectInfo } from './types.js';
|
|
8
8
|
export declare class BundleLoader {
|
|
9
9
|
private entities;
|
|
10
10
|
private topology;
|
|
@@ -20,3 +20,31 @@ export declare class BundleLoader {
|
|
|
20
20
|
getEntityByName(name: string): JstfEntity | undefined;
|
|
21
21
|
isLoaded(): boolean;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Wraps N BundleLoader instances for multi-project access.
|
|
25
|
+
* In single-project mode (1 dir), behaves identically to BundleLoader.
|
|
26
|
+
* In multi-project mode, `project` param is required on all accessors.
|
|
27
|
+
*/
|
|
28
|
+
export declare class MultiLoader {
|
|
29
|
+
private projects;
|
|
30
|
+
private projectPaths;
|
|
31
|
+
private projectEntities;
|
|
32
|
+
private loaded;
|
|
33
|
+
private dirs;
|
|
34
|
+
constructor(projectDirs: string[]);
|
|
35
|
+
load(): void;
|
|
36
|
+
/** Get the resolved project name when only one project is loaded. */
|
|
37
|
+
private defaultProject;
|
|
38
|
+
private resolveProject;
|
|
39
|
+
getEntities(project?: string): JstfEntity[];
|
|
40
|
+
getTopology(project?: string): Topology | null;
|
|
41
|
+
getManifest(project?: string): Manifest | null;
|
|
42
|
+
getEntityById(id: string, project?: string): JstfEntity | undefined;
|
|
43
|
+
getEntityByName(name: string, project?: string): JstfEntity | undefined;
|
|
44
|
+
getProjectNames(): string[];
|
|
45
|
+
getProjectInfo(): ProjectInfo[];
|
|
46
|
+
isMultiProject(): boolean;
|
|
47
|
+
isLoaded(): boolean;
|
|
48
|
+
hasProject(name: string): boolean;
|
|
49
|
+
totalEntities(): number;
|
|
50
|
+
}
|
package/dist/loader.js
CHANGED
|
@@ -67,3 +67,122 @@ export class BundleLoader {
|
|
|
67
67
|
return this.loaded;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
// ─── Multi-Project Loader ─────────────────────────────────────────
|
|
71
|
+
/**
|
|
72
|
+
* Wraps N BundleLoader instances for multi-project access.
|
|
73
|
+
* In single-project mode (1 dir), behaves identically to BundleLoader.
|
|
74
|
+
* In multi-project mode, `project` param is required on all accessors.
|
|
75
|
+
*/
|
|
76
|
+
export class MultiLoader {
|
|
77
|
+
projects = new Map();
|
|
78
|
+
projectPaths = new Map();
|
|
79
|
+
projectEntities = new Map();
|
|
80
|
+
loaded = false;
|
|
81
|
+
dirs;
|
|
82
|
+
constructor(projectDirs) {
|
|
83
|
+
this.dirs = projectDirs.length > 0 ? projectDirs : [process.cwd()];
|
|
84
|
+
}
|
|
85
|
+
load() {
|
|
86
|
+
for (const dir of this.dirs) {
|
|
87
|
+
const loader = new BundleLoader(dir);
|
|
88
|
+
try {
|
|
89
|
+
loader.load();
|
|
90
|
+
const manifest = loader.getManifest();
|
|
91
|
+
const name = manifest?.projectName || path.basename(dir);
|
|
92
|
+
this.projects.set(name, loader);
|
|
93
|
+
this.projectPaths.set(name, dir);
|
|
94
|
+
// Stamp entities with _project
|
|
95
|
+
const entities = loader.getEntities();
|
|
96
|
+
for (const e of entities) {
|
|
97
|
+
e._project = name;
|
|
98
|
+
}
|
|
99
|
+
this.projectEntities.set(name, entities);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
process.stderr.write(`Warning: Skipping ${dir}: ${err.message}\n`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
this.loaded = true;
|
|
106
|
+
}
|
|
107
|
+
/** Get the resolved project name when only one project is loaded. */
|
|
108
|
+
defaultProject() {
|
|
109
|
+
if (this.projects.size === 1)
|
|
110
|
+
return [...this.projects.keys()][0];
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
resolveProject(project) {
|
|
114
|
+
return project || this.defaultProject();
|
|
115
|
+
}
|
|
116
|
+
getEntities(project) {
|
|
117
|
+
if (!this.loaded)
|
|
118
|
+
this.load();
|
|
119
|
+
const p = this.resolveProject(project);
|
|
120
|
+
if (p)
|
|
121
|
+
return this.projectEntities.get(p) || [];
|
|
122
|
+
return []; // multi-project with no project specified
|
|
123
|
+
}
|
|
124
|
+
getTopology(project) {
|
|
125
|
+
if (!this.loaded)
|
|
126
|
+
this.load();
|
|
127
|
+
const p = this.resolveProject(project);
|
|
128
|
+
if (p)
|
|
129
|
+
return this.projects.get(p)?.getTopology() || null;
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
getManifest(project) {
|
|
133
|
+
if (!this.loaded)
|
|
134
|
+
this.load();
|
|
135
|
+
const p = this.resolveProject(project);
|
|
136
|
+
if (p)
|
|
137
|
+
return this.projects.get(p)?.getManifest() || null;
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
getEntityById(id, project) {
|
|
141
|
+
return this.getEntities(project).find(e => e.id === id);
|
|
142
|
+
}
|
|
143
|
+
getEntityByName(name, project) {
|
|
144
|
+
return this.getEntities(project).find(e => {
|
|
145
|
+
const structName = typeof e.struct === 'string' ? e.struct : e.struct?.name;
|
|
146
|
+
return e.id === name || structName === name;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
getProjectNames() {
|
|
150
|
+
if (!this.loaded)
|
|
151
|
+
this.load();
|
|
152
|
+
return [...this.projects.keys()];
|
|
153
|
+
}
|
|
154
|
+
getProjectInfo() {
|
|
155
|
+
if (!this.loaded)
|
|
156
|
+
this.load();
|
|
157
|
+
const result = [];
|
|
158
|
+
for (const [name, loader] of this.projects) {
|
|
159
|
+
const manifest = loader.getManifest();
|
|
160
|
+
result.push({
|
|
161
|
+
name,
|
|
162
|
+
path: this.projectPaths.get(name) || '',
|
|
163
|
+
entityCount: loader.getEntities().length,
|
|
164
|
+
languages: manifest?.languages || [],
|
|
165
|
+
commitSha: manifest?.commitSha || '',
|
|
166
|
+
extractedAt: manifest?.extractedAt || '',
|
|
167
|
+
layers: manifest?.layers || {},
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
isMultiProject() {
|
|
173
|
+
return this.projects.size > 1;
|
|
174
|
+
}
|
|
175
|
+
isLoaded() {
|
|
176
|
+
return this.loaded && this.projects.size > 0;
|
|
177
|
+
}
|
|
178
|
+
hasProject(name) {
|
|
179
|
+
return this.projects.has(name);
|
|
180
|
+
}
|
|
181
|
+
totalEntities() {
|
|
182
|
+
let total = 0;
|
|
183
|
+
for (const entities of this.projectEntities.values()) {
|
|
184
|
+
total += entities.length;
|
|
185
|
+
}
|
|
186
|
+
return total;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interpretation Functor Tools
|
|
3
|
+
*
|
|
4
|
+
* Each functor is I: J -> D — projecting the 9D JSTF-T coordinate space
|
|
5
|
+
* onto a domain-specific judgment. These are composite analyses built
|
|
6
|
+
* from the primitive dimensions (sigma, epsilon, delta, kappa, chi, tau, rho).
|
|
7
|
+
*/
|
|
8
|
+
export declare function findDeadCode(args: {
|
|
9
|
+
include_tests?: boolean;
|
|
10
|
+
project?: string;
|
|
11
|
+
}): unknown;
|
|
12
|
+
export declare function findLayerViolations(args?: {
|
|
13
|
+
project?: string;
|
|
14
|
+
}): unknown;
|
|
15
|
+
export declare function getCouplingMetrics(args: {
|
|
16
|
+
group_by?: 'module' | 'layer';
|
|
17
|
+
project?: string;
|
|
18
|
+
}): unknown;
|
|
19
|
+
export declare function getAuthMatrix(args?: {
|
|
20
|
+
project?: string;
|
|
21
|
+
}): unknown;
|
|
22
|
+
export declare function findErrorGaps(args?: {
|
|
23
|
+
project?: string;
|
|
24
|
+
}): unknown;
|
|
25
|
+
export declare function getTestCoverage(args: {
|
|
26
|
+
weight_by_blast_radius?: boolean;
|
|
27
|
+
project?: string;
|
|
28
|
+
}): unknown;
|
|
29
|
+
export declare function getOptimalContext(args: {
|
|
30
|
+
target_entity: string;
|
|
31
|
+
max_tokens?: number;
|
|
32
|
+
strategy?: 'bfs' | 'blast_radius';
|
|
33
|
+
project?: string;
|
|
34
|
+
}): unknown;
|