@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/indexer.js
CHANGED
|
@@ -33,53 +33,93 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.IndexUnavailableError = void 0;
|
|
37
|
+
exports.buildFromMonorepo = buildFromMonorepo;
|
|
36
38
|
exports.buildIndex = buildIndex;
|
|
37
39
|
exports.readSourceFile = readSourceFile;
|
|
38
40
|
const fs = __importStar(require("fs"));
|
|
39
41
|
const path = __importStar(require("path"));
|
|
40
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Thrown when no SDK index can be produced — neither a monorepo to scan nor a
|
|
44
|
+
* bundled index shipped in the package. Callers must surface this as an error,
|
|
45
|
+
* never as an empty (and therefore confidently-wrong) index.
|
|
46
|
+
*/
|
|
47
|
+
class IndexUnavailableError extends Error {
|
|
48
|
+
constructor(message) {
|
|
49
|
+
super(message);
|
|
50
|
+
this.name = 'IndexUnavailableError';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.IndexUnavailableError = IndexUnavailableError;
|
|
54
|
+
/**
|
|
55
|
+
* Prebuilt index shipped in the published tarball (`data/sdk-index.json`), so
|
|
56
|
+
* the server works outside the monorepo. Generated by
|
|
57
|
+
* `scripts/build-mcp-bundle.mjs` at pack time.
|
|
58
|
+
*/
|
|
59
|
+
const BUNDLED_INDEX_PATH = path.join(__dirname, '..', 'data', 'sdk-index.json');
|
|
60
|
+
/** A pnpm-workspace dir is only the SDK root if it actually contains the SDK. */
|
|
61
|
+
function isSdkRoot(dir) {
|
|
62
|
+
return (fs.existsSync(path.join(dir, 'SDK_MANIFEST.json')) ||
|
|
63
|
+
fs.existsSync(path.join(dir, 'packages', 'core', 'package.json')));
|
|
64
|
+
}
|
|
65
|
+
/** Walk up from `__dirname` for a *valid* SDK root; `null` when not found. */
|
|
66
|
+
function detectRepoRoot() {
|
|
41
67
|
let dir = path.resolve(__dirname);
|
|
42
68
|
while (dir !== path.dirname(dir)) {
|
|
43
|
-
if (fs.existsSync(path.join(dir, 'pnpm-workspace.yaml')))
|
|
69
|
+
if (fs.existsSync(path.join(dir, 'pnpm-workspace.yaml')) && isSdkRoot(dir))
|
|
44
70
|
return dir;
|
|
45
71
|
dir = path.dirname(dir);
|
|
46
72
|
}
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
function loadRepoData(root) {
|
|
76
|
+
const topPkgDir = path.join(root, 'packages');
|
|
77
|
+
const manifest = new Map();
|
|
78
|
+
try {
|
|
79
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(root, 'SDK_MANIFEST.json'), 'utf-8'));
|
|
80
|
+
for (const pkg of parsed.packages ?? []) {
|
|
81
|
+
if (pkg && typeof pkg.name === 'string') {
|
|
82
|
+
manifest.set(pkg.name, { version: pkg.version, description: pkg.description, domain: pkg.domain });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Manifest missing/unreadable — fall back to filesystem discovery only.
|
|
88
|
+
}
|
|
89
|
+
const ast = new Map();
|
|
90
|
+
try {
|
|
91
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(root, 'scripts', 'mcp-symbols.ast.json'), 'utf-8'));
|
|
92
|
+
for (const [name, pkg] of Object.entries(parsed.packages ?? {})) {
|
|
93
|
+
if (pkg?.symbols)
|
|
94
|
+
ast.set(name, pkg.symbols);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Optional artifact — fall back to the regex scan.
|
|
99
|
+
}
|
|
100
|
+
return { topPkgDir, manifest, ast };
|
|
101
|
+
}
|
|
102
|
+
function loadBundledIndex() {
|
|
103
|
+
try {
|
|
104
|
+
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;
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function findPackageDirs(TOP_PKG_DIR) {
|
|
72
114
|
const dirs = [];
|
|
73
|
-
|
|
74
|
-
|
|
115
|
+
if (!fs.existsSync(TOP_PKG_DIR))
|
|
116
|
+
return dirs;
|
|
117
|
+
for (const entry of fs.readdirSync(TOP_PKG_DIR, { withFileTypes: true })) {
|
|
118
|
+
if (!entry.isDirectory())
|
|
75
119
|
continue;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const pj = path.join(base, entry.name, 'package.json');
|
|
80
|
-
if (fs.existsSync(pj))
|
|
81
|
-
dirs.push(path.join(base, entry.name));
|
|
82
|
-
}
|
|
120
|
+
const pj = path.join(TOP_PKG_DIR, entry.name, 'package.json');
|
|
121
|
+
if (fs.existsSync(pj))
|
|
122
|
+
dirs.push(path.join(TOP_PKG_DIR, entry.name));
|
|
83
123
|
}
|
|
84
124
|
return dirs.sort();
|
|
85
125
|
}
|
|
@@ -96,66 +136,169 @@ function hasSubdir(dir, name) {
|
|
|
96
136
|
return fs.existsSync(d) && fs.statSync(d).isDirectory();
|
|
97
137
|
}
|
|
98
138
|
function hasTestDir(dir) {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
function parseExports(dir, pkgName) {
|
|
106
|
-
const indexFiles = [
|
|
107
|
-
path.join(dir, 'src', 'index.ts'),
|
|
108
|
-
path.join(dir, 'src', 'index.js'),
|
|
139
|
+
const candidates = [
|
|
140
|
+
path.join(dir, 'src', '__tests__'),
|
|
141
|
+
path.join(dir, 'src', 'test'),
|
|
142
|
+
path.join(dir, 'test'),
|
|
143
|
+
path.join(dir, 'tests'),
|
|
109
144
|
];
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
145
|
+
return candidates.some(d => fs.existsSync(d));
|
|
146
|
+
}
|
|
147
|
+
function resolveModule(fromFile, spec) {
|
|
148
|
+
if (!spec.startsWith('.'))
|
|
149
|
+
return null;
|
|
150
|
+
const base = path.resolve(path.dirname(fromFile), spec.replace(/\.js$/, ''));
|
|
151
|
+
const candidates = [`${base}.ts`, `${base}.tsx`, path.join(base, 'index.ts')];
|
|
152
|
+
for (const c of candidates) {
|
|
153
|
+
if (fs.existsSync(c) && fs.statSync(c).isFile())
|
|
154
|
+
return c;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
/** Leading JSDoc (`/** … */`) or contiguous `//` comment above a declaration. */
|
|
159
|
+
function leadingDoc(lines, lineIndex) {
|
|
160
|
+
let j = lineIndex - 1;
|
|
161
|
+
while (j >= 0 && lines[j].trim() === '')
|
|
162
|
+
j--;
|
|
163
|
+
if (j < 0)
|
|
164
|
+
return {};
|
|
165
|
+
const t = lines[j].trim();
|
|
166
|
+
if (t.endsWith('*/')) {
|
|
167
|
+
const parts = [];
|
|
168
|
+
let k = j;
|
|
169
|
+
while (k >= 0) {
|
|
170
|
+
const line = lines[k].trim();
|
|
171
|
+
parts.unshift(line);
|
|
172
|
+
if (line.startsWith('/**') || line.startsWith('/*'))
|
|
173
|
+
break;
|
|
174
|
+
k--;
|
|
175
|
+
}
|
|
176
|
+
const raw = parts
|
|
177
|
+
.join('\n')
|
|
178
|
+
.replace(/^\/\*\*?/, '')
|
|
179
|
+
.replace(/\*\/$/, '')
|
|
180
|
+
.split('\n')
|
|
181
|
+
.map(l => l.replace(/^\s*\* ?/, ''))
|
|
182
|
+
.join('\n')
|
|
183
|
+
.trim();
|
|
184
|
+
const deprecated = /@deprecated/.test(raw);
|
|
185
|
+
const stripped = raw.replace(/@\w+[^\n]*/g, '').replace(/\n{2,}/g, '\n').trim();
|
|
186
|
+
return { doc: stripped || raw, ...(deprecated ? { deprecated } : {}) };
|
|
187
|
+
}
|
|
188
|
+
if (t.startsWith('//')) {
|
|
189
|
+
const parts = [];
|
|
190
|
+
let k = j;
|
|
191
|
+
while (k >= 0 && lines[k].trim().startsWith('//')) {
|
|
192
|
+
parts.unshift(lines[k].trim().replace(/^\/\/ ?/, ''));
|
|
193
|
+
k--;
|
|
194
|
+
}
|
|
195
|
+
const raw = parts.join('\n').trim();
|
|
196
|
+
return { doc: raw, ...(/@deprecated/.test(raw) ? { deprecated: true } : {}) };
|
|
197
|
+
}
|
|
198
|
+
return {};
|
|
199
|
+
}
|
|
200
|
+
/** Best-effort declaration text up to the body/terminator. */
|
|
201
|
+
function captureSignature(lines, startIdx, stopAtBrace) {
|
|
202
|
+
let text = '';
|
|
203
|
+
for (let j = startIdx; j < Math.min(lines.length, startIdx + 8); j++) {
|
|
204
|
+
text += (j > startIdx ? ' ' : '') + lines[j].trim();
|
|
205
|
+
const brace = text.indexOf('{');
|
|
206
|
+
const semi = text.indexOf(';');
|
|
207
|
+
let cut = -1;
|
|
208
|
+
if (stopAtBrace) {
|
|
209
|
+
if (brace >= 0)
|
|
210
|
+
cut = brace;
|
|
211
|
+
else if (semi >= 0)
|
|
212
|
+
cut = semi;
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
if (semi >= 0)
|
|
216
|
+
cut = semi;
|
|
217
|
+
else if (brace >= 0)
|
|
218
|
+
cut = brace;
|
|
219
|
+
}
|
|
220
|
+
if (cut >= 0) {
|
|
221
|
+
text = text.slice(0, cut);
|
|
114
222
|
break;
|
|
115
223
|
}
|
|
116
224
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
225
|
+
return text.replace(/\s+/g, ' ').replace(/^export\s+/, '').replace(/[,\s]+$/, '').trim();
|
|
226
|
+
}
|
|
227
|
+
const KIND_TO_ARRAY = {
|
|
228
|
+
function: 'functions',
|
|
229
|
+
type: 'types',
|
|
230
|
+
class: 'classes',
|
|
231
|
+
interface: 'interfaces',
|
|
232
|
+
const: 'consts',
|
|
233
|
+
variable: 'consts',
|
|
234
|
+
};
|
|
235
|
+
function setSymbol(symbols, name, meta) {
|
|
236
|
+
if (name && !name.endsWith('}') && !symbols.has(name))
|
|
237
|
+
symbols.set(name, meta);
|
|
238
|
+
}
|
|
239
|
+
function collectExports(file, symbols, visited) {
|
|
240
|
+
if (visited.has(file))
|
|
241
|
+
return;
|
|
242
|
+
visited.add(file);
|
|
243
|
+
let content;
|
|
244
|
+
try {
|
|
245
|
+
content = fs.readFileSync(file, 'utf-8');
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
120
250
|
const lines = content.split('\n');
|
|
121
251
|
let i = 0;
|
|
122
252
|
while (i < lines.length) {
|
|
123
253
|
const trimmed = lines[i].trim();
|
|
124
|
-
const
|
|
125
|
-
if (
|
|
126
|
-
|
|
254
|
+
const starAsMatch = trimmed.match(/^export \* as (\w+) from ['"]([^'"]+)['"]/);
|
|
255
|
+
if (starAsMatch) {
|
|
256
|
+
setSymbol(symbols, starAsMatch[1], { kind: 'type', ...leadingDoc(lines, i) });
|
|
257
|
+
const target = resolveModule(file, starAsMatch[2]);
|
|
258
|
+
if (target)
|
|
259
|
+
collectExports(target, symbols, visited);
|
|
127
260
|
i++;
|
|
128
261
|
continue;
|
|
129
262
|
}
|
|
130
|
-
const
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
263
|
+
const starMatch = trimmed.match(/^export \* from ['"]([^'"]+)['"]/);
|
|
264
|
+
if (starMatch) {
|
|
265
|
+
const target = resolveModule(file, starMatch[1]);
|
|
266
|
+
if (target)
|
|
267
|
+
collectExports(target, symbols, visited);
|
|
268
|
+
i++;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const fnMatch = trimmed.match(/^export (async )?function\*? (\w+)/);
|
|
272
|
+
if (fnMatch) {
|
|
273
|
+
setSymbol(symbols, fnMatch[2], { kind: 'function', signature: captureSignature(lines, i, true), ...leadingDoc(lines, i) });
|
|
137
274
|
i++;
|
|
138
275
|
continue;
|
|
139
276
|
}
|
|
140
|
-
const clsMatch = trimmed.match(/^export class (\w+)/);
|
|
277
|
+
const clsMatch = trimmed.match(/^export (abstract )?class (\w+)/);
|
|
141
278
|
if (clsMatch) {
|
|
142
|
-
|
|
279
|
+
setSymbol(symbols, clsMatch[2], { kind: 'class', signature: captureSignature(lines, i, true), ...leadingDoc(lines, i) });
|
|
143
280
|
i++;
|
|
144
281
|
continue;
|
|
145
282
|
}
|
|
146
283
|
const ifaceMatch = trimmed.match(/^export interface (\w+)/);
|
|
147
284
|
if (ifaceMatch) {
|
|
148
|
-
|
|
285
|
+
setSymbol(symbols, ifaceMatch[1], { kind: 'interface', signature: captureSignature(lines, i, true), ...leadingDoc(lines, i) });
|
|
149
286
|
i++;
|
|
150
287
|
continue;
|
|
151
288
|
}
|
|
152
289
|
const constMatch = trimmed.match(/^export (const|let|var) (\w+)/);
|
|
153
290
|
if (constMatch) {
|
|
154
|
-
|
|
291
|
+
setSymbol(symbols, constMatch[2], { kind: 'const', signature: captureSignature(lines, i, false), ...leadingDoc(lines, i) });
|
|
155
292
|
i++;
|
|
156
293
|
continue;
|
|
157
294
|
}
|
|
158
|
-
|
|
295
|
+
const typeMatch = trimmed.match(/^export type (\w+)\s*[=<]/);
|
|
296
|
+
if (typeMatch) {
|
|
297
|
+
setSymbol(symbols, typeMatch[1], { kind: 'type', signature: captureSignature(lines, i, false), ...leadingDoc(lines, i) });
|
|
298
|
+
i++;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
// Multi-line `export { ... }` / `export type { ... }` (inline or re-export).
|
|
159
302
|
if (trimmed.startsWith('export {') || trimmed.startsWith('export type {')) {
|
|
160
303
|
const isType = trimmed.startsWith('export type {');
|
|
161
304
|
let block = trimmed;
|
|
@@ -164,22 +307,28 @@ function parseExports(dir, pkgName) {
|
|
|
164
307
|
j++;
|
|
165
308
|
block += ' ' + lines[j].trim();
|
|
166
309
|
}
|
|
167
|
-
// Extract name list between { and }
|
|
168
310
|
const braceMatch = block.match(/\{([^}]+)\}/);
|
|
169
311
|
if (braceMatch) {
|
|
170
312
|
for (const part of braceMatch[1].split(',')) {
|
|
171
|
-
|
|
172
|
-
if (!
|
|
313
|
+
let clause = part.trim();
|
|
314
|
+
if (!clause)
|
|
173
315
|
continue;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
316
|
+
// Inline type modifiers: `export { type Foo }` / `export { type Foo as Bar }`.
|
|
317
|
+
// Strip the leading `type` keyword before resolving the exposed name,
|
|
318
|
+
// otherwise the symbol is recorded literally as "type Foo".
|
|
319
|
+
let inlineType = false;
|
|
320
|
+
const typeKw = clause.match(/^type\s+([\s\S]+)$/);
|
|
321
|
+
if (typeKw) {
|
|
322
|
+
inlineType = true;
|
|
323
|
+
clause = typeKw[1].trim();
|
|
182
324
|
}
|
|
325
|
+
const name = clause.split(/\s+as\s+/).pop()?.trim() || '';
|
|
326
|
+
// Reject anything that isn't a plain identifier (guards against
|
|
327
|
+
// malformed fragments leaking in as fake symbols).
|
|
328
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(name))
|
|
329
|
+
continue;
|
|
330
|
+
const kind = isType || inlineType || /^[A-Z]/.test(name) ? 'type' : 'function';
|
|
331
|
+
setSymbol(symbols, name, { kind });
|
|
183
332
|
}
|
|
184
333
|
}
|
|
185
334
|
i = j + 1;
|
|
@@ -187,67 +336,142 @@ function parseExports(dir, pkgName) {
|
|
|
187
336
|
}
|
|
188
337
|
i++;
|
|
189
338
|
}
|
|
339
|
+
}
|
|
340
|
+
function parseExports(dir) {
|
|
341
|
+
const symbols = new Map();
|
|
342
|
+
const candidates = [path.join(dir, 'src', 'index.ts'), path.join(dir, 'src', 'index.js')];
|
|
343
|
+
const entry = candidates.find(f => fs.existsSync(f));
|
|
344
|
+
if (entry)
|
|
345
|
+
collectExports(entry, symbols, new Set());
|
|
346
|
+
return symbols;
|
|
347
|
+
}
|
|
348
|
+
function exportsFromSymbols(symbols) {
|
|
349
|
+
const exports = { functions: [], types: [], classes: [], interfaces: [], consts: [] };
|
|
350
|
+
for (const [name, meta] of symbols)
|
|
351
|
+
exports[KIND_TO_ARRAY[meta.kind]].push(name);
|
|
190
352
|
return exports;
|
|
191
353
|
}
|
|
192
|
-
|
|
354
|
+
/** Overlay exact AST metadata (authoritative) onto the regex-scanned symbols. */
|
|
355
|
+
function mergeAst(symbols, ast) {
|
|
356
|
+
if (!ast)
|
|
357
|
+
return;
|
|
358
|
+
for (const [name, meta] of Object.entries(ast)) {
|
|
359
|
+
const existing = symbols.get(name);
|
|
360
|
+
symbols.set(name, { ...existing, ...meta, kind: meta.kind });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/** Scan a monorepo root into an `SdkIndex`. */
|
|
364
|
+
function buildFromMonorepo(root, now = Date.now()) {
|
|
365
|
+
const { topPkgDir, manifest: MANIFEST, ast: AST } = loadRepoData(root);
|
|
193
366
|
const packages = {};
|
|
194
367
|
const symbolIndex = {};
|
|
195
368
|
const domainMap = {};
|
|
196
|
-
for (const dir of findPackageDirs()) {
|
|
369
|
+
for (const dir of findPackageDirs(topPkgDir)) {
|
|
197
370
|
const pkg = readPackageJson(dir);
|
|
198
371
|
if (!pkg || !pkg.name)
|
|
199
372
|
continue;
|
|
373
|
+
const manifest = MANIFEST.get(pkg.name);
|
|
374
|
+
if (MANIFEST.size > 0 && !manifest)
|
|
375
|
+
continue;
|
|
200
376
|
const dirName = path.basename(dir);
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
const hasTests = hasTestDir(dir);
|
|
206
|
-
const exports = parseExports(dir, pkg.name);
|
|
207
|
-
const domain = classifyDomain(dirName);
|
|
377
|
+
const symbols = parseExports(dir);
|
|
378
|
+
mergeAst(symbols, AST.get(pkg.name));
|
|
379
|
+
const exports = exportsFromSymbols(symbols);
|
|
380
|
+
const domain = manifest?.domain || 'other';
|
|
208
381
|
const idx = {
|
|
209
382
|
name: pkg.name,
|
|
210
383
|
dir: dirName,
|
|
211
|
-
version: pkg.version || '0.0.0',
|
|
212
|
-
description: pkg.description || '',
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
384
|
+
version: manifest?.version || pkg.version || '0.0.0',
|
|
385
|
+
description: manifest?.description || pkg.description || '',
|
|
386
|
+
...(Array.isArray(pkg.keywords) ? { keywords: pkg.keywords } : {}),
|
|
387
|
+
dependencies: Object.keys(pkg.dependencies || {}),
|
|
388
|
+
devDependencies: Object.keys(pkg.devDependencies || {}),
|
|
389
|
+
hasRust: hasSubdir(dir, 'rust') || hasSubdir(dir, 'rust-toolchain'),
|
|
390
|
+
hasGo: hasSubdir(dir, 'go'),
|
|
391
|
+
hasTests: hasTestDir(dir),
|
|
218
392
|
exports,
|
|
393
|
+
symbols: Object.fromEntries(symbols),
|
|
219
394
|
domain,
|
|
220
395
|
};
|
|
221
396
|
packages[pkg.name] = idx;
|
|
222
397
|
if (!domainMap[domain])
|
|
223
398
|
domainMap[domain] = [];
|
|
224
399
|
domainMap[domain].push(pkg.name);
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
for (const [name, kind] of allExports) {
|
|
237
|
-
if (!symbolIndex[name])
|
|
238
|
-
symbolIndex[name] = [];
|
|
239
|
-
symbolIndex[name].push({ package: pkg.name, kind });
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
return { generatedAt: Date.now(), packages, symbolIndex, domainMap };
|
|
400
|
+
for (const [name, meta] of symbols) {
|
|
401
|
+
(symbolIndex[name] ??= []).push({
|
|
402
|
+
package: pkg.name,
|
|
403
|
+
kind: meta.kind,
|
|
404
|
+
...(meta.signature !== undefined ? { signature: meta.signature } : {}),
|
|
405
|
+
...(meta.doc !== undefined ? { doc: meta.doc } : {}),
|
|
406
|
+
...(meta.deprecated ? { deprecated: true } : {}),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return { generatedAt: now, packages, symbolIndex, domainMap };
|
|
243
411
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
412
|
+
/**
|
|
413
|
+
* Build the SDK index. Prefers a live monorepo scan; falls back to the bundled
|
|
414
|
+
* index shipped in the package. Throws {@link IndexUnavailableError} when
|
|
415
|
+
* neither is available — an empty index is never returned, because downstream
|
|
416
|
+
* tools would otherwise answer "not found" as if it were fact.
|
|
417
|
+
*/
|
|
418
|
+
function buildIndex(options = {}) {
|
|
419
|
+
const root = options.rootDir === undefined ? detectRepoRoot() : options.rootDir;
|
|
420
|
+
if (root)
|
|
421
|
+
return buildFromMonorepo(root);
|
|
422
|
+
if (!options.noBundled) {
|
|
423
|
+
const bundled = loadBundledIndex();
|
|
424
|
+
if (bundled)
|
|
425
|
+
return bundled;
|
|
251
426
|
}
|
|
427
|
+
throw new IndexUnavailableError('@totemsdk/mcp-server: SDK index unavailable — not running inside the Totem SDK '
|
|
428
|
+
+ 'monorepo and no bundled index was shipped. Reinstall @totemsdk/mcp-server '
|
|
429
|
+
+ '(expected data/sdk-index.json) or run from the SDK repository.');
|
|
430
|
+
}
|
|
431
|
+
/** Map a package name (`@totemsdk/core`) or dir (`core`) to its absolute directory. */
|
|
432
|
+
const _pkgDirCache = new Map();
|
|
433
|
+
function packageDir(name) {
|
|
434
|
+
if (_pkgDirCache.has(name))
|
|
435
|
+
return _pkgDirCache.get(name) ?? null;
|
|
436
|
+
const root = detectRepoRoot();
|
|
437
|
+
if (!root) {
|
|
438
|
+
_pkgDirCache.set(name, null);
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
const TOP_PKG_DIR = path.join(root, 'packages');
|
|
442
|
+
const direct = path.join(TOP_PKG_DIR, name);
|
|
443
|
+
if (fs.existsSync(path.join(direct, 'package.json'))) {
|
|
444
|
+
_pkgDirCache.set(name, direct);
|
|
445
|
+
return direct;
|
|
446
|
+
}
|
|
447
|
+
if (fs.existsSync(TOP_PKG_DIR)) {
|
|
448
|
+
for (const entry of fs.readdirSync(TOP_PKG_DIR, { withFileTypes: true })) {
|
|
449
|
+
if (!entry.isDirectory())
|
|
450
|
+
continue;
|
|
451
|
+
const dir = path.join(TOP_PKG_DIR, entry.name);
|
|
452
|
+
const pj = path.join(dir, 'package.json');
|
|
453
|
+
if (!fs.existsSync(pj))
|
|
454
|
+
continue;
|
|
455
|
+
try {
|
|
456
|
+
if (JSON.parse(fs.readFileSync(pj, 'utf-8')).name === name) {
|
|
457
|
+
_pkgDirCache.set(name, dir);
|
|
458
|
+
return dir;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
catch { /* ignore unreadable package.json */ }
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
_pkgDirCache.set(name, null);
|
|
252
465
|
return null;
|
|
253
466
|
}
|
|
467
|
+
/** Read a file from a package's `src/` directory (for the `read-source` tool). */
|
|
468
|
+
function readSourceFile(pkgName, filePath) {
|
|
469
|
+
const dir = packageDir(pkgName);
|
|
470
|
+
if (!dir)
|
|
471
|
+
return null;
|
|
472
|
+
const base = path.resolve(dir, 'src');
|
|
473
|
+
const resolved = path.resolve(base, filePath);
|
|
474
|
+
if (resolved !== base && !resolved.startsWith(base + path.sep))
|
|
475
|
+
return null;
|
|
476
|
+
return fs.existsSync(resolved) ? fs.readFileSync(resolved, 'utf-8') : null;
|
|
477
|
+
}
|
package/dist/resources.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { SdkIndex } from './types.js';
|
|
2
|
+
/** Extract a markdown section by heading slug/text (for `totemsdk://rfc/014#6-design`). */
|
|
3
|
+
export declare function extractSection(markdown: string, anchor: string): string | null;
|
|
4
|
+
/** MIME type for a resource URI. */
|
|
5
|
+
export declare function resourceMimeType(uri: string): string;
|
|
2
6
|
export declare function handleResourceRead(uri: string, index: SdkIndex): string | null;
|
|
3
7
|
export declare function listResources(index: SdkIndex): Array<{
|
|
4
8
|
uri: string;
|