@papyruslabsai/seshat-mcp 0.13.6 → 0.14.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.
Files changed (2) hide show
  1. package/dist/index.js +121 -16
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -35,6 +35,74 @@ All tools are read-only and safe to call speculatively — there is no cost to t
35
35
 
36
36
  get_blast_radius and get_optimal_context are designed to be called iteratively. Start with any entity, then feed discovered entities back in to expand your understanding. Each round reveals new structure that informs where to look next. When answering "what does this system do?" questions, a few rounds of blast_radius → get_entity → blast_radius on the newly discovered symbols will build a complete picture faster than reading files.`;
37
37
  const TIER_ORDER = ['cartographer', 'pro', 'analyst', 'architect', 'founder'];
38
+ const REVELATION_ORDER = ['core', 'navigate', 'security', 'quality', 'full'];
39
+ // Which tools appear at each stage (cumulative — each stage includes all previous)
40
+ const TOOL_REVELATION = {
41
+ // Always visible
42
+ get_account_status: 'core',
43
+ list_projects: 'core',
44
+ // Core loop — the curiosity engine (available immediately)
45
+ get_entity: 'core',
46
+ get_blast_radius: 'core',
47
+ get_dependencies: 'core',
48
+ get_optimal_context: 'core',
49
+ // Navigation — after agent starts exploring (revealed after ~3 queries)
50
+ query_entities: 'navigate',
51
+ list_modules: 'navigate',
52
+ get_topology: 'navigate',
53
+ get_data_flow: 'navigate',
54
+ find_by_constraint: 'navigate',
55
+ // Security surface — after architecture is understood (~8 queries)
56
+ get_auth_matrix: 'security',
57
+ find_exposure_leaks: 'security',
58
+ // Quality & audit — after deep exploration (~15 queries)
59
+ find_dead_code: 'quality',
60
+ find_layer_violations: 'quality',
61
+ get_coupling_metrics: 'quality',
62
+ find_error_gaps: 'quality',
63
+ get_test_coverage: 'quality',
64
+ find_runtime_violations: 'quality',
65
+ find_ownership_violations: 'quality',
66
+ query_traits: 'quality',
67
+ find_semantic_clones: 'quality',
68
+ // Architect — always gated by tier, shown when quality is visible
69
+ estimate_task_cost: 'full',
70
+ simulate_mutation: 'full',
71
+ create_symbol: 'full',
72
+ diff_bundle: 'full',
73
+ conflict_matrix: 'full',
74
+ query_data_targets: 'full',
75
+ };
76
+ // Query thresholds for each stage transition
77
+ const REVELATION_THRESHOLDS = {
78
+ core: 0, // Immediate
79
+ navigate: 3, // After a few core loop calls
80
+ security: 8, // After architecture is understood
81
+ quality: 15, // After deep exploration
82
+ full: 25, // Power user territory
83
+ };
84
+ // Session state for progressive revelation
85
+ let _sessionQueryCount = 0;
86
+ let _currentStage = 'core';
87
+ let _serverRef = null; // Stored so we can send notifications
88
+ function stageAtLeast(current, required) {
89
+ return REVELATION_ORDER.indexOf(current) >= REVELATION_ORDER.indexOf(required);
90
+ }
91
+ function checkRevelation() {
92
+ for (const stage of REVELATION_ORDER) {
93
+ if (_sessionQueryCount >= REVELATION_THRESHOLDS[stage]) {
94
+ if (REVELATION_ORDER.indexOf(stage) > REVELATION_ORDER.indexOf(_currentStage)) {
95
+ const previousStage = _currentStage;
96
+ _currentStage = stage;
97
+ // Notify client that tool list has changed
98
+ if (_serverRef) {
99
+ _serverRef.notification({ method: 'notifications/tools/list_changed' });
100
+ process.stderr.write(`[Seshat] Revelation: ${previousStage} → ${stage} (${_sessionQueryCount} queries). Notified client.\n`);
101
+ }
102
+ }
103
+ }
104
+ }
105
+ }
38
106
  const TOOL_TIERS = {
39
107
  // Cartographer (free) — explore, navigate, and assess security surface
40
108
  list_projects: 'cartographer',
@@ -444,6 +512,8 @@ const TOOLS = [
444
512
  },
445
513
  ];
446
514
  // ─── Project Resolution (Fallback) ────────────────────────────────
515
+ // Cache for the last project used — so tools auto-scope after list_projects
516
+ let _lastKnownProject;
447
517
  function resolveProjectName() {
448
518
  // If SESHAT_PROJECTS is set to a single name, use it
449
519
  if (process.env.SESHAT_PROJECTS && !process.env.SESHAT_PROJECTS.includes(',') && !process.env.SESHAT_PROJECTS.includes('*')) {
@@ -459,8 +529,16 @@ function resolveProjectName() {
459
529
  }
460
530
  catch { }
461
531
  }
462
- // Ultimate fallback to directory name
463
- return path.basename(process.cwd());
532
+ // If we've seen a project from list_projects, use that
533
+ if (_lastKnownProject)
534
+ return _lastKnownProject;
535
+ // Only fall back to CWD basename if there's a .seshat/ dir (i.e., it's actually a Seshat project)
536
+ // This prevents agents running from unrelated directories from sending bogus project names
537
+ if (fs.existsSync(path.join(process.cwd(), '.seshat'))) {
538
+ return path.basename(process.cwd());
539
+ }
540
+ // No project could be resolved — let the API return a helpful error
541
+ return undefined;
464
542
  }
465
543
  // ─── Cloud API helper ─────────────────────────────────────────────
466
544
  function getCloudUrl(path) {
@@ -473,11 +551,13 @@ function getCloudUrl(path) {
473
551
  async function main() {
474
552
  const server = new Server({
475
553
  name: 'seshat',
476
- version: '0.13.4',
554
+ version: '0.14.0',
477
555
  }, {
478
- capabilities: { tools: {} },
556
+ capabilities: { tools: { listChanged: true } },
479
557
  instructions: SERVER_INSTRUCTIONS,
480
558
  });
559
+ // Store server ref for sending notifications from revelation system
560
+ _serverRef = server;
481
561
  // ─── Dynamic ListTools — only expose tools the user can access ──
482
562
  server.setRequestHandler(ListToolsRequestSchema, async () => {
483
563
  const apiKey = process.env.SESHAT_API_KEY;
@@ -498,13 +578,19 @@ async function main() {
498
578
  // If unavailable, default to cartographer (free tier tools only)
499
579
  }
500
580
  }
501
- // Only return tools the user's tier can actually use
581
+ // Filter tools by BOTH tier access AND revelation stage
502
582
  const visibleTools = TOOLS.filter((tool) => {
583
+ // Tier gate: user must have access
503
584
  const requiredTier = TOOL_TIERS[tool.name];
504
- if (!requiredTier)
505
- return true; // get_account_status — always visible
506
- return tierAtLeast(userTier, requiredTier);
585
+ if (requiredTier && !tierAtLeast(userTier, requiredTier))
586
+ return false;
587
+ // Revelation gate: tool must be revealed at current stage
588
+ const requiredStage = TOOL_REVELATION[tool.name];
589
+ if (requiredStage && !stageAtLeast(_currentStage, requiredStage))
590
+ return false;
591
+ return true;
507
592
  });
593
+ process.stderr.write(`[Seshat] ListTools: stage=${_currentStage}, queries=${_sessionQueryCount}, tools=${visibleTools.length}/${TOOLS.length}\n`);
508
594
  return { tools: visibleTools };
509
595
  });
510
596
  // ─── CallTool handler ──────────────────────────────────────────
@@ -575,22 +661,34 @@ async function main() {
575
661
  }
576
662
  }
577
663
  // Determine the project hash for this workspace
578
- const project_hash = (args && typeof args === 'object' && 'project' in args)
579
- ? String(args.project)
580
- : resolveProjectName();
664
+ // list_projects is unscoped it returns ALL projects for the user
665
+ const project_hash = name === 'list_projects'
666
+ ? undefined
667
+ : (args && typeof args === 'object' && 'project' in args)
668
+ ? String(args.project)
669
+ : resolveProjectName();
670
+ // If no project could be resolved and this isn't list_projects, tell the LLM how to fix it
671
+ if (!project_hash && name !== 'list_projects') {
672
+ return {
673
+ content: [{ type: 'text', text: JSON.stringify({
674
+ error: 'No project specified. Call list_projects first to see available projects, then pass the project name as the "project" argument.',
675
+ hint: 'If list_projects returns empty, the repo may not be synced yet. Use the Ptah demo API (POST /api/demo/create) to import a repo first.',
676
+ }, null, 2) }],
677
+ isError: true,
678
+ };
679
+ }
581
680
  try {
582
681
  // Proxy the tool call to the cloud
682
+ const body = { tool: name, args };
683
+ if (project_hash)
684
+ body.project_hash = project_hash;
583
685
  const res = await fetch(getCloudUrl('/api/mcp/execute'), {
584
686
  method: 'POST',
585
687
  headers: {
586
688
  'Content-Type': 'application/json',
587
689
  'x-api-key': apiKey
588
690
  },
589
- body: JSON.stringify({
590
- tool: name,
591
- project_hash,
592
- args
593
- })
691
+ body: JSON.stringify(body)
594
692
  });
595
693
  if (!res.ok) {
596
694
  const errorText = await res.text();
@@ -600,6 +698,13 @@ async function main() {
600
698
  };
601
699
  }
602
700
  const result = await res.json();
701
+ // Cache the first project from list_projects so subsequent tool calls auto-scope
702
+ if (name === 'list_projects' && result.projects && result.projects.length > 0) {
703
+ _lastKnownProject = result.projects[0].name;
704
+ }
705
+ // Progressive revelation: count queries and check for stage transitions
706
+ _sessionQueryCount++;
707
+ checkRevelation();
603
708
  // Separate _meta into assistant-only content so it doesn't clutter
604
709
  // the user-visible response. The LLM still sees it for context.
605
710
  if (result._meta) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papyruslabsai/seshat-mcp",
3
- "version": "0.13.6",
3
+ "version": "0.14.0",
4
4
  "description": "Semantic MCP server — exposes a codebase's structure, dependencies, and constraints as queryable tools",
5
5
  "type": "module",
6
6
  "bin": {