@assistant-ui/mcp-docs-server 0.1.35 → 0.1.38
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/.docs/organized/code-examples/waterfall.md +1 -1
- package/.docs/organized/code-examples/with-a2a.md +2 -2
- package/.docs/organized/code-examples/with-ag-ui.md +2 -2
- package/.docs/organized/code-examples/with-ai-sdk-v6.md +4 -4
- package/.docs/organized/code-examples/with-artifacts.md +4 -4
- package/.docs/organized/code-examples/with-assistant-transport.md +70 -54
- package/.docs/organized/code-examples/with-browser-extension.md +4 -4
- package/.docs/organized/code-examples/with-chain-of-thought.md +40 -89
- package/.docs/organized/code-examples/with-cloud-standalone.md +4 -4
- package/.docs/organized/code-examples/with-cloud.md +4 -4
- package/.docs/organized/code-examples/with-custom-thread-list.md +4 -4
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +7 -7
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +7 -7
- package/.docs/organized/code-examples/with-eve.md +343 -0
- package/.docs/organized/code-examples/with-expo.md +910 -933
- package/.docs/organized/code-examples/with-external-store.md +2 -2
- package/.docs/organized/code-examples/with-ffmpeg.md +5 -8
- package/.docs/organized/code-examples/with-generative-ui.md +6 -6
- package/.docs/organized/code-examples/with-google-adk.md +3 -3
- package/.docs/organized/code-examples/with-heat-graph.md +1 -1
- package/.docs/organized/code-examples/with-image-generation.md +4 -4
- package/.docs/organized/code-examples/with-interactables.md +166 -338
- package/.docs/organized/code-examples/with-langchain.md +4 -4
- package/.docs/organized/code-examples/with-langgraph.md +20 -157
- package/.docs/organized/code-examples/with-livekit.md +8 -8
- package/.docs/organized/code-examples/with-mcp.md +19 -10
- package/.docs/organized/code-examples/with-opencode.md +3 -3
- package/.docs/organized/code-examples/with-pi.md +9 -7
- package/.docs/organized/code-examples/with-react-hook-form.md +13 -6
- package/.docs/organized/code-examples/with-react-ink-web.md +5 -5
- package/.docs/organized/code-examples/with-react-ink.md +1 -1
- package/.docs/organized/code-examples/with-react-router.md +11 -11
- package/.docs/organized/code-examples/with-resumable-stream.md +5 -5
- package/.docs/organized/code-examples/with-store.md +1 -1
- package/.docs/organized/code-examples/with-tanstack.md +7 -7
- package/.docs/organized/code-examples/with-tap-runtime.md +2 -2
- package/.docs/organized/code-examples/with-virtualized-thread.md +44 -24
- package/.docs/raw/docs/(docs)/cli.mdx +5 -3
- package/.docs/raw/docs/(docs)/devtools.mdx +25 -2
- package/.docs/raw/docs/(docs)/index.mdx +5 -2
- package/.docs/raw/docs/(docs)/installation.mdx +5 -2
- package/.docs/raw/docs/(reference)/api-reference/external-store/message-conversion.mdx +16 -16
- package/.docs/raw/docs/(reference)/api-reference/hooks/composer-triggers.mdx +73 -42
- package/.docs/raw/docs/(reference)/api-reference/hooks/model-context.mdx +3 -20
- package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +234 -166
- package/.docs/raw/docs/(reference)/api-reference/integrations/eve.mdx +51 -0
- package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +4 -1
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +70 -38
- package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar.mdx +30 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/attachment.mdx +7 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/branch-picker.mdx +26 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/chain-of-thought.mdx +15 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +103 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +16 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +56 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/queue-item.mdx +12 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/selection-toolbar.mdx +12 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/suggestion.mdx +14 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +57 -1
- package/.docs/raw/docs/(reference)/api-reference/tools/index.mdx +6 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/interactables-legacy.mdx +55 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/interactables.mdx +151 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/rendering.mdx +15 -15
- package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +20 -20
- package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +25 -9
- package/.docs/raw/docs/cloud/langgraph.mdx +1 -1
- package/.docs/raw/docs/guides/headless-composer-input.mdx +113 -0
- package/.docs/raw/docs/guides/latex.mdx +28 -22
- package/.docs/raw/docs/guides/mentions.mdx +32 -7
- package/.docs/raw/docs/guides/slash-commands.mdx +3 -6
- package/.docs/raw/docs/guides/speech.mdx +5 -7
- package/.docs/raw/docs/guides/virtualization.mdx +77 -7
- package/.docs/raw/docs/guides/voice.mdx +3 -2
- package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +4 -12
- package/.docs/raw/docs/integrations/auth/better-auth.mdx +7 -10
- package/.docs/raw/docs/integrations/auth/clerk.mdx +3 -9
- package/.docs/raw/docs/integrations/auth/next-auth.mdx +3 -8
- package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +11 -8
- package/.docs/raw/docs/integrations/index.mdx +6 -13
- package/.docs/raw/docs/integrations/observability/helicone.mdx +3 -4
- package/.docs/raw/docs/integrations/observability/langfuse.mdx +3 -5
- package/.docs/raw/docs/integrations/observability/langsmith.mdx +5 -4
- package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +4 -3
- package/.docs/raw/docs/migrations/react-langgraph-v0-7.mdx +2 -2
- package/.docs/raw/docs/primitives/composer.mdx +8 -0
- package/.docs/raw/docs/primitives/thread-list.mdx +30 -6
- package/.docs/raw/docs/primitives/thread.mdx +24 -0
- package/.docs/raw/docs/runtimes/a2a/quickstart.mdx +1 -1
- package/.docs/raw/docs/runtimes/ag-ui/quickstart.mdx +1 -1
- package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +24 -1
- package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +1 -1
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +54 -0
- package/.docs/raw/docs/runtimes/eve/overview.mdx +100 -0
- package/.docs/raw/docs/runtimes/eve/quickstart.mdx +143 -0
- package/.docs/raw/docs/runtimes/google-adk/quickstart.mdx +1 -1
- package/.docs/raw/docs/runtimes/langchain.mdx +388 -20
- package/.docs/raw/docs/runtimes/langgraph/quickstart.mdx +1 -1
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +1 -1
- package/.docs/raw/docs/runtimes/opencode/hooks.mdx +1 -1
- package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +6 -0
- package/.docs/raw/docs/tools/defining-tools.mdx +9 -0
- package/.docs/raw/docs/tools/generative-ui.mdx +37 -0
- package/.docs/raw/docs/tools/interactables-legacy.mdx +410 -0
- package/.docs/raw/docs/tools/interactables.mdx +892 -223
- package/.docs/raw/docs/tools/mcp-apps.mdx +12 -3
- package/.docs/raw/docs/tools/mcp.mdx +9 -4
- package/.docs/raw/docs/tools/multi-agent.mdx +5 -0
- package/.docs/raw/docs/tools/user-managed-mcp.mdx +17 -3
- package/.docs/raw/docs/ui/composer-trigger-popover.mdx +2 -0
- package/.docs/raw/docs/ui/file.mdx +1 -1
- package/.docs/raw/docs/ui/follow-up-suggestions.mdx +80 -0
- package/.docs/raw/docs/ui/model-selector.mdx +38 -5
- package/.docs/raw/docs/ui/part-grouping.mdx +38 -0
- package/.docs/raw/docs/utilities/react-o11y.mdx +92 -6
- package/README.md +1 -1
- package/dist/constants.d.ts +2 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -2
- package/dist/index.js.map +1 -1
- package/dist/prepare-docs/copy-raw.d.ts.map +1 -1
- package/dist/prepare-docs/copy-raw.js +0 -4
- package/dist/prepare-docs/copy-raw.js.map +1 -1
- package/dist/tools/docs.d.ts.map +1 -1
- package/dist/tools/docs.js +18 -6
- package/dist/tools/docs.js.map +1 -1
- package/dist/tools/examples.d.ts +3 -1
- package/dist/tools/examples.d.ts.map +1 -1
- package/dist/tools/examples.js +1 -1
- package/dist/tools/examples.js.map +1 -1
- package/dist/tools/resources.d.ts +7 -0
- package/dist/tools/resources.d.ts.map +1 -0
- package/dist/tools/resources.js +74 -0
- package/dist/tools/resources.js.map +1 -0
- package/dist/tools/search.d.ts +33 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +39 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/tests/test-setup.d.ts.map +1 -1
- package/dist/tools/tests/test-setup.js +3 -1
- package/dist/tools/tests/test-setup.js.map +1 -1
- package/dist/utils/mdx.d.ts +2 -1
- package/dist/utils/mdx.d.ts.map +1 -1
- package/dist/utils/mdx.js +19 -2
- package/dist/utils/mdx.js.map +1 -1
- package/dist/utils/paths.d.ts +2 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +18 -1
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/search.d.ts +10 -0
- package/dist/utils/search.d.ts.map +1 -0
- package/dist/utils/search.js +97 -0
- package/dist/utils/search.js.map +1 -0
- package/package.json +4 -4
- package/src/constants.ts +2 -0
- package/src/index.ts +28 -6
- package/src/prepare-docs/copy-raw.ts +0 -5
- package/src/tools/docs.ts +30 -4
- package/src/tools/examples.ts +4 -2
- package/src/tools/resources.ts +114 -0
- package/src/tools/search.ts +46 -0
- package/src/tools/tests/completions.test.ts +46 -0
- package/src/tools/tests/directory-size-cap.test.ts +50 -0
- package/src/tools/tests/mcp-protocol.test.ts +21 -1
- package/src/tools/tests/resources.test.ts +102 -0
- package/src/tools/tests/search.test.ts +37 -0
- package/src/tools/tests/test-setup.ts +2 -0
- package/src/utils/mdx.ts +21 -1
- package/src/utils/paths.ts +22 -0
- package/src/utils/search.ts +131 -0
- package/.docs/raw/blog/2024-07-29-hello/index.mdx +0 -64
- package/.docs/raw/blog/2024-09-11/index.mdx +0 -10
- package/.docs/raw/blog/2024-12-15/index.mdx +0 -10
- package/.docs/raw/blog/2025-01-31-changelog/index.mdx +0 -127
- package/.docs/raw/blog/2026-03-launch-week/index.mdx +0 -258
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { DOCS_PATH, MDX_EXTENSION } from "../constants.js";
|
|
2
|
+
import { getAvailablePaths, pathExists } from "./paths.js";
|
|
3
|
+
import { readMDXFile } from "./mdx.js";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
//#region src/utils/search.ts
|
|
6
|
+
const SCORE_WEIGHTS = {
|
|
7
|
+
FULL_QUERY: {
|
|
8
|
+
title: 10,
|
|
9
|
+
path: 8,
|
|
10
|
+
description: 5
|
|
11
|
+
},
|
|
12
|
+
TOKEN: {
|
|
13
|
+
title: 3,
|
|
14
|
+
path: 2,
|
|
15
|
+
description: 1,
|
|
16
|
+
content: .5
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
let indexPromise = null;
|
|
20
|
+
function deriveTitle(path, frontmatter) {
|
|
21
|
+
const fmTitle = frontmatter["title"];
|
|
22
|
+
if (typeof fmTitle === "string" && fmTitle.trim()) return fmTitle;
|
|
23
|
+
return path.split("/").pop() ?? path;
|
|
24
|
+
}
|
|
25
|
+
async function buildIndex() {
|
|
26
|
+
const paths = await getAvailablePaths();
|
|
27
|
+
return (await Promise.all(paths.map(async (path) => {
|
|
28
|
+
const filePath = join(DOCS_PATH, `${path}${MDX_EXTENSION}`);
|
|
29
|
+
if (!await pathExists(filePath)) return null;
|
|
30
|
+
const mdx = await readMDXFile(filePath);
|
|
31
|
+
if (!mdx) return null;
|
|
32
|
+
const description = mdx.frontmatter["description"];
|
|
33
|
+
return {
|
|
34
|
+
path,
|
|
35
|
+
title: deriveTitle(path, mdx.frontmatter),
|
|
36
|
+
description: typeof description === "string" ? description : "",
|
|
37
|
+
content: mdx.content,
|
|
38
|
+
excerpt: mdx.excerpt ?? ""
|
|
39
|
+
};
|
|
40
|
+
}))).filter((entry) => entry !== null);
|
|
41
|
+
}
|
|
42
|
+
function getIndex() {
|
|
43
|
+
if (!indexPromise) indexPromise = buildIndex().catch((error) => {
|
|
44
|
+
indexPromise = null;
|
|
45
|
+
throw error;
|
|
46
|
+
});
|
|
47
|
+
return indexPromise;
|
|
48
|
+
}
|
|
49
|
+
function makeSnippet(content, tokens, excerpt) {
|
|
50
|
+
const lower = content.toLowerCase();
|
|
51
|
+
for (const token of tokens) {
|
|
52
|
+
const idx = lower.indexOf(token);
|
|
53
|
+
if (idx >= 0) {
|
|
54
|
+
const start = Math.max(0, idx - 60);
|
|
55
|
+
const end = Math.min(content.length, idx + token.length + 100);
|
|
56
|
+
const slice = content.slice(start, end).replace(/\s+/g, " ").trim();
|
|
57
|
+
return `${start > 0 ? "…" : ""}${slice}${end < content.length ? "…" : ""}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (excerpt) return excerpt.slice(0, 160);
|
|
61
|
+
const firstLine = content.split("\n").find((line) => line.trim());
|
|
62
|
+
return firstLine ? firstLine.replace(/^#+\s*/, "").slice(0, 160) : "";
|
|
63
|
+
}
|
|
64
|
+
function scoreEntry(entry, query, tokens) {
|
|
65
|
+
const title = entry.title.toLowerCase();
|
|
66
|
+
const path = entry.path.toLowerCase();
|
|
67
|
+
const description = entry.description.toLowerCase();
|
|
68
|
+
const content = entry.content.toLowerCase();
|
|
69
|
+
let score = 0;
|
|
70
|
+
if (title.includes(query)) score += SCORE_WEIGHTS.FULL_QUERY.title;
|
|
71
|
+
if (path.includes(query)) score += SCORE_WEIGHTS.FULL_QUERY.path;
|
|
72
|
+
if (description.includes(query)) score += SCORE_WEIGHTS.FULL_QUERY.description;
|
|
73
|
+
for (const token of tokens) {
|
|
74
|
+
if (title.includes(token)) score += SCORE_WEIGHTS.TOKEN.title;
|
|
75
|
+
if (path.includes(token)) score += SCORE_WEIGHTS.TOKEN.path;
|
|
76
|
+
if (description.includes(token)) score += SCORE_WEIGHTS.TOKEN.description;
|
|
77
|
+
if (content.includes(token)) score += SCORE_WEIGHTS.TOKEN.content;
|
|
78
|
+
}
|
|
79
|
+
return score;
|
|
80
|
+
}
|
|
81
|
+
async function searchDocs(query, limit = 10) {
|
|
82
|
+
const normalized = query.trim().toLowerCase();
|
|
83
|
+
if (!normalized) return [];
|
|
84
|
+
const tokens = normalized.split(/\s+/).filter(Boolean);
|
|
85
|
+
return (await getIndex()).map((entry) => ({
|
|
86
|
+
entry,
|
|
87
|
+
score: scoreEntry(entry, normalized, tokens)
|
|
88
|
+
})).filter((scored) => scored.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map(({ entry }) => ({
|
|
89
|
+
path: entry.path,
|
|
90
|
+
title: entry.title,
|
|
91
|
+
snippet: makeSnippet(entry.content, tokens, entry.excerpt)
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { searchDocs };
|
|
96
|
+
|
|
97
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","names":[],"sources":["../../src/utils/search.ts"],"sourcesContent":["import { join } from \"node:path\";\nimport { DOCS_PATH, MDX_EXTENSION } from \"../constants.js\";\nimport { getAvailablePaths, pathExists } from \"./paths.js\";\nimport { readMDXFile } from \"./mdx.js\";\n\nconst SCORE_WEIGHTS = {\n FULL_QUERY: { title: 10, path: 8, description: 5 },\n TOKEN: { title: 3, path: 2, description: 1, content: 0.5 },\n} as const;\n\ninterface IndexEntry {\n path: string;\n title: string;\n description: string;\n content: string;\n excerpt: string;\n}\n\nexport interface SearchResult {\n path: string;\n title: string;\n snippet: string;\n}\n\nlet indexPromise: Promise<IndexEntry[]> | null = null;\n\nfunction deriveTitle(\n path: string,\n frontmatter: Record<string, unknown>,\n): string {\n const fmTitle = frontmatter[\"title\"];\n if (typeof fmTitle === \"string\" && fmTitle.trim()) return fmTitle;\n return path.split(\"/\").pop() ?? path;\n}\n\nasync function buildIndex(): Promise<IndexEntry[]> {\n const paths = await getAvailablePaths();\n const entries = await Promise.all(\n paths.map(async (path): Promise<IndexEntry | null> => {\n const filePath = join(DOCS_PATH, `${path}${MDX_EXTENSION}`);\n if (!(await pathExists(filePath))) return null;\n const mdx = await readMDXFile(filePath);\n if (!mdx) return null;\n const description = mdx.frontmatter[\"description\"];\n return {\n path,\n title: deriveTitle(path, mdx.frontmatter),\n description: typeof description === \"string\" ? description : \"\",\n content: mdx.content,\n excerpt: mdx.excerpt ?? \"\",\n };\n }),\n );\n return entries.filter((entry): entry is IndexEntry => entry !== null);\n}\n\nfunction getIndex(): Promise<IndexEntry[]> {\n if (!indexPromise) {\n indexPromise = buildIndex().catch((error) => {\n indexPromise = null;\n throw error;\n });\n }\n return indexPromise;\n}\n\nfunction makeSnippet(\n content: string,\n tokens: string[],\n excerpt: string,\n): string {\n const lower = content.toLowerCase();\n for (const token of tokens) {\n const idx = lower.indexOf(token);\n if (idx >= 0) {\n const start = Math.max(0, idx - 60);\n const end = Math.min(content.length, idx + token.length + 100);\n const slice = content.slice(start, end).replace(/\\s+/g, \" \").trim();\n return `${start > 0 ? \"…\" : \"\"}${slice}${end < content.length ? \"…\" : \"\"}`;\n }\n }\n if (excerpt) return excerpt.slice(0, 160);\n const firstLine = content.split(\"\\n\").find((line) => line.trim());\n return firstLine ? firstLine.replace(/^#+\\s*/, \"\").slice(0, 160) : \"\";\n}\n\nfunction scoreEntry(\n entry: IndexEntry,\n query: string,\n tokens: string[],\n): number {\n const title = entry.title.toLowerCase();\n const path = entry.path.toLowerCase();\n const description = entry.description.toLowerCase();\n const content = entry.content.toLowerCase();\n\n let score = 0;\n if (title.includes(query)) score += SCORE_WEIGHTS.FULL_QUERY.title;\n if (path.includes(query)) score += SCORE_WEIGHTS.FULL_QUERY.path;\n if (description.includes(query))\n score += SCORE_WEIGHTS.FULL_QUERY.description;\n\n for (const token of tokens) {\n if (title.includes(token)) score += SCORE_WEIGHTS.TOKEN.title;\n if (path.includes(token)) score += SCORE_WEIGHTS.TOKEN.path;\n if (description.includes(token)) score += SCORE_WEIGHTS.TOKEN.description;\n if (content.includes(token)) score += SCORE_WEIGHTS.TOKEN.content;\n }\n return score;\n}\n\nexport async function searchDocs(\n query: string,\n limit = 10,\n): Promise<SearchResult[]> {\n const normalized = query.trim().toLowerCase();\n if (!normalized) return [];\n const tokens = normalized.split(/\\s+/).filter(Boolean);\n\n const index = await getIndex();\n return index\n .map((entry) => ({ entry, score: scoreEntry(entry, normalized, tokens) }))\n .filter((scored) => scored.score > 0)\n .sort((a, b) => b.score - a.score)\n .slice(0, limit)\n .map(({ entry }) => ({\n path: entry.path,\n title: entry.title,\n snippet: makeSnippet(entry.content, tokens, entry.excerpt),\n }));\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/mcp-docs-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.38",
|
|
4
4
|
"description": "MCP server for assistant-ui documentation and examples",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"zod": "^4.4.3"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@types/node": "^
|
|
40
|
+
"@types/node": "^26.0.0",
|
|
41
41
|
"tsx": "^4.22.4",
|
|
42
|
-
"vitest": "^4.1.
|
|
43
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
42
|
+
"vitest": "^4.1.9",
|
|
43
|
+
"@assistant-ui/x-buildutils": "0.0.17"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public",
|
package/src/constants.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { docsTools } from "./tools/docs.js";
|
|
4
4
|
import { examplesTools } from "./tools/examples.js";
|
|
5
|
+
import { searchTools } from "./tools/search.js";
|
|
6
|
+
import { registerResources } from "./tools/resources.js";
|
|
5
7
|
import { logger } from "./utils/logger.js";
|
|
6
8
|
import { PACKAGE_DIR } from "./constants.js";
|
|
7
9
|
|
|
@@ -17,18 +19,38 @@ export const server = new McpServer({
|
|
|
17
19
|
version: packageJson.version,
|
|
18
20
|
});
|
|
19
21
|
|
|
20
|
-
server.
|
|
22
|
+
server.registerTool(
|
|
21
23
|
docsTools.name,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
{
|
|
25
|
+
title: "assistant-ui Documentation",
|
|
26
|
+
description: docsTools.description,
|
|
27
|
+
inputSchema: docsTools.parameters,
|
|
28
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
29
|
+
},
|
|
24
30
|
docsTools.execute,
|
|
25
31
|
);
|
|
26
|
-
server.
|
|
32
|
+
server.registerTool(
|
|
27
33
|
examplesTools.name,
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
{
|
|
35
|
+
title: "assistant-ui Examples",
|
|
36
|
+
description: examplesTools.description,
|
|
37
|
+
inputSchema: examplesTools.parameters,
|
|
38
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
39
|
+
},
|
|
30
40
|
examplesTools.execute,
|
|
31
41
|
);
|
|
42
|
+
server.registerTool(
|
|
43
|
+
searchTools.name,
|
|
44
|
+
{
|
|
45
|
+
title: "Search assistant-ui Documentation",
|
|
46
|
+
description: searchTools.description,
|
|
47
|
+
inputSchema: searchTools.parameters,
|
|
48
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
49
|
+
},
|
|
50
|
+
searchTools.execute,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
registerResources(server);
|
|
32
54
|
|
|
33
55
|
export async function runServer() {
|
|
34
56
|
try {
|
|
@@ -4,7 +4,6 @@ import { logger } from "../utils/logger.js";
|
|
|
4
4
|
import { ROOT_DIR } from "../constants.js";
|
|
5
5
|
|
|
6
6
|
const DOCS_SOURCE = join(ROOT_DIR, "apps/docs/content/docs");
|
|
7
|
-
const BLOG_SOURCE = join(ROOT_DIR, "apps/docs/content/blog");
|
|
8
7
|
const DOCS_DEST = join(ROOT_DIR, "packages/mcp-docs-server/.docs/raw");
|
|
9
8
|
|
|
10
9
|
async function copyDir(src: string, dest: string): Promise<void> {
|
|
@@ -43,10 +42,6 @@ export async function copyRaw(): Promise<void> {
|
|
|
43
42
|
await copyDir(DOCS_SOURCE, docsPath);
|
|
44
43
|
logger.info(`Copied documentation to ${docsPath}`);
|
|
45
44
|
|
|
46
|
-
const blogPath = join(DOCS_DEST, "blog");
|
|
47
|
-
await copyDir(BLOG_SOURCE, blogPath);
|
|
48
|
-
logger.info(`Copied blog posts to ${blogPath}`);
|
|
49
|
-
|
|
50
45
|
logger.info("Raw documentation copy complete");
|
|
51
46
|
} catch (error) {
|
|
52
47
|
logger.error("Failed to copy raw documentation", error);
|
package/src/tools/docs.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { z } from "zod/v3";
|
|
2
2
|
import { stat, lstat } from "node:fs/promises";
|
|
3
3
|
import { join, extname } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
DOCS_PATH,
|
|
6
|
+
MDX_EXTENSION,
|
|
7
|
+
MAX_FILE_SIZE,
|
|
8
|
+
MAX_DIRECTORY_CONTENT_SIZE,
|
|
9
|
+
} from "../constants.js";
|
|
5
10
|
import { logger } from "../utils/logger.js";
|
|
6
11
|
import {
|
|
7
12
|
listDirContents,
|
|
@@ -30,6 +35,7 @@ interface DocResult {
|
|
|
30
35
|
files?: string[];
|
|
31
36
|
directories?: string[];
|
|
32
37
|
suggestions?: string[];
|
|
38
|
+
hint?: string;
|
|
33
39
|
error?: string;
|
|
34
40
|
}
|
|
35
41
|
|
|
@@ -79,16 +85,33 @@ async function readDocumentation(docPath: string): Promise<DocResult> {
|
|
|
79
85
|
const { directories, files } = await listDirContents(fullPath);
|
|
80
86
|
|
|
81
87
|
const contents: Record<string, string> = {};
|
|
88
|
+
let aggregateSize = 0;
|
|
89
|
+
let truncated = false;
|
|
82
90
|
for (const file of files) {
|
|
91
|
+
let fileSize: number;
|
|
92
|
+
try {
|
|
93
|
+
fileSize = (await stat(join(fullPath, file))).size;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
logger.warn(
|
|
96
|
+
`Failed to stat MDX file: ${join(fullPath, file)}`,
|
|
97
|
+
error,
|
|
98
|
+
);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
aggregateSize += fileSize;
|
|
102
|
+
if (aggregateSize > MAX_DIRECTORY_CONTENT_SIZE) {
|
|
103
|
+
truncated = true;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
83
106
|
const mdxContent = await readMDXFile(join(fullPath, file));
|
|
84
107
|
if (mdxContent) {
|
|
85
|
-
|
|
86
|
-
|
|
108
|
+
contents[file.replace(MDX_EXTENSION, "")] =
|
|
109
|
+
formatMDXContent(mdxContent);
|
|
87
110
|
}
|
|
88
111
|
}
|
|
89
112
|
|
|
90
113
|
const content =
|
|
91
|
-
Object.keys(contents).length > 0
|
|
114
|
+
!truncated && Object.keys(contents).length > 0
|
|
92
115
|
? JSON.stringify(contents, null, 2)
|
|
93
116
|
: undefined;
|
|
94
117
|
return {
|
|
@@ -98,6 +121,9 @@ async function readDocumentation(docPath: string): Promise<DocResult> {
|
|
|
98
121
|
directories,
|
|
99
122
|
files: files.map((f) => f.replace(MDX_EXTENSION, "")),
|
|
100
123
|
...(content !== undefined && { content }),
|
|
124
|
+
...(truncated && {
|
|
125
|
+
hint: `Directory content exceeds ${MAX_DIRECTORY_CONTENT_SIZE} bytes and was omitted; request individual files by path to retrieve their content.`,
|
|
126
|
+
}),
|
|
101
127
|
};
|
|
102
128
|
}
|
|
103
129
|
}
|
package/src/tools/examples.ts
CHANGED
|
@@ -15,7 +15,7 @@ const examplesInputSchema = z.object({
|
|
|
15
15
|
),
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
async function listCodeExamples(): Promise<string[]> {
|
|
18
|
+
export async function listCodeExamples(): Promise<string[]> {
|
|
19
19
|
try {
|
|
20
20
|
const files = await readdir(CODE_EXAMPLES_PATH);
|
|
21
21
|
return files
|
|
@@ -28,7 +28,9 @@ async function listCodeExamples(): Promise<string[]> {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
async function readCodeExample(
|
|
31
|
+
export async function readCodeExample(
|
|
32
|
+
exampleName: string,
|
|
33
|
+
): Promise<string | null> {
|
|
32
34
|
try {
|
|
33
35
|
const sanitized = sanitizePath(exampleName);
|
|
34
36
|
const filePath = join(CODE_EXAMPLES_PATH, `${sanitized}.md`);
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {
|
|
2
|
+
McpServer,
|
|
3
|
+
ResourceTemplate,
|
|
4
|
+
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { DOCS_PATH, MDX_EXTENSION } from "../constants.js";
|
|
7
|
+
import { getAvailableDocFiles } from "../utils/paths.js";
|
|
8
|
+
import { readMDXFileSafe, formatMDXContent } from "../utils/mdx.js";
|
|
9
|
+
import { sanitizePath } from "../utils/security.js";
|
|
10
|
+
import { listCodeExamples, readCodeExample } from "./examples.js";
|
|
11
|
+
import { logger } from "../utils/logger.js";
|
|
12
|
+
|
|
13
|
+
const MARKDOWN_MIME_TYPE = "text/markdown";
|
|
14
|
+
const DOCS_SCHEME = "aui-docs";
|
|
15
|
+
const EXAMPLE_SCHEME = "aui-example";
|
|
16
|
+
|
|
17
|
+
function templateVarToPath(value: string | string[] | undefined): string {
|
|
18
|
+
if (Array.isArray(value)) return value.join("/");
|
|
19
|
+
return value ?? "";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface MarkdownResourceConfig {
|
|
23
|
+
name: string;
|
|
24
|
+
scheme: string;
|
|
25
|
+
variable: string;
|
|
26
|
+
title: string;
|
|
27
|
+
description: string;
|
|
28
|
+
notFoundLabel: string;
|
|
29
|
+
list: () => Promise<string[]>;
|
|
30
|
+
read: (key: string) => Promise<string | null>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function registerMarkdownResource(
|
|
34
|
+
server: McpServer,
|
|
35
|
+
config: MarkdownResourceConfig,
|
|
36
|
+
): void {
|
|
37
|
+
const {
|
|
38
|
+
name,
|
|
39
|
+
scheme,
|
|
40
|
+
variable,
|
|
41
|
+
title,
|
|
42
|
+
description,
|
|
43
|
+
notFoundLabel,
|
|
44
|
+
list,
|
|
45
|
+
read,
|
|
46
|
+
} = config;
|
|
47
|
+
server.registerResource(
|
|
48
|
+
name,
|
|
49
|
+
new ResourceTemplate(`${scheme}:///{+${variable}}`, {
|
|
50
|
+
list: async () => ({
|
|
51
|
+
resources: (await list()).map((key) => ({
|
|
52
|
+
uri: `${scheme}:///${key}`,
|
|
53
|
+
name: key,
|
|
54
|
+
mimeType: MARKDOWN_MIME_TYPE,
|
|
55
|
+
})),
|
|
56
|
+
}),
|
|
57
|
+
complete: {
|
|
58
|
+
[variable]: async (value) => {
|
|
59
|
+
const needle = value.toLowerCase();
|
|
60
|
+
return (await list()).filter((key) =>
|
|
61
|
+
key.toLowerCase().includes(needle),
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
{ title, description, mimeType: MARKDOWN_MIME_TYPE },
|
|
67
|
+
async (uri, variables) => {
|
|
68
|
+
const key = templateVarToPath(variables[variable]);
|
|
69
|
+
const text = await read(key);
|
|
70
|
+
if (text === null) {
|
|
71
|
+
throw new Error(`${notFoundLabel} not found: ${key}`);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
contents: [{ uri: uri.href, mimeType: MARKDOWN_MIME_TYPE, text }],
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function readDocResource(path: string): Promise<string | null> {
|
|
81
|
+
const sanitized = sanitizePath(path);
|
|
82
|
+
const mdx = await readMDXFileSafe(
|
|
83
|
+
join(DOCS_PATH, `${sanitized}${MDX_EXTENSION}`),
|
|
84
|
+
);
|
|
85
|
+
return mdx ? formatMDXContent(mdx) : null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function registerResources(server: McpServer): void {
|
|
89
|
+
registerMarkdownResource(server, {
|
|
90
|
+
name: "assistant-ui-docs",
|
|
91
|
+
scheme: DOCS_SCHEME,
|
|
92
|
+
variable: "path",
|
|
93
|
+
title: "assistant-ui Documentation",
|
|
94
|
+
description:
|
|
95
|
+
"Individual assistant-ui documentation pages, readable as markdown by path.",
|
|
96
|
+
notFoundLabel: "Documentation",
|
|
97
|
+
list: getAvailableDocFiles,
|
|
98
|
+
read: readDocResource,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
registerMarkdownResource(server, {
|
|
102
|
+
name: "assistant-ui-examples",
|
|
103
|
+
scheme: EXAMPLE_SCHEME,
|
|
104
|
+
variable: "name",
|
|
105
|
+
title: "assistant-ui Examples",
|
|
106
|
+
description:
|
|
107
|
+
"Complete assistant-ui example projects, readable as markdown.",
|
|
108
|
+
notFoundLabel: "Example",
|
|
109
|
+
list: listCodeExamples,
|
|
110
|
+
read: readCodeExample,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
logger.debug("Registered docs and examples resources");
|
|
114
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from "zod/v3";
|
|
2
|
+
import { logger } from "../utils/logger.js";
|
|
3
|
+
import { formatMCPResponse } from "../utils/mcp-format.js";
|
|
4
|
+
import { searchDocs } from "../utils/search.js";
|
|
5
|
+
|
|
6
|
+
const searchInputSchema = z.object({
|
|
7
|
+
query: z
|
|
8
|
+
.string()
|
|
9
|
+
.min(1)
|
|
10
|
+
.describe("Keyword(s) to search assistant-ui documentation for."),
|
|
11
|
+
limit: z
|
|
12
|
+
.number()
|
|
13
|
+
.int()
|
|
14
|
+
.min(1)
|
|
15
|
+
.max(25)
|
|
16
|
+
.default(10)
|
|
17
|
+
.describe("Maximum number of results to return (default 10)."),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const searchTools = {
|
|
21
|
+
name: "assistantUISearch",
|
|
22
|
+
description:
|
|
23
|
+
"Search assistant-ui documentation by keyword. Returns ranked matches with their doc path, title, and a snippet. Fetch full content with assistantUIDocs using a returned path.",
|
|
24
|
+
parameters: searchInputSchema.shape,
|
|
25
|
+
execute: async ({ query, limit }: z.infer<typeof searchInputSchema>) => {
|
|
26
|
+
logger.info(`Searching documentation for: ${query}`);
|
|
27
|
+
try {
|
|
28
|
+
const results = await searchDocs(query, limit);
|
|
29
|
+
if (results.length === 0) {
|
|
30
|
+
return formatMCPResponse({
|
|
31
|
+
query,
|
|
32
|
+
results: [],
|
|
33
|
+
total: 0,
|
|
34
|
+
hint: 'No matches. Use assistantUIDocs with "/" to list all sections.',
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return formatMCPResponse({ query, results, total: results.length });
|
|
38
|
+
} catch (error) {
|
|
39
|
+
logger.error("Failed to search documentation", error);
|
|
40
|
+
return formatMCPResponse({
|
|
41
|
+
error: "Failed to search documentation",
|
|
42
|
+
message: error instanceof Error ? error.message : String(error),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { server } from "../../index.js";
|
|
3
|
+
|
|
4
|
+
function handler(method: string) {
|
|
5
|
+
const handlers = (server as any).server._requestHandlers;
|
|
6
|
+
const h = handlers.get(method);
|
|
7
|
+
if (!h) throw new Error(`No handler for ${method}`);
|
|
8
|
+
return h;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function complete(uri: string, name: string, value: string) {
|
|
12
|
+
return handler("completion/complete")(
|
|
13
|
+
{
|
|
14
|
+
method: "completion/complete",
|
|
15
|
+
params: { ref: { type: "ref/resource", uri }, argument: { name, value } },
|
|
16
|
+
},
|
|
17
|
+
{},
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("resource argument completions", () => {
|
|
22
|
+
it("suggests doc paths matching the partial value", async () => {
|
|
23
|
+
const res = await complete("aui-docs:///{+path}", "path", "api");
|
|
24
|
+
expect(res.completion.values.length).toBeGreaterThan(0);
|
|
25
|
+
expect(
|
|
26
|
+
res.completion.values.every((v: string) =>
|
|
27
|
+
v.toLowerCase().includes("api"),
|
|
28
|
+
),
|
|
29
|
+
).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("suggests example names matching the partial value", async () => {
|
|
33
|
+
const res = await complete("aui-example:///{+name}", "name", "with");
|
|
34
|
+
expect(res.completion.values.length).toBeGreaterThan(0);
|
|
35
|
+
expect(
|
|
36
|
+
res.completion.values.every((v: string) =>
|
|
37
|
+
v.toLowerCase().includes("with"),
|
|
38
|
+
),
|
|
39
|
+
).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("returns no values for a nonsense partial", async () => {
|
|
43
|
+
const res = await complete("aui-docs:///{+path}", "path", "zzzqqxnope");
|
|
44
|
+
expect(res.completion.values).toHaveLength(0);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
vi.mock("../../utils/paths.js", async (importOriginal) => ({
|
|
4
|
+
...(await importOriginal<typeof import("../../utils/paths.js")>()),
|
|
5
|
+
listDirContents: vi.fn(async () => ({
|
|
6
|
+
directories: [],
|
|
7
|
+
files: ["first.mdx", "second.mdx"],
|
|
8
|
+
})),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
vi.mock("node:fs/promises", async (importOriginal) => {
|
|
12
|
+
const actual = await importOriginal<typeof import("node:fs/promises")>();
|
|
13
|
+
return {
|
|
14
|
+
...actual,
|
|
15
|
+
stat: vi.fn(async (path: string) => ({
|
|
16
|
+
isFile: () => path.endsWith(".mdx"),
|
|
17
|
+
isDirectory: () => !path.endsWith(".mdx"),
|
|
18
|
+
size: 600 * 1024,
|
|
19
|
+
})),
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
vi.mock("../../utils/mdx.js", async (importOriginal) => ({
|
|
24
|
+
...(await importOriginal<typeof import("../../utils/mdx.js")>()),
|
|
25
|
+
readMDXFile: vi.fn(async () => ({
|
|
26
|
+
content: "x".repeat(600 * 1024),
|
|
27
|
+
frontmatter: {},
|
|
28
|
+
})),
|
|
29
|
+
formatMDXContent: (mdx: { content: string }) => mdx.content,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
import { docsTools } from "../docs.js";
|
|
33
|
+
|
|
34
|
+
function parse(result: { content: Array<{ text: string }> }) {
|
|
35
|
+
return JSON.parse(result.content[0]!.text);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe("assistantUIDocs directory aggregate size cap", () => {
|
|
39
|
+
it("omits inlined content and returns a hint when a directory exceeds the cap", async () => {
|
|
40
|
+
const result = await docsTools.execute({
|
|
41
|
+
paths: ["(reference)/api-reference/primitives"],
|
|
42
|
+
});
|
|
43
|
+
const parsed = parse(result);
|
|
44
|
+
|
|
45
|
+
expect(parsed.type).toBe("directory");
|
|
46
|
+
expect(parsed.files).toEqual(["first", "second"]);
|
|
47
|
+
expect(parsed.content).toBeUndefined();
|
|
48
|
+
expect(parsed.hint).toContain("exceeds");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -55,7 +55,7 @@ describe("MCP Protocol Integration", () => {
|
|
|
55
55
|
|
|
56
56
|
expect(result).toBeDefined();
|
|
57
57
|
expect(result.tools).toBeInstanceOf(Array);
|
|
58
|
-
expect(result.tools).toHaveLength(
|
|
58
|
+
expect(result.tools).toHaveLength(3);
|
|
59
59
|
|
|
60
60
|
// Check the tools have proper JSON schemas
|
|
61
61
|
const docsTool = result.tools.find(
|
|
@@ -73,6 +73,26 @@ describe("MCP Protocol Integration", () => {
|
|
|
73
73
|
expect(examplesTool.inputSchema).toBeDefined();
|
|
74
74
|
expect(examplesTool.inputSchema.type).toBe("object");
|
|
75
75
|
expect(examplesTool.inputSchema.properties).toBeDefined();
|
|
76
|
+
|
|
77
|
+
const searchTool = result.tools.find(
|
|
78
|
+
(t: any) => t.name === "assistantUISearch",
|
|
79
|
+
);
|
|
80
|
+
expect(searchTool).toBeDefined();
|
|
81
|
+
expect(searchTool.inputSchema).toBeDefined();
|
|
82
|
+
expect(searchTool.inputSchema.type).toBe("object");
|
|
83
|
+
expect(searchTool.inputSchema.properties).toBeDefined();
|
|
84
|
+
|
|
85
|
+
// registerTool metadata is surfaced on tools/list: `title` is a
|
|
86
|
+
// top-level field, `annotations` carries only the hint flags.
|
|
87
|
+
expect(docsTool.title).toBe("assistant-ui Documentation");
|
|
88
|
+
expect(docsTool.annotations?.readOnlyHint).toBe(true);
|
|
89
|
+
expect(docsTool.annotations?.openWorldHint).toBe(false);
|
|
90
|
+
expect(examplesTool.title).toBe("assistant-ui Examples");
|
|
91
|
+
expect(examplesTool.annotations?.readOnlyHint).toBe(true);
|
|
92
|
+
expect(examplesTool.annotations?.openWorldHint).toBe(false);
|
|
93
|
+
expect(searchTool.title).toBe("Search assistant-ui Documentation");
|
|
94
|
+
expect(searchTool.annotations?.readOnlyHint).toBe(true);
|
|
95
|
+
expect(searchTool.annotations?.openWorldHint).toBe(false);
|
|
76
96
|
});
|
|
77
97
|
|
|
78
98
|
it("should handle CallTool request for assistantUIDocs", async () => {
|