@papyruslabsai/seshat-mcp 0.12.0 → 0.12.1

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 CHANGED
@@ -1 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @papyruslabs/seshat-mcp — Semantic Code Analysis MCP Server (Cloud Proxy)
4
+ *
5
+ * This CLI server declares the full suite of Seshat tools to Claude/AI clients,
6
+ * but executes all logic remotely on the Ptah Cloud API for zero-conflict
7
+ * collaboration and deep analytics.
8
+ */
1
9
  export {};
package/dist/index.js CHANGED
@@ -1,84 +1,17 @@
1
- !/usr/bin / env;
2
- node;
1
+ #!/usr/bin/env node
3
2
  /**
4
- * @papyruslabs/seshat-mcp — Semantic Code Analysis MCP Server
3
+ * @papyruslabs/seshat-mcp — Semantic Code Analysis MCP Server (Cloud Proxy)
5
4
  *
6
- * Exposes a codebase's structure, dependencies, and constraints as queryable
7
- * MCP tools. Reads pre-extracted analysis data from .seshat/_bundle.json.
8
- *
9
- * Multi-project mode:
10
- * Set SESHAT_PROJECTS env var to comma-separated paths or a glob pattern.
11
- * In multi-project mode, all tools require a `project` parameter.
12
- * Use `list_projects` to see available projects.
13
- *
14
- * Single-project mode (default):
15
- * When SESHAT_PROJECTS is not set, loads from CWD. No `project` param needed.
16
- *
17
- * 20 tools across 4 categories:
18
- * Discovery: list_projects, query_entities, get_entity, list_modules, get_topology
19
- * Graph: get_dependencies, get_data_flow, find_by_constraint, get_blast_radius
20
- * Analysis: find_dead_code, find_layer_violations, get_coupling_metrics,
21
- * get_auth_matrix, find_error_gaps, get_test_coverage,
22
- * get_optimal_context, estimate_task_cost, report_actual_burn
23
- * Diff: diff_bundle, conflict_matrix
24
- *
25
- * Usage:
26
- * npx @papyruslabs/seshat-mcp # single project (CWD)
27
- * SESHAT_PROJECTS=/path/a,/path/b npx @papyruslabs/seshat-mcp # multi-project
28
- * SESHAT_PROJECTS="/home/user/projects/*" npx @papyruslabs/seshat-mcp # glob
5
+ * This CLI server declares the full suite of Seshat tools to Claude/AI clients,
6
+ * but executes all logic remotely on the Ptah Cloud API for zero-conflict
7
+ * collaboration and deep analytics.
29
8
  */
30
9
  import fs from 'fs';
31
10
  import path from 'path';
32
11
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
33
12
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
34
13
  import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
