@papyruslabsai/seshat-mcp 0.14.2 → 0.16.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 CHANGED
@@ -6,7 +6,6 @@
6
6
  * proxied to the Ptah Cloud API. Tool descriptions are written as triggers —
7
7
  * they tell the LLM *when* to reach for each tool, not just what it does.
8
8
  */
9
- import fs from 'fs';
10
9
  import path from 'path';
11
10
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
12
11
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
@@ -15,136 +14,63 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextpro
15
14
  // Sent to the LLM at connection time. This is the "first contact" pitch.
16
15
  const SERVER_INSTRUCTIONS = `Seshat provides structural code analysis backed by a compiled intermediate representation — not heuristic guesses or text search. Every function, class, and route in the synced codebase has been extracted into a typed symbol graph with dependency edges, data flow, constraints, and architectural layer tags. Results are precise and complete — if Seshat says a function has 3 callers, it has exactly 3 callers.
17
16
 
17
+ GETTING STARTED — If list_projects returns empty, the current project hasn't been synced yet. Use the sync_project tool to import it:
18
+ 1. Detect the git remote: run \`git remote get-url origin\` in the user's terminal
19
+ 2. Call sync_project with that repo URL
20
+ 3. Wait for extraction to complete (typically 5-30 seconds depending on repo size)
21
+ 4. Then call list_projects again — the project will now appear
22
+ Note: For private repos, sync_project will return a GitHub authorization URL. Direct the user to open it in their browser to connect their GitHub account, then retry sync_project. Once connected, all future private repo syncs work automatically.
23
+
18
24
  Use Seshat tools instead of grep/Read when you need to understand code structure. Each tool maps to a question you're already asking:
25
+
26
+ Setup & Navigation:
19
27
  - "What projects are loaded?" → list_projects
28
+ - "Sync this repo to Seshat" → sync_project
20
29
  - "How is the codebase organized?" → list_modules
21
30
  - "What's the full API surface?" → get_topology
31
+ - "What tier am I on / what tools are available?" → get_account_status
32
+
33
+ Understanding Code:
22
34
  - "Find functions by name or layer" → query_entities
23
35
  - "Deep-dive a single function" → get_entity
24
36
  - "Who calls this / what does it call?" → get_dependencies
25
- - "What breaks if I change this?" → get_blast_radius
26
37
  - "What data does this read/write/mutate?" → get_data_flow
38
+ - "What should I read before modifying X?" → get_optimal_context
27
39
  - "Which functions touch the DB / require auth / throw?" → find_by_constraint
28
40
  - "What reads or writes the 'users' table?" → find_by_constraint(table="users")
41
+ - "Find functions that can fail, including transitively" → query_traits(trait="fallible")
42
+
43
+ Change Planning:
44
+ - "What breaks if I change this?" → get_blast_radius
45
+ - "Is there dead code I can safely delete?" → find_dead_code
46
+ - "Is there copy-pasted logic I should consolidate?" → find_semantic_clones
47
+
48
+ Security & Quality Audits:
29
49
  - "Which endpoints require auth and which don't?" → get_auth_matrix
30
50
  - "Where is sensitive data exposed without protection?" → find_exposure_leaks
31
- - "What should I read before modifying X?" → get_optimal_context
32
- - "What tier am I on / what tools are available?" → get_account_status
51
+ - "Where are errors thrown but never caught?" → find_error_gaps
52
+ - "Are there architecture violations (e.g. routes calling repos directly)?" → find_layer_violations
53
+ - "Does framework-agnostic code import framework-specific code?" → find_runtime_violations
54
+ - "Are there memory/lifecycle/ownership issues?" → find_ownership_violations
55
+
56
+ Metrics:
57
+ - "How coupled is the codebase? Where are the hotspots?" → get_coupling_metrics
58
+ - "Which functions are tested and which aren't?" → get_test_coverage
33
59
 
34
60
  All tools are read-only and safe to call speculatively — there is no cost to trying them.
35
61
 
36
62
  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
- 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
- }
106
- const TOOL_TIERS = {
107
- // Cartographer (free) — explore, navigate, and assess security surface
108
- list_projects: 'cartographer',
109
- query_entities: 'cartographer',
110
- get_entity: 'cartographer',
111
- get_dependencies: 'cartographer',
112
- get_data_flow: 'cartographer',
113
- find_by_constraint: 'cartographer',
114
- get_blast_radius: 'cartographer',
115
- list_modules: 'cartographer',
116
- get_topology: 'cartographer',
117
- get_optimal_context: 'cartographer',
118
- get_auth_matrix: 'cartographer',
119
- find_exposure_leaks: 'cartographer',
120
- // Analyst (tier 2) — audit and diagnose
121
- find_dead_code: 'analyst',
122
- find_layer_violations: 'analyst',
123
- get_coupling_metrics: 'analyst',
124
- find_error_gaps: 'analyst',
125
- get_test_coverage: 'analyst',
126
- find_runtime_violations: 'analyst',
127
- find_ownership_violations: 'analyst',
128
- query_traits: 'analyst',
129
- find_semantic_clones: 'analyst',
130
- // Architect (tier 3) — simulate, estimate, and act
131
- estimate_task_cost: 'architect',
132
- simulate_mutation: 'architect',
133
- create_symbol: 'architect',
134
- diff_bundle: 'architect',
135
- conflict_matrix: 'architect',
136
- query_data_targets: 'architect',
137
- };
138
- const TIER_LABELS = {
139
- cartographer: 'Cartographer (Free)',
140
- pro: 'Seshat Pro',
141
- analyst: 'Seshat Shield',
142
- architect: 'Architect',
143
- founder: 'Founder (All Access)',
144
- };
145
- function tierAtLeast(userTier, requiredTier) {
146
- return TIER_ORDER.indexOf(userTier) >= TIER_ORDER.indexOf(requiredTier);
147
- }
63
+ // ─── Private Tools (Ptah IP never exposed publicly) ────────────
64
+ // These tools are reserved for the Ptah write layer. They exist in
65
+ // the codebase but are never listed, never hinted at, never mentioned.
66
+ const PRIVATE_TOOLS = new Set([
67
+ 'estimate_task_cost',
68
+ 'simulate_mutation',
69
+ 'create_symbol',
70
+ 'diff_bundle',
71
+ 'conflict_matrix',
72
+ 'query_data_targets',
73
+ ]);
148
74
  // ─── Shared Definitions ──────────────────────────────────────────
149
75
  const projectParam = {
150
76
  type: 'string',
@@ -167,10 +93,21 @@ const TOOLS = [
167
93
  // ─── Cartographer (Free Tier) ─────────────────────────────────────
168
94
  {
169
95
  name: 'list_projects',
170
- description: 'Start here. Returns all synced codebases with their size, language, and project name. You need the project name for every other tool.',
96
+ description: 'Start here. Returns all synced codebases with their size, language, and project name. You need the project name for every other tool. If this returns empty, use sync_project to import the current repo.',
171
97
  inputSchema: { type: 'object', properties: {} },
172
98
  annotations: READ_ONLY_OPEN,
173
99
  },
100
+ {
101
+ name: 'sync_project',
102
+ description: 'Import a public GitHub repo into Seshat for structural analysis. Call this when list_projects returns empty or when the user wants to analyze a new repo. Detects the git remote automatically if no URL is provided. Extraction typically takes 5-30 seconds. After syncing, call list_projects to confirm the project is available.',
103
+ inputSchema: {
104
+ type: 'object',
105
+ properties: {
106
+ repo_url: { type: 'string', description: 'Public GitHub repo URL (e.g., https://github.com/org/repo). If omitted, tries to detect from the current git remote.' },
107
+ },
108
+ },
109
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
110
+ },
174
111
  {
175
112
  name: 'query_entities',
176
113
  description: 'Like grep but for code structure. Find functions, classes, and routes by name, architectural layer (route/service/component), or module. Returns matching symbols with their type, file, and layer — use this instead of grep when you need to find code by what it does, not by text content.',
@@ -327,10 +264,14 @@ const TOOLS = [
327
264
  },
328
265
  {
329
266
  name: 'get_auth_matrix',
330
- description: 'Audit authentication coverage. Shows which API routes and controllers require auth and which don\'t, plus inconsistencies like database access without auth checks. Use this for security reviews.',
267
+ description: 'Audit authentication coverage. Shows which API routes and controllers require auth and which don\'t, plus inconsistencies like database access without auth checks. For large codebases, use the module parameter to drill into a specific module/directory.',
331
268
  inputSchema: {
332
269
  type: 'object',
333
- properties: { project: projectParam },
270
+ properties: {
271
+ project: projectParam,
272
+ module: { type: 'string', description: 'Filter to routes/controllers in a specific module or directory path (optional).' },
273
+ layer: { type: 'string', description: 'Filter to a specific layer: "route" or "controller" (optional).' },
274
+ },
334
275
  },
335
276
  annotations: READ_ONLY_OPEN,
336
277
  },
@@ -511,6 +452,11 @@ const TOOLS = [
511
452
  annotations: READ_ONLY_OPEN,
512
453
  },
513
454
  ];
455
+ // ─── _meta Steering Hints ────────────────────────────────────────
456
+ // After each tool call, suggest the next logical tool based on what
457
+ // the agent has been doing. This replaces progressive revelation —
458
+ // all tools are visible, but hints guide the agent toward the right
459
+ // tool at the right time.
514
460
  // ─── Project Resolution (Fallback) ────────────────────────────────
515
461
  // Cache for the last project used — so tools auto-scope after list_projects
516
462
  let _lastKnownProject;
@@ -519,25 +465,10 @@ function resolveProjectName() {
519
465
  if (process.env.SESHAT_PROJECTS && !process.env.SESHAT_PROJECTS.includes(',') && !process.env.SESHAT_PROJECTS.includes('*')) {
520
466
  return path.basename(process.env.SESHAT_PROJECTS);
521
467
  }
522
- // Fallback to reading local manifest if it exists
523
- const manifestPath = path.join(process.cwd(), '.seshat', 'manifest.json');
524
- if (fs.existsSync(manifestPath)) {
525
- try {
526
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
527
- if (manifest.projectName)
528
- return manifest.projectName;
529
- }
530
- catch { }
531
- }
532
- // If we've seen a project from list_projects, use that
468
+ // If we've seen a project from list_projects or sync_project, use that
533
469
  if (_lastKnownProject)
534
470
  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
471
+ // No project could be resolved caller should use list_projects or sync_project
541
472
  return undefined;
542
473
  }
543
474
  // ─── Cloud API helper ─────────────────────────────────────────────
@@ -551,46 +482,15 @@ function getCloudUrl(path) {
551
482
  async function main() {
552
483
  const server = new Server({
553
484
  name: 'seshat',
554
- version: '0.14.0',
485
+ version: '0.16.0',
555
486
  }, {
556
- capabilities: { tools: { listChanged: true } },
487
+ capabilities: { tools: {} },
557
488
  instructions: SERVER_INSTRUCTIONS,
558
489
  });
559
- // Store server ref for sending notifications from revelation system
560
- _serverRef = server;
561
- // ─── Dynamic ListTools — only expose tools the user can access ──
490
+ // ─── ListTools all public tools, Architect tools hidden (Ptah IP) ──
562
491
  server.setRequestHandler(ListToolsRequestSchema, async () => {
563
- const apiKey = process.env.SESHAT_API_KEY;
564
- let userTier = 'cartographer';
565
- // Fetch account status to determine which tools to expose
566
- if (apiKey) {
567
- try {
568
- const res = await fetch(getCloudUrl('/api/mcp/account'), {
569
- method: 'GET',
570
- headers: { 'x-api-key': apiKey },
571
- });
572
- if (res.ok) {
573
- const account = await res.json();
574
- userTier = account.tier || 'cartographer';
575
- }
576
- }
577
- catch {
578
- // If unavailable, default to cartographer (free tier tools only)
579
- }
580
- }
581
- // Filter tools by BOTH tier access AND revelation stage
582
- const visibleTools = TOOLS.filter((tool) => {
583
- // Tier gate: user must have access
584
- const requiredTier = TOOL_TIERS[tool.name];
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;
592
- });
593
- process.stderr.write(`[Seshat] ListTools: stage=${_currentStage}, queries=${_sessionQueryCount}, tools=${visibleTools.length}/${TOOLS.length}\n`);
492
+ const visibleTools = TOOLS.filter((tool) => !PRIVATE_TOOLS.has(tool.name));
493
+ process.stderr.write(`[Seshat] ListTools: ${visibleTools.length} tools (${PRIVATE_TOOLS.size} private)\n`);
594
494
  return { tools: visibleTools };
595
495
  });
596
496
  // ─── CallTool handler ──────────────────────────────────────────
@@ -599,7 +499,14 @@ async function main() {
599
499
  const apiKey = process.env.SESHAT_API_KEY;
600
500
  if (!apiKey) {
601
501
  return {
602
- content: [{ type: 'text', text: JSON.stringify({ error: 'SESHAT_API_KEY environment variable is required. Get your free key at https://ptah.papyruslabs.ai' }, null, 2) }],
502
+ content: [{ type: 'text', text: JSON.stringify({ error: 'SESHAT_API_KEY environment variable is required. Get your free key at https://seshat.papyruslabs.ai' }, null, 2) }],
503
+ isError: true,
504
+ };
505
+ }
506
+ // Block private Ptah tools from being called
507
+ if (PRIVATE_TOOLS.has(name)) {
508
+ return {
509
+ content: [{ type: 'text', text: JSON.stringify({ error: `Unknown tool: ${name}` }, null, 2) }],
603
510
  isError: true,
604
511
  };
605
512
  }
@@ -618,37 +525,16 @@ async function main() {
618
525
  };
619
526
  }
620
527
  const account = await res.json();
621
- const userTier = account.tier || 'cartographer';
622
- const credits = account.credits || 0;
623
- // Build response: lead with what you CAN do, not what you can't
624
- const availableTools = [];
625
- const upgradeTeaser = {};
626
- for (const [toolName, requiredTier] of Object.entries(TOOL_TIERS)) {
627
- if (tierAtLeast(userTier, requiredTier)) {
628
- availableTools.push(toolName);
629
- }
630
- else {
631
- if (!upgradeTeaser[TIER_LABELS[requiredTier]]) {
632
- upgradeTeaser[TIER_LABELS[requiredTier]] = [];
633
- }
634
- upgradeTeaser[TIER_LABELS[requiredTier]].push(toolName);
635
- }
636
- }
528
+ // List all public tools
529
+ const publicTools = TOOLS
530
+ .filter(t => !PRIVATE_TOOLS.has(t.name))
531
+ .map(t => t.name);
637
532
  const response = {
638
- tier: userTier,
639
- tier_label: TIER_LABELS[userTier],
640
- ptah_credits: credits,
641
- your_tools: availableTools,
642
- tool_count: `${availableTools.length} tools available`,
533
+ status: 'active',
534
+ tools_available: publicTools.length,
535
+ your_tools: publicTools,
536
+ dashboard: 'https://seshat.papyruslabs.ai/dashboard',
643
537
  };
644
- // Only mention upgrades if there are locked tools, and frame positively
645
- if (Object.keys(upgradeTeaser).length > 0) {
646
- const totalLocked = Object.values(upgradeTeaser).reduce((sum, t) => sum + t.length, 0);
647
- response.upgrades_available = {
648
- summary: `${totalLocked} additional diagnostic and simulation tools available with a tier upgrade — find dead code, coupling hotspots, test gaps, layer violations, and simulate changes before making them.`,
649
- url: 'https://ptah.papyruslabs.ai/settings/billing',
650
- };
651
- }
652
538
  return {
653
539
  content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
654
540
  };
@@ -660,6 +546,133 @@ async function main() {
660
546
  };
661
547
  }
662
548
  }
549
+ // ─── sync_project ──────────────────────────────────────────
550
+ if (name === 'sync_project') {
551
+ let repoUrl = args?.repo_url;
552
+ // If no URL provided, try to detect git remote
553
+ if (!repoUrl) {
554
+ try {
555
+ const { execSync } = await import('child_process');
556
+ const remote = execSync('git remote get-url origin', { timeout: 5000 }).toString().trim();
557
+ // Normalize SSH URLs to HTTPS
558
+ const sshMatch = remote.match(/^git@github\.com:(.+)\.git$/);
559
+ repoUrl = sshMatch ? `https://github.com/${sshMatch[1]}` : remote.replace(/\.git$/, '');
560
+ }
561
+ catch {
562
+ return {
563
+ content: [{ type: 'text', text: JSON.stringify({
564
+ error: 'No repo_url provided and could not detect git remote. Please provide the GitHub repo URL.',
565
+ hint: 'Example: sync_project({ repo_url: "https://github.com/org/repo" })',
566
+ }, null, 2) }],
567
+ isError: true,
568
+ };
569
+ }
570
+ }
571
+ try {
572
+ const res = await fetch(getCloudUrl('/api/extract/create'), {
573
+ method: 'POST',
574
+ headers: {
575
+ 'Content-Type': 'application/json',
576
+ 'x-api-key': apiKey,
577
+ },
578
+ body: JSON.stringify({ repo_url: repoUrl }),
579
+ });
580
+ if (!res.ok) {
581
+ const errorText = await res.text();
582
+ return {
583
+ content: [{ type: 'text', text: JSON.stringify({ error: `Sync failed (${res.status}): ${errorText}` }, null, 2) }],
584
+ isError: true,
585
+ };
586
+ }
587
+ const result = await res.json();
588
+ // If private repo needs GitHub auth
589
+ if (result.status === 'github_auth_required') {
590
+ return {
591
+ content: [{ type: 'text', text: JSON.stringify({
592
+ status: 'github_auth_required',
593
+ message: `This repository is private and requires GitHub authentication.\n\nTo connect your GitHub account, open this URL in your browser:\n\n${result.auth_url}\n\nAfter authorizing, try sync_project again.`,
594
+ auth_url: result.auth_url,
595
+ }, null, 2) }],
596
+ isError: true,
597
+ };
598
+ }
599
+ // If already ready, return immediately
600
+ if (result.status === 'ready') {
601
+ _lastKnownProject = result.repo_name;
602
+ return {
603
+ content: [{ type: 'text', text: JSON.stringify({
604
+ status: 'ready',
605
+ project: result.repo_name,
606
+ total_entities: result.total_entities,
607
+ language: result.language,
608
+ message: 'Project synced and ready. You can now use all Seshat tools with this project.',
609
+ cached: result.cached || false,
610
+ }, null, 2) }],
611
+ };
612
+ }
613
+ // If queued/extracting, poll until complete
614
+ if (result.status === 'queued' || result.status === 'extracting') {
615
+ const pollUrl = getCloudUrl(`/api/extract/status/${result.repo_name}`);
616
+ const maxAttempts = 60; // 5 minutes max
617
+ for (let i = 0; i < maxAttempts; i++) {
618
+ await new Promise(r => setTimeout(r, 5000));
619
+ try {
620
+ const pollRes = await fetch(pollUrl, { headers: { 'x-api-key': apiKey } });
621
+ if (pollRes.ok) {
622
+ const pollResult = await pollRes.json();
623
+ if (pollResult.status === 'ready') {
624
+ _lastKnownProject = pollResult.repo_name;
625
+ return {
626
+ content: [{ type: 'text', text: JSON.stringify({
627
+ status: 'ready',
628
+ project: pollResult.repo_name,
629
+ total_entities: pollResult.total_entities,
630
+ language: pollResult.language,
631
+ message: 'Project synced and ready. You can now use all Seshat tools with this project.',
632
+ }, null, 2) }],
633
+ };
634
+ }
635
+ if (pollResult.status === 'github_auth_required') {
636
+ return {
637
+ content: [{ type: 'text', text: JSON.stringify({
638
+ status: 'github_auth_required',
639
+ message: `This repository is private and requires GitHub authentication.\n\nTo connect your GitHub account, open this URL in your browser:\n\n${pollResult.auth_url || 'https://seshat.papyruslabs.ai/dashboard'}\n\nAfter authorizing, try sync_project again.`,
640
+ auth_url: pollResult.auth_url,
641
+ }, null, 2) }],
642
+ isError: true,
643
+ };
644
+ }
645
+ if (pollResult.status === 'failed') {
646
+ return {
647
+ content: [{ type: 'text', text: JSON.stringify({
648
+ error: `Extraction failed: ${pollResult.error || 'Unknown error'}`,
649
+ hint: 'The repo may be too large, private, or contain unsupported file types.',
650
+ }, null, 2) }],
651
+ isError: true,
652
+ };
653
+ }
654
+ }
655
+ }
656
+ catch { /* continue polling */ }
657
+ }
658
+ return {
659
+ content: [{ type: 'text', text: JSON.stringify({
660
+ status: 'timeout',
661
+ message: 'Extraction is taking longer than expected. Try calling list_projects in a minute to check if it completed.',
662
+ }, null, 2) }],
663
+ };
664
+ }
665
+ return {
666
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
667
+ };
668
+ }
669
+ catch (err) {
670
+ return {
671
+ content: [{ type: 'text', text: JSON.stringify({ error: `Sync failed: ${err.message}` }, null, 2) }],
672
+ isError: true,
673
+ };
674
+ }
675
+ }
663
676
  // Determine the project hash for this workspace
