@totemsdk/mcp-server 0.2.0 → 0.4.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/resources.js CHANGED
@@ -33,6 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.extractSection = extractSection;
37
+ exports.resourceMimeType = resourceMimeType;
36
38
  exports.handleResourceRead = handleResourceRead;
37
39
  exports.listResources = listResources;
38
40
  const template_catalog_js_1 = require("./template-catalog.js");
@@ -58,7 +60,7 @@ const PAPERS = [
58
60
  {
59
61
  uri: 'totemsdk://papers/red',
60
62
  name: 'Connect Red Paper',
61
- description: 'dApp-wallet wire protocol specification — all 49 methods, every parameter, every response shape, error codes, security model',
63
+ description: 'dApp-wallet wire protocol specification — every method, parameter, response shape, error code and the security model',
62
64
  file: 'TOTEM_CONNECT_RED_PAPER.md',
63
65
  },
64
66
  {
@@ -81,11 +83,158 @@ const PAPERS = [
81
83
  },
82
84
  {
83
85
  uri: 'totemsdk://papers/gold',
84
- name: 'Tokenomics Gold Paper',
85
- description: 'Two-asset model — MINIMA as sovereign collateral backbone (Colour Coins, The Burn, Provider Bonds), TOTEM as service revenue token (staking, fee distribution, slashing, treasury)',
86
- file: 'TOTEM_TOKENOMICS_GOLD_PAPER.md',
86
+ name: 'Network Economics Gold Paper',
87
+ description: 'Progressive decentralisation; MINIMA as settlement/native collateral where appropriate; $TOTEM as a potential network coordination and service-assurance asset (provider bonding, DAO stewardship, community-heavy genesis); no token assumed — network must earn the right to need one',
88
+ file: 'TOTEM_NETWORK_ECONOMICS_GOLD_PAPER.md',
89
+ },
90
+ {
91
+ uri: 'totemsdk://papers/purple',
92
+ name: 'Intelligence & Decision Purple Paper',
93
+ description: 'Bounded intelligence for autonomous systems — Intelligence vs Decision separation, bounded candidate spaces, the deterministic shell around probabilistic models, effective authority, governed Edge runtime, receipts and the Physical AI trust stack',
94
+ file: 'TOTEM_INTELLIGENCE_DECISION_PURPLE_PAPER.md',
87
95
  },
88
96
  ];
97
+ /** Derive a name/description for a doc from its first heading + first paragraph. */
98
+ function docMeta(file, fallbackName) {
99
+ const content = readRepoFile(file) ?? '';
100
+ const lines = content.split('\n');
101
+ const titleIdx = lines.findIndex(l => l.startsWith('# '));
102
+ const name = titleIdx >= 0 ? lines[titleIdx].replace(/^#\s+/, '').trim() : fallbackName;
103
+ let description = '';
104
+ for (let i = titleIdx + 1; i < lines.length; i++) {
105
+ const t = lines[i].trim();
106
+ if (!t || t.startsWith('#') || t.startsWith('**Status') || t.startsWith('---') || t.startsWith('>'))
107
+ continue;
108
+ description = t.replace(/[*_`]/g, '').trim();
109
+ break;
110
+ }
111
+ const clean = description || name || fallbackName;
112
+ // Cap at the first sentence / ~160 chars. Discovery must stay cheap: an
113
+ // unbounded first paragraph bloats every client's resource list.
114
+ const firstSentence = clean.split(/(?<=\.)\s/)[0];
115
+ const bounded = (firstSentence.length <= 160 ? firstSentence : clean.slice(0, 160)).trim();
116
+ return { name: name || fallbackName, description: bounded.length > 0 ? bounded : name || fallbackName };
117
+ }
118
+ /**
119
+ * RFCs and audits are discovered from disk, so a new `docs/rfc/RFC-*.md` or
120
+ * `docs/audits/*.md` is exposed automatically — there is no hand-maintained
121
+ * list to forget.
122
+ */
123
+ function discoverDocs() {
124
+ const docs = [];
125
+ const rfcDir = path.join(REPO_ROOT, 'docs', 'rfc');
126
+ if (fs.existsSync(rfcDir)) {
127
+ for (const f of fs.readdirSync(rfcDir).sort()) {
128
+ const m = f.match(/^RFC-(\d+)-(.+)\.md$/);
129
+ if (!m)
130
+ continue;
131
+ const file = path.join('docs', 'rfc', f);
132
+ const meta = docMeta(file, `RFC-${m[1]}`);
133
+ // Amendments share an RFC number with the original; give them a distinct
134
+ // URI (`totemsdk://rfc/007/amendment-a`) so a reader never retrieves an
135
+ // amendment when they asked for the base specification.
136
+ const amend = m[2].match(/^AMENDMENT-([A-Z])/i);
137
+ const uri = amend
138
+ ? `totemsdk://rfc/${m[1]}/amendment-${amend[1].toLowerCase()}`
139
+ : `totemsdk://rfc/${m[1]}`;
140
+ docs.push({ uri, ...meta, file });
141
+ }
142
+ }
143
+ const auditDir = path.join(REPO_ROOT, 'docs', 'audits');
144
+ if (fs.existsSync(auditDir)) {
145
+ for (const f of fs.readdirSync(auditDir).sort()) {
146
+ if (!f.endsWith('.md'))
147
+ continue;
148
+ const base = f.replace(/\.md$/, '');
149
+ const slug = base.replace(/-\d{4}-\d{2}$/, '');
150
+ const file = path.join('docs', 'audits', f);
151
+ const meta = docMeta(file, base);
152
+ docs.push({ uri: `totemsdk://audit/${slug}`, ...meta, file });
153
+ }
154
+ }
155
+ // Worked, runnable end-to-end scenarios (usually backed by a test under
156
+ // packages/sdk-tests). Discovered from disk like RFCs/audits.
157
+ const exampleDir = path.join(REPO_ROOT, 'docs', 'examples');
158
+ if (fs.existsSync(exampleDir)) {
159
+ for (const f of fs.readdirSync(exampleDir).sort()) {
160
+ if (!f.endsWith('.md'))
161
+ continue;
162
+ const slug = f.replace(/\.md$/, '');
163
+ const file = path.join('docs', 'examples', f);
164
+ const meta = docMeta(file, slug);
165
+ docs.push({ uri: `totemsdk://example/${slug}`, ...meta, file });
166
+ }
167
+ }
168
+ return docs;
169
+ }
170
+ const DOCS = discoverDocs();
171
+ /** Generated structured catalogs (parsed from source, not stored). */
172
+ const CATALOGS = [
173
+ { uri: 'totemsdk://connect/methods', name: 'Connect Methods', description: 'The dApp-wallet wire protocol method catalog (@totemsdk/connect)' },
174
+ { uri: 'totemsdk://edge/capabilities', name: 'Edge Capabilities', description: 'Canonical EdgeCapability strings (@totemsdk/edge)' },
175
+ { uri: 'totemsdk://rfc', name: 'RFC Index', description: 'Index of SDK RFCs' },
176
+ ];
177
+ function readRepoFile(rel) {
178
+ const p = path.join(REPO_ROOT, rel);
179
+ return fs.existsSync(p) ? fs.readFileSync(p, 'utf-8') : null;
180
+ }
181
+ function slugify(s) {
182
+ return s.toLowerCase().replace(/[`*_]/g, '').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
183
+ }
184
+ /** Extract a markdown section by heading slug/text (for `totemsdk://rfc/014#6-design`). */
185
+ function extractSection(markdown, anchor) {
186
+ const lines = markdown.split('\n');
187
+ const decoded = anchor.replace(/%20/g, ' ').toLowerCase();
188
+ let start = -1;
189
+ let level = 0;
190
+ for (let i = 0; i < lines.length; i++) {
191
+ const m = lines[i].match(/^(#{1,6})\s+(.*)$/);
192
+ if (!m)
193
+ continue;
194
+ const text = m[2].trim();
195
+ if (slugify(text) === slugify(anchor) || text.toLowerCase().includes(decoded)) {
196
+ start = i;
197
+ level = m[1].length;
198
+ break;
199
+ }
200
+ }
201
+ if (start < 0)
202
+ return null;
203
+ let end = lines.length;
204
+ for (let i = start + 1; i < lines.length; i++) {
205
+ const m = lines[i].match(/^(#{1,6})\s+/);
206
+ if (m && m[1].length <= level) {
207
+ end = i;
208
+ break;
209
+ }
210
+ }
211
+ return lines.slice(start, end).join('\n').trim();
212
+ }
213
+ function connectMethods() {
214
+ const src = readRepoFile(path.join('packages', 'connect', 'src', 'index.ts'));
215
+ if (!src)
216
+ return [];
217
+ return [...new Set([...src.matchAll(/method:\s*'([A-Za-z_]+)'/g)].map(m => m[1]))].sort();
218
+ }
219
+ function edgeCapabilities() {
220
+ const src = readRepoFile(path.join('packages', 'edge', 'src', 'capabilities.ts'));
221
+ if (!src)
222
+ return [];
223
+ return [...new Set([...src.matchAll(/'([a-z][a-z0-9-]*:[a-z0-9-]+)'/g)].map(m => m[1]))].sort();
224
+ }
225
+ /** MIME type for a resource URI. */
226
+ function resourceMimeType(uri) {
227
+ const base = uri.split('#')[0];
228
+ if (base === 'totemsdk://conventions' || base === 'totemsdk://templates')
229
+ return 'text/markdown';
230
+ if (/^totemsdk:\/\/(papers|rfc|audit|example)\/[^/]+(\/[^/]+)?$/.test(base))
231
+ return 'text/markdown';
232
+ if (base === 'totemsdk://packages' || base === 'totemsdk://domain-map' || base === 'totemsdk://rfc')
233
+ return 'application/json';
234
+ if (/^totemsdk:\/\/(packages|symbol|connect|edge)(\/|$)/.test(base))
235
+ return 'application/json';
236
+ return 'text/plain';
237
+ }
89
238
  const CONVENTIONS = `# Totem SDK Conventions
90
239
 
91
240
  ## Package Structure
@@ -103,11 +252,11 @@ const CONVENTIONS = `# Totem SDK Conventions
103
252
  ## Canonical JSON
104
253
  - Recursive deterministic JSON with sorted object keys
105
254
  - Used as input to all hashing and signing operations
106
- - Each package has its own canonicalJson() (no shared util)
255
+ - Shared: import { canonicalJson, hashCanonical, toHex, sha3_256 } from '@totemsdk/core'
107
256
 
108
257
  ## Hashing
109
258
  - SHA3-256 via @totemsdk/core
110
- - Domain-prefixed: sha3-256(domain + canonicalJson(data))
259
+ - Domain-separated: hashCanonical('<DOMAIN>_V1', data) == sha3-256(domain + canonicalJson(data))
111
260
  - Domain constants like 'TOTEM_GOVERNANCE_PROPOSAL_V1'
112
261
 
113
262
  ## Signing
@@ -130,17 +279,26 @@ const CONVENTIONS = `# Totem SDK Conventions
130
279
  - Promise<T> everywhere, typed event emitters, no Observables
131
280
  - Action lifecycle: propose -> reserve -> execute -> confirm/fail/unknown`;
132
281
  function handleResourceRead(uri, index) {
133
- const paperMatch = uri.match(/^totemsdk:\/\/papers\/(\w+)$/);
134
- if (paperMatch) {
135
- const paper = PAPERS.find(p => p.uri === uri);
136
- if (paper) {
137
- const filePath = path.join(REPO_ROOT, paper.file);
138
- if (fs.existsSync(filePath)) {
139
- return fs.readFileSync(filePath, 'utf-8');
140
- }
141
- return `Paper file not found: ${paper.file}`;
282
+ if (uri === 'totemsdk://connect/methods')
283
+ return JSON.stringify(connectMethods(), null, 2);
284
+ if (uri === 'totemsdk://edge/capabilities')
285
+ return JSON.stringify(edgeCapabilities(), null, 2);
286
+ if (uri === 'totemsdk://rfc') {
287
+ return JSON.stringify(DOCS.filter(d => d.uri.startsWith('totemsdk://rfc/')).map(d => ({ uri: d.uri, name: d.name, description: d.description })), null, 2);
288
+ }
289
+ const base = uri.split('#')[0];
290
+ const readable = [...PAPERS, ...DOCS].find(d => d.uri === base);
291
+ if (readable) {
292
+ const filePath = path.join(REPO_ROOT, readable.file);
293
+ if (!fs.existsSync(filePath))
294
+ return `Document not found: ${readable.file}`;
295
+ const content = fs.readFileSync(filePath, 'utf-8');
296
+ const hash = uri.indexOf('#');
297
+ if (hash >= 0) {
298
+ const section = extractSection(content, uri.slice(hash + 1));
299
+ return section ?? `Section not found: ${uri.slice(hash + 1)}`;
142
300
  }
143
- return null;
301
+ return content;
144
302
  }
145
303
  if (uri === 'totemsdk://packages') {
146
304
  return JSON.stringify(Object.values(index.packages).map(p => ({
@@ -203,10 +361,12 @@ function handleResourceRead(uri, index) {
203
361
  function listResources(index) {
204
362
  const resources = [
205
363
  { uri: 'totemsdk://packages', name: 'All Packages', description: `List of all ${Object.keys(index.packages).length} SDK packages with metadata` },
206
- { uri: 'totemsdk://templates', name: 'All KISSVM Templates', description: 'All 45 KISSVM script templates with import paths and descriptions' },
364
+ { uri: 'totemsdk://templates', name: 'All KISSVM Templates', description: `All ${(0, template_catalog_js_1.getAllTemplates)().length} KISSVM script templates with import paths and descriptions` },
207
365
  { uri: 'totemsdk://conventions', name: 'Coding Conventions', description: 'Totem SDK coding conventions and patterns' },
208
366
  { uri: 'totemsdk://domain-map', name: 'Domain Map', description: 'Packages grouped by domain layer' },
209
367
  ...PAPERS.map(p => ({ uri: p.uri, name: p.name, description: p.description })),
368
+ ...DOCS.map(d => ({ uri: d.uri, name: d.name, description: d.description })),
369
+ ...CATALOGS,
210
370
  ];
211
371
  for (const [domain, pkgs] of Object.entries(index.domainMap)) {
212
372
  resources.push({
@@ -232,10 +392,16 @@ function listResources(index) {
232
392
  description: `Dependencies of ${name}`,
233
393
  });
234
394
  }
235
- const topSymbols = Object.entries(index.symbolIndex)
236
- .filter(([_, v]) => v.length <= 3)
237
- .slice(0, 200);
238
- for (const [symbol, entries] of topSymbols) {
395
+ // Symbol lookups are served by the `search-symbol` tool (query-driven), which
396
+ // is far cheaper than enumerating ~3.7k symbols as resources. Only surface a
397
+ // small set of the most unambiguous symbols (exported by exactly one package)
398
+ // so `totemsdk://symbol/{name}` remains discoverable without bloating
399
+ // discovery to hundreds of entries.
400
+ const uniqueSymbols = Object.entries(index.symbolIndex)
401
+ .filter(([_, v]) => v.length === 1)
402
+ .sort(([a], [b]) => a.localeCompare(b))
403
+ .slice(0, 50);
404
+ for (const [symbol, entries] of uniqueSymbols) {
239
405
  resources.push({
240
406
  uri: `totemsdk://symbol/${symbol}`,
241
407
  name: `Symbol: ${symbol}`,
package/dist/tools.d.ts CHANGED
@@ -1,2 +1,8 @@
1
- import type { SdkIndex, ToolResponse } from './types.js';
1
+ import type { SdkIndex, ToolDefinition, ToolResponse } from './types.js';
2
+ /**
3
+ * The single source of truth for the MCP tool catalog. `index.ts` advertises
4
+ * these and `handleToolCall` dispatches them; the `mcp.test.ts` drift guard
5
+ * asserts the two never diverge.
6
+ */
7
+ export declare const TOOL_DEFINITIONS: ToolDefinition[];
2
8
  export declare function handleToolCall(name: string, args: any, index: SdkIndex): ToolResponse;
package/dist/tools.js CHANGED
@@ -1,7 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TOOL_DEFINITIONS = void 0;
3
4
  exports.handleToolCall = handleToolCall;
4
5
  const template_catalog_js_1 = require("./template-catalog.js");
6
+ const indexer_js_1 = require("./indexer.js");
7
+ const index_store_js_1 = require("./index-store.js");
8
+ /**
9
+ * The single source of truth for the MCP tool catalog. `index.ts` advertises
10
+ * these and `handleToolCall` dispatches them; the `mcp.test.ts` drift guard
11
+ * asserts the two never diverge.
12
+ */
13
+ exports.TOOL_DEFINITIONS = [
14
+ { name: 'search-symbol', description: 'Search for a symbol (function, type, class) across all packages', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Partial symbol name to search' } }, required: ['query'] } },
15
+ { name: 'find-type', description: 'Find type definitions (interfaces, classes, type aliases) matching a pattern', inputSchema: { type: 'object', properties: { pattern: { type: 'string', description: 'Type name pattern to search' } }, required: ['pattern'] } },
16
+ { name: 'dependency-graph', description: 'Get dependency graph for a package — inbound dependents or outbound dependencies', inputSchema: { type: 'object', properties: { package: { type: 'string', description: 'Package name (e.g. @totemsdk/edge-opcua)' }, direction: { type: 'string', enum: ['in', 'out', 'all'], description: 'Dependency direction' } }, required: ['package'] } },
17
+ { name: 'validate-import', description: 'Check whether a cross-package import is valid', inputSchema: { type: 'object', properties: { from: { type: 'string', description: 'Source package name' }, to: { type: 'string', description: 'Target package name' }, symbol: { type: 'string', description: 'Optional: specific symbol to check' } }, required: ['from', 'to'] } },
18
+ { name: 'scaffold-adapter', description: 'Generate boilerplate for a new edge protocol adapter', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Package name suffix' }, protocol: { type: 'string', description: 'Protocol name (PascalCase)' }, commands: { type: 'array', items: { type: 'string' }, description: 'Transport port methods' } }, required: ['name', 'protocol'] } },
19
+ { name: 'scaffold-package', description: 'Generate boilerplate for a new @totemsdk package', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Package name (without @totemsdk/ prefix)' }, deps: { type: 'array', items: { type: 'string' }, description: 'Dependency package names' } }, required: ['name'] } },
20
+ { name: 'package-stats', description: 'Get statistics about a package — export counts, Rust/Go, tests, deps', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Package name' } }, required: ['name'] } },
21
+ { name: 'list-exports', description: 'List exports of a package', inputSchema: { type: 'object', properties: { package: { type: 'string', description: 'Package name' }, kind: { type: 'string', enum: ['function', 'type', 'interface', 'class', 'const', ''], description: 'Filter by export kind' }, filter: { type: 'string', description: 'Filter by name substring' } }, required: ['package'] } },
22
+ { name: 'suggest-template', description: 'Suggest KISSVM script templates matching a use case — describes what you want to do and returns matching templates with import paths', inputSchema: { type: 'object', properties: { usecase: { type: 'string', description: 'Describe what you want to do (e.g. "time-lock funds until a block height", "vote tally with quorum", "identity verification")' } }, required: ['usecase'] } },
23
+ { name: 'read-source', description: "Read a source file from a package's src/ directory (e.g. package '@totemsdk/core', path 'treekey.ts')", inputSchema: { type: 'object', properties: { package: { type: 'string', description: 'Package name (e.g. @totemsdk/core)' }, path: { type: 'string', description: 'Path relative to the package src/ directory' } }, required: ['package', 'path'] } },
24
+ { name: 'list-packages', description: 'List SDK packages, optionally filtered by domain or name substring', inputSchema: { type: 'object', properties: { domain: { type: 'string', description: 'Optional domain filter (e.g. edge/runtime... see totemsdk://domain-map)' }, filter: { type: 'string', description: 'Optional package-name substring filter' } } } },
25
+ { name: 'search-packages', description: 'Search packages by keyword across name, description, keywords and exports (e.g. "wots lease coordination")', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Free-text query' }, domain: { type: 'string', description: 'Optional domain filter' }, limit: { type: 'number', description: 'Max results (default 15)' } }, required: ['query'] } },
26
+ { name: 'refresh-index', description: 'Rebuild the SDK index from the filesystem (development convenience) and report counts', inputSchema: { type: 'object', properties: {} } },
27
+ ];
5
28
  function handleToolCall(name, args, index) {
6
29
  switch (name) {
7
30
  case 'search-symbol': return searchSymbol(args, index);
@@ -13,9 +36,93 @@ function handleToolCall(name, args, index) {
13
36
  case 'package-stats': return packageStats(args, index);
14
37
  case 'list-exports': return listExports(args, index);
15
38
  case 'suggest-template': return suggestTemplate(args);
39
+ case 'read-source': return readSource(args);
40
+ case 'list-packages': return listPackages(args, index);
41
+ case 'search-packages': return searchPackages(args, index);
42
+ case 'refresh-index': return refreshIndexTool();
16
43
  default: return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
17
44
  }
18
45
  }
46
+ function refreshIndexTool() {
47
+ const idx = (0, index_store_js_1.refreshIndex)();
48
+ return {
49
+ content: [{
50
+ type: 'text',
51
+ text: `Index rebuilt: ${Object.keys(idx.packages).length} packages, ${Object.keys(idx.symbolIndex).length} symbols, ${Object.keys(idx.domainMap).length} domains.`,
52
+ }],
53
+ };
54
+ }
55
+ function searchPackages(args, index) {
56
+ const query = String(args.query || '').toLowerCase().trim();
57
+ if (!query)
58
+ return { content: [{ type: 'text', text: 'query is required' }], isError: true };
59
+ const domain = args.domain;
60
+ const limit = Number.isFinite(args.limit) ? Number(args.limit) : 15;
61
+ const terms = query.split(/\s+/).filter(Boolean);
62
+ const scored = Object.values(index.packages)
63
+ .filter(p => !domain || p.domain === domain)
64
+ .map(p => {
65
+ const haystackName = p.name.toLowerCase();
66
+ const haystackDesc = p.description.toLowerCase();
67
+ const haystackKw = (p.keywords ?? []).join(' ').toLowerCase();
68
+ const haystackExports = [
69
+ ...p.exports.functions, ...p.exports.types, ...p.exports.classes, ...p.exports.interfaces, ...p.exports.consts,
70
+ ].join(' ').toLowerCase();
71
+ let score = 0;
72
+ for (const t of terms) {
73
+ if (haystackName.includes(t))
74
+ score += 4;
75
+ if (haystackKw.includes(t))
76
+ score += 3;
77
+ if (haystackExports.includes(t))
78
+ score += 2;
79
+ if (haystackDesc.includes(t))
80
+ score += 1;
81
+ }
82
+ return { p, score };
83
+ })
84
+ .filter(r => r.score > 0)
85
+ .sort((a, b) => b.score - a.score || a.p.name.localeCompare(b.p.name))
86
+ .slice(0, limit);
87
+ if (scored.length === 0) {
88
+ return { content: [{ type: 'text', text: `No packages matched '${query}'` }] };
89
+ }
90
+ return {
91
+ content: [{
92
+ type: 'text',
93
+ text: `Found ${scored.length} package(s) for '${query}':\n\n` +
94
+ scored.map(r => ` ${r.p.name}@${r.p.version} [${r.p.domain}] (score ${r.score}) — ${r.p.description}`).join('\n'),
95
+ }],
96
+ };
97
+ }
98
+ function readSource(args) {
99
+ const pkg = args.package;
100
+ const file = args.path;
101
+ if (!pkg || !file)
102
+ return { content: [{ type: 'text', text: 'package and path are required' }], isError: true };
103
+ const content = (0, indexer_js_1.readSourceFile)(pkg, file);
104
+ if (content === null) {
105
+ return { content: [{ type: 'text', text: `Source not found: ${pkg}/src/${file}` }], isError: true };
106
+ }
107
+ return { content: [{ type: 'text', text: content }] };
108
+ }
109
+ function listPackages(args, index) {
110
+ const domain = args.domain;
111
+ const filter = args.filter?.toLowerCase();
112
+ const rows = Object.values(index.packages)
113
+ .filter(p => (!domain || p.domain === domain) && (!filter || p.name.toLowerCase().includes(filter)))
114
+ .sort((a, b) => a.name.localeCompare(b.name));
115
+ if (rows.length === 0) {
116
+ return { content: [{ type: 'text', text: 'No packages matched' }] };
117
+ }
118
+ return {
119
+ content: [{
120
+ type: 'text',
121
+ text: `${rows.length} package(s):\n\n` +
122
+ rows.map(p => ` ${p.name}@${p.version} [${p.domain}] — ${p.description}`).join('\n'),
123
+ }],
124
+ };
125
+ }
19
126
  function searchSymbol(args, index) {
20
127
  const query = (args.query || '').toLowerCase();
21
128
  if (!query)
@@ -24,7 +131,13 @@ function searchSymbol(args, index) {
24
131
  for (const [symbol, entries] of Object.entries(index.symbolIndex)) {
25
132
  if (symbol.toLowerCase().includes(query)) {
26
133
  for (const entry of entries) {
27
- results.push({ symbol, package: entry.package, kind: entry.kind });
134
+ results.push({
135
+ symbol,
136
+ package: entry.package,
137
+ kind: entry.kind,
138
+ ...(entry.signature !== undefined ? { signature: entry.signature } : {}),
139
+ ...(entry.deprecated ? { deprecated: true } : {}),
140
+ });
28
141
  }
29
142
  }
30
143
  }
@@ -36,7 +149,10 @@ function searchSymbol(args, index) {
36
149
  content: [{
37
150
  type: 'text',
38
151
  text: `Found ${results.length} match(es) for '${query}':\n\n` +
39
- results.map(r => ` ${r.symbol} (${r.kind}) — ${r.package}`).join('\n'),
152
+ results
153
+ .map(r => ` ${r.symbol} (${r.kind}) — ${r.package}${r.deprecated ? ' [deprecated]' : ''}` +
154
+ (r.signature ? `\n ${r.signature}` : ''))
155
+ .join('\n'),
40
156
  }],
41
157
  };
42
158
  }
@@ -46,17 +162,16 @@ function findType(args, index) {
46
162
  return { content: [{ type: 'text', text: 'pattern is required' }], isError: true };
47
163
  const results = [];
48
164
  for (const [pkgName, pkg] of Object.entries(index.packages)) {
49
- for (const t of pkg.exports.interfaces) {
50
- if (t.toLowerCase().includes(pattern))
51
- results.push({ package: pkgName, type: t, kind: 'interface' });
52
- }
53
- for (const t of pkg.exports.types) {
54
- if (t.toLowerCase().includes(pattern))
55
- results.push({ package: pkgName, type: t, kind: 'type' });
56
- }
57
- for (const c of pkg.exports.classes) {
58
- if (c.toLowerCase().includes(pattern))
59
- results.push({ package: pkgName, type: c, kind: 'class' });
165
+ for (const entry of typeEntries(pkg)) {
166
+ if (entry.name.toLowerCase().includes(pattern)) {
167
+ results.push({
168
+ package: pkgName,
169
+ type: entry.name,
170
+ kind: entry.kind,
171
+ ...(entry.signature !== undefined ? { signature: entry.signature } : {}),
172
+ ...(entry.deprecated ? { deprecated: true } : {}),
173
+ });
174
+ }
60
175
  }
61
176
  }
62
177
  results.sort((a, b) => a.type.localeCompare(b.type));
@@ -67,10 +182,31 @@ function findType(args, index) {
67
182
  content: [{
68
183
  type: 'text',
69
184
  text: `Found ${results.length} type(s) for '${pattern}':\n\n` +
70
- results.map(r => ` ${r.type} (${r.kind}) — ${r.package}`).join('\n'),
185
+ results
186
+ .map(r => ` ${r.type} (${r.kind}) — ${r.package}${r.deprecated ? ' [deprecated]' : ''}` +
187
+ (r.signature ? `\n ${r.signature}` : ''))
188
+ .join('\n'),
71
189
  }],
72
190
  };
73
191
  }
192
+ /** Type/interface/class entries for a package (uses `symbols` when present, else `exports`). */
193
+ function typeEntries(pkg) {
194
+ if (pkg.symbols && Object.keys(pkg.symbols).length > 0) {
195
+ return Object.entries(pkg.symbols)
196
+ .filter(([, m]) => m.kind === 'type' || m.kind === 'interface' || m.kind === 'class')
197
+ .map(([name, m]) => ({
198
+ name,
199
+ kind: m.kind,
200
+ ...(m.signature !== undefined ? { signature: m.signature } : {}),
201
+ ...(m.deprecated ? { deprecated: true } : {}),
202
+ }));
203
+ }
204
+ return [
205
+ ...pkg.exports.interfaces.map(n => ({ name: n, kind: 'interface' })),
206
+ ...pkg.exports.types.map(n => ({ name: n, kind: 'type' })),
207
+ ...pkg.exports.classes.map(n => ({ name: n, kind: 'class' })),
208
+ ];
209
+ }
74
210
  function dependencyGraph(args, index) {
75
211
  const pkgName = args.package;
76
212
  const direction = args.direction || 'out';
@@ -434,30 +570,36 @@ function listExports(args, index) {
434
570
  }
435
571
  const pkg = index.packages[pkgName];
436
572
  const sections = [];
573
+ const render = (s) => {
574
+ const m = pkg.symbols?.[s];
575
+ const dep = m?.deprecated ? ' · deprecated' : '';
576
+ return ` - \`${s}\`${dep}${m?.signature ? `\n ${m.signature}` : ''}`;
577
+ };
578
+ const pick = (list) => (filter ? list.filter(s => s.toLowerCase().includes(filter)) : list);
437
579
  if (!kind || kind === 'function') {
438
- const items = filter ? pkg.exports.functions.filter(s => s.toLowerCase().includes(filter)) : pkg.exports.functions;
580
+ const items = pick(pkg.exports.functions);
439
581
  if (items.length)
440
- sections.push(`**Functions (${items.length}):**\n` + items.map(s => ` - \`${s}\``).join('\n'));
582
+ sections.push(`**Functions (${items.length}):**\n` + items.map(render).join('\n'));
441
583
  }
442
584
  if (!kind || kind === 'interface') {
443
- const items = filter ? pkg.exports.interfaces.filter(s => s.toLowerCase().includes(filter)) : pkg.exports.interfaces;
585
+ const items = pick(pkg.exports.interfaces);
444
586
  if (items.length)
445
- sections.push(`**Interfaces (${items.length}):**\n` + items.map(s => ` - \`${s}\``).join('\n'));
587
+ sections.push(`**Interfaces (${items.length}):**\n` + items.map(render).join('\n'));
446
588
  }
447
589
  if (!kind || kind === 'type') {
448
- const items = filter ? pkg.exports.types.filter(s => s.toLowerCase().includes(filter)) : pkg.exports.types;
590
+ const items = pick(pkg.exports.types);
449
591
  if (items.length)
450
- sections.push(`**Named Types (${items.length}):**\n` + items.map(s => ` - \`${s}\``).join('\n'));
592
+ sections.push(`**Named Types (${items.length}):**\n` + items.map(render).join('\n'));
451
593
  }
452
594
  if (!kind || kind === 'class') {
453
- const items = filter ? pkg.exports.classes.filter(s => s.toLowerCase().includes(filter)) : pkg.exports.classes;
595
+ const items = pick(pkg.exports.classes);
454
596
  if (items.length)
455
- sections.push(`**Classes (${items.length}):**\n` + items.map(s => ` - \`${s}\``).join('\n'));
597
+ sections.push(`**Classes (${items.length}):**\n` + items.map(render).join('\n'));
456
598
  }
457
599
  if (!kind || kind === 'const') {
458
- const items = filter ? pkg.exports.consts.filter(s => s.toLowerCase().includes(filter)) : pkg.exports.consts;
600
+ const items = pick(pkg.exports.consts);
459
601
  if (items.length)
460
- sections.push(`**Constants (${items.length}):**\n` + items.map(s => ` - \`${s}\``).join('\n'));
602
+ sections.push(`**Constants (${items.length}):**\n` + items.map(render).join('\n'));
461
603
  }
462
604
  return {
463
605
  content: [{ type: 'text', text: sections.join('\n\n') || `No exports found for '${pkgName}'` }],
package/dist/types.d.ts CHANGED
@@ -3,12 +3,17 @@ export interface PackageIndex {
3
3
  dir: string;
4
4
  version: string;
5
5
  description: string;
6
+ keywords?: string[];
6
7
  dependencies: string[];
7
8
  devDependencies: string[];
8
9
  hasRust: boolean;
9
10
  hasGo: boolean;
10
11
  hasTests: boolean;
11
12
  exports: PackageExports;
13
+ /** Per-symbol metadata (signature, doc, deprecation) keyed by symbol name. */
14
+ symbols?: {
15
+ [name: string]: SymbolMeta;
16
+ };
12
17
  domain: string;
13
18
  }
14
19
  export interface PackageExports {
@@ -18,9 +23,22 @@ export interface PackageExports {
18
23
  interfaces: string[];
19
24
  consts: string[];
20
25
  }
26
+ export type SymbolKind = 'function' | 'type' | 'class' | 'interface' | 'const' | 'variable';
27
+ export interface SymbolMeta {
28
+ kind: SymbolKind;
29
+ /** Best-effort declaration text (params/return/extends), whitespace-normalized. */
30
+ signature?: string;
31
+ /** Leading JSDoc/line-comment text, if any. */
32
+ doc?: string;
33
+ /** True when the doc contains an `@deprecated` tag. */
34
+ deprecated?: boolean;
35
+ }
21
36
  export interface SymbolEntry {
22
37
  package: string;
23
- kind: 'function' | 'type' | 'class' | 'interface' | 'const' | 'variable';
38
+ kind: SymbolKind;
39
+ signature?: string;
40
+ doc?: string;
41
+ deprecated?: boolean;
24
42
  }
25
43
  export interface DomainMap {
26
44
  [domain: string]: string[];
@@ -35,6 +53,11 @@ export interface SdkIndex {
35
53
  };
36
54
  domainMap: DomainMap;
37
55
  }
56
+ export interface ToolDefinition {
57
+ name: string;
58
+ description: string;
59
+ inputSchema: unknown;
60
+ }
38
61
  export interface ToolResponse {
39
62
  content: Array<{
40
63
  type: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@totemsdk/mcp-server",
3
- "version": "0.2.0",
4
- "description": "MCP server exposing the full Totem SDK package set — metadata, types, exports, dependency graphs, and scaffolding tools for 55 packages",
3
+ "version": "0.4.0",
4
+ "description": "MCP server exposing the full Totem SDK package set — metadata, types, exports, dependency graphs, and scaffolding tools",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "bin": {
@@ -16,6 +16,7 @@
16
16
  },
17
17
  "files": [
18
18
  "dist",
19
+ "data",
19
20
  "README.md",
20
21
  "LICENSE"
21
22
  ],
@@ -37,7 +38,7 @@
37
38
  },
38
39
  "license": "MIT",
39
40
  "author": "Totem SDK",
40
- "homepage": "https://totem.ing",
41
+ "homepage": "https://docs.totem.ing",
41
42
  "bugs": {
42
43
  "url": "https://github.com/Totem-Edge/totem-sdk/issues"
43
44
  },
@@ -54,8 +55,8 @@
54
55
  "sdk-index"
55
56
  ],
56
57
  "scripts": {
57
- "build": "tsc",
58
- "clean": "rm -rf dist",
58
+ "build": "tsc && node ../../scripts/build-mcp-bundle.mjs",
59
+ "clean": "rm -rf dist data",
59
60
  "start": "node dist/index.js",
60
61
  "test": "jest"
61
62
  }