35
- import { MultiLoader } from './loader.js';
36
- import { bootstrap } from './bootstrap.js';
37
- import { logTelemetry, isSupabaseConfigured } from './supabase.js';
38
- import { queryEntities, getEntity, getDependencies, getDataFlow, findByConstraint, getBlastRadius, listModules, getTopology, } from './tools/index.js';
39
- import { findDeadCode, findLayerViolations, getCouplingMetrics, getAuthMatrix, findErrorGaps, getTestCoverage, getOptimalContext, estimateTaskCost, reportActualBurn, find_runtime_violations, find_ownership_violations, query_traits, simulate_mutation, query_data_targets, find_exposure_leaks, find_semantic_clones, create_symbol, } from './tools/functors.js';
40
- import { diffBundle, conflictMatrix, } from './tools/diff.js';
41
- // ─── Project Discovery ───────────────────────────────────────────
42
- /**
43
- * Discover project directories from SESHAT_PROJECTS env var.
44
- *
45
- * - Not set → [process.cwd()] (backward compat)
46
- * - Comma-separated paths → resolve each
47
- * - Glob pattern (contains *) → scan parent dir for subdirs with .seshat/_bundle.json
48
- */
49
- function discoverProjects() {
50
- const env = process.env.SESHAT_PROJECTS;
51
- if (!env)
52
- return [process.cwd()];
53
- const trimmed = env.trim();
54
- // Glob pattern: /path/to/projects/*
55
- if (trimmed.includes('*')) {
56
- const globDir = trimmed.replace(/\/?\*$/, '');
57
- const resolved = path.resolve(globDir);
58
- if (!fs.existsSync(resolved)) {
59
- process.stderr.write(`SESHAT_PROJECTS glob dir not found: ${resolved}\n`);
60
- return [process.cwd()];
61
- }
62
- const dirs = [];
63
- for (const entry of fs.readdirSync(resolved, { withFileTypes: true })) {
64
- if (!entry.isDirectory())
65
- continue;
66
- const candidate = path.join(resolved, entry.name);
67
- const bundlePath = path.join(candidate, '.seshat', '_bundle.json');
68
- if (fs.existsSync(bundlePath)) {
69
- dirs.push(candidate);
70
- }
71
- }
72
- if (dirs.length === 0) {
73
- process.stderr.write(`SESHAT_PROJECTS: no dirs with .seshat/_bundle.json found in ${resolved}\n`);
74
- return [process.cwd()];
75
- }
76
- return dirs;
77
- }
78
- // Comma-separated paths
79
- return trimmed.split(',').map(p => path.resolve(p.trim())).filter(p => p.length > 0);
80
- }
81
- // ─── Project param definition (injected into all tool schemas) ───
14
+ // ─── Project param definition ───
82
15
  const projectParam = {
83
16
  type: 'string',
84
17
  description: 'Project name (required in multi-project mode). Use list_projects to see available projects.',
@@ -88,10 +21,7 @@ const TOOLS = [
88
21
  {
89
22
  name: 'list_projects',
90
23
  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.',
91
- inputSchema: {
92
- type: 'object',
93
- properties: {},
94
- },
24
+ inputSchema: { type: 'object', properties: {} },
95
25
  },
96
26
  {
97
27
  name: 'query_entities',
@@ -100,26 +30,11 @@ const TOOLS = [
100
30
  type: 'object',
101
31
  properties: {
102
32
  project: projectParam,
103
- query: {
104
- type: 'string',
105
- description: 'Search term matches against entity name, ID, source file, and module',
106
- },
107
- layer: {
108
- type: 'string',
109
- description: 'Filter by architectural layer: route, controller, service, repository, utility, hook, component, schema',
110
- },
111
- module: {
112
- type: 'string',
113
- description: 'Filter by module (partial match)',
114
- },
115
- language: {
116
- type: 'string',
117
- description: 'Filter by source language: javascript, typescript, python, go, rust, etc.',
118
- },
119
- limit: {
120
- type: 'number',
121
- description: 'Max results to return (default: 50)',
122
- },
33
+ query: { type: 'string', description: 'Search term — matches against entity name, ID, source file, and module' },
34
+ layer: { type: 'string', description: 'Filter by architectural layer: route, controller, service, repository, utility, hook, component, schema' },
35
+ module: { type: 'string', description: 'Filter by module (partial match)' },
36
+ language: { type: 'string', description: 'Filter by source language: javascript, typescript, python, go, rust, etc.' },
37
+ limit: { type: 'number', description: 'Max results to return (default: 50)' },
123
38
  },
124
39
  },
125
40
  },
@@ -130,10 +45,7 @@ const TOOLS = [
130
45
  type: 'object',
131
46
  properties: {
132
47
  project: projectParam,
133
- id: {
134
- type: 'string',
135
- description: 'Entity ID or name',
136
- },
48
+ id: { type: 'string', description: 'Entity ID or name' },
137
49
  },
138
50
  required: ['id'],
139
51
  },
@@ -145,19 +57,9 @@ const TOOLS = [
145
57
  type: 'object',
146
58
  properties: {
147
59
  project: projectParam,
148
- entity_id: {
149
- type: 'string',
150
- description: 'Entity ID or name',
151
- },
152
- direction: {
153
- type: 'string',
154
- enum: ['callers', 'callees', 'both'],
155
- description: 'Which direction to traverse (default: both)',
156
- },
157
- depth: {
158
- type: 'number',
159
- description: 'How many levels deep to traverse (default: 2)',
160
- },
60
+ entity_id: { type: 'string', description: 'Entity ID or name' },
61
+ direction: { type: 'string', enum: ['callers', 'callees', 'both'], description: 'Which direction to traverse (default: both)' },
62
+ depth: { type: 'number', description: 'How many levels deep to traverse (default: 2)' },
161
63
  },
162
64
  required: ['entity_id'],
163
65
  },
@@ -169,10 +71,7 @@ const TOOLS = [
169
71
  type: 'object',
170
72
  properties: {
171
73
  project: projectParam,
172
- entity_id: {
173
- type: 'string',
174
- description: 'Entity ID or name',
175
- },
74
+ entity_id: { type: 'string', description: 'Entity ID or name' },
176
75
  },
177
76
  required: ['entity_id'],
178
77
  },
@@ -184,10 +83,7 @@ const TOOLS = [
184
83
  type: 'object',
185
84
  properties: {
186
85
  project: projectParam,
187
- constraint: {
188
- type: 'string',
189
- description: 'Constraint tag to search for: AUTH, VALIDATED, PURE, THROWS, DB_ACCESS, NETWORK_IO, IMP, etc.',
190
- },
86
+ constraint: { type: 'string', description: 'Constraint tag to search for: AUTH, VALIDATED, PURE, THROWS, DB_ACCESS, NETWORK_IO, IMP, etc.' },
191
87
  },
192
88
  required: ['constraint'],
193
89
  },
@@ -199,11 +95,7 @@ const TOOLS = [
199
95
  type: 'object',
200
96
  properties: {
201
97
  project: projectParam,
202
- entity_ids: {
203
- type: 'array',
204
- items: { type: 'string' },
205
- description: 'Array of entity IDs or names to compute blast radius for',
206
- },
98
+ entity_ids: { type: 'array', items: { type: 'string' }, description: 'Array of entity IDs or names to compute blast radius for' },
207
99
  },
208
100
  required: ['entity_ids'],
209
101
  },
@@ -215,11 +107,7 @@ const TOOLS = [
215
107
  type: 'object',
216
108
  properties: {
217
109
  project: projectParam,
218
- group_by: {
219
- type: 'string',
220
- enum: ['layer', 'module', 'file', 'language'],
221
- description: 'How to group entities (default: layer)',
222
- },
110
+ group_by: { type: 'string', enum: ['layer', 'module', 'file', 'language'], description: 'How to group entities (default: layer)' },
223
111
  },
224
112
  },
225
113
  },
@@ -228,9 +116,7 @@ const TOOLS = [
228
116
  description: 'Get the API topology: routes, plugins, auth patterns, and database tables. Built from architectural context and dependency analysis across all entities.',
229
117
  inputSchema: {
230
118
  type: 'object',
231
- properties: {
232
- project: projectParam,
233
- },
119
+ properties: { project: projectParam },
234
120
  },
235
121
  },
236
122
  // ─── Analysis Tools ─────────────────────────────────────────────
@@ -241,21 +127,16 @@ const TOOLS = [
241
127
  type: 'object',
242
128
  properties: {
243
129
  project: projectParam,
244
- include_tests: {
245
- type: 'boolean',
246
- description: 'Include test entities in dead code results (default: false)',
247
- },
130
+ include_tests: { type: 'boolean', description: 'Include test entities in dead code results (default: false)' },
248
131
  },
249
132
  },
250
133
  },
