@transcend-io/mcp-server-docs 0.3.25 → 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 CHANGED
@@ -70,7 +70,7 @@ See [CONTRIBUTING.md](../../../CONTRIBUTING.md#mcp-servers) for workspace layout
70
70
 
71
71
  ## Tools
72
72
 
73
- - `docs_list` — List/search the Transcend docs index (`llms.txt`); optional `section` and `keyword` filters
73
+ - `docs_list` — Browse the Transcend docs sections (`llms.txt`), list one section's articles with `section`, or full-text search titles and article bodies with `query` (ranked snippets; then `docs_fetch`)
74
74
  - `docs_fetch` — Fetch full markdown for a docs article URL (host restricted to `docs.transcend.io`)
75
75
 
76
76
  ## Related packages
package/dist/cli.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { t as getDocsTools } from "./tools-Cc8ZQVE0.mjs";
2
+ import { t as getDocsTools } from "./tools-BMbaq7_0.mjs";
3
3
  import { createMCPServer } from "@transcend-io/mcp-server-base";
4
4
  //#endregion
5
5
  //#region src/cli.ts
6
6
  createMCPServer({
7
7
  name: "transcend-mcp-docs",
8
- version: "0.3.25",
8
+ version: "0.4.0",
9
9
  requireStartupAuth: false,
10
10
  oauthScopes: [],
11
11
  getTools: getDocsTools
package/dist/index.d.mts CHANGED
@@ -28,6 +28,28 @@ declare function assertDocsHost(url: string): void;
28
28
  /** Clears in-memory caches (for tests). */
29
29
  declare function resetDocsCachesForTests(): void;
30
30
  //#endregion
31
+ //#region src/docsSearch.d.ts
32
+ /** Ranked article hit returned by docs_list full-text search. */
33
+ interface DocsSearchHit {
34
+ /** Human-readable article title. */
35
+ title: string;
36
+ /** Section header from llms.txt. */
37
+ section: string;
38
+ /** Absolute URL to the article markdown. */
39
+ url: string;
40
+ /** Orama BM25 score. */
41
+ score: number;
42
+ /** Short excerpt around the first query term. */
43
+ snippet: string;
44
+ }
45
+ /** Full-text search over hydrated docs (title, URL path, section, body). */
46
+ declare function searchDocs(query: string, options?: {
47
+ /** Optional llms.txt section name (exact match). */section?: string;
48
+ }): Promise<{
49
+ /** Ranked hits for this page. */hits: DocsSearchHit[]; /** Total matches before the fixed page size. */
50
+ totalCount: number;
51
+ }>;
52
+ //#endregion
31
53
  //#region src/tools/index.d.ts
32
54
  declare function getDocsTools(_clients?: ToolClients): ToolDefinition[];
33
55
  //#endregion
@@ -40,9 +62,9 @@ type DocsFetchInput = z.infer<typeof DocsFetchSchema>;
40
62
  //#region src/tools/docs_list.d.ts
41
63
  declare const DocsListSchema: z.ZodObject<{
42
64
  section: z.ZodOptional<z.ZodString>;
43
- keyword: z.ZodOptional<z.ZodString>;
65
+ query: z.ZodOptional<z.ZodString>;
44
66
  }, z.core.$strip>;
45
67
  type DocsListInput = z.infer<typeof DocsListSchema>;
46
68
  //#endregion
47
- export { DOCS_HOST, type DocEntry, type DocsFetchInput, DocsFetchSchema, type DocsListInput, DocsListSchema, LLMS_TXT_URL, assertDocsHost, getBody, getDocsTools, getIndex, parseLlmsTxt, resetDocsCachesForTests };
69
+ export { DOCS_HOST, type DocEntry, type DocsFetchInput, DocsFetchSchema, type DocsListInput, DocsListSchema, type DocsSearchHit, LLMS_TXT_URL, assertDocsHost, getBody, getDocsTools, getIndex, parseLlmsTxt, resetDocsCachesForTests, searchDocs };
48
70
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/docsIndex.ts","../src/tools/index.ts","../src/tools/docs_fetch.ts","../src/tools/docs_list.ts"],"mappings":";;;;cAGa,YAAA;;cAGA,SAAA;;UASI,QAAA;EAZQ;EAcvB,OAAA;EAXW;EAaX,KAAA;;EAEA,GAAA;AAAA;AANF;;;;AAAA,iBAagB,YAAA,CAAa,GAAA,WAAc,QAAA;;iBAuDrB,QAAA,CAAA,GAAY,OAAA,CAAQ,QAAA;;iBASpB,OAAA,CAAQ,GAAA,WAAc,OAAA;AAhE5C;AAAA,iBA0EgB,cAAA,CAAe,GAAA;;iBAaf,uBAAA,CAAA;;;iBC9GA,YAAA,CAAa,QAAA,GAAW,WAAA,GAAc,cAAA;;;cCEzC,eAAA,EAAe,CAAA,CAAA,SAAA;;;KAMhB,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,eAAA;;;cCT/B,cAAA,EAAc,CAAA,CAAA,SAAA;;;;KAUf,aAAA,GAAgB,CAAA,CAAE,KAAA,QAAa,cAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/docsIndex.ts","../src/docsSearch.ts","../src/tools/index.ts","../src/tools/docs_fetch.ts","../src/tools/docs_list.ts"],"mappings":";;;;cAKa,YAAA;;cAGA,SAAA;;UASI,QAAA;EAZQ;EAcvB,OAAA;EAXW;EAaX,KAAA;;EAEA,GAAA;AAAA;AANF;;;;AAAA,iBAagB,YAAA,CAAa,GAAA,WAAc,QAAA;;iBA2DrB,QAAA,CAAA,GAAY,OAAA,CAAQ,QAAA;;iBASpB,OAAA,CAAQ,GAAA,WAAc,OAAA;AApE5C;AAAA,iBA8EgB,cAAA,CAAe,GAAA;;iBAaf,uBAAA,CAAA;;;;UCvFC,aAAA;;EAEf,KAAA;ED/BuB;ECiCvB,OAAA;EDjCuB;ECmCvB,GAAA;EDhCW;ECkCX,KAAA;;EAEA,OAAA;AAAA;;iBA0GoB,UAAA,CACpB,KAAA,UACA,OAAA;EDrIA,oDCuIE,OAAA;AAAA,IAED,OAAA;EDrIE,iCCuIH,IAAA,EAAM,aAAA,IDhIQ;ECkId,UAAA;AAAA;;;iBC3Jc,YAAA,CAAa,QAAA,GAAW,WAAA,GAAc,cAAA;;;cCEzC,eAAA,EAAe,CAAA,CAAA,SAAA;;;KAMhB,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,eAAA;;;cCQ/B,cAAA,EAAc,CAAA,CAAA,SAAA;;;;KAkBf,aAAA,GAAgB,CAAA,CAAE,KAAA,QAAa,cAAA"}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as LLMS_TXT_URL, c as getIndex, i as DOCS_HOST, l as parseLlmsTxt, n as DocsListSchema, o as assertDocsHost, r as DocsFetchSchema, s as getBody, t as getDocsTools, u as resetDocsCachesForTests } from "./tools-Cc8ZQVE0.mjs";
2
- export { DOCS_HOST, DocsFetchSchema, DocsListSchema, LLMS_TXT_URL, assertDocsHost, getBody, getDocsTools, getIndex, parseLlmsTxt, resetDocsCachesForTests };
1
+ import { a as LLMS_TXT_URL, c as getIndex, d as searchDocs, i as DOCS_HOST, l as parseLlmsTxt, n as DocsListSchema, o as assertDocsHost, r as DocsFetchSchema, s as getBody, t as getDocsTools, u as resetDocsCachesForTests } from "./tools-BMbaq7_0.mjs";
2
+ export { DOCS_HOST, DocsFetchSchema, DocsListSchema, LLMS_TXT_URL, assertDocsHost, getBody, getDocsTools, getIndex, parseLlmsTxt, resetDocsCachesForTests, searchDocs };
@@ -0,0 +1,317 @@
1
+ import { LRUCache } from "lru-cache";
2
+ import { create, insertMultiple, search } from "@orama/orama";
3
+ import { ErrorCode, ToolError, createListResult, createToolResult, defineTool, groupBy, z } from "@transcend-io/mcp-server-base";
4
+ //#region src/docsSearch.ts
5
+ const SEARCH_CACHE_KEY = "search";
6
+ const SEARCH_INDEX_TTL_MS = 1440 * 60 * 1e3;
7
+ /**
8
+ * Results per search. On a labeled benchmark the targets that missed the top
9
+ * ten sat at a median rank of 16 and 19, so one page deeper takes hit@k from
10
+ * 87% to 95% on title terms and 88% to 95% on natural questions for about 434
11
+ * extra tokens. Almost nothing recoverable lives past rank 30; those queries
12
+ * need different terms, not more pages, which is why there is no offset.
13
+ */
14
+ const SEARCH_LIMIT = 20;
15
+ const HYDRATE_CONCURRENCY = 15;
16
+ const SNIPPET_CHARS = 200;
17
+ const SNIPPET_PREFIX = 40;
18
+ const DOC_SCHEMA = {
19
+ title: "string",
20
+ urlPath: "string",
21
+ section: "string",
22
+ body: "string",
23
+ url: "string"
24
+ };
25
+ function createDocsDb() {
26
+ return create({ schema: DOC_SCHEMA });
27
+ }
28
+ function urlToSearchText(url) {
29
+ try {
30
+ return new URL(url).pathname.replace(/\.md$/i, "").split(/[-_/]+/).filter(Boolean).join(" ");
31
+ } catch {
32
+ return "";
33
+ }
34
+ }
35
+ function extractSnippet(text, query) {
36
+ const collapsed = text.replace(/\s+/g, " ").trim();
37
+ if (!collapsed) return "";
38
+ const terms = query.toLowerCase().split(/\s+/).filter((term) => term.length > 1);
39
+ const lower = collapsed.toLowerCase();
40
+ let matchIndex = -1;
41
+ for (const term of terms) {
42
+ matchIndex = lower.indexOf(term);
43
+ if (matchIndex !== -1) break;
44
+ }
45
+ if (matchIndex === -1) return collapsed.length > SNIPPET_CHARS ? `${collapsed.slice(0, SNIPPET_CHARS).trim()}…` : collapsed;
46
+ const start = Math.max(0, matchIndex - SNIPPET_PREFIX);
47
+ const end = Math.min(collapsed.length, start + SNIPPET_CHARS);
48
+ const prefix = start > 0 ? "…" : "";
49
+ const suffix = end < collapsed.length ? "…" : "";
50
+ return `${prefix}${collapsed.slice(start, end).trim()}${suffix}`;
51
+ }
52
+ async function mapWithConcurrency(items, concurrency, mapper) {
53
+ if (items.length === 0) return [];
54
+ const results = new Array(items.length);
55
+ let next = 0;
56
+ async function worker() {
57
+ while (next < items.length) {
58
+ const index = next;
59
+ next += 1;
60
+ results[index] = await mapper(items[index]);
61
+ }
62
+ }
63
+ await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
64
+ return results;
65
+ }
66
+ async function hydrateEntry(entry) {
67
+ let body = "";
68
+ try {
69
+ body = await getBody(entry.url);
70
+ } catch {}
71
+ return {
72
+ id: entry.url,
73
+ title: entry.title,
74
+ urlPath: urlToSearchText(entry.url),
75
+ section: entry.section,
76
+ body,
77
+ url: entry.url
78
+ };
79
+ }
80
+ async function buildSearchIndex() {
81
+ const documents = await mapWithConcurrency(await getIndex(), HYDRATE_CONCURRENCY, hydrateEntry);
82
+ const db = createDocsDb();
83
+ if (documents.length > 0) await Promise.resolve(insertMultiple(db, documents));
84
+ return db;
85
+ }
86
+ const searchIndexCache = new LRUCache({
87
+ max: 1,
88
+ ttl: SEARCH_INDEX_TTL_MS,
89
+ allowStale: true,
90
+ allowStaleOnFetchRejection: true,
91
+ fetchMethod: async () => buildSearchIndex()
92
+ });
93
+ /** Clears the Orama index (called from {@link resetDocsCachesForTests}). */
94
+ function clearSearchIndexCache() {
95
+ searchIndexCache.clear();
96
+ }
97
+ /** Full-text search over hydrated docs (title, URL path, section, body). */
98
+ async function searchDocs(query, options = {}) {
99
+ const term = query.trim();
100
+ if (!term) return {
101
+ hits: [],
102
+ totalCount: 0
103
+ };
104
+ const db = await searchIndexCache.fetch(SEARCH_CACHE_KEY);
105
+ if (!db) throw new Error("Failed to build documentation search index");
106
+ const results = await Promise.resolve(search(db, {
107
+ term,
108
+ limit: SEARCH_LIMIT,
109
+ tolerance: 0,
110
+ threshold: .3,
111
+ boost: {
112
+ title: 4,
113
+ urlPath: 2,
114
+ section: 1.5,
115
+ body: 1
116
+ },
117
+ ...options.section ? { where: { section: options.section } } : {}
118
+ }));
119
+ return {
120
+ hits: results.hits.map((hit) => {
121
+ const document = hit.document;
122
+ return {
123
+ title: document.title,
124
+ section: document.section,
125
+ url: document.url,
126
+ score: hit.score,
127
+ snippet: extractSnippet(`${document.title} ${document.body}`, term)
128
+ };
129
+ }),
130
+ totalCount: results.count
131
+ };
132
+ }
133
+ //#endregion
134
+ //#region src/docsIndex.ts
135
+ /** Public docs index URL (llms.txt). */
136
+ const LLMS_TXT_URL = "https://docs.transcend.io/llms.txt";
137
+ /** Allowed hostname for fetched documentation articles. */
138
+ const DOCS_HOST = "docs.transcend.io";
139
+ const INDEX_CACHE_KEY = "index";
140
+ const DEFAULT_INDEX_TTL_MS = 1440 * 60 * 1e3;
141
+ const DEFAULT_BODY_TTL_MS = 360 * 60 * 1e3;
142
+ const FETCH_TIMEOUT_MS = 8e3;
143
+ const MAX_BODY_CACHE_ENTRIES = 600;
144
+ /**
145
+ * Parses llms.txt markdown into structured entries.
146
+ * Expects `## Section` headers and `- [Title](url)` list items.
147
+ */
148
+ function parseLlmsTxt(raw) {
149
+ const entries = [];
150
+ let currentSection = "";
151
+ for (const line of raw.split("\n")) {
152
+ const sectionMatch = /^## (.+)$/.exec(line);
153
+ if (sectionMatch?.[1]) {
154
+ currentSection = sectionMatch[1].trim();
155
+ continue;
156
+ }
157
+ const linkMatch = /^- \[(.+)\]\((.+)\)\s*$/.exec(line);
158
+ if (linkMatch?.[1] && linkMatch[2] && currentSection) {
159
+ const title = linkMatch[1].trim();
160
+ if (!title) continue;
161
+ entries.push({
162
+ section: currentSection,
163
+ title,
164
+ url: linkMatch[2].trim()
165
+ });
166
+ }
167
+ }
168
+ return entries;
169
+ }
170
+ async function fetchText(url, signal) {
171
+ const response = await fetch(url, {
172
+ headers: { Accept: "text/markdown, text/plain" },
173
+ signal: signal ?? AbortSignal.timeout(FETCH_TIMEOUT_MS)
174
+ });
175
+ if (!response.ok) throw new Error(`Failed to fetch ${url}: HTTP ${response.status}`);
176
+ return response.text();
177
+ }
178
+ const indexCache = new LRUCache({
179
+ max: 1,
180
+ ttl: DEFAULT_INDEX_TTL_MS,
181
+ allowStale: true,
182
+ allowStaleOnFetchRejection: true,
183
+ fetchMethod: async (_key, _stale, { signal }) => {
184
+ return parseLlmsTxt(await fetchText(LLMS_TXT_URL, signal));
185
+ }
186
+ });
187
+ const bodyCache = new LRUCache({
188
+ max: MAX_BODY_CACHE_ENTRIES,
189
+ ttl: DEFAULT_BODY_TTL_MS,
190
+ allowStale: true,
191
+ allowStaleOnFetchRejection: true,
192
+ fetchMethod: async (url, _stale, { signal }) => fetchText(url, signal)
193
+ });
194
+ /** Returns the parsed llms.txt index (cached with TTL). */
195
+ async function getIndex() {
196
+ const result = await indexCache.fetch(INDEX_CACHE_KEY);
197
+ if (!result) throw new Error("Failed to load documentation index");
198
+ return result;
199
+ }
200
+ /** Fetches and caches markdown body for a docs.transcend.io article URL. */
201
+ async function getBody(url) {
202
+ assertDocsHost(url);
203
+ const result = await bodyCache.fetch(url);
204
+ if (result === void 0) throw new Error(`Failed to fetch documentation: ${url}`);
205
+ return result;
206
+ }
207
+ /** Validates that a URL targets docs.transcend.io (SSRF guard for fetch). */
208
+ function assertDocsHost(url) {
209
+ let parsed;
210
+ try {
211
+ parsed = new URL(url);
212
+ } catch {
213
+ throw new Error(`Invalid documentation URL: ${url}`);
214
+ }
215
+ if (parsed.hostname !== "docs.transcend.io") throw new Error(`URL host must be ${DOCS_HOST}, got ${parsed.hostname}`);
216
+ }
217
+ /** Clears in-memory caches (for tests). */
218
+ function resetDocsCachesForTests() {
219
+ indexCache.clear();
220
+ bodyCache.clear();
221
+ clearSearchIndexCache();
222
+ }
223
+ //#endregion
224
+ //#region src/tools/docs_fetch.ts
225
+ /** Maximum markdown characters returned per article (long pages are truncated). */
226
+ const MAX_BODY_CHARS = 12e4;
227
+ const DocsFetchSchema = z.object({ url: z.string().url().describe("Absolute https URL of a docs.transcend.io article (.md) from docs_list.") });
228
+ function formatBody(body, url) {
229
+ return `${body.length > MAX_BODY_CHARS ? `${body.slice(0, MAX_BODY_CHARS)}\n\n...(truncated)` : body}\n\nSource: ${url}`;
230
+ }
231
+ function createDocsFetchTool(_clients) {
232
+ return defineTool({
233
+ name: "docs_fetch",
234
+ description: "Fetch full markdown content for a Transcend documentation article by URL. Use urls returned by docs_list.",
235
+ category: "Documentation",
236
+ readOnly: true,
237
+ requireAuth: false,
238
+ annotations: {
239
+ readOnlyHint: true,
240
+ destructiveHint: false,
241
+ idempotentHint: true
242
+ },
243
+ zodSchema: DocsFetchSchema,
244
+ handler: async ({ url }) => {
245
+ assertDocsHost(url);
246
+ return createToolResult(true, {
247
+ url,
248
+ markdown: formatBody(await getBody(url), url)
249
+ });
250
+ }
251
+ });
252
+ }
253
+ //#endregion
254
+ //#region src/tools/docs_list.ts
255
+ /**
256
+ * Cap on articles returned when listing a section. A listing entry is a title
257
+ * and a URL, roughly a fifth the size of a ranked hit with its snippet, so this
258
+ * sits above the search limit; the largest section would otherwise return 125
259
+ * articles and ~22KB.
260
+ */
261
+ const SECTION_LISTING_LIMIT = 50;
262
+ const DocsListSchema = z.object({
263
+ section: z.string().optional().describe("List the articles in this llms.txt section (exact match). Omit both arguments to see the available section names."),
264
+ query: z.string().optional().describe("Full-text search terms. Prefer the two or three most distinctive words for the topic over a whole sentence, since common words match most articles and blur the ranking. At most 20 results come back and there is no paging, so narrow the terms rather than expecting to page.")
265
+ });
266
+ function createDocsListTool(_clients) {
267
+ return defineTool({
268
+ name: "docs_list",
269
+ description: "List or full-text search Transcend documentation. With query, ranks articles by title and body and returns snippets; with section alone, lists that section; with neither, returns the section names. Pick the best matching url, then call docs_fetch.",
270
+ category: "Documentation",
271
+ readOnly: true,
272
+ requireAuth: false,
273
+ annotations: {
274
+ readOnlyHint: true,
275
+ destructiveHint: false,
276
+ idempotentHint: true
277
+ },
278
+ zodSchema: DocsListSchema,
279
+ handler: async ({ section, query }) => {
280
+ if (query !== void 0 && query.trim() === "") throw new ToolError(ErrorCode.VALIDATION_ERROR, "query was empty. Pass the most distinctive terms for the topic, or omit query entirely to browse the sections.");
281
+ const entries = await getIndex();
282
+ const sectionCounts = groupBy(entries, "section");
283
+ if (section !== void 0 && !Object.hasOwn(sectionCounts, section)) throw new ToolError(ErrorCode.VALIDATION_ERROR, `Unknown section '${section}'. Valid sections: ${Object.keys(sectionCounts).join(", ")}.`);
284
+ const terms = query?.trim();
285
+ if (terms) {
286
+ const { hits, totalCount } = await searchDocs(terms, { section });
287
+ return createListResult(hits, {
288
+ totalCount,
289
+ ...totalCount > hits.length && { paginationNote: `Showing the ${hits.length} highest-ranked of ${totalCount} matches. There is no paging: use more distinctive terms to narrow the search.` }
290
+ });
291
+ }
292
+ if (section === void 0) return createListResult(Object.entries(sectionCounts).map(([name, articleCount]) => ({
293
+ section: name,
294
+ articleCount
295
+ })), { paginationNote: `Sections only. Call docs_list again with section to list its articles, or with query to search all ${entries.length} articles.` });
296
+ const filtered = entries.filter((entry) => entry.section === section);
297
+ const page = filtered.slice(0, SECTION_LISTING_LIMIT);
298
+ return createListResult(page.map(({ title, section: entrySection, url }) => ({
299
+ title,
300
+ section: entrySection,
301
+ url
302
+ })), {
303
+ totalCount: filtered.length,
304
+ ...filtered.length > page.length && { paginationNote: `Showing ${page.length} of ${filtered.length} articles in '${section}'. There is no paging: add query to search within this section.` }
305
+ });
306
+ }
307
+ });
308
+ }
309
+ //#endregion
310
+ //#region src/tools/index.ts
311
+ function getDocsTools(_clients) {
312
+ return [createDocsListTool(), createDocsFetchTool()];
313
+ }
314
+ //#endregion
315
+ export { LLMS_TXT_URL as a, getIndex as c, searchDocs as d, DOCS_HOST as i, parseLlmsTxt as l, DocsListSchema as n, assertDocsHost as o, DocsFetchSchema as r, getBody as s, getDocsTools as t, resetDocsCachesForTests as u };
316
+
317
+ //# sourceMappingURL=tools-BMbaq7_0.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools-BMbaq7_0.mjs","names":[],"sources":["../src/docsSearch.ts","../src/docsIndex.ts","../src/tools/docs_fetch.ts","../src/tools/docs_list.ts","../src/tools/index.ts"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@transcend-io/mcp-server-docs",
3
- "version": "0.3.25",
3
+ "version": "0.4.0",
4
4
  "description": "Transcend MCP Server — Documentation lookup tools.",
5
5
  "homepage": "https://github.com/transcend-io/tools/tree/main/packages/mcp/mcp-server-docs",
6
6
  "license": "Apache-2.0",
@@ -31,9 +31,10 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@modelcontextprotocol/sdk": "^1.29.0",
34
+ "@orama/orama": "^3.1.18",
34
35
  "lru-cache": "^11.5.1",
35
36
  "zod": "^4.3.6",
36
- "@transcend-io/mcp-server-base": "1.7.4"
37
+ "@transcend-io/mcp-server-base": "1.8.0"
37
38
  },
38
39
  "devDependencies": {
39
40
  "@arethetypeswrong/cli": "^0.18.2",
@@ -1,163 +0,0 @@
1
- import { LRUCache } from "lru-cache";
2
- import { createListResult, createToolResult, defineTool, z } from "@transcend-io/mcp-server-base";
3
- //#region src/docsIndex.ts
4
- /** Public docs index URL (llms.txt). */
5
- const LLMS_TXT_URL = "https://docs.transcend.io/llms.txt";
6
- /** Allowed hostname for fetched documentation articles. */
7
- const DOCS_HOST = "docs.transcend.io";
8
- const INDEX_CACHE_KEY = "index";
9
- const DEFAULT_INDEX_TTL_MS = 1440 * 60 * 1e3;
10
- const DEFAULT_BODY_TTL_MS = 360 * 60 * 1e3;
11
- const FETCH_TIMEOUT_MS = 8e3;
12
- const MAX_BODY_CACHE_ENTRIES = 400;
13
- /**
14
- * Parses llms.txt markdown into structured entries.
15
- * Expects `## Section` headers and `- [Title](url)` list items.
16
- */
17
- function parseLlmsTxt(raw) {
18
- const entries = [];
19
- let currentSection = "";
20
- for (const line of raw.split("\n")) {
21
- const sectionMatch = /^## (.+)$/.exec(line);
22
- if (sectionMatch?.[1]) {
23
- currentSection = sectionMatch[1].trim();
24
- continue;
25
- }
26
- const linkMatch = /^- \[(.+)\]\((.+)\)\s*$/.exec(line);
27
- if (linkMatch?.[1] && linkMatch[2] && currentSection) entries.push({
28
- section: currentSection,
29
- title: linkMatch[1].trim(),
30
- url: linkMatch[2].trim()
31
- });
32
- }
33
- return entries;
34
- }
35
- async function fetchText(url, signal) {
36
- const response = await fetch(url, {
37
- headers: { Accept: "text/markdown, text/plain" },
38
- signal: signal ?? AbortSignal.timeout(FETCH_TIMEOUT_MS)
39
- });
40
- if (!response.ok) throw new Error(`Failed to fetch ${url}: HTTP ${response.status}`);
41
- return response.text();
42
- }
43
- const indexCache = new LRUCache({
44
- max: 1,
45
- ttl: DEFAULT_INDEX_TTL_MS,
46
- allowStale: true,
47
- allowStaleOnFetchRejection: true,
48
- fetchMethod: async (_key, _stale, { signal }) => {
49
- return parseLlmsTxt(await fetchText(LLMS_TXT_URL, signal));
50
- }
51
- });
52
- const bodyCache = new LRUCache({
53
- max: MAX_BODY_CACHE_ENTRIES,
54
- ttl: DEFAULT_BODY_TTL_MS,
55
- allowStale: true,
56
- allowStaleOnFetchRejection: true,
57
- fetchMethod: async (url, _stale, { signal }) => fetchText(url, signal)
58
- });
59
- /** Returns the parsed llms.txt index (cached with TTL). */
60
- async function getIndex() {
61
- const result = await indexCache.fetch(INDEX_CACHE_KEY);
62
- if (!result) throw new Error("Failed to load documentation index");
63
- return result;
64
- }
65
- /** Fetches and caches markdown body for a docs.transcend.io article URL. */
66
- async function getBody(url) {
67
- assertDocsHost(url);
68
- const result = await bodyCache.fetch(url);
69
- if (result === void 0) throw new Error(`Failed to fetch documentation: ${url}`);
70
- return result;
71
- }
72
- /** Validates that a URL targets docs.transcend.io (SSRF guard for fetch). */
73
- function assertDocsHost(url) {
74
- let parsed;
75
- try {
76
- parsed = new URL(url);
77
- } catch {
78
- throw new Error(`Invalid documentation URL: ${url}`);
79
- }
80
- if (parsed.hostname !== "docs.transcend.io") throw new Error(`URL host must be ${DOCS_HOST}, got ${parsed.hostname}`);
81
- }
82
- /** Clears in-memory caches (for tests). */
83
- function resetDocsCachesForTests() {
84
- indexCache.clear();
85
- bodyCache.clear();
86
- }
87
- //#endregion
88
- //#region src/tools/docs_fetch.ts
89
- /** Maximum markdown characters returned per article (long pages are truncated). */
90
- const MAX_BODY_CHARS = 12e4;
91
- const DocsFetchSchema = z.object({ url: z.string().url().describe("Absolute https URL of a docs.transcend.io article (.md) from docs_list.") });
92
- function formatBody(body, url) {
93
- return `${body.length > MAX_BODY_CHARS ? `${body.slice(0, MAX_BODY_CHARS)}\n\n...(truncated)` : body}\n\nSource: ${url}`;
94
- }
95
- function createDocsFetchTool(_clients) {
96
- return defineTool({
97
- name: "docs_fetch",
98
- description: "Fetch full markdown content for a Transcend documentation article by URL. Use urls returned by docs_list.",
99
- category: "Documentation",
100
- readOnly: true,
101
- requireAuth: false,
102
- annotations: {
103
- readOnlyHint: true,
104
- destructiveHint: false,
105
- idempotentHint: true
106
- },
107
- zodSchema: DocsFetchSchema,
108
- handler: async ({ url }) => {
109
- assertDocsHost(url);
110
- return createToolResult(true, {
111
- url,
112
- markdown: formatBody(await getBody(url), url)
113
- });
114
- }
115
- });
116
- }
117
- //#endregion
118
- //#region src/tools/docs_list.ts
119
- const DocsListSchema = z.object({
120
- section: z.string().optional().describe("Filter articles to this llms.txt section name (exact match)."),
121
- keyword: z.string().optional().describe("Case-insensitive substring filter applied to article titles.")
122
- });
123
- function filterEntries(entries, section, keyword) {
124
- let result = entries;
125
- if (section) result = result.filter((entry) => entry.section === section);
126
- if (keyword) {
127
- const lower = keyword.toLowerCase();
128
- result = result.filter((entry) => entry.title.toLowerCase().includes(lower));
129
- }
130
- return result.map(({ title, section: entrySection, url }) => ({
131
- title,
132
- section: entrySection,
133
- url
134
- }));
135
- }
136
- function createDocsListTool(_clients) {
137
- return defineTool({
138
- name: "docs_list",
139
- description: "List Transcend documentation articles from the public docs index. Pick the best matching url, then call docs_fetch for full markdown content.",
140
- category: "Documentation",
141
- readOnly: true,
142
- requireAuth: false,
143
- annotations: {
144
- readOnlyHint: true,
145
- destructiveHint: false,
146
- idempotentHint: true
147
- },
148
- zodSchema: DocsListSchema,
149
- handler: async ({ section, keyword }) => {
150
- const filtered = filterEntries(await getIndex(), section, keyword);
151
- return createListResult(filtered, { totalCount: filtered.length });
152
- }
153
- });
154
- }
155
- //#endregion
156
- //#region src/tools/index.ts
157
- function getDocsTools(_clients) {
158
- return [createDocsListTool(), createDocsFetchTool()];
159
- }
160
- //#endregion
161
- export { LLMS_TXT_URL as a, getIndex as c, DOCS_HOST as i, parseLlmsTxt as l, DocsListSchema as n, assertDocsHost as o, DocsFetchSchema as r, getBody as s, getDocsTools as t, resetDocsCachesForTests as u };
162
-
163
- //# sourceMappingURL=tools-Cc8ZQVE0.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tools-Cc8ZQVE0.mjs","names":[],"sources":["../src/docsIndex.ts","../src/tools/docs_fetch.ts","../src/tools/docs_list.ts","../src/tools/index.ts"],"mappings":"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"}