@totemsdk/mcp-server 0.3.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/indexer.js CHANGED
@@ -310,10 +310,24 @@ function collectExports(file, symbols, visited) {
310
310
  const braceMatch = block.match(/\{([^}]+)\}/);
311
311
  if (braceMatch) {
312
312
  for (const part of braceMatch[1].split(',')) {
313
- const name = part.trim().split(/\s+as\s+/).pop()?.trim() || '';
314
- if (!name)
313
+ let clause = part.trim();
314
+ if (!clause)
315
315
  continue;
316
- const kind = isType || /^[A-Z]/.test(name) ? 'type' : 'function';
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();
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';
317
331
  setSymbol(symbols, name, { kind });
318
332
  }
319
333
  }
package/dist/resources.js CHANGED
@@ -108,7 +108,12 @@ function docMeta(file, fallbackName) {
108
108
  description = t.replace(/[*_`]/g, '').trim();
109
109
  break;
110
110
  }
111
- return { name: name || fallbackName, description: description || name || fallbackName };
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 };
112
117
  }
113
118
  /**
114
119
  * RFCs and audits are discovered from disk, so a new `docs/rfc/RFC-*.md` or
@@ -125,7 +130,14 @@ function discoverDocs() {
125
130
  continue;
126
131
  const file = path.join('docs', 'rfc', f);
127
132
  const meta = docMeta(file, `RFC-${m[1]}`);
128
- docs.push({ uri: `totemsdk://rfc/${m[1]}`, ...meta, file });
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 });
129
141
  }
130
142
  }
131
143
  const auditDir = path.join(REPO_ROOT, 'docs', 'audits');
@@ -140,6 +152,19 @@ function discoverDocs() {
140
152
  docs.push({ uri: `totemsdk://audit/${slug}`, ...meta, file });
141
153
  }
142
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
+ }
143
168
  return docs;
144
169
  }
145
170
  const DOCS = discoverDocs();
@@ -202,7 +227,7 @@ function resourceMimeType(uri) {
202
227
  const base = uri.split('#')[0];
203
228
  if (base === 'totemsdk://conventions' || base === 'totemsdk://templates')
204
229
  return 'text/markdown';
205
- if (/^totemsdk:\/\/(papers|rfc|audit)\/[^/]+$/.test(base))
230
+ if (/^totemsdk:\/\/(papers|rfc|audit|example)\/[^/]+(\/[^/]+)?$/.test(base))
206
231
  return 'text/markdown';
207
232
  if (base === 'totemsdk://packages' || base === 'totemsdk://domain-map' || base === 'totemsdk://rfc')
208
233
  return 'application/json';
@@ -367,10 +392,16 @@ function listResources(index) {
367
392
  description: `Dependencies of ${name}`,
368
393
  });
369
394
  }
370
- const topSymbols = Object.entries(index.symbolIndex)
371
- .filter(([_, v]) => v.length <= 3)
372
- .slice(0, 200);
373
- 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) {
374
405
  resources.push({
375
406
  uri: `totemsdk://symbol/${symbol}`,
376
407
  name: `Symbol: ${symbol}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@totemsdk/mcp-server",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
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",