251
134
  {
252
135
  name: 'find_layer_violations',
253
- 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 architectural layer classification.',
136
+ 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).',
254
137
  inputSchema: {
255
138
  type: 'object',
256
- properties: {
257
- project: projectParam,
258
- },
139
+ properties: { project: projectParam },
259
140
  },
260
141
  },
261
142
  {
@@ -265,11 +146,7 @@ const TOOLS = [
265
146
  type: 'object',
266
147
  properties: {
267
148
  project: projectParam,
268
- group_by: {
269
- type: 'string',
270
- enum: ['module', 'layer'],
271
- description: 'Group entities by module or layer (default: module)',
272
- },
149
+ group_by: { type: 'string', enum: ['module', 'layer'], description: 'Group entities by module or layer (default: module)' },
273
150
  },
274
151
  },
275
152
  },
@@ -278,9 +155,7 @@ const TOOLS = [
278
155
  description: 'Analyze authentication coverage across all API-facing entities. Shows which routes/controllers have auth requirements and which don\'t. Detects inconsistencies like DB access without auth.',
279
156
  inputSchema: {
280
157
  type: 'object',
281
- properties: {
282
- project: projectParam,
283
- },
158
+ properties: { project: projectParam },
284
159
  },
285
160
  },
286
161
  {
@@ -288,9 +163,7 @@ const TOOLS = [
288
163
  description: 'Find error handling gaps: entities that throw or have network/db side effects whose callers lack try/catch. These are crash risk points where exceptions can propagate unhandled.',
289
164
  inputSchema: {
290
165
  type: 'object',
291
- properties: {
292
- project: projectParam,
293
- },
166
+ properties: { project: projectParam },
294
167
  },
295
168
  },
296
169
  {
@@ -300,10 +173,7 @@ const TOOLS = [
300
173
  type: 'object',
301
174
  properties: {
302
175
  project: projectParam,
303
- weight_by_blast_radius: {
304
- type: 'boolean',
305
- description: 'Rank uncovered entities by blast radius to prioritize testing (default: false, slower)',
306
- },
176
+ weight_by_blast_radius: { type: 'boolean', description: 'Rank uncovered entities by blast radius to prioritize testing (default: false, slower)' },
307
177
  },
308
178
  },
309
179
  },
@@ -314,19 +184,9 @@ const TOOLS = [
314
184
  type: 'object',
315
185
  properties: {
316
186
  project: projectParam,
317
- target_entity: {
318
- type: 'string',
319
- description: 'Entity ID or name to build context around',
320
- },
321
- max_tokens: {
322
- type: 'number',
323
- description: 'Token budget for the context window (default: 8000)',
324
- },
325
- strategy: {
326
- type: 'string',
327
- enum: ['bfs', 'blast_radius'],
328
- description: 'Traversal strategy: bfs (faster, local neighborhood) or blast_radius (full affected set)',
329
- },
187
+ target_entity: { type: 'string', description: 'Entity ID or name to build context around' },
188
+ max_tokens: { type: 'number', description: 'Token budget for the context window (default: 8000)' },
189
+ strategy: { type: 'string', enum: ['bfs', 'blast_radius'], description: 'Traversal strategy: bfs (faster, local neighborhood) or blast_radius (full affected set)' },
330
190
  },
331
191
  required: ['target_entity'],
332
192
  },
@@ -338,78 +198,39 @@ const TOOLS = [
338
198
  type: 'object',
339
199
  properties: {
340
200
  project: projectParam,
341
- target_entities: {
342
- type: 'array',
343
- items: { type: 'string' },
344
- description: 'Entity IDs or names that will be modified',
345
- },
346
- context_budget: {
347
- type: 'number',
348
- description: 'LLM context window token budget (default: 200000)',
349
- },
201
+ target_entities: { type: 'array', items: { type: 'string' }, description: 'Entity IDs or names that will be modified' },
202
+ context_budget: { type: 'number', description: 'LLM context window token budget (default: 200000)' },
350
203
  },
351
204
  required: ['target_entities'],
352
205
  },
353
206
  },
354
207
  {
355
208
  name: 'report_actual_burn',
356
- description: 'Close the calibration feedback loop: report actual token usage against a prior prediction from estimate_task_cost. Computes drift (actual - predicted) / predicted. Also supports listing recent predictions with aggregate calibration stats, or abandoning predictions for cancelled tasks.',
209
+ description: 'Close the calibration feedback loop: report actual token usage against a prior prediction from estimate_task_cost.',
357
210
  inputSchema: {
358
211
  type: 'object',
359
212
  properties: {
360
- prediction_id: {
361
- type: 'string',
362
- description: 'The prediction ID returned by estimate_task_cost. Required for complete/abandon actions.',
363
- },
364
- actual_input_tokens: {
365
- type: 'number',
366
- description: 'Actual input tokens consumed (from LLM usage metadata).',
367
- },
368
- actual_output_tokens: {
369
- type: 'number',
370
- description: 'Actual output tokens consumed (from LLM usage metadata).',
371
- },
372
- actual_total_tokens: {
373
- type: 'number',
374
- description: 'Actual total tokens consumed (input + output).',
375
- },
376
- model: {
377
- type: 'string',
378
- description: 'Model used (e.g. claude-opus-4-6, claude-sonnet-4-6).',
379
- },
380
- action: {
381
- type: 'string',
382
- enum: ['complete', 'abandon', 'list'],
383
- description: 'Action: "complete" reports actuals (default), "abandon" marks prediction as cancelled, "list" shows recent predictions with calibration stats.',
384
- },
213
+ prediction_id: { type: 'string', description: 'The prediction ID returned by estimate_task_cost. Required for complete/abandon actions.' },
214
+ actual_input_tokens: { type: 'number', description: 'Actual input tokens consumed (from LLM usage metadata).' },
215
+ actual_output_tokens: { type: 'number', description: 'Actual output tokens consumed (from LLM usage metadata).' },
216
+ actual_total_tokens: { type: 'number', description: 'Actual total tokens consumed (input + output).' },
217
+ model: { type: 'string', description: 'Model used (e.g. claude-opus-4-6, claude-sonnet-4-6).' },
218
+ action: { type: 'string', enum: ['complete', 'abandon', 'list'], description: 'Action: "complete" reports actuals (default), "abandon" marks prediction as cancelled, "list" shows recent predictions with calibration stats.' },
385
219
  project: projectParam,
386
- notes: {
387
- type: 'string',
388
- description: 'Optional notes about the task outcome.',
389
- },
220
+ notes: { type: 'string', description: 'Optional notes about the task outcome.' },
390
221
  },
391
222
  },
392
223
  },
