@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.
- package/dist/index.js +121 -16
- 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
|
-
//
|
|
463
|
-
|
|
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.
|
|
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
|
-
//
|
|
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
|
|
506
|
-
|
|
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
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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