@totemsdk/mcp-server 0.4.0 → 0.5.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/indexer.js CHANGED
@@ -72,6 +72,24 @@ function detectRepoRoot() {
72
72
  }
73
73
  return null;
74
74
  }
75
+ /** Read the checked-out Git revision without spawning a process. */
76
+ function readGitRevision(root) {
77
+ try {
78
+ const git = path.join(root, '.git');
79
+ let head = fs.readFileSync(path.join(git, 'HEAD'), 'utf-8').trim();
80
+ if (head.startsWith('ref: ')) {
81
+ const ref = head.slice(5).trim();
82
+ const refPath = path.join(git, ref);
83
+ head = fs.existsSync(refPath)
84
+ ? fs.readFileSync(refPath, 'utf-8').trim()
85
+ : '';
86
+ }
87
+ return /^[0-9a-f]{40,64}$/.test(head) ? head : undefined;
88
+ }
89
+ catch {
90
+ return undefined;
91
+ }
92
+ }
75
93
  function loadRepoData(root) {
76
94
  const topPkgDir = path.join(root, 'packages');
77
95
  const manifest = new Map();
@@ -97,13 +115,29 @@ function loadRepoData(root) {
97
115
  catch {
98
116
  // Optional artifact — fall back to the regex scan.
99
117
  }
100
- return { topPkgDir, manifest, ast };
118
+ const maturity = new Map();
119
+ try {
120
+ const gates = JSON.parse(fs.readFileSync(path.join(root, 'scripts', 'workspace-gates.config.json'), 'utf-8'));
121
+ for (const [dir, meta] of Object.entries(gates.packages ?? {})) {
122
+ if (!meta?.maturity)
123
+ continue;
124
+ try {
125
+ const pj = JSON.parse(fs.readFileSync(path.join(root, dir, 'package.json'), 'utf-8'));
126
+ if (pj.name)
127
+ maturity.set(pj.name, meta.maturity);
128
+ }
129
+ catch { /* unreadable package */ }
130
+ }
131
+ }
132
+ catch { /* optional */ }
133
+ return { topPkgDir, manifest, maturity, ast };
101
134
  }
102
135
  function loadBundledIndex() {
103
136
  try {
104
137
  const idx = JSON.parse(fs.readFileSync(BUNDLED_INDEX_PATH, 'utf-8'));
105
- if (idx && typeof idx === 'object' && idx.packages && typeof idx.packages === 'object')
106
- return idx;
138
+ if (idx && typeof idx === 'object' && idx.packages && typeof idx.packages === 'object') {
139
+ return { ...idx, schemaVersion: idx.schemaVersion ?? 2, source: idx.source ?? 'bundled' };
140
+ }
107
141
  return null;
108
142
  }
109
143
  catch {
@@ -362,15 +396,18 @@ function mergeAst(symbols, ast) {
362
396
  }
363
397
  /** Scan a monorepo root into an `SdkIndex`. */
364
398
  function buildFromMonorepo(root, now = Date.now()) {
365
- const { topPkgDir, manifest: MANIFEST, ast: AST } = loadRepoData(root);
399
+ const { topPkgDir, manifest: MANIFEST, maturity: MATURITY, ast: AST } = loadRepoData(root);
366
400
  const packages = {};
367
401
  const symbolIndex = {};
368
402
  const domainMap = {};
403
+ const manifestNames = new Set(MANIFEST.keys());
404
+ const discoveredNames = new Set();
369
405
  for (const dir of findPackageDirs(topPkgDir)) {
370
406
  const pkg = readPackageJson(dir);
371
407
  if (!pkg || !pkg.name)
372
408
  continue;
373
409
  const manifest = MANIFEST.get(pkg.name);
410
+ discoveredNames.add(pkg.name);
374
411
  if (MANIFEST.size > 0 && !manifest)
375
412
  continue;
376
413
  const dirName = path.basename(dir);
@@ -386,12 +423,20 @@ function buildFromMonorepo(root, now = Date.now()) {
386
423
  ...(Array.isArray(pkg.keywords) ? { keywords: pkg.keywords } : {}),
387
424
  dependencies: Object.keys(pkg.dependencies || {}),
388
425
  devDependencies: Object.keys(pkg.devDependencies || {}),
426
+ dependencyGroups: {
427
+ runtime: { ...(pkg.dependencies || {}) },
428
+ peer: { ...(pkg.peerDependencies || {}) },
429
+ optional: { ...(pkg.optionalDependencies || {}) },
430
+ dev: { ...(pkg.devDependencies || {}) },
431
+ },
432
+ ...(pkg.exports && typeof pkg.exports === 'object' ? { exportsMap: pkg.exports } : {}),
389
433
  hasRust: hasSubdir(dir, 'rust') || hasSubdir(dir, 'rust-toolchain'),
390
434
  hasGo: hasSubdir(dir, 'go'),
391
435
  hasTests: hasTestDir(dir),
392
436
  exports,
393
437
  symbols: Object.fromEntries(symbols),
394
438
  domain,
439
+ ...(MATURITY.get(pkg.name) ? { maturity: MATURITY.get(pkg.name) } : {}),
395
440
  };
396
441
  packages[pkg.name] = idx;
397
442
  if (!domainMap[domain])
@@ -407,7 +452,39 @@ function buildFromMonorepo(root, now = Date.now()) {
407
452
  });
408
453
  }
409
454
  }
410
- return { generatedAt: now, packages, symbolIndex, domainMap };
455
+ // Normalise workspace protocol ranges to the concrete workspace version so
456
+ // graph output is actionable outside pnpm. External ranges are preserved.
457
+ for (const pkg of Object.values(packages)) {
458
+ for (const kind of ['runtime', 'peer', 'optional', 'dev']) {
459
+ const group = pkg.dependencyGroups?.[kind];
460
+ if (!group)
461
+ continue;
462
+ for (const [dep, range] of Object.entries(group)) {
463
+ if (String(range).startsWith('workspace:')) {
464
+ const version = packages[dep]?.version;
465
+ group[dep] = version ? `^${version}` : String(range);
466
+ }
467
+ }
468
+ }
469
+ }
470
+ const missingFromIndex = [...manifestNames].filter((name) => !packages[name]).sort();
471
+ const extraInIndex = [...discoveredNames].filter((name) => !manifestNames.has(name)).sort();
472
+ const sourceRevision = readGitRevision(root);
473
+ return {
474
+ schemaVersion: 2,
475
+ source: 'monorepo',
476
+ ...(sourceRevision ? { sourceRevision } : {}),
477
+ coverage: {
478
+ manifestCount: manifestNames.size,
479
+ discoveredCount: discoveredNames.size,
480
+ missingFromIndex,
481
+ extraInIndex,
482
+ },
483
+ generatedAt: now,
484
+ packages,
485
+ symbolIndex,
486
+ domainMap,
487
+ };
411
488
  }
412
489
  /**
413
490
  * Build the SDK index. Prefers a live monorepo scan; falls back to the bundled
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Optional verified recipe accelerators. They are fixtures for the shared
3
+ * planner/scaffolder, not special-cased branches and not the taxonomy of
4
+ * supported applications.
5
+ */
6
+ import type { PlanCompositionInput, CompositionPlan } from './composition.js';
7
+ import type { SdkIndex } from './types.js';
8
+ export interface RecipeDefinition {
9
+ id: string;
10
+ title: string;
11
+ boundary: string;
12
+ input: Omit<PlanCompositionInput, 'brief'>;
13
+ }
14
+ export declare const RECIPES: RecipeDefinition[];
15
+ export declare function planRecipe(recipe: RecipeDefinition, index: SdkIndex): CompositionPlan;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ /**
3
+ * Optional verified recipe accelerators. They are fixtures for the shared
4
+ * planner/scaffolder, not special-cased branches and not the taxonomy of
5
+ * supported applications.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.RECIPES = void 0;
9
+ exports.planRecipe = planRecipe;
10
+ const composition_js_1 = require("./composition.js");
11
+ exports.RECIPES = [
12
+ {
13
+ id: 'digital-product-passport',
14
+ title: 'Digital Product Passport',
15
+ boundary: 'Passport assembles attested event evidence; generated summaries are not certified digital twins.',
16
+ input: {
17
+ runtime: 'node-esm',
18
+ requiredPackages: ['@totemsdk/edge-mqtt', '@totemsdk/core', '@totemsdk/proof', '@totemsdk/proofgraph'],
19
+ suggestedPackages: ['@totemsdk/manifest', '@totemsdk/identity', '@totemsdk/authority', '@totemsdk/intelligence', '@totemsdk/qvac', '@totemsdk/storage'],
20
+ requiredCapabilities: ['attested event evidence', 'manifest identity', 'proof graph', 'local AI summary'],
21
+ },
22
+ },
23
+ {
24
+ id: 'machinepay-edge',
25
+ title: 'MachinePay Edge Gateway',
26
+ boundary: 'The gateway executes only within a signed policy envelope; payment receipts do not certify the physical outcome.',
27
+ input: {
28
+ runtime: 'node-esm',
29
+ requiredPackages: ['@totemsdk/edge', '@totemsdk/edge-mqtt', '@totemsdk/omnia', '@totemsdk/agent-policy'],
30
+ suggestedPackages: ['@totemsdk/manifest', '@totemsdk/proofgraph', '@totemsdk/root-identity'],
31
+ requiredCapabilities: ['edge device runtime', 'mqtt transport', 'payment channel', 'agent policy'],
32
+ },
33
+ },
34
+ ];
35
+ function planRecipe(recipe, index) {
36
+ return (0, composition_js_1.planComposition)({ ...recipe.input, brief: `${recipe.title}. ${recipe.boundary}` }, index);
37
+ }
package/dist/resources.js CHANGED
@@ -54,7 +54,7 @@ const PAPERS = [
54
54
  {
55
55
  uri: 'totemsdk://papers/yellow',
56
56
  name: 'Core Yellow Paper',
57
- description: 'WOTS+ signatures, TreeKey hierarchy, MMR proofs, byte-exact serialization, lease/watermark coordination, Rust/WASM engine',
57
+ description: 'WOTS signatures, TreeKey hierarchy, MMR proofs, byte-exact serialization, lease/watermark coordination, Rust/WASM engine',
58
58
  file: 'TOTEM_CORE_YELLOW_PAPER.md',
59
59
  },
60
60
  {
@@ -278,6 +278,17 @@ const CONVENTIONS = `# Totem SDK Conventions
278
278
  ## Async
279
279
  - Promise<T> everywhere, typed event emitters, no Observables
280
280
  - Action lifecycle: propose -> reserve -> execute -> confirm/fail/unknown`;
281
+ function decodeResourceSegment(value) {
282
+ try {
283
+ const decoded = decodeURIComponent(value);
284
+ if (!decoded || decoded.includes('..') || decoded.includes('\\') || decoded.includes('\0'))
285
+ return null;
286
+ return decoded;
287
+ }
288
+ catch {
289
+ return null;
290
+ }
291
+ }
281
292
  function handleResourceRead(uri, index) {
282
293
  if (uri === 'totemsdk://connect/methods')
283
294
  return JSON.stringify(connectMethods(), null, 2);
@@ -300,6 +311,16 @@ function handleResourceRead(uri, index) {
300
311
  }
301
312
  return content;
302
313
  }
314
+ if (uri === 'totemsdk://index') {
315
+ return JSON.stringify({
316
+ schemaVersion: index.schemaVersion ?? null,
317
+ source: index.source ?? 'unknown',
318
+ sourceRevision: index.sourceRevision ?? null,
319
+ generatedAt: index.generatedAt,
320
+ packageCount: Object.keys(index.packages).length,
321
+ coverage: index.coverage ?? null,
322
+ }, null, 2);
323
+ }
303
324
  if (uri === 'totemsdk://packages') {
304
325
  return JSON.stringify(Object.values(index.packages).map(p => ({
305
326
  name: p.name,
@@ -331,29 +352,61 @@ function handleResourceRead(uri, index) {
331
352
  }
332
353
  const pkgMatch = uri.match(/^totemsdk:\/\/packages\/([^/]+)$/);
333
354
  if (pkgMatch) {
334
- const pkg = index.packages[pkgMatch[1]];
355
+ const name = decodeResourceSegment(pkgMatch[1]);
356
+ if (!name)
357
+ return null;
358
+ const pkg = index.packages[name];
335
359
  return pkg ? JSON.stringify(pkg, null, 2) : null;
336
360
  }
361
+ const pkgContractsMatch = uri.match(/^totemsdk:\/\/packages\/([^/]+)\/contracts$/);
362
+ if (pkgContractsMatch) {
363
+ const name = decodeResourceSegment(pkgContractsMatch[1]);
364
+ if (!name)
365
+ return null;
366
+ const pkg = index.packages[name];
367
+ if (!pkg)
368
+ return null;
369
+ return JSON.stringify({
370
+ contentKind: 'contract',
371
+ package: pkg.name,
372
+ version: pkg.version,
373
+ origin: index.source ?? 'unknown',
374
+ sourceRevision: index.sourceRevision ?? null,
375
+ symbols: pkg.symbols ?? {},
376
+ }, null, 2);
377
+ }
337
378
  const pkgExportsMatch = uri.match(/^totemsdk:\/\/packages\/([^/]+)\/exports$/);
338
379
  if (pkgExportsMatch) {
339
- const pkg = index.packages[pkgExportsMatch[1]];
380
+ const name = decodeResourceSegment(pkgExportsMatch[1]);
381
+ if (!name)
382
+ return null;
383
+ const pkg = index.packages[name];
340
384
  return pkg ? JSON.stringify(pkg.exports, null, 2) : null;
341
385
  }
342
386
  const pkgDepsMatch = uri.match(/^totemsdk:\/\/packages\/([^/]+)\/dependencies$/);
343
387
  if (pkgDepsMatch) {
344
- const pkg = index.packages[pkgDepsMatch[1]];
388
+ const name = decodeResourceSegment(pkgDepsMatch[1]);
389
+ if (!name)
390
+ return null;
391
+ const pkg = index.packages[name];
345
392
  if (!pkg)
346
393
  return null;
347
394
  return JSON.stringify({ dependencies: pkg.dependencies, devDependencies: pkg.devDependencies }, null, 2);
348
395
  }
349
396
  const domainMatch = uri.match(/^totemsdk:\/\/packages\/by-domain\/(.+)$/);
350
397
  if (domainMatch) {
351
- const pkgs = index.domainMap[domainMatch[1]];
398
+ const domain = decodeResourceSegment(domainMatch[1]);
399
+ if (!domain)
400
+ return null;
401
+ const pkgs = index.domainMap[domain];
352
402
  return pkgs ? JSON.stringify(pkgs.map(n => index.packages[n]).filter(Boolean), null, 2) : null;
353
403
  }
354
404
  const symbolMatch = uri.match(/^totemsdk:\/\/symbol\/(.+)$/);
355
405
  if (symbolMatch) {
356
- const entries = index.symbolIndex[symbolMatch[1]];
406
+ const symbol = decodeResourceSegment(symbolMatch[1]);
407
+ if (!symbol)
408
+ return null;
409
+ const entries = index.symbolIndex[symbol];
357
410
  return entries ? JSON.stringify(entries, null, 2) : null;
358
411
  }
359
412
  return null;
@@ -362,6 +415,7 @@ function listResources(index) {
362
415
  const resources = [
363
416
  { uri: 'totemsdk://packages', name: 'All Packages', description: `List of all ${Object.keys(index.packages).length} SDK packages with metadata` },
364
417
  { uri: 'totemsdk://templates', name: 'All KISSVM Templates', description: `All ${(0, template_catalog_js_1.getAllTemplates)().length} KISSVM script templates with import paths and descriptions` },
418
+ { uri: 'totemsdk://index', name: 'Index Provenance', description: 'Index schema, source revision, package count and coverage reconciliation' },
365
419
  { uri: 'totemsdk://conventions', name: 'Coding Conventions', description: 'Totem SDK coding conventions and patterns' },
366
420
  { uri: 'totemsdk://domain-map', name: 'Domain Map', description: 'Packages grouped by domain layer' },
367
421
  ...PAPERS.map(p => ({ uri: p.uri, name: p.name, description: p.description })),
@@ -391,6 +445,11 @@ function listResources(index) {
391
445
  name: `Dependencies: ${name}`,
392
446
  description: `Dependencies of ${name}`,
393
447
  });
448
+ resources.push({
449
+ uri: `totemsdk://packages/${encodeURIComponent(name)}/contracts`,
450
+ name: `Contracts: ${name}`,
451
+ description: `Exact exported contract metadata for ${name} (contentKind: contract)`,
452
+ });
394
453
  }
395
454
  // Symbol lookups are served by the `search-symbol` tool (query-driven), which
396
455
  // is far cheaper than enumerating ~3.7k symbols as resources. Only surface a