393
224
  // ─── Analysis & Impact Tools ───────────────────────────────────
394
225
  {
395
226
  name: 'find_runtime_violations',
396
- description: 'Analyze the call graph for runtime environment leaks. Finds architectural boundary issues where framework-agnostic code improperly imports framework-specific code (e.g. pure logic calling React hooks) or where incompatible frameworks mix directly.',
397
- inputSchema: {
398
- type: 'object',
399
- properties: {
400
- project: projectParam,
401
- },
402
- },
227
+ description: 'Analyze the call graph for runtime environment leaks. Finds architectural boundary issues where framework-agnostic code improperly imports framework-specific code.',
228
+ inputSchema: { type: 'object', properties: { project: projectParam } },
403
229
  },
404
230
  {
405
231
  name: 'find_ownership_violations',
406
232
  description: 'Analyze the codebase for memory and lifecycle constraints. Flags entities with complex ownership rules, unsafe blocks, escaping boundaries, or illegal mutability patterns on borrowed references.',
407
- inputSchema: {
408
- type: 'object',
409
- properties: {
410
- project: projectParam,
411
- },
412
- },
233
+ inputSchema: { type: 'object', properties: { project: projectParam } },
413
234
  },
414
235
  {
415
236
  name: 'query_traits',
@@ -418,25 +239,19 @@ const TOOLS = [
418
239
  type: 'object',
419
240
  properties: {
420
241
  project: projectParam,
421
- trait: {
422
- type: 'string',
423
- description: 'The trait or capability to search for (e.g., "fallible", "asyncContext")',
424
- },
242
+ trait: { type: 'string', description: 'The trait or capability to search for (e.g., "fallible", "asyncContext")' },
425
243
  },
426
244
  required: ['trait'],
427
245
  },
428
246
  },
