@papyruslabsai/seshat-mcp 0.12.1 → 0.12.3

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 +186 -79
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11,6 +11,47 @@ import path from 'path';
11
11
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
12
12
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
13
13
  import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
14
+ const TIER_ORDER = ['cartographer', 'analyst', 'architect'];
15
+ const TOOL_TIERS = {
16
+ // Cartographer (free) — explore and navigate
17
+ list_projects: 'cartographer',
18
+ query_entities: 'cartographer',
19
+ get_entity: 'cartographer',
20
+ get_dependencies: 'cartographer',
21
+ get_data_flow: 'cartographer',
22
+ find_by_constraint: 'cartographer',
23
+ get_blast_radius: 'cartographer',
24
+ list_modules: 'cartographer',
25
+ get_topology: 'cartographer',
26
+ get_optimal_context: 'cartographer',
27
+ // Analyst (tier 2) — audit and analyze
28
+ find_dead_code: 'analyst',
29
+ find_layer_violations: 'analyst',
30
+ get_coupling_metrics: 'analyst',
31
+ get_auth_matrix: 'analyst',
32
+ find_error_gaps: 'analyst',
33
+ get_test_coverage: 'analyst',
34
+ find_runtime_violations: 'analyst',
35
+ find_ownership_violations: 'analyst',
36
+ query_traits: 'analyst',
37
+ find_exposure_leaks: 'analyst',
38
+ find_semantic_clones: 'analyst',
39
+ // Architect (tier 3) — simulate, estimate, and act
40
+ estimate_task_cost: 'architect',
41
+ simulate_mutation: 'architect',
42
+ create_symbol: 'architect',
43
+ diff_bundle: 'architect',
44
+ conflict_matrix: 'architect',
45
+ query_data_targets: 'architect',
46
+ };
47
+ const TIER_LABELS = {
48
+ cartographer: 'Cartographer (Free)',
49
+ analyst: 'Analyst (Tier 2)',
50
+ architect: 'Architect (Tier 3)',
51
+ };
52
+ function tierAtLeast(userTier, requiredTier) {
53
+ return TIER_ORDER.indexOf(userTier) >= TIER_ORDER.indexOf(requiredTier);
54
+ }
14
55
  // ─── Project param definition ───
15
56
  const projectParam = {
16
57
  type: 'string',
@@ -18,6 +59,11 @@ const projectParam = {
18
59
  };
19
60
  // ─── Tool Definitions ─────────────────────────────────────────────
20
61
  const TOOLS = [
62
+ {
63
+ name: 'get_account_status',
64
+ description: 'Check your Ptah account: current tier, Ptah Credits balance, and which tools are available or locked. Call this to see what you can do and what upgrades are available.',
65
+ inputSchema: { type: 'object', properties: {} },
66
+ },
21
67
  {
22
68
  name: 'list_projects',
23
69
  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.',
@@ -119,7 +165,21 @@ const TOOLS = [
119
165
  properties: { project: projectParam },
120
166
  },
121
167
  },
122
- // ─── Analysis Tools ─────────────────────────────────────────────
168
+ {
169
+ name: 'get_optimal_context',
170
+ description: 'Compute the optimal set of related entities to include in an LLM context window for a target entity. Uses greedy knapsack: maximizes relevance per token. Returns entities ranked by relevance with token estimates.',
171
+ inputSchema: {
172
+ type: 'object',
173
+ properties: {
174
+ project: projectParam,
175
+ target_entity: { type: 'string', description: 'Entity ID or name to build context around' },
176
+ max_tokens: { type: 'number', description: 'Token budget for the context window (default: 8000)' },
177
+ strategy: { type: 'string', enum: ['bfs', 'blast_radius'], description: 'Traversal strategy: bfs (faster, local neighborhood) or blast_radius (full affected set)' },
178
+ },
179
+ required: ['target_entity'],
180
+ },
181
+ },
182
+ // ─── Analyst Tools (Tier 2) ───────────────────────────────────────
123
183
  {
124
184
  name: 'find_dead_code',
125
185
  description: 'Find unreachable entities (dead code candidates). BFS from entry points (routes, exported functions, tests, plugins) through the call graph. Entities not reachable from any entry point are flagged.',
@@ -178,70 +238,55 @@ const TOOLS = [
178
238
  },
179
239
  },
180
240
  {
181
- name: 'get_optimal_context',
182
- description: 'Compute the optimal set of related entities to include in an LLM context window for a target entity. Uses greedy knapsack: maximizes relevance per token. Returns entities ranked by relevance with token estimates.',
183
- inputSchema: {
184
- type: 'object',
185
- properties: {
186
- project: projectParam,
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)' },
190
- },
191
- required: ['target_entity'],
192
- },
241
+ name: 'find_runtime_violations',
242
+ description: 'Analyze the call graph for runtime environment leaks. Finds architectural boundary issues where framework-agnostic code improperly imports framework-specific code.',
243
+ inputSchema: { type: 'object', properties: { project: projectParam } },
193
244
  },
194
245
  {
195
- name: 'estimate_task_cost',
196
- description: 'Estimate token cost of a code change BEFORE starting work. Computes blast radius, sums source token counts across affected entities, and projects total token burn including iteration cycles. Call this before planning to check if a task fits within your token budget.',
246
+ name: 'find_ownership_violations',
247
+ 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.',
248
+ inputSchema: { type: 'object', properties: { project: projectParam } },
249
+ },
250
+ {
251
+ name: 'query_traits',
252
+ description: 'Search the codebase for specific functional capabilities. Allows you to find entities by their abstract traits (e.g., "fallible", "asyncContext", "generator") regardless of their structural syntax.',
197
253
  inputSchema: {
198
254
  type: 'object',
199
255
  properties: {
200
256
  project: projectParam,
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)' },
257
+ trait: { type: 'string', description: 'The trait or capability to search for (e.g., "fallible", "asyncContext")' },
203
258
  },
204
- required: ['target_entities'],
259
+ required: ['trait'],
205
260
  },
206
261
  },
