@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.
@@ -3,28 +3,51 @@
3
3
  *
4
4
  * Each tool exposes a dimension or computation over the 9D JSTF-T coordinate space.
5
5
  * Tools operate on the in-memory entity bundle loaded from .seshat/_bundle.json.
6
+ *
7
+ * Multi-project: when multiple projects are loaded, each tool accepts an optional
8
+ * `project` parameter. In multi-project mode, `project` is required.
6
9
  */
7
10
  import { buildCallGraph, computeBlastRadius } from '../graph.js';
8
11
  // Lazy-initialized shared state
9
12
  let loader;
10
- let graph = null;
11
- export function initTools(bundleLoader) {
12
- loader = bundleLoader;
13
- graph = null; // Reset graph cache when loader changes
13
+ let graphCache = new Map();
14
+ export function initTools(multiLoader) {
15
+ loader = multiLoader;
16
+ graphCache = new Map();
17
+ }
18
+ export function getLoader() {
19
+ return loader;
20
+ }
21
+ export function getGraph(project) {
22
+ const key = project || '__default__';
23
+ if (!graphCache.has(key)) {
24
+ graphCache.set(key, buildCallGraph(loader.getEntities(project)));
25
+ }
26
+ return graphCache.get(key);
14
27
  }
15
- function getGraph() {
16
- if (!graph) {
17
- graph = buildCallGraph(loader.getEntities());
28
+ /**
29
+ * Validate project param. Returns error string if invalid, null if OK.
30
+ * In single-project mode, project is optional (defaults to the only project).
31
+ * In multi-project mode, project is required.
32
+ */
33
+ export function validateProject(project) {
34
+ if (!loader.isMultiProject())
35
+ return null; // single project, always OK
36
+ if (!project) {
37
+ return `Multiple projects loaded. You must specify a "project" parameter. Available: ${loader.getProjectNames().join(', ')}`;
38
+ }
39
+ if (!loader.hasProject(project)) {
40
+ return `Project "${project}" not found. Available: ${loader.getProjectNames().join(', ')}`;
18
41
  }
19
- return graph;
42
+ return null;
20
43
  }
21
44
  // ─── Helper: Extract entity name from struct ─────────────────────
22
- function entityName(e) {
45
+ export function entityName(e) {
23
46
  if (typeof e.struct === 'string')
24
47
  return e.struct;
25
48
  return e.struct?.name || e.id || 'anonymous';
26
49
  }
27
- function entityLayer(e) {
50
+ export function entityLayer(e) {
28
51
  // Use explicit layer if specific enough
29
52
  const explicit = e.context?.layer?.toLowerCase();
30
53
  if (explicit && explicit !== 'module' && explicit !== 'unknown')
@@ -55,11 +78,12 @@ function entityLayer(e) {
55
78
  return 'test';
56
79
  return explicit || 'other';
57
80
  }
58
- function entitySummary(e) {
81
+ export function entitySummary(e) {
59
82
  const constraintTags = normalizeConstraints(e.constraints);
60
83
  return {
61
84
  id: e.id,
62
85
  name: entityName(e),
86
+ ...(e._project ? { project: e._project } : {}),
63
87
  layer: entityLayer(e),
64
88
  module: e.context?.module || null,
65
89
  sourceFile: e._sourceFile || null,
@@ -70,7 +94,7 @@ function entitySummary(e) {
70
94
  callCount: Array.isArray(e.edges?.calls) ? e.edges.calls.length : 0,
71
95
  };
72
96
  }
73
- function normalizeConstraints(constraints) {
97
+ export function normalizeConstraints(constraints) {
74
98
  if (!constraints)
75
99
  return [];
76
100
  if (Array.isArray(constraints))
@@ -124,7 +148,7 @@ function normalizeConstraints(constraints) {
124
148
  * Deep search constraints — matches against raw constraint object fields too,
125
149
  * not just normalized tags.
126
150
  */
127
- function constraintMatches(constraints, target) {
151
+ export function constraintMatches(constraints, target) {
128
152
  // First check normalized tags
129
153
  const tags = normalizeConstraints(constraints);
130
154
  if (tags.some(t => t.toUpperCase().includes(target.toUpperCase())))
@@ -139,8 +163,11 @@ function constraintMatches(constraints, target) {
139
163
  }
140
164
  // ─── Tool: query_entities ─────────────────────────────────────────
141
165
  export function queryEntities(args) {
166
+ const projErr = validateProject(args.project);
167
+ if (projErr)
168
+ return { error: projErr };
142
169
  const { query, layer, module, language, limit = 50 } = args;
143
- let results = loader.getEntities();
170
+ let results = loader.getEntities(args.project);
144
171
  if (layer) {
145
172
  results = results.filter(e => entityLayer(e).toLowerCase() === layer.toLowerCase());
146
173
  }
@@ -170,7 +197,10 @@ export function queryEntities(args) {
170
197
  }
171
198
  // ─── Tool: get_entity ─────────────────────────────────────────────
172
199
  export function getEntity(args) {
173
- const entity = loader.getEntityById(args.id) || loader.getEntityByName(args.id);
200
+ const projErr = validateProject(args.project);
201
+ if (projErr)
202
+ return { error: projErr };
203
+ const entity = loader.getEntityById(args.id, args.project) || loader.getEntityByName(args.id, args.project);
174
204
  if (!entity) {
175
205
  return { error: `Entity not found: ${args.id}` };
176
206
  }
@@ -178,6 +208,7 @@ export function getEntity(args) {
178
208
  return {
179
209
  id: entity.id,
180
210
  name: entityName(entity),
211
+ ...(entity._project ? { project: entity._project } : {}),
181
212
  sourceFile: entity._sourceFile,
182
213
  sourceLanguage: entity._sourceLanguage,
183
214
  jstfFilename: entity._jstfFilename,
@@ -196,9 +227,12 @@ export function getEntity(args) {
196
227
  }
197
228
  // ─── Tool: get_dependencies ───────────────────────────────────────
198
229
  export function getDependencies(args) {
230
+ const projErr = validateProject(args.project);
231
+ if (projErr)
232
+ return { error: projErr };
199
233
  const { entity_id, direction = 'both', depth = 2 } = args;
200
- const g = getGraph();
201
- const entity = loader.getEntityById(entity_id) || loader.getEntityByName(entity_id);
234
+ const g = getGraph(args.project);
235
+ const entity = loader.getEntityById(entity_id, args.project) || loader.getEntityByName(entity_id, args.project);
202
236
  if (!entity) {
203
237
  return { error: `Entity not found: ${entity_id}` };
204
238
  }
@@ -226,7 +260,7 @@ export function getDependencies(args) {
226
260
  }
227
261
  return result;
228
262
  }
229
- function collectTransitive(adjacency, startId, maxDepth) {
263
+ export function collectTransitive(adjacency, startId, maxDepth) {
230
264
  const visited = new Set();
231
265
  const queue = [[startId, 0]];
232
266
  visited.add(startId);
@@ -249,7 +283,10 @@ function collectTransitive(adjacency, startId, maxDepth) {
249
283
  }
250
284
  // ─── Tool: get_data_flow ──────────────────────────────────────────
251
285
  export function getDataFlow(args) {
252
- const entity = loader.getEntityById(args.entity_id) || loader.getEntityByName(args.entity_id);
286
+ const projErr = validateProject(args.project);
287
+ if (projErr)
288
+ return { error: projErr };
289
+ const entity = loader.getEntityById(args.entity_id, args.project) || loader.getEntityByName(args.entity_id, args.project);
253
290
  if (!entity) {
254
291
  return { error: `Entity not found: ${args.entity_id}` };
255
292
  }
@@ -262,8 +299,11 @@ export function getDataFlow(args) {
262
299
  }
263
300
  // ─── Tool: find_by_constraint ─────────────────────────────────────
264
301
  export function findByConstraint(args) {
302
+ const projErr = validateProject(args.project);
303
+ if (projErr)
304
+ return { error: projErr };
265
305
  const target = args.constraint;
266
- const results = loader.getEntities().filter(e => constraintMatches(e.constraints, target));
306
+ const results = loader.getEntities(args.project).filter(e => constraintMatches(e.constraints, target));
267
307
  return {
268
308
  constraint: args.constraint,
269
309
  total: results.length,
@@ -272,12 +312,15 @@ export function findByConstraint(args) {
272
312
  }
273
313
  // ─── Tool: get_blast_radius ───────────────────────────────────────
274
314
  export function getBlastRadius(args) {
275
- const g = getGraph();
315
+ const projErr = validateProject(args.project);
316
+ if (projErr)
317
+ return { error: projErr };
318
+ const g = getGraph(args.project);
276
319
  // Resolve names to IDs
277
320
  const resolvedIds = new Set();
278
321
  const notFound = [];
279
322
  for (const nameOrId of args.entity_ids) {
280
- const entity = loader.getEntityById(nameOrId) || loader.getEntityByName(nameOrId);
323
+ const entity = loader.getEntityById(nameOrId, args.project) || loader.getEntityByName(nameOrId, args.project);
281
324
  if (entity) {
282
325
  resolvedIds.add(entity.id);
283
326
  }
@@ -306,8 +349,11 @@ export function getBlastRadius(args) {
306
349
  }
307
350
  // ─── Tool: list_modules ───────────────────────────────────────────
308
351
  export function listModules(args) {
352
+ const projErr = validateProject(args.project);
353
+ if (projErr)
354
+ return { error: projErr };
309
355
  const { group_by = 'layer' } = args;
310
- const entities = loader.getEntities();
356
+ const entities = loader.getEntities(args.project);
311
357
  const groups = new Map();
312
358
  for (const e of entities) {
313
359
  let key;
@@ -339,8 +385,11 @@ export function listModules(args) {
339
385
  };
340
386
  }
341
387
  // ─── Tool: get_topology ───────────────────────────────────────────
342
- export function getTopology() {
343
- const topology = loader.getTopology();
388
+ export function getTopology(args) {
389
+ const projErr = validateProject(args?.project);
390
+ if (projErr)
391
+ return { error: projErr };
392
+ const topology = loader.getTopology(args?.project);
344
393
  if (!topology) {
345
394
  return { error: 'No topology data found. The _topology.json file may not have been generated.' };
346
395
  }
package/dist/types.d.ts CHANGED
@@ -11,6 +11,7 @@ export interface JstfEntity {
11
11
  _jstfFilename?: string;
12
12
  _sourceFile?: string | null;
13
13
  _sourceLanguage?: string;
14
+ _project?: string;
14
15
  /** σ — Structure: function shape, signature, modifiers */
15
16
  struct?: {
16
17
  name?: string;
@@ -55,6 +56,7 @@ export interface JstfEntity {
55
56
  target: string;
56
57
  operation?: string;
57
58
  }>;
59
+ tables?: string[];
58
60
  sources?: unknown[];
59
61
  returns?: unknown[];
60
62
  [key: string]: unknown;
@@ -67,14 +69,27 @@ export interface JstfEntity {
67
69
  field: string;
68
70
  rule: string;
69
71
  }>;
72
+ auth?: string[] | 'none';
73
+ authRequired?: boolean;
74
+ purity?: string;
75
+ throws?: boolean;
76
+ sideEffects?: string[];
77
+ errorHandling?: {
78
+ tryCatch?: boolean;
79
+ catchClause?: boolean;
80
+ finally?: boolean;
81
+ errorBoundary?: boolean;
82
+ };
70
83
  [key: string]: unknown;
71
84
  } | string[];
72
85
  /** χ — Context: architectural position, visibility, layer */
73
86
  context?: {
74
87
  layer?: string;
88
+ layerSource?: string;
75
89
  module?: string;
76
90
  path?: string;
77
91
  exposure?: string;
92
+ visibility?: string;
78
93
  traffic?: string;
79
94
  criticality?: string;
80
95
  [key: string]: unknown;
@@ -82,7 +97,18 @@ export interface JstfEntity {
82
97
  /** λ — Ownership: memory ownership, lifetimes, borrowing */
83
98
  ownership?: Record<string, unknown>;
84
99
  /** τ — Traits: type capabilities, bounds, markers */
85
- traits?: string[] | Record<string, unknown>;
100
+ traits?: string[] | {
101
+ self?: {
102
+ asyncContext?: boolean;
103
+ fallible?: boolean;
104
+ [key: string]: unknown;
105
+ };
106
+ params?: Record<string, {
107
+ bounds?: string[];
108
+ markers?: string[];
109
+ }>;
110
+ [key: string]: unknown;
111
+ };
86
112
  /** ρ — Runtime: reactive model, async platform, framework */
87
113
  runtime?: {
88
114
  async?: string;
@@ -120,3 +146,12 @@ export interface Manifest {
120
146
  languages: string[];
121
147
  layers: Record<string, number>;
122
148
  }
149
+ export interface ProjectInfo {
150
+ name: string;
151
+ path: string;
152
+ entityCount: number;
153
+ languages: string[];
154
+ commitSha: string;
155
+ extractedAt: string;
156
+ layers: Record<string, number>;
157
+ }
package/package.json CHANGED
@@ -1,35 +1,35 @@
1
- {
2
- "name": "@papyruslabsai/seshat-mcp",
3
- "version": "0.1.0",
4
- "description": "Semantic MCP server — exposes a codebase's 9D JSTF-T coordinate space as queryable tools",
5
- "type": "module",
6
- "bin": {
7
- "seshat-mcp": "./dist/index.js"
8
- },
9
- "main": "./dist/index.js",
10
- "scripts": {
11
- "build": "tsc",
12
- "dev": "tsc --watch",
13
- "start": "node dist/index.js"
14
- },
15
- "dependencies": {
16
- "@modelcontextprotocol/sdk": "^1.12.1"
17
- },
18
- "devDependencies": {
19
- "typescript": "^5.5.0",
20
- "@types/node": "^20.0.0"
21
- },
22
- "engines": {
23
- "node": ">=20"
24
- },
25
- "files": [
26
- "dist/"
27
- ],
28
- "repository": {
29
- "type": "git",
30
- "url": "https://github.com/papyruslabs-ai/seshat.git",
31
- "directory": "packages/seshat-mcp"
32
- },
33
- "keywords": ["mcp", "jstf", "semantic", "code-analysis", "seshat"],
34
- "license": "MIT"
35
- }
1
+ {
2
+ "name": "@papyruslabsai/seshat-mcp",
3
+ "version": "0.3.0",
4
+ "description": "Semantic MCP server — exposes a codebase's 9D JSTF-T coordinate space as queryable tools",
5
+ "type": "module",
6
+ "bin": {
7
+ "seshat-mcp": "./dist/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "start": "node dist/index.js"
14
+ },
15
+ "dependencies": {
16
+ "@modelcontextprotocol/sdk": "^1.12.1"
17
+ },
18
+ "devDependencies": {
19
+ "typescript": "^5.5.0",
20
+ "@types/node": "^20.0.0"
21
+ },
22
+ "engines": {
23
+ "node": ">=20"
24
+ },
25
+ "files": [
26
+ "dist/"
27
+ ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/papyruslabs-ai/seshat.git",
31
+ "directory": "packages/seshat-mcp"
32
+ },
33
+ "keywords": ["mcp", "jstf", "semantic", "code-analysis", "seshat"],
34
+ "license": "MIT"
35
+ }