429
247
  {
430
248
  name: 'simulate_mutation',
431
- description: 'The Impact Simulator. Proposes a hypothetical change to an entity (like adding a "fallible" or "auth" requirement) and simulates the architectural fallout upstream and downstream. Returns a blueprint of exactly which other entities will break and what fixes they require.',
249
+ description: 'The Impact Simulator. Proposes a hypothetical change to an entity and simulates the architectural fallout upstream and downstream. Returns a blueprint of exactly which other entities will break and what fixes they require.',
432
250
  inputSchema: {
433
251
  type: 'object',
434
252
  properties: {
435
253
  project: projectParam,
436
- entity_id: {
437
- type: 'string',
438
- description: 'The target entity to analyze.',
439
- },
254
+ entity_id: { type: 'string', description: 'The target entity to analyze.' },
440
255
  mutation: {
441
256
  type: 'object',
442
257
  description: 'The hypothetical change to apply.',
@@ -458,28 +273,20 @@ const TOOLS = [
458
273
  },
459
274
  {
460
275
  name: 'query_data_targets',
461
- description: 'Search the codebase for data interactions to find all entities that read or write to a specific database table, state object, or data source. This acts as a reverse-index for data flow, essential for planning migrations or state refactors.',
276
+ description: 'Search the codebase for data interactions to find all entities that read or write to a specific database table, state object, or data source.',
462
277
  inputSchema: {
463
278
  type: 'object',
464
279
  properties: {
465
280
  project: projectParam,
466
- target_name: {
467
- type: 'string',
468
- description: 'The name of the database table, state object, or data source to query (e.g., "users", "auth_token").',
469
- },
281
+ target_name: { type: 'string', description: 'The name of the database table, state object, or data source to query (e.g., "users", "auth_token").' },
470
282
  },
471
283
  required: ['target_name'],
472
284
  },
473
285
  },
474
286
  {
475
287
  name: 'find_exposure_leaks',
476
- description: 'Analyze the call graph for architectural visibility leaks. Flags paths where a "public" or "api" entity directly accesses a "private" internal entity, potentially leaking sensitive data or bypassing internal service boundaries.',
477
- inputSchema: {
478
- type: 'object',
479
- properties: {
480
- project: projectParam,
481
- },
482
- },
288
+ description: 'Analyze the call graph for architectural visibility leaks. Flags paths where a "public" or "api" entity directly accesses a "private" internal entity.',
289
+ inputSchema: { type: 'object', properties: { project: projectParam } },
483
290
  },
484
291
  {
485
292
  name: 'find_semantic_clones',
@@ -488,36 +295,21 @@ const TOOLS = [
488
295
  type: 'object',
489
296
  properties: {
490
297
  project: projectParam,
491
- min_complexity: {
492
- type: 'number',
493
- description: 'Minimum number of logic expressions required to constitute a match (default: 5).',
494
- },
298
+ min_complexity: { type: 'number', description: 'Minimum number of logic expressions required to constitute a match (default: 5).' },
495
299
  },
496
300
  },
497
301
  },
498
302
  {
499
303
  name: 'create_symbol',
500
- description: 'Register a new code symbol in the architectural graph. This does not write to disk, but allows the Impact Simulator and Conflict Matrix to reason about a symbol that is pending creation. Essential for planning new features that introduce new files or modules.',
304
+ description: 'Register a new code symbol in the architectural graph. This does not write to disk, but allows the Impact Simulator and Conflict Matrix to reason about a symbol that is pending creation.',
501
305
  inputSchema: {
502
306
  type: 'object',
503
307
  properties: {
504
308
  project: projectParam,
505
- id: {
506
- type: 'string',
507
- description: 'The unique ID or name for the new symbol.',
508
- },
509
- source_file: {
510
- type: 'string',
511
- description: 'The relative path to the file where this symbol will be created.',
512
- },
513
- layer: {
514
- type: 'string',
515
- description: 'The architectural layer (e.g., "service", "route", "component").',
516
- },
517
- description: {
518
- type: 'string',
519
- description: 'Brief description of the symbol\'s purpose.',
520
- },
309
+ id: { type: 'string', description: 'The unique ID or name for the new symbol.' },
310
+ source_file: { type: 'string', description: 'The relative path to the file where this symbol will be created.' },
311
+ layer: { type: 'string', description: 'The architectural layer (e.g., "service", "route", "component").' },
312
+ description: { type: 'string', description: 'Brief description of the symbol\'s purpose.' },
521
313
  },
522
314
  required: ['id', 'source_file'],
523
315
  },
@@ -530,14 +322,8 @@ const TOOLS = [
530
322
  type: 'object',
531
323
  properties: {
532
324
  project: projectParam,
533
- worktree_path: {
534
- type: 'string',
535
- description: 'Absolute path to the worktree or branch checkout to compare against the loaded project',
536
- },
537
- include_unchanged: {
538
- type: 'boolean',
539
- description: 'Include unchanged entities in the output (default: false)',
540
- },
325
+ worktree_path: { type: 'string', description: 'Absolute path to the worktree or branch checkout to compare against the loaded project' },
326
+ include_unchanged: { type: 'boolean', description: 'Include unchanged entities in the output (default: false)' },
541
327
  },
542
328
  required: ['worktree_path'],
543
329
  },
@@ -554,24 +340,10 @@ const TOOLS = [
554
340
  items: {
555
341
  type: 'object',
556
342
  properties: {
557
- id: {
558
- type: 'string',
559
- description: 'Unique task identifier (e.g. "add-dark-mode")',
560
- },
561
- entity_ids: {
562
- type: 'array',
563
- items: { type: 'string' },
564
- description: 'Symbol names or IDs that this task will modify',
565
- },
566
- dimensions: {
567
- type: 'array',
568
- items: { type: 'string' },
569
- description: 'Optional: Code scopes this task will modify. (Legacy support, no longer required for Tier 2 safety).',
570
- },
571
- expand_blast_radius: {
572
- type: 'boolean',
573
- description: 'Include transitively affected symbols in the conflict check (default: false)',
574
- },
343
+ id: { type: 'string', description: 'Unique task identifier (e.g. "add-dark-mode")' },
344
+ entity_ids: { type: 'array', items: { type: 'string' }, description: 'Symbol names or IDs that this task will modify' },
345
+ dimensions: { type: 'array', items: { type: 'string' }, description: 'Optional: Code scopes this task will modify.' },
346
+ expand_blast_radius: { type: 'boolean', description: 'Include transitively affected symbols in the conflict check (default: false)' },
575
347
  },
576
348
  required: ['id', 'entity_ids'],
577
349
  },
@@ -582,229 +354,87 @@ const TOOLS = [
582
354
  },
583
355
  },
584
356
  ];
585
- // ─── Server Setup ─────────────────────────────────────────────────
586
- async function main() {
587
- // Discover and load projects
588
- const projectDirs = discoverProjects();
589
- let loader = new MultiLoader(projectDirs);
590
- try {
591
- loader.load();
592
- }
593
- catch (err) {
594
- process.stderr.write(`Warning: ${err.message}\n`);
357
+ // ─── Project Resolution (Fallback) ────────────────────────────────
358
+ function resolveProjectName() {
359
+ // If SESHAT_PROJECTS is set to a single name, use it
360
+ if (process.env.SESHAT_PROJECTS && !process.env.SESHAT_PROJECTS.includes(',') && !process.env.SESHAT_PROJECTS.includes('*')) {
361
+ return path.basename(process.env.SESHAT_PROJECTS);
595
362
  }
596
- // Auto-bootstrap: if no projects loaded, try to extract from CWD
597
- if (!loader.isLoaded() && !process.env.SESHAT_PROJECTS) {
598
- const cwd = process.cwd();
599
- const seshatDir = path.join(cwd, '.seshat');
600
- // Only bootstrap when .seshat/ doesn't exist at all (not corruption)
601
- if (!fs.existsSync(seshatDir)) {
602
- process.stderr.write(`[seshat-mcp] No .seshat/ found — attempting auto-bootstrap...\n`);
603
- const result = await bootstrap(cwd);
604
- if (result.success) {
605
- // Re-create loader and retry
606
- loader = new MultiLoader([cwd]);
607
- try {
608
- loader.load();
609
- process.stderr.write(`[seshat-mcp] Auto-bootstrap succeeded: ${loader.totalEntities()} entities loaded\n`);
610
- }
611
- catch (err) {
612
- process.stderr.write(`[seshat-mcp] Auto-bootstrap produced files but load failed: ${err.message}\n`);
613
- }
614
- }
615
- else {
616
- process.stderr.write(`[seshat-mcp] Auto-bootstrap failed: ${result.error || 'unknown error'}\n`);
617
- process.stderr.write(`[seshat-mcp] Starting with 0 entities. Run extraction manually or push to trigger CI.\n`);
618
- }
363
+ // Fallback to reading local manifest if it exists
364
+ const manifestPath = path.join(process.cwd(), '.seshat', 'manifest.json');
365
+ if (fs.existsSync(manifestPath)) {
366
+ try {
367
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
368
+ if (manifest.projectName)
369
+ return manifest.projectName;
619
370
  }
371
+ catch { }
620
372
  }
621
- initTools(loader);
622
- // Build server name
623
- const projectNames = loader.getProjectNames();
624
- const totalEntities = loader.totalEntities();
625
- const isMulti = loader.isMultiProject();
626
- let serverLabel;
627
- if (!loader.isLoaded()) {
628
- serverLabel = 'seshat-mcp (no projects loaded)';
629
- }
630
- else if (isMulti) {
631
- serverLabel = `seshat-mcp (${projectNames.length} projects, ${totalEntities} entities)`;
632
- }
633
- else {
634
- const manifest = loader.getManifest();
635
- serverLabel = `seshat-mcp (${manifest?.projectName || projectNames[0] || 'unknown'})`;
636
- }
373
+ // Ultimate fallback to directory name
374
+ return path.basename(process.cwd());
375
+ }
376
+ // ─── Server Setup ─────────────────────────────────────────────────
377
+ async function main() {
637
378
  const server = new Server({
638
- name: serverLabel,
639
- version: '0.5.0',
379
+ name: 'seshat-mcp-cloud-proxy',
380
+ version: '1.0.0',
640
381
  }, {
641
- capabilities: {
642
- tools: {},
643
- },
382
+ capabilities: { tools: {} },
644
383
  });
645
- // List available tools
646
384
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
647
385
  tools: TOOLS,
648
386
  }));
649
- // Handle tool calls
650
387
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
651
388
  const { name, arguments: args } = request.params;
652
- const startTime = Date.now();
389
+ // We expect the user to provide an API key for the cloud service
390
+ const apiKey = process.env.SESHAT_API_KEY;
391
+ if (!apiKey) {
392
+ return {
393
+ content: [{ type: 'text', text: JSON.stringify({ error: 'SESHAT_API_KEY environment variable is required to use Ptah Cloud tools.' }, null, 2) }],
394
+ isError: true,
395
+ };
396
+ }
397
+ // Determine the project hash for this workspace
398
+ const project_hash = (args && typeof args === 'object' && 'project' in args)
399
+ ? String(args.project)
400
+ : resolveProjectName();
401
+ const PTAH_CLOUD_URL = process.env.PTAH_CLOUD_URL || 'https://api.papyruslabs.ai/api/mcp/execute';
653
402
  try {
654
- if (!loader.isLoaded()) {
403
+ // Blindly proxy the tool call to the cloud
404
+ const res = await fetch(PTAH_CLOUD_URL, {
405
+ method: 'POST',
406
+ headers: {
407
+ 'Content-Type': 'application/json',
408
+ 'x-api-key': apiKey
409
+ },
410
+ body: JSON.stringify({
411
+ tool: name,
412
+ project_hash,
413
+ args
414
+ })
415
+ });
416
+ if (!res.ok) {
417
+ const errorText = await res.text();
655
418
  return {
656
- content: [{
657
- type: 'text',
658
- text: JSON.stringify({
659
- error: 'No projects loaded. Ensure .seshat/_bundle.json exists or set SESHAT_PROJECTS env var.',
660
- }, null, 2),
661
- }],
419
+ content: [{ type: 'text', text: JSON.stringify({ error: `Cloud API Error (${res.status}): ${errorText}` }, null, 2) }],
420
+ isError: true
662
421
  };
663
422
  }
664
- let result;
665
- switch (name) { // Meta
666
- case 'list_projects':
667
- result = {
668
- multiProject: isMulti,
669
- projects: loader.getProjectInfo(),
670
- totalEntities,
671
- hint: isMulti
672
- ? 'Pass the project name as the "project" parameter to all other tools.'
673
- : 'Single project mode — the "project" parameter is optional.',
674
- };
675
- break;
676
- // Core tools
677
- case 'query_entities':
678
- result = queryEntities(args);
679
- break;
680
- case 'get_entity':
681
- result = getEntity(args);
682
- break;
683
- case 'get_dependencies':
684
- result = getDependencies(args);
685
- break;
686
- case 'get_data_flow':
687
- result = getDataFlow(args);
688
- break;
689
- case 'find_by_constraint':
690
- result = findByConstraint(args);
691
- break;
692
- case 'get_blast_radius':
693
- result = getBlastRadius(args);
694
- break;
695
- case 'list_modules':
696
- result = listModules(args);
697
- break;
698
- case 'get_topology':
699
- result = getTopology(args);
700
- break;
701
- // Analysis Tools
702
- case 'find_dead_code':
703
- result = findDeadCode(args);
704
- break;
705
- case 'find_layer_violations':
706
- result = findLayerViolations(args);
707
- break;
708
- case 'get_coupling_metrics':
709
- result = getCouplingMetrics(args);
710
- break;
711
- case 'get_auth_matrix':
712
- result = getAuthMatrix(args);
713
- break;
714
- case 'find_error_gaps':
715
- result = findErrorGaps(args);
716
- break;
717
- case 'get_test_coverage':
718
- result = getTestCoverage(args);
719
- break;
720
- case 'get_optimal_context':
721
- result = getOptimalContext(args);
722
- break;
723
- case 'estimate_task_cost':
724
- result = await estimateTaskCost(args);
725
- break;
726
- case 'report_actual_burn':
727
- result = await reportActualBurn(args);
728
- break;
729
- // Analysis & Impact Tools
730
- case 'find_runtime_violations':
731
- result = find_runtime_violations(args);
732
- break;
733
- case 'find_ownership_violations':
734
- result = find_ownership_violations(args);
735
- break;
736
- case 'query_traits':
737
- result = query_traits(args);
738
- break;
739
- case 'simulate_mutation':
740
- result = simulate_mutation(args);
741
- break;
742
- case 'query_data_targets':
743
- result = query_data_targets(args);
744
- break;
745
- case 'find_exposure_leaks':
746
- result = find_exposure_leaks(args);
747
- break;
748
- case 'find_semantic_clones':
749
- result = find_semantic_clones(args);
750
- break;
751
- case 'create_symbol':
752
- result = create_symbol(args);
753
- break;
754
- // Diff Tools
755
- case 'diff_bundle':
756
- result = await diffBundle(args);
757
- break;
758
- case 'conflict_matrix':
759
- result = conflictMatrix(args);
760
- break;
761
- default:
762
- result = { error: `Unknown tool: ${name}` };
763
- }
764
- // Log telemetry for the tool execution
765
- if (isSupabaseConfigured() && name !== 'list_projects') {
766
- const executionMs = Date.now() - startTime;
767
- const projectHash = args && typeof args === 'object' && 'project' in args
768
- ? String(args.project)
769
- : loader.getProjectNames()[0] || 'unknown';
770
- // Fire-and-forget telemetry log
771
- logTelemetry({
772
- tool_name: name,
773
- project_hash: projectHash,
774
- execution_ms: executionMs,
775
- // user_id will be added by the Ptah API gateway if passing through there,
776
- // or stay null if running strictly local CLI without an injected token.
777
- }).catch(() => { });
778
- }
423
+ const result = await res.json();
779
424
  return {
780
- content: [{
781
- type: 'text',
782
- text: JSON.stringify(result, null, 2),
783
- }],
425
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
784
426
  };
785
427
  }
786
428
  catch (err) {
787
429
  return {
788
- content: [{
789
- type: 'text',
790
- text: JSON.stringify({
791
- error: `Tool ${name} failed: ${err.message}`,
792
- }, null, 2),
793
- }],
430
+ content: [{ type: 'text', text: JSON.stringify({ error: `Cloud proxy failed: ${err.message}` }, null, 2) }],
794
431
  isError: true,
795
432
  };
796
433
  }
797
434
  });
798
- // Start stdio transport
799
435
  const transport = new StdioServerTransport();
800
436
  await server.connect(transport);
801
- if (isMulti) {
802
- process.stderr.write(`Seshat MCP server started: ${projectNames.length} projects (${totalEntities} entities) — ${projectNames.join(', ')}\n`);
803
- }
804
- else {
805
- const manifest = loader.getManifest();
806
- process.stderr.write(`Seshat MCP server started: ${manifest?.projectName || 'unknown'} (${totalEntities} entities)\n`);
807
- }
437
+ process.stderr.write(`Seshat Cloud Proxy started. Routing requests to Ptah API...\n`);
808
438
  }
809
439
  main().catch((err) => {
810
440
  process.stderr.write(`Fatal: ${err.message}\n`);
@@ -7,6 +7,7 @@
7
7
  * These tools compare TWO entity sets (base vs branch, or task vs task),
8
8
  * unlike the single-bundle queries in index.ts and functors.ts.
9
9
  */
10
+ import type { ProjectLoader } from '../types.js';
10
11
  export declare function diffBundle(args: {
11
12
  worktree_path: string;
12
13
  project?: string;
@@ -5,7 +5,7 @@
5
5
  * Each tool computes derived insights (dead code, layer violations,
6
6
  * coupling metrics, auth coverage, etc.) from the entity data.
7
7
  */
8
- import type { JstfEntity } from '../types.js';
8
+ import type { JstfEntity, ProjectLoader } from '../types.js';
9
9
  /**
10
10
  * Estimate the token cost of loading an entity's source code into an LLM context.
11
11
  * Uses real sourceTokens from the extraction pipeline when available (v0.3.2+),
@@ -16,19 +16,19 @@ export declare function findDeadCode(args: {
16
16
  include_tests?: boolean;
17
17
  project?: string;
18
18
  }, loader: ProjectLoader): unknown;
19
- export declare function findLayerViolations(args?: {
19
+ export declare function findLayerViolations(args: {
20
20
  project?: string;
21
- }): unknown;
21
+ }, loader: ProjectLoader): unknown;
22
22
  export declare function getCouplingMetrics(args: {
23
23
  group_by?: 'module' | 'layer';
24
24
  project?: string;
25
25
  }, loader: ProjectLoader): unknown;
26
- export declare function getAuthMatrix(args?: {
26
+ export declare function getAuthMatrix(args: {
27
27
  project?: string;
28
- }): unknown;
29
- export declare function findErrorGaps(args?: {
28
+ }, loader: ProjectLoader): unknown;
29
+ export declare function findErrorGaps(args: {
30
30
  project?: string;
31
- }): unknown;
31
+ }, loader: ProjectLoader): unknown;
32
32
  export declare function getTestCoverage(args: {
33
33
  weight_by_blast_radius?: boolean;
34
34
  project?: string;
@@ -66,12 +66,12 @@ export declare function reportActualBurn(args: {
66
66
  project?: string;
67
67
  notes?: string;
68
68
  }, loader: ProjectLoader): Promise<unknown>;
69
- export declare function find_runtime_violations(args?: {
69
+ export declare function find_runtime_violations(args: {
70
70
  project?: string;
71
- }): unknown;
72
- export declare function find_ownership_violations(args?: {
71
+ }, loader: ProjectLoader): unknown;
72
+ export declare function find_ownership_violations(args: {
73
73
  project?: string;
74
- }): unknown;
74
+ }, loader: ProjectLoader): unknown;
75
75
  export declare function query_traits(args: {
76
76
  trait: string;
77
77
  project?: string;
@@ -91,13 +91,13 @@ export declare function query_data_targets(args: {
91
91
  target_name: string;
92
92
  project?: string;
93
93
  }, loader: ProjectLoader): unknown;
94
- export declare function find_exposure_leaks(args?: {
94
+ export declare function find_exposure_leaks(args: {
95
95
  project?: string;
96
- }): unknown;
97
- export declare function find_semantic_clones(args?: {
96
+ }, loader: ProjectLoader): unknown;
97
+ export declare function find_semantic_clones(args: {
98
98
  project?: string;
99
99
  min_complexity?: number;
100
- }): unknown;
100
+ }, loader: ProjectLoader): unknown;
101
101
  export declare function create_symbol(args: {
102
102
  id: string;
103
103
  source_file: string;
@@ -151,7 +151,7 @@ export function findDeadCode(args, loader) {
151
151
  };
152
152
  }
153
153
  // ─── Tool: find_layer_violations ─────────────────────────────────
154
- export function findLayerViolations(args) {
154
+ export function findLayerViolations(args, loader) {
155
155
  const projErr = validateProject(args?.project, loader);
156
156
  if (projErr)
157
157
  return { error: projErr };
@@ -278,7 +278,7 @@ export function getCouplingMetrics(args, loader) {
278
278
  };
279
279
  }
280
280
  // ─── Tool: get_auth_matrix ───────────────────────────────────────
281
- export function getAuthMatrix(args) {
281
+ export function getAuthMatrix(args, loader) {
282
282
  const projErr = validateProject(args?.project, loader);
283
283
  if (projErr)
284
284
  return { error: projErr };
@@ -335,7 +335,7 @@ export function getAuthMatrix(args) {
335
335
  };
336
336
  }
337
337
  // ─── Tool: find_error_gaps ───────────────────────────────────────
338
- export function findErrorGaps(args) {
338
+ export function findErrorGaps(args, loader) {
339
339
  const projErr = validateProject(args?.project, loader);
340
340
  if (projErr)
341
341
  return { error: projErr };
@@ -822,7 +822,7 @@ export async function reportActualBurn(args, loader) {
822
822
  };
823
823
  }
824
824
  // ─── Tool: find_runtime_violations (Runtime Context) ────────────
825
- export function find_runtime_violations(args) {
825
+ export function find_runtime_violations(args, loader) {
826
826
  const projErr = validateProject(args?.project, loader);
827
827
  if (projErr)
828
828
  return { error: projErr };
@@ -863,7 +863,7 @@ export function find_runtime_violations(args) {
863
863
  };
864
864
  }
865
865
  // ─── Tool: find_ownership_violations (Ownership & Lifetimes) ─────
866
- export function find_ownership_violations(args) {
866
+ export function find_ownership_violations(args, loader) {
867
867
  const projErr = validateProject(args?.project, loader);
868
868
  if (projErr)
869
869
  return { error: projErr };
@@ -1079,7 +1079,7 @@ export function query_data_targets(args, loader) {
1079
1079
  };
1080
1080
  }
1081
1081
  // ─── Tool: find_exposure_leaks (Architectural Visibility) ───────
1082
- export function find_exposure_leaks(args) {
1082
+ export function find_exposure_leaks(args, loader) {
1083
1083
  const projErr = validateProject(args?.project, loader);
1084
1084
  if (projErr)
1085
1085
  return { error: projErr };
@@ -1114,7 +1114,7 @@ export function find_exposure_leaks(args) {
1114
1114
  }
1115
1115
  // ─── Tool: find_semantic_clones (Logic Analysis) ────────────────
1116
1116
  import { createHash } from 'crypto';
1117
- export function find_semantic_clones(args) {
1117
+ export function find_semantic_clones(args, loader) {
1118
1118
  const projErr = validateProject(args?.project, loader);
1119
1119
  if (projErr)
1120
1120
  return { error: projErr };
@@ -7,7 +7,7 @@
7
7
  * Multi-project: when multiple projects are loaded, each tool accepts an optional
8
8
  * `project` parameter. In multi-project mode, `project` is required.
9
9
  */
10
- import type { JstfEntity } from '../types.js';
10
+ import type { JstfEntity, ProjectLoader } from '../types.js';
11
11
  import { type CallGraph } from '../graph.js';
12
12
  export declare function getGraph(project: string | undefined, loader: ProjectLoader): CallGraph;
13
13
  /**
package/package.json CHANGED
@@ -1,12 +1,19 @@
1
1
  {
2
2
  "name": "@papyruslabsai/seshat-mcp",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Semantic MCP server — exposes a codebase's structure, dependencies, and constraints as queryable tools",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "seshat-mcp": "./dist/index.js"
8
8
  },
9
9
  "main": "./dist/index.js",
10
+ "exports": {
11
+ ".": "./dist/index.js",
12
+ "./tools": "./dist/tools/index.js",
13
+ "./functors": "./dist/tools/functors.js",
14
+ "./diff": "./dist/tools/diff.js",
15
+ "./types": "./dist/types.js"
16
+ },
10
17
  "scripts": {
11
18
  "build": "tsc",
12
19
  "dev": "tsc --watch",