664
677
  // list_projects is unscoped — it returns ALL projects for the user
665
678
  const project_hash = name === 'list_projects'
@@ -672,7 +685,7 @@ async function main() {
672
685
  return {
673
686
  content: [{ type: 'text', text: JSON.stringify({
674
687
  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.',
688
+ hint: 'If list_projects returns empty, use sync_project to import the current repo first.',
676
689
  }, null, 2) }],
677
690
  isError: true,
678
691
  };
@@ -702,12 +715,10 @@ async function main() {
702
715
  if (name === 'list_projects' && result.projects && result.projects.length > 0) {
703
716
  _lastKnownProject = result.projects[0].name;
704
717
  }
705
- // Progressive revelation: count queries and check for stage transitions
706
- _sessionQueryCount++;
707
- checkRevelation();
708
718
  // Separate _meta into assistant-only content so it doesn't clutter
709
719
  // the user-visible response. The LLM still sees it for context.
710
- if (result._meta) {
720
+ // Server-side _meta now includes cross-tool recommendations.
721
+ if (result._meta && Object.keys(result._meta).length > 0) {
711
722
  const meta = result._meta;
712
723
  delete result._meta;
713
724
  return {
@@ -717,6 +728,9 @@ async function main() {
717
728
  ],
718
729
  };
719
730
  }
731
+ // Strip empty _meta
732
+ if (result._meta)
733
+ delete result._meta;
720
734
  return {
721
735
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
722
736
  };
@@ -730,7 +744,7 @@ async function main() {
730
744
  });
731
745
  const transport = new StdioServerTransport();
732
746
  await server.connect(transport);
733
- process.stderr.write(`Seshat MCP v0.13.1 connected. Structural code analysis ready.\n`);
747
+ process.stderr.write(`Seshat MCP v0.16.0 connected. Structural intelligence ready.\n`);
734
748
  }
735
749
  main().catch((err) => {
736
750
  process.stderr.write(`Fatal: ${err.message}\n`);
@@ -25,6 +25,8 @@ export declare function getCouplingMetrics(args: {
25
25
  }, loader: ProjectLoader): unknown;
26
26
  export declare function getAuthMatrix(args: {
27
27
  project?: string;
28
+ module?: string;
29
+ layer?: string;
28
30
  }, loader: ProjectLoader): unknown;
29
31
  export declare function findErrorGaps(args: {
30
32
  project?: string;
@@ -296,11 +296,24 @@ export function getAuthMatrix(args, loader) {
296
296
  if (projErr)
297
297
  return { error: projErr };
298
298
  const entities = loader.getEntities(args?.project);
299
- const apiEntities = entities.filter((e) => {
299
+ let apiEntities = entities.filter((e) => {
300
300
  const layer = entityLayer(e);
301
301
  return layer === 'route' || layer === 'controller' ||
302
302
  e.context?.exposure === 'api';
303
303
  });
304
+ // Optional module filter — drill into a specific module/directory
305
+ if (args?.module) {
306
+ const mod = args.module.toLowerCase();
307
+ apiEntities = apiEntities.filter((e) => {
308
+ const eModule = (e.context?.module || e._sourceFile || '').toLowerCase();
309
+ return eModule.includes(mod);
310
+ });
311
+ }
312
+ // Optional layer filter
313
+ if (args?.layer) {
314
+ const targetLayer = args.layer.toLowerCase();
315
+ apiEntities = apiEntities.filter((e) => entityLayer(e) === targetLayer);
316
+ }
304
317
  const withAuth = [];
305
318
  const withoutAuth = [];
306
319
  const inconsistencies = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papyruslabsai/seshat-mcp",
3
- "version": "0.14.2",
3
+ "version": "0.16.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": {