207
262
  {
208
- name: 'report_actual_burn',
209
- description: 'Close the calibration feedback loop: report actual token usage against a prior prediction from estimate_task_cost.',
263
+ name: 'find_exposure_leaks',
264
+ description: 'Analyze the call graph for architectural visibility leaks. Flags paths where a "public" or "api" entity directly accesses a "private" internal entity.',
265
+ inputSchema: { type: 'object', properties: { project: projectParam } },
266
+ },
267
+ {
268
+ name: 'find_semantic_clones',
269
+ description: 'Analyze the codebase for duplicated logic blocks. Normalizes variables and compares code structure to identify identical algorithms written across different files or even different languages.',
210
270
  inputSchema: {
211
271
  type: 'object',
212
272
  properties: {
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.' },
219
273
  project: projectParam,
220
- notes: { type: 'string', description: 'Optional notes about the task outcome.' },
274
+ min_complexity: { type: 'number', description: 'Minimum number of logic expressions required to constitute a match (default: 5).' },
221
275
  },
222
276
  },
223
277
  },
224
- // ─── Analysis & Impact Tools ───────────────────────────────────
225
- {
226
- name: 'find_runtime_violations',
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 } },
229
- },
230
- {
231
- name: 'find_ownership_violations',
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.',
233
- inputSchema: { type: 'object', properties: { project: projectParam } },
234
- },
278
+ // ─── Architect Tools (Tier 3) ─────────────────────────────────────
235
279
  {
236
- name: 'query_traits',
237
- description: 'Search the codebase for specific functional capabilities. Allows you to find entities by their abstract traits (e.g., "fallible", "asyncContext", "generator") regardless of their structural syntax.',
280
+ name: 'estimate_task_cost',
281
+ description: 'Estimate token cost of a code change BEFORE starting work. Computes blast radius, sums source token counts across affected entities, and projects total token burn including iteration cycles. Call this before planning to check if a task fits within your token budget.',
238
282
  inputSchema: {
239
283
  type: 'object',
240
284
  properties: {
241
285
  project: projectParam,
242
- trait: { type: 'string', description: 'The trait or capability to search for (e.g., "fallible", "asyncContext")' },
286
+ target_entities: { type: 'array', items: { type: 'string' }, description: 'Entity IDs or names that will be modified' },
287
+ context_budget: { type: 'number', description: 'LLM context window token budget (default: 200000)' },
243
288
  },
244
- required: ['trait'],
289
+ required: ['target_entities'],
245
290
  },
246
291
  },
247
292
  {
@@ -271,34 +316,6 @@ const TOOLS = [
271
316
  required: ['entity_id', 'mutation'],
272
317
  },
273
318
  },
274
- {
275
- name: 'query_data_targets',
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.',
277
- inputSchema: {
278
- type: 'object',
279
- properties: {
280
- project: projectParam,
281
- target_name: { type: 'string', description: 'The name of the database table, state object, or data source to query (e.g., "users", "auth_token").' },
282
- },
283
- required: ['target_name'],
284
- },
285
- },
286
- {
287
- name: 'find_exposure_leaks',
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 } },
290
- },
291
- {
292
- name: 'find_semantic_clones',
293
- description: 'Analyze the codebase for duplicated logic blocks. Normalizes variables and compares code structure to identify identical algorithms written across different files or even different languages.',
294
- inputSchema: {
295
- type: 'object',
296
- properties: {
297
- project: projectParam,
298
- min_complexity: { type: 'number', description: 'Minimum number of logic expressions required to constitute a match (default: 5).' },
299
- },
300
- },
301
- },
302
319
  {
303
320
  name: 'create_symbol',
304
321
  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.',
@@ -314,7 +331,6 @@ const TOOLS = [
314
331
  required: ['id', 'source_file'],
315
332
  },
316
333
  },
317
- // ─── Diff Tools ─────────────────────────────────────────────────
318
334
  {
319
335
  name: 'diff_bundle',
320
336
  description: 'Compare code structure between a worktree and the loaded project. Shows which symbols were added, removed, or modified — not a line diff, but a structural diff showing changed signatures, dependencies, and implementation logic.',
@@ -353,6 +369,18 @@ const TOOLS = [
353
369
  required: ['tasks'],
354
370
  },
355
371
  },
372
+ {
373
+ name: 'query_data_targets',
374
+ 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.',
375
+ inputSchema: {
376
+ type: 'object',
377
+ properties: {
378
+ project: projectParam,
379
+ target_name: { type: 'string', description: 'The name of the database table, state object, or data source to query (e.g., "users", "auth_token").' },
380
+ },
381
+ required: ['target_name'],
382
+ },
383
+ },
356
384
  ];
357
385
  // ─── Project Resolution (Fallback) ────────────────────────────────
358
386
  function resolveProjectName() {
@@ -373,20 +401,53 @@ function resolveProjectName() {
373
401
  // Ultimate fallback to directory name
374
402
  return path.basename(process.cwd());
375
403
  }
404
+ // ─── Cloud API helper ─────────────────────────────────────────────
405
+ function getCloudUrl(path) {
406
+ const base = process.env.PTAH_CLOUD_URL || 'https://api.papyruslabs.ai';
407
+ // If PTAH_CLOUD_URL includes a full path (legacy), strip it back to the base
408
+ const baseUrl = base.replace(/\/api\/mcp\/execute\/?$/, '');
409
+ return `${baseUrl}${path}`;
410
+ }
376
411
  // ─── Server Setup ─────────────────────────────────────────────────
377
412
  async function main() {
378
413
  const server = new Server({
379
414
  name: 'seshat-mcp-cloud-proxy',
380
- version: '1.0.0',
415
+ version: '0.12.3',
381
416
  }, {
382
417
  capabilities: { tools: {} },
383
418
  });
384
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
385
- tools: TOOLS,
386
- }));
419
+ // ─── #3: Dynamic ListTools — only expose tools the user can access ──
420
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
421
+ const apiKey = process.env.SESHAT_API_KEY;
422
+ let userTier = 'cartographer';
423
+ // Fetch account status to determine which tools to expose
424
+ if (apiKey) {
425
+ try {
426
+ const res = await fetch(getCloudUrl('/api/mcp/account'), {
427
+ method: 'GET',
428
+ headers: { 'x-api-key': apiKey },
429
+ });
430
+ if (res.ok) {
431
+ const account = await res.json();
432
+ userTier = account.tier || 'cartographer';
433
+ }
434
+ }
435
+ catch {
436
+ // If unavailable, default to cartographer (free tier tools only)
437
+ }
438
+ }
439
+ // Only return tools the user's tier can actually use
440
+ const visibleTools = TOOLS.filter((tool) => {
441
+ const requiredTier = TOOL_TIERS[tool.name];
442
+ if (!requiredTier)
443
+ return true; // get_account_status — always visible
444
+ return tierAtLeast(userTier, requiredTier);
445
+ });
446
+ return { tools: visibleTools };
447
+ });
448
+ // ─── CallTool handler with #1 (_meta piggyback) and #2 (get_account_status) ──
387
449
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
388
450
  const { name, arguments: args } = request.params;
389
- // We expect the user to provide an API key for the cloud service
390
451
  const apiKey = process.env.SESHAT_API_KEY;
391
452
  if (!apiKey) {
392
453
  return {
@@ -394,14 +455,57 @@ async function main() {
394
455
  isError: true,
395
456
  };
396
457
  }
458
+ // ─── #2: get_account_status tool ──────────────────────────────
459
+ if (name === 'get_account_status') {
460
+ try {
461
+ const res = await fetch(getCloudUrl('/api/mcp/account'), {
462
+ method: 'GET',
463
+ headers: { 'x-api-key': apiKey },
464
+ });
465
+ if (!res.ok) {
466
+ const errorText = await res.text();
467
+ return {
468
+ content: [{ type: 'text', text: JSON.stringify({ error: `Failed to fetch account status (${res.status}): ${errorText}` }, null, 2) }],
469
+ isError: true,
470
+ };
471
+ }
472
+ const account = await res.json();
473
+ const userTier = account.tier || 'cartographer';
474
+ const credits = account.credits || 0;
475
+ // Build tool availability breakdown
476
+ const toolsByTier = {
477
+ cartographer: { available: [], locked: [] },
478
+ analyst: { available: [], locked: [] },
479
+ architect: { available: [], locked: [] },
480
+ };
481
+ for (const [toolName, requiredTier] of Object.entries(TOOL_TIERS)) {
482
+ const bucket = tierAtLeast(userTier, requiredTier) ? 'available' : 'locked';
483
+ toolsByTier[requiredTier][bucket].push(toolName);
484
+ }
485
+ return {
486
+ content: [{ type: 'text', text: JSON.stringify({
487
+ tier: userTier,
488
+ tier_label: TIER_LABELS[userTier],
489
+ ptah_credits: credits,
490
+ tools: toolsByTier,
491
+ upgrade_url: 'https://ptah.papyruslabs.ai/settings/billing',
492
+ }, null, 2) }],
493
+ };
494
+ }
495
+ catch (err) {
496
+ return {
497
+ content: [{ type: 'text', text: JSON.stringify({ error: `Account status check failed: ${err.message}` }, null, 2) }],
498
+ isError: true,
499
+ };
500
+ }
501
+ }
397
502
  // Determine the project hash for this workspace
398
503
  const project_hash = (args && typeof args === 'object' && 'project' in args)
399
504
  ? String(args.project)
400
505
  : resolveProjectName();
401
- const PTAH_CLOUD_URL = process.env.PTAH_CLOUD_URL || 'https://api.papyruslabs.ai/api/mcp/execute';
402
506
  try {
403
- // Blindly proxy the tool call to the cloud
404
- const res = await fetch(PTAH_CLOUD_URL, {
507
+ // Proxy the tool call to the cloud
508
+ const res = await fetch(getCloudUrl('/api/mcp/execute'), {
405
509
  method: 'POST',
406
510
  headers: {
407
511
  'Content-Type': 'application/json',
@@ -421,6 +525,9 @@ async function main() {
421
525
  };
422
526
  }
423
527
  const result = await res.json();
528
+ // ─── #1: _meta piggyback — append account context to every response ──
529
+ // The cloud API includes _meta in its response when available.
530
+ // If it doesn't, we pass through the result as-is.
424
531
  return {
425
532
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
426
533
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papyruslabsai/seshat-mcp",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
4
4
  "description": "Semantic MCP server — exposes a codebase's structure, dependencies, and constraints as queryable tools",
5
5
  "type": "module",
6
6
  "bin": {