@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/README.md +9 -2
- package/data/sdk-index.json +1 -0
- package/dist/index-store.d.ts +18 -0
- package/dist/index-store.js +38 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.js +99 -114
- package/dist/indexer.d.ts +27 -1
- package/dist/indexer.js +344 -120
- package/dist/resources.d.ts +4 -0
- package/dist/resources.js +187 -21
- package/dist/tools.d.ts +7 -1
- package/dist/tools.js +166 -24
- package/dist/types.d.ts +24 -1
- package/package.json +6 -5
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 —
|
|
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: '
|
|
85
|
-
description: '
|
|
86
|
-
file: '
|
|
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
|
-
-
|
|
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-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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({
|
|
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
|
|
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
|
|
50
|
-
if (
|
|
51
|
-
results.push({
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
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 =
|
|
580
|
+
const items = pick(pkg.exports.functions);
|
|
439
581
|
if (items.length)
|
|
440
|
-
sections.push(`**Functions (${items.length}):**\n` + items.map(
|
|
582
|
+
sections.push(`**Functions (${items.length}):**\n` + items.map(render).join('\n'));
|
|
441
583
|
}
|
|
442
584
|
if (!kind || kind === 'interface') {
|
|
443
|
-
const items =
|
|
585
|
+
const items = pick(pkg.exports.interfaces);
|
|
444
586
|
if (items.length)
|
|
445
|
-
sections.push(`**Interfaces (${items.length}):**\n` + items.map(
|
|
587
|
+
sections.push(`**Interfaces (${items.length}):**\n` + items.map(render).join('\n'));
|
|
446
588
|
}
|
|
447
589
|
if (!kind || kind === 'type') {
|
|
448
|
-
const items =
|
|
590
|
+
const items = pick(pkg.exports.types);
|
|
449
591
|
if (items.length)
|
|
450
|
-
sections.push(`**Named Types (${items.length}):**\n` + items.map(
|
|
592
|
+
sections.push(`**Named Types (${items.length}):**\n` + items.map(render).join('\n'));
|
|
451
593
|
}
|
|
452
594
|
if (!kind || kind === 'class') {
|
|
453
|
-
const items =
|
|
595
|
+
const items = pick(pkg.exports.classes);
|
|
454
596
|
if (items.length)
|
|
455
|
-
sections.push(`**Classes (${items.length}):**\n` + items.map(
|
|
597
|
+
sections.push(`**Classes (${items.length}):**\n` + items.map(render).join('\n'));
|
|
456
598
|
}
|
|
457
599
|
if (!kind || kind === 'const') {
|
|
458
|
-
const items =
|
|
600
|
+
const items = pick(pkg.exports.consts);
|
|
459
601
|
if (items.length)
|
|
460
|
-
sections.push(`**Constants (${items.length}):**\n` + items.map(
|
|
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:
|
|
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.
|
|
4
|
-
"description": "MCP server exposing the full Totem SDK package set — metadata, types, exports, dependency graphs, and scaffolding tools
|
|
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
|
}
|