@assistant-ui/mcp-docs-server 0.1.17 → 0.1.19

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.
Files changed (202) hide show
  1. package/.docs/organized/code-examples/with-ag-ui.md +518 -234
  2. package/.docs/organized/code-examples/{with-ai-sdk-v5.md → with-ai-sdk-v6.md} +476 -189
  3. package/.docs/organized/code-examples/with-assistant-transport.md +503 -301
  4. package/.docs/organized/code-examples/with-cloud.md +524 -226
  5. package/.docs/organized/code-examples/with-custom-thread-list.md +433 -146
  6. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +2241 -0
  7. package/.docs/organized/code-examples/with-external-store.md +517 -231
  8. package/.docs/organized/code-examples/with-ffmpeg.md +500 -220
  9. package/.docs/organized/code-examples/with-langgraph.md +630 -319
  10. package/.docs/organized/code-examples/with-parent-id-grouping.md +517 -231
  11. package/.docs/organized/code-examples/with-react-hook-form.md +517 -233
  12. package/.docs/organized/code-examples/with-react-router.md +2167 -0
  13. package/.docs/organized/code-examples/{store-example.md → with-store.md} +18 -22
  14. package/.docs/organized/code-examples/with-tanstack.md +23 -41
  15. package/.docs/raw/blog/2025-01-31-changelog/index.mdx +0 -2
  16. package/.docs/raw/docs/{about-assistantui.mdx → (docs)/about-assistantui.mdx} +2 -1
  17. package/.docs/raw/docs/{architecture.mdx → (docs)/architecture.mdx} +3 -2
  18. package/.docs/raw/docs/{cli.mdx → (docs)/cli.mdx} +1 -19
  19. package/.docs/raw/docs/{copilots → (docs)/copilots}/make-assistant-readable.mdx +1 -0
  20. package/.docs/raw/docs/{copilots → (docs)/copilots}/make-assistant-tool-ui.mdx +2 -1
  21. package/.docs/raw/docs/{copilots → (docs)/copilots}/make-assistant-tool.mdx +2 -1
  22. package/.docs/raw/docs/{copilots → (docs)/copilots}/model-context.mdx +1 -0
  23. package/.docs/raw/docs/{copilots → (docs)/copilots}/motivation.mdx +1 -0
  24. package/.docs/raw/docs/{copilots → (docs)/copilots}/use-assistant-instructions.mdx +1 -0
  25. package/.docs/raw/docs/{devtools.mdx → (docs)/devtools.mdx} +4 -4
  26. package/.docs/raw/docs/{guides/Attachments.mdx → (docs)/guides/attachments.mdx} +4 -5
  27. package/.docs/raw/docs/{guides/Branching.mdx → (docs)/guides/branching.mdx} +2 -1
  28. package/.docs/raw/docs/{guides → (docs)/guides}/context-api.mdx +1 -0
  29. package/.docs/raw/docs/(docs)/guides/dictation.mdx +370 -0
  30. package/.docs/raw/docs/{guides/Editing.mdx → (docs)/guides/editing.mdx} +1 -0
  31. package/.docs/raw/docs/{guides/Latex.mdx → (docs)/guides/latex.mdx} +1 -2
  32. package/.docs/raw/docs/{guides/Speech.mdx → (docs)/guides/speech.mdx} +9 -10
  33. package/.docs/raw/docs/{guides/ToolUI.mdx → (docs)/guides/tool-ui.mdx} +15 -14
  34. package/.docs/raw/docs/{guides/Tools.mdx → (docs)/guides/tools.mdx} +10 -7
  35. package/.docs/raw/docs/{getting-started.mdx → (docs)/index.mdx} +17 -22
  36. package/.docs/raw/docs/{mcp-docs-server.mdx → (docs)/mcp-docs-server.mdx} +1 -2
  37. package/.docs/raw/docs/{api-reference/context-providers/AssistantRuntimeProvider.mdx → (reference)/api-reference/context-providers/assistant-runtime-provider.mdx} +2 -1
  38. package/.docs/raw/docs/{api-reference/context-providers/TextMessagePartProvider.mdx → (reference)/api-reference/context-providers/text-message-part-provider.mdx} +2 -1
  39. package/.docs/raw/docs/{api-reference → (reference)/api-reference}/integrations/react-data-stream.mdx +2 -1
  40. package/.docs/raw/docs/{api-reference → (reference)/api-reference}/integrations/react-hook-form.mdx +2 -1
  41. package/.docs/raw/docs/{api-reference → (reference)/api-reference}/integrations/vercel-ai-sdk.mdx +2 -2
  42. package/.docs/raw/docs/{api-reference → (reference)/api-reference}/overview.mdx +1 -1
  43. package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar-more.mdx +327 -0
  44. package/.docs/raw/docs/{api-reference/primitives/ActionBar.mdx → (reference)/api-reference/primitives/action-bar.mdx} +3 -1
  45. package/.docs/raw/docs/{api-reference/primitives/AssistantIf.mdx → (reference)/api-reference/primitives/assistant-if.mdx} +2 -2
  46. package/.docs/raw/docs/{api-reference/primitives/AssistantModal.mdx → (reference)/api-reference/primitives/assistant-modal.mdx} +3 -1
  47. package/.docs/raw/docs/{api-reference/primitives/Attachment.mdx → (reference)/api-reference/primitives/attachment.mdx} +3 -2
  48. package/.docs/raw/docs/{api-reference/primitives/BranchPicker.mdx → (reference)/api-reference/primitives/branch-picker.mdx} +2 -1
  49. package/.docs/raw/docs/{api-reference/primitives/Composer.mdx → (reference)/api-reference/primitives/composer.mdx} +101 -2
  50. package/.docs/raw/docs/{api-reference → (reference)/api-reference}/primitives/composition.mdx +1 -0
  51. package/.docs/raw/docs/{api-reference/primitives/Error.mdx → (reference)/api-reference/primitives/error.mdx} +2 -1
  52. package/.docs/raw/docs/{api-reference/primitives/MessagePart.mdx → (reference)/api-reference/primitives/message-part.mdx} +2 -2
  53. package/.docs/raw/docs/{api-reference/primitives/Message.mdx → (reference)/api-reference/primitives/message.mdx} +2 -1
  54. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list-item-more.mdx +221 -0
  55. package/.docs/raw/docs/{api-reference/primitives/ThreadListItem.mdx → (reference)/api-reference/primitives/thread-list-item.mdx} +2 -1
  56. package/.docs/raw/docs/{api-reference/primitives/ThreadList.mdx → (reference)/api-reference/primitives/thread-list.mdx} +2 -1
  57. package/.docs/raw/docs/{api-reference/primitives/Thread.mdx → (reference)/api-reference/primitives/thread.mdx} +2 -1
  58. package/.docs/raw/docs/{api-reference/runtimes/AssistantRuntime.mdx → (reference)/api-reference/runtimes/assistant-runtime.mdx} +2 -1
  59. package/.docs/raw/docs/{api-reference/runtimes/AttachmentRuntime.mdx → (reference)/api-reference/runtimes/attachment-runtime.mdx} +3 -2
  60. package/.docs/raw/docs/{api-reference/runtimes/ComposerRuntime.mdx → (reference)/api-reference/runtimes/composer-runtime.mdx} +2 -1
  61. package/.docs/raw/docs/{api-reference/runtimes/MessagePartRuntime.mdx → (reference)/api-reference/runtimes/message-part-runtime.mdx} +3 -2
  62. package/.docs/raw/docs/{api-reference/runtimes/MessageRuntime.mdx → (reference)/api-reference/runtimes/message-runtime.mdx} +3 -2
  63. package/.docs/raw/docs/{api-reference/runtimes/ThreadListItemRuntime.mdx → (reference)/api-reference/runtimes/thread-list-item-runtime.mdx} +2 -1
  64. package/.docs/raw/docs/{api-reference/runtimes/ThreadListRuntime.mdx → (reference)/api-reference/runtimes/thread-list-runtime.mdx} +2 -1
  65. package/.docs/raw/docs/{api-reference/runtimes/ThreadRuntime.mdx → (reference)/api-reference/runtimes/thread-runtime.mdx} +3 -5
  66. package/.docs/raw/docs/{legacy/styled/AssistantModal.mdx → (reference)/legacy/styled/assistant-modal.mdx} +2 -3
  67. package/.docs/raw/docs/{legacy/styled/Decomposition.mdx → (reference)/legacy/styled/decomposition.mdx} +1 -0
  68. package/.docs/raw/docs/{legacy/styled/Markdown.mdx → (reference)/legacy/styled/markdown.mdx} +2 -4
  69. package/.docs/raw/docs/{legacy/styled/Scrollbar.mdx → (reference)/legacy/styled/scrollbar.mdx} +2 -1
  70. package/.docs/raw/docs/{legacy/styled/ThreadWidth.mdx → (reference)/legacy/styled/thread-width.mdx} +1 -0
  71. package/.docs/raw/docs/{legacy/styled/Thread.mdx → (reference)/legacy/styled/thread.mdx} +2 -3
  72. package/.docs/raw/docs/{migrations → (reference)/migrations}/deprecation-policy.mdx +1 -0
  73. package/.docs/raw/docs/{migrations → (reference)/migrations}/react-langgraph-v0-7.mdx +1 -2
  74. package/.docs/raw/docs/{migrations → (reference)/migrations}/v0-11.mdx +1 -0
  75. package/.docs/raw/docs/{migrations → (reference)/migrations}/v0-12.mdx +1 -0
  76. package/.docs/raw/docs/{react-compatibility.mdx → (reference)/react-compatibility.mdx} +2 -3
  77. package/.docs/raw/docs/cloud/authorization.mdx +1 -0
  78. package/.docs/raw/docs/cloud/overview.mdx +1 -0
  79. package/.docs/raw/docs/cloud/persistence/ai-sdk.mdx +2 -3
  80. package/.docs/raw/docs/cloud/persistence/langgraph.mdx +5 -7
  81. package/.docs/raw/docs/runtimes/ai-sdk/use-chat.mdx +9 -8
  82. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +2 -3
  83. package/.docs/raw/docs/runtimes/assistant-transport.mdx +7 -6
  84. package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +38 -3
  85. package/.docs/raw/docs/runtimes/custom/external-store.mdx +6 -8
  86. package/.docs/raw/docs/runtimes/custom/local.mdx +43 -16
  87. package/.docs/raw/docs/runtimes/data-stream.mdx +32 -4
  88. package/.docs/raw/docs/runtimes/helicone.mdx +1 -0
  89. package/.docs/raw/docs/runtimes/langgraph/index.mdx +3 -3
  90. package/.docs/raw/docs/runtimes/langgraph/tutorial/index.mdx +1 -0
  91. package/.docs/raw/docs/runtimes/langgraph/tutorial/introduction.mdx +1 -0
  92. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +1 -0
  93. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +1 -0
  94. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +2 -1
  95. package/.docs/raw/docs/runtimes/langserve.mdx +2 -2
  96. package/.docs/raw/docs/runtimes/mastra/full-stack-integration.mdx +4 -5
  97. package/.docs/raw/docs/runtimes/mastra/overview.mdx +1 -0
  98. package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +3 -4
  99. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +2 -4
  100. package/.docs/raw/docs/ui/assistant-modal.mdx +163 -0
  101. package/.docs/raw/docs/ui/assistant-sidebar.mdx +90 -0
  102. package/.docs/raw/docs/ui/attachment.mdx +227 -0
  103. package/.docs/raw/docs/ui/{Markdown.mdx → markdown.mdx} +11 -6
  104. package/.docs/raw/docs/ui/{Mermaid.mdx → mermaid.mdx} +12 -5
  105. package/.docs/raw/docs/ui/{PartGrouping.mdx → part-grouping.mdx} +4 -6
  106. package/.docs/raw/docs/ui/reasoning.mdx +148 -0
  107. package/.docs/raw/docs/ui/{Scrollbar.mdx → scrollbar.mdx} +9 -7
  108. package/.docs/raw/docs/ui/sources.mdx +87 -0
  109. package/.docs/raw/docs/ui/{SyntaxHighlighting.mdx → syntax-highlighting.mdx} +9 -5
  110. package/.docs/raw/docs/ui/thread-list.mdx +275 -0
  111. package/.docs/raw/docs/ui/{Thread.mdx → thread.mdx} +5 -6
  112. package/.docs/raw/docs/ui/tool-fallback.mdx +112 -0
  113. package/.docs/raw/docs/ui/tool-group.mdx +214 -0
  114. package/dist/constants.d.ts +10 -0
  115. package/dist/constants.d.ts.map +1 -0
  116. package/dist/constants.js +14 -0
  117. package/dist/constants.js.map +1 -0
  118. package/dist/index.d.ts +4 -0
  119. package/dist/index.d.ts.map +1 -0
  120. package/dist/index.js +33 -1
  121. package/dist/index.js.map +1 -0
  122. package/dist/prepare-docs/code-examples.d.ts +2 -0
  123. package/dist/prepare-docs/code-examples.d.ts.map +1 -0
  124. package/dist/prepare-docs/code-examples.js +129 -0
  125. package/dist/prepare-docs/code-examples.js.map +1 -0
  126. package/dist/prepare-docs/copy-raw.d.ts +2 -0
  127. package/dist/prepare-docs/copy-raw.d.ts.map +1 -0
  128. package/dist/prepare-docs/copy-raw.js +50 -0
  129. package/dist/prepare-docs/copy-raw.js.map +1 -0
  130. package/dist/prepare-docs/prepare.d.ts +2 -0
  131. package/dist/prepare-docs/prepare.d.ts.map +1 -0
  132. package/dist/prepare-docs/prepare.js +18 -195
  133. package/dist/prepare-docs/prepare.js.map +1 -0
  134. package/dist/stdio.d.ts +3 -0
  135. package/dist/stdio.d.ts.map +1 -0
  136. package/dist/stdio.js +4 -5
  137. package/dist/stdio.js.map +1 -0
  138. package/dist/tools/docs.d.ts +23 -0
  139. package/dist/tools/docs.d.ts.map +1 -0
  140. package/dist/tools/docs.js +168 -0
  141. package/dist/tools/docs.js.map +1 -0
  142. package/dist/tools/examples.d.ts +23 -0
  143. package/dist/tools/examples.d.ts.map +1 -0
  144. package/dist/tools/examples.js +95 -0
  145. package/dist/tools/examples.js.map +1 -0
  146. package/dist/tools/tests/test-setup.d.ts +4 -0
  147. package/dist/tools/tests/test-setup.d.ts.map +1 -0
  148. package/dist/tools/tests/test-setup.js +36 -0
  149. package/dist/tools/tests/test-setup.js.map +1 -0
  150. package/dist/utils/logger.d.ts +7 -0
  151. package/dist/utils/logger.d.ts.map +1 -0
  152. package/dist/utils/logger.js +20 -0
  153. package/dist/utils/logger.js.map +1 -0
  154. package/dist/utils/mcp-format.d.ts +7 -0
  155. package/dist/utils/mcp-format.d.ts.map +1 -0
  156. package/dist/utils/mcp-format.js +11 -0
  157. package/dist/utils/mcp-format.js.map +1 -0
  158. package/dist/utils/mdx.d.ts +9 -0
  159. package/dist/utils/mdx.d.ts.map +1 -0
  160. package/dist/utils/mdx.js +27 -0
  161. package/dist/utils/mdx.js.map +1 -0
  162. package/dist/utils/paths.d.ts +8 -0
  163. package/dist/utils/paths.d.ts.map +1 -0
  164. package/dist/utils/paths.js +84 -0
  165. package/dist/utils/paths.js.map +1 -0
  166. package/dist/utils/security.d.ts +2 -0
  167. package/dist/utils/security.d.ts.map +1 -0
  168. package/dist/utils/security.js +43 -0
  169. package/dist/utils/security.js.map +1 -0
  170. package/package.json +37 -19
  171. package/src/constants.ts +22 -0
  172. package/src/index.ts +51 -0
  173. package/src/prepare-docs/code-examples.ts +158 -0
  174. package/src/prepare-docs/copy-raw.ts +55 -0
  175. package/src/prepare-docs/prepare.ts +24 -0
  176. package/src/stdio.ts +7 -0
  177. package/src/tools/docs.ts +207 -0
  178. package/src/tools/examples.ts +107 -0
  179. package/src/tools/tests/docs.test.ts +124 -0
  180. package/src/tools/tests/examples.test.ts +94 -0
  181. package/src/tools/tests/integration.test.ts +46 -0
  182. package/src/tools/tests/json-parsing.test.ts +23 -0
  183. package/src/tools/tests/mcp-protocol.test.ts +133 -0
  184. package/src/tools/tests/path-traversal.test.ts +81 -0
  185. package/src/tools/tests/test-setup.ts +40 -0
  186. package/src/utils/logger.ts +20 -0
  187. package/src/utils/mcp-format.ts +12 -0
  188. package/src/utils/mdx.ts +39 -0
  189. package/src/utils/paths.ts +114 -0
  190. package/src/utils/security.ts +52 -0
  191. package/src/utils/tests/security.test.ts +119 -0
  192. package/.docs/raw/docs/index.mdx +0 -7
  193. package/.docs/raw/docs/ui/AssistantModal.mdx +0 -45
  194. package/.docs/raw/docs/ui/AssistantSidebar.mdx +0 -41
  195. package/.docs/raw/docs/ui/Attachment.mdx +0 -84
  196. package/.docs/raw/docs/ui/Reasoning.mdx +0 -152
  197. package/.docs/raw/docs/ui/ThreadList.mdx +0 -90
  198. package/.docs/raw/docs/ui/ToolFallback.mdx +0 -63
  199. package/.docs/raw/docs/ui/ToolGroup.mdx +0 -96
  200. package/dist/chunk-M2RKUM66.js +0 -38
  201. package/dist/chunk-NVNFQ5ZO.js +0 -423
  202. /package/.docs/raw/docs/{copilots → (docs)/copilots}/assistant-frame.mdx +0 -0
@@ -0,0 +1,107 @@
1
+ import { z } from "zod/v3";
2
+ import { readFile, readdir, lstat } from "node:fs/promises";
3
+ import { join, extname } from "node:path";
4
+ import { CODE_EXAMPLES_PATH, MAX_FILE_SIZE } from "../constants.js";
5
+ import { logger } from "../utils/logger.js";
6
+ import { formatMCPResponse } from "../utils/mcp-format.js";
7
+ import { sanitizePath } from "../utils/security.js";
8
+
9
+ const examplesInputSchema = z.object({
10
+ example: z
11
+ .string()
12
+ .optional()
13
+ .describe(
14
+ 'Example name (e.g., "with-ai-sdk-v6"). Leave empty to list all examples.',
15
+ ),
16
+ });
17
+
18
+ async function listCodeExamples(): Promise<string[]> {
19
+ try {
20
+ const files = await readdir(CODE_EXAMPLES_PATH);
21
+ return files
22
+ .filter((file) => extname(file) === ".md")
23
+ .map((file) => file.replace(".md", ""))
24
+ .sort();
25
+ } catch (error) {
26
+ logger.error("Failed to list code examples", error);
27
+ return [];
28
+ }
29
+ }
30
+
31
+ async function readCodeExample(exampleName: string): Promise<string | null> {
32
+ try {
33
+ const sanitized = sanitizePath(exampleName);
34
+ const filePath = join(CODE_EXAMPLES_PATH, `${sanitized}.md`);
35
+
36
+ const stats = await lstat(filePath);
37
+ if (stats.isSymbolicLink()) {
38
+ logger.warn(`Attempted to read symlink: ${filePath}`);
39
+ return null;
40
+ }
41
+
42
+ if (stats.size > MAX_FILE_SIZE) {
43
+ logger.warn(`File size exceeds limit: ${filePath} (${stats.size} bytes)`);
44
+ return null;
45
+ }
46
+
47
+ const content = await readFile(filePath, "utf-8");
48
+ return content;
49
+ } catch (error) {
50
+ logger.error(`Failed to read example: ${exampleName}`, error);
51
+ return null;
52
+ }
53
+ }
54
+
55
+ export const examplesTools = {
56
+ name: "assistantUIExamples",
57
+ description:
58
+ "List available examples or retrieve complete code for a specific example",
59
+ parameters: examplesInputSchema.shape,
60
+ execute: async ({ example }: z.infer<typeof examplesInputSchema>) => {
61
+ try {
62
+ if (!example) {
63
+ logger.info("Listing all available examples");
64
+ const examples = await listCodeExamples();
65
+
66
+ if (examples.length === 0) {
67
+ return formatMCPResponse({
68
+ error:
69
+ "No examples found. Please run documentation preparation first.",
70
+ hint: "Run: pnpm prepare-docs",
71
+ });
72
+ }
73
+
74
+ return formatMCPResponse({
75
+ type: "list",
76
+ examples,
77
+ total: examples.length,
78
+ hint: "Use example parameter to get complete code for any example",
79
+ });
80
+ }
81
+
82
+ logger.info(`Retrieving example: ${example}`);
83
+ const content = await readCodeExample(example);
84
+
85
+ if (!content) {
86
+ const availableExamples = await listCodeExamples();
87
+ return formatMCPResponse({
88
+ error: `Example not found: ${example}`,
89
+ availableExamples,
90
+ hint: "Use without example parameter to list all available examples",
91
+ });
92
+ }
93
+
94
+ return formatMCPResponse({
95
+ type: "example",
96
+ name: example,
97
+ content,
98
+ });
99
+ } catch (error) {
100
+ logger.error("Failed to retrieve examples", error);
101
+ return formatMCPResponse({
102
+ error: "Failed to retrieve examples",
103
+ message: error instanceof Error ? error.message : String(error),
104
+ });
105
+ }
106
+ },
107
+ };
@@ -0,0 +1,124 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { testContext } from "./test-setup.js";
3
+ import * as fs from "node:fs/promises";
4
+
5
+ vi.mock("fs/promises", async () => {
6
+ const { readdir, readFile, stat, lstat } = await import("node:fs/promises");
7
+ return {
8
+ readdir,
9
+ readFile,
10
+ stat,
11
+ lstat: vi.fn(lstat),
12
+ };
13
+ });
14
+
15
+ describe("assistantUIDocs", () => {
16
+ beforeEach(() => {
17
+ vi.clearAllMocks();
18
+ });
19
+
20
+ afterEach(() => {
21
+ vi.restoreAllMocks();
22
+ });
23
+ it("should list root directory contents", async () => {
24
+ const result = await testContext.callTool("assistantUIDocs", {
25
+ paths: ["/"],
26
+ });
27
+
28
+ expect(result.path).toBe("/");
29
+ expect(result.found).toBe(true);
30
+ expect(result.type).toBe("directory");
31
+ expect(result.directories).toContain("(docs)");
32
+ expect(result.directories).toContain("(reference)");
33
+ expect(result.directories).toContain("ui");
34
+ });
35
+
36
+ it("should retrieve specific documentation file", async () => {
37
+ const result = await testContext.callTool("assistantUIDocs", {
38
+ paths: ["(docs)/index"],
39
+ });
40
+
41
+ expect(result.path).toBe("(docs)/index");
42
+ expect(result.found).toBe(true);
43
+ expect(result.type).toBe("file");
44
+ expect(result.content).toBeDefined();
45
+ expect(result.content).toContain("assistant-ui");
46
+ });
47
+
48
+ it("should handle non-existent paths", async () => {
49
+ const result = await testContext.callTool("assistantUIDocs", {
50
+ paths: ["non-existent-path"],
51
+ });
52
+
53
+ expect(result.error).toBeDefined();
54
+ expect(result.error).toContain("Documentation not found");
55
+ expect(result.suggestions).toBeDefined();
56
+ });
57
+
58
+ it("should support multiple path requests", async () => {
59
+ const result = await testContext.callTool("assistantUIDocs", {
60
+ paths: ["(docs)/index", "(reference)/api-reference/primitives/thread"],
61
+ });
62
+
63
+ expect(result.results).toBeDefined();
64
+ expect(result.results).toHaveLength(2);
65
+ expect(result.results[0].path).toBe("(docs)/index");
66
+ expect(result.results[1].path).toBe(
67
+ "(reference)/api-reference/primitives/thread",
68
+ );
69
+ });
70
+
71
+ it("should list directory contents with files", async () => {
72
+ const result = await testContext.callTool("assistantUIDocs", {
73
+ paths: ["(reference)/api-reference/primitives"],
74
+ });
75
+
76
+ expect(result.path).toBe("(reference)/api-reference/primitives");
77
+ expect(result.found).toBe(true);
78
+ expect(result.type).toBe("directory");
79
+ expect(result.files).toContain("thread");
80
+ expect(result.files).toContain("message");
81
+ expect(result.files).toContain("composer");
82
+ });
83
+
84
+ it("should parse MDX files with frontmatter", async () => {
85
+ const result = await testContext.callTool("assistantUIDocs", {
86
+ paths: ["(docs)/index"],
87
+ });
88
+
89
+ expect(result.content).toBeDefined();
90
+ expect(result.content).toContain("title:");
91
+ expect(result.content).toContain("assistant-ui");
92
+ });
93
+
94
+ it("should skip symlinks and large files", async () => {
95
+ const mockedLstat = vi.mocked(fs.lstat);
96
+
97
+ mockedLstat.mockResolvedValueOnce({
98
+ isSymbolicLink: () => true,
99
+ isFile: () => false,
100
+ isDirectory: () => false,
101
+ } as any);
102
+
103
+ const symlinkResult = await testContext.callTool("assistantUIDocs", {
104
+ paths: ["symlink-test"],
105
+ });
106
+ expect(symlinkResult.error).toBe(
107
+ "Symlinks are not allowed for security reasons",
108
+ );
109
+
110
+ mockedLstat.mockRejectedValueOnce(new Error("ENOENT"));
111
+ mockedLstat.mockResolvedValueOnce({
112
+ isSymbolicLink: () => false,
113
+ isFile: () => true,
114
+ size: 11 * 1024 * 1024,
115
+ } as any);
116
+
117
+ const largeFileResult = await testContext.callTool("assistantUIDocs", {
118
+ paths: ["large-file"],
119
+ });
120
+ expect(largeFileResult.error).toContain(
121
+ "File size exceeds maximum allowed size",
122
+ );
123
+ });
124
+ });
@@ -0,0 +1,94 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { testContext } from "./test-setup.js";
3
+ import * as fs from "node:fs/promises";
4
+
5
+ vi.mock("fs/promises", async () => {
6
+ const { readdir, readFile, lstat } = await import("node:fs/promises");
7
+ return {
8
+ readdir,
9
+ readFile,
10
+ lstat: vi.fn(lstat),
11
+ };
12
+ });
13
+
14
+ describe("assistantUIExamples", () => {
15
+ it("should list all available examples", async () => {
16
+ const result = await testContext.callTool("assistantUIExamples", {});
17
+
18
+ expect(result.type).toBe("list");
19
+ expect(result.examples).toBeDefined();
20
+ expect(Array.isArray(result.examples)).toBe(true);
21
+ expect(result.examples.length).toBeGreaterThan(0);
22
+ expect(result.examples).toContain("with-ai-sdk-v6");
23
+ expect(result.examples).toContain("with-langgraph");
24
+ expect(result.total).toBe(result.examples.length);
25
+ });
26
+
27
+ it("should retrieve specific example code", async () => {
28
+ const result = await testContext.callTool("assistantUIExamples", {
29
+ example: "with-ai-sdk-v6",
30
+ });
31
+
32
+ expect(result.type).toBe("example");
33
+ expect(result.name).toBe("with-ai-sdk-v6");
34
+ expect(result.content).toBeDefined();
35
+ expect(result.content).toContain("# Example: with-ai-sdk-v6");
36
+ expect(result.content).toContain("app/api/chat/route.ts");
37
+ expect(result.content).toContain("streamText");
38
+ });
39
+
40
+ it("should handle non-existent examples", async () => {
41
+ const result = await testContext.callTool("assistantUIExamples", {
42
+ example: "non-existent-example",
43
+ });
44
+
45
+ expect(result.error).toBeDefined();
46
+ expect(result.error).toContain("Example not found");
47
+ expect(result.availableExamples).toBeDefined();
48
+ expect(Array.isArray(result.availableExamples)).toBe(true);
49
+ });
50
+
51
+ it("should include all files in example", async () => {
52
+ const result = await testContext.callTool("assistantUIExamples", {
53
+ example: "with-ai-sdk-v6",
54
+ });
55
+
56
+ expect(result.content).toContain("package.json");
57
+ expect(result.content).toContain("components/assistant-ui/thread.tsx");
58
+ });
59
+
60
+ it("should handle empty example parameter", async () => {
61
+ const result = await testContext.callTool("assistantUIExamples", {
62
+ example: undefined,
63
+ });
64
+
65
+ expect(result.type).toBe("list");
66
+ expect(result.hint).toBeDefined();
67
+ });
68
+
69
+ it("should skip symlinks and large files", async () => {
70
+ const mockedLstat = vi.mocked(fs.lstat);
71
+
72
+ mockedLstat.mockResolvedValueOnce({
73
+ isSymbolicLink: () => true,
74
+ isFile: () => false,
75
+ size: 0,
76
+ } as any);
77
+
78
+ const symlinkResult = await testContext.callTool("assistantUIExamples", {
79
+ example: "symlink-example",
80
+ });
81
+ expect(symlinkResult.error).toContain("Example not found");
82
+
83
+ mockedLstat.mockResolvedValueOnce({
84
+ isSymbolicLink: () => false,
85
+ isFile: () => true,
86
+ size: 11 * 1024 * 1024, // 11MB - exceeds MAX_FILE_SIZE
87
+ } as any);
88
+
89
+ const largeFileResult = await testContext.callTool("assistantUIExamples", {
90
+ example: "large-example",
91
+ });
92
+ expect(largeFileResult.error).toContain("Example not found");
93
+ });
94
+ });
@@ -0,0 +1,46 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { server } from "../../index.js";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { docsTools } from "../docs.js";
5
+ import { examplesTools } from "../examples.js";
6
+
7
+ describe("MCP Server Integration", () => {
8
+ it("should be an instance of McpServer", () => {
9
+ expect(server).toBeInstanceOf(McpServer);
10
+ });
11
+
12
+ it("should have tools with correct properties", () => {
13
+ expect(docsTools.name).toBe("assistantUIDocs");
14
+ expect(docsTools.description).toContain(
15
+ "Retrieve assistant-ui documentation",
16
+ );
17
+ expect(docsTools.parameters).toBeDefined();
18
+ expect(docsTools.execute).toBeInstanceOf(Function);
19
+
20
+ expect(examplesTools.name).toBe("assistantUIExamples");
21
+ expect(examplesTools.description).toContain("List available examples");
22
+ expect(examplesTools.parameters).toBeDefined();
23
+ expect(examplesTools.execute).toBeInstanceOf(Function);
24
+ });
25
+
26
+ it("should have valid input schemas", () => {
27
+ // docsTools.parameters is the Zod schema shape
28
+ expect(docsTools.parameters).toBeDefined();
29
+ expect(Object.keys(docsTools.parameters)).toContain("paths");
30
+
31
+ expect(examplesTools.parameters).toBeDefined();
32
+ expect(Object.keys(examplesTools.parameters)).toContain("example");
33
+ });
34
+
35
+ it("should execute tools successfully", async () => {
36
+ const docsResult = await docsTools.execute({ paths: ["/"] });
37
+ expect(docsResult).toBeDefined();
38
+ expect(docsResult.content).toBeDefined();
39
+ expect(docsResult.content[0].type).toBe("text");
40
+
41
+ const examplesResult = await examplesTools.execute({});
42
+ expect(examplesResult).toBeDefined();
43
+ expect(examplesResult.content).toBeDefined();
44
+ expect(examplesResult.content[0].type).toBe("text");
45
+ });
46
+ });
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect, vi, afterEach } from "vitest";
2
+ import { testContext } from "./test-setup.js";
3
+ import { docsTools } from "../docs.js";
4
+
5
+ describe("JSON parsing error handling", () => {
6
+ afterEach(() => {
7
+ vi.restoreAllMocks();
8
+ });
9
+
10
+ it("should provide helpful error message for invalid JSON", async () => {
11
+ vi.spyOn(docsTools, "execute").mockResolvedValue({
12
+ content: [{ text: "invalid json {not valid}" }],
13
+ });
14
+
15
+ await expect(
16
+ testContext.callTool("assistantUIDocs", { paths: ["/"] }),
17
+ ).rejects.toThrow(/Tool assistantUIDocs returned invalid JSON/);
18
+
19
+ await expect(
20
+ testContext.callTool("assistantUIDocs", { paths: ["/"] }),
21
+ ).rejects.toThrow(/invalid json \{not valid\}/);
22
+ });
23
+ });
@@ -0,0 +1,133 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { server } from "../../index.js";
3
+ import {
4
+ InitializeRequestSchema,
5
+ ListToolsRequestSchema,
6
+ CallToolRequestSchema,
7
+ type InitializeRequest,
8
+ type ListToolsRequest,
9
+ type CallToolRequest,
10
+ } from "@modelcontextprotocol/sdk/types.js";
11
+
12
+ describe("MCP Protocol Integration", () => {
13
+ // These tests verify the MCP protocol layer handles requests correctly
14
+ // and that parameter schemas are properly converted to JSON schemas
15
+ it("should handle Initialize request", async () => {
16
+ const request: InitializeRequest = {
17
+ method: "initialize",
18
+ params: {
19
+ protocolVersion: "2024-11-05",
20
+ capabilities: {
21
+ tools: {},
22
+ },
23
+ clientInfo: {
24
+ name: "test-client",
25
+ version: "1.0.0",
26
+ },
27
+ },
28
+ };
29
+
30
+ // Parse and validate the request
31
+ const parsed = InitializeRequestSchema.parse(request);
32
+ expect(parsed).toBeDefined();
33
+
34
+ // The server should have an initialize handler set up
35
+ const handlers = (server as any).server._requestHandlers;
36
+ expect(handlers).toBeDefined();
37
+ expect(handlers.get("initialize")).toBeDefined();
38
+ });
39
+
40
+ it("should handle ListTools request", async () => {
41
+ const request: ListToolsRequest = {
42
+ method: "tools/list",
43
+ params: {},
44
+ };
45
+
46
+ // Parse and validate the request
47
+ const parsed = ListToolsRequestSchema.parse(request);
48
+ expect(parsed).toBeDefined();
49
+
50
+ // The server should have a tools/list handler
51
+ const handlers = (server as any).server._requestHandlers;
52
+ expect(handlers.get("tools/list")).toBeDefined();
53
+
54
+ // Call the handler
55
+ const handler = handlers.get("tools/list");
56
+ const result = await handler(parsed, {});
57
+
58
+ expect(result).toBeDefined();
59
+ expect(result.tools).toBeInstanceOf(Array);
60
+ expect(result.tools).toHaveLength(2);
61
+
62
+ // Check the tools have proper JSON schemas
63
+ const docsTool = result.tools.find(
64
+ (t: any) => t.name === "assistantUIDocs",
65
+ );
66
+ expect(docsTool).toBeDefined();
67
+ expect(docsTool.inputSchema).toBeDefined();
68
+ expect(docsTool.inputSchema.type).toBe("object");
69
+ expect(docsTool.inputSchema.properties).toBeDefined();
70
+
71
+ const examplesTool = result.tools.find(
72
+ (t: any) => t.name === "assistantUIExamples",
73
+ );
74
+ expect(examplesTool).toBeDefined();
75
+ expect(examplesTool.inputSchema).toBeDefined();
76
+ expect(examplesTool.inputSchema.type).toBe("object");
77
+ expect(examplesTool.inputSchema.properties).toBeDefined();
78
+ });
79
+
80
+ it("should handle CallTool request for assistantUIDocs", async () => {
81
+ const request: CallToolRequest = {
82
+ method: "tools/call",
83
+ params: {
84
+ name: "assistantUIDocs",
85
+ arguments: {
86
+ paths: ["/"],
87
+ },
88
+ },
89
+ };
90
+
91
+ // Parse and validate the request
92
+ const parsed = CallToolRequestSchema.parse(request);
93
+ expect(parsed).toBeDefined();
94
+
95
+ // The server should have a tools/call handler
96
+ const handlers = (server as any).server._requestHandlers;
97
+ expect(handlers.get("tools/call")).toBeDefined();
98
+
99
+ // Call the handler through the MCP protocol layer
100
+ const handler = handlers.get("tools/call");
101
+ const result = await handler(parsed, {});
102
+
103
+ expect(result).toBeDefined();
104
+ expect(result.content).toBeDefined();
105
+ expect(result.content[0].type).toBe("text");
106
+ });
107
+
108
+ it("should handle CallTool request for assistantUIExamples with no arguments", async () => {
109
+ const request: CallToolRequest = {
110
+ method: "tools/call",
111
+ params: {
112
+ name: "assistantUIExamples",
113
+ arguments: {},
114
+ },
115
+ };
116
+
117
+ // Parse and validate the request
118
+ const parsed = CallToolRequestSchema.parse(request);
119
+ expect(parsed).toBeDefined();
120
+
121
+ // The server should have a tools/call handler
122
+ const handlers = (server as any).server._requestHandlers;
123
+ expect(handlers.get("tools/call")).toBeDefined();
124
+
125
+ // Call the handler
126
+ const handler = handlers.get("tools/call");
127
+ const result = await handler(parsed, {});
128
+
129
+ expect(result).toBeDefined();
130
+ expect(result.content).toBeDefined();
131
+ expect(result.content[0].type).toBe("text");
132
+ });
133
+ });
@@ -0,0 +1,81 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { testContext } from "./test-setup.js";
3
+
4
+ describe("Path Traversal Security", () => {
5
+ describe("assistantUIDocs tool", () => {
6
+ const maliciousPaths = [
7
+ "../../../../etc/passwd",
8
+ "../../../package.json",
9
+ "..\\..\\..\\windows\\system32",
10
+ "/etc/passwd",
11
+ "docs/../../../sensitive-file",
12
+ "./../../private/keys",
13
+ ];
14
+
15
+ maliciousPaths.forEach((path) => {
16
+ it(`should block path traversal attempt: ${path}`, async () => {
17
+ const result = await testContext.callTool("assistantUIDocs", {
18
+ paths: [path],
19
+ });
20
+
21
+ expect(result.error).toBeDefined();
22
+ expect(result.error).toContain("Invalid path");
23
+ expect(result.content).toBeUndefined();
24
+ });
25
+ });
26
+
27
+ it("should handle multiple paths with one malicious", async () => {
28
+ const result = await testContext.callTool("assistantUIDocs", {
29
+ paths: ["(docs)/index", "../../../../etc/passwd", "(docs)/guides"],
30
+ });
31
+
32
+ expect(result.results).toBeDefined();
33
+ expect(result.results).toHaveLength(3);
34
+
35
+ expect(result.results[0].found).toBe(true);
36
+ expect(result.results[1].error).toContain("Invalid path");
37
+ expect(result.results[2].found).toBe(true);
38
+ });
39
+ });
40
+
41
+ describe("assistantUIExamples tool", () => {
42
+ const maliciousExamples = [
43
+ "../../../../etc/passwd",
44
+ "../../../src/index",
45
+ "..\\..\\..\\config",
46
+ "/root/.ssh/id_rsa",
47
+ "examples/../../../private",
48
+ ];
49
+
50
+ maliciousExamples.forEach((example) => {
51
+ it(`should block path traversal attempt: ${example}`, async () => {
52
+ const result = await testContext.callTool("assistantUIExamples", {
53
+ example,
54
+ });
55
+
56
+ expect(result.error).toBeDefined();
57
+ expect(result.error).toContain("Example not found");
58
+ expect(result.content).toBeUndefined();
59
+ });
60
+ });
61
+ });
62
+
63
+ describe("Valid paths should still work", () => {
64
+ it("should allow valid documentation paths", async () => {
65
+ const result = await testContext.callTool("assistantUIDocs", {
66
+ paths: ["(docs)/index", "(reference)/api-reference/primitives/thread"],
67
+ });
68
+
69
+ expect(result.results).toBeDefined();
70
+ expect(result.results).toHaveLength(2);
71
+ expect(result.results.every((r: any) => r.found)).toBe(true);
72
+ });
73
+
74
+ it("should allow valid example names", async () => {
75
+ const result = await testContext.callTool("assistantUIExamples", {});
76
+
77
+ expect(result.examples).toBeDefined();
78
+ expect(Array.isArray(result.examples)).toBe(true);
79
+ });
80
+ });
81
+ });
@@ -0,0 +1,40 @@
1
+ import { beforeAll } from "vitest";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { PACKAGE_DIR } from "../../constants.js";
5
+ import { docsTools } from "../docs.js";
6
+ import { examplesTools } from "../examples.js";
7
+
8
+ const tools = {
9
+ assistantUIDocs: docsTools,
10
+ assistantUIExamples: examplesTools,
11
+ };
12
+
13
+ export const testContext = {
14
+ callTool: async (name: string, args: any) => {
15
+ const tool = tools[name as keyof typeof tools];
16
+ if (!tool) {
17
+ throw new Error(`Tool ${name} not found`);
18
+ }
19
+ const result = await tool.execute(args);
20
+
21
+ const text = result.content?.[0]?.text;
22
+ if (text === undefined) {
23
+ throw new Error(`Tool ${name} returned no content`);
24
+ }
25
+ try {
26
+ return JSON.parse(text);
27
+ } catch (error) {
28
+ throw new Error(
29
+ `Tool ${name} returned invalid JSON. Output: ${text}\nParse error: ${error instanceof Error ? error.message : String(error)}`,
30
+ );
31
+ }
32
+ },
33
+ };
34
+
35
+ beforeAll(() => {
36
+ const docsPath = join(PACKAGE_DIR, ".docs");
37
+ if (!existsSync(docsPath)) {
38
+ throw new Error("Documentation not prepared. Run: pnpm build");
39
+ }
40
+ });
@@ -0,0 +1,20 @@
1
+ import { IS_PREPARE_MODE } from "../constants.js";
2
+
3
+ export const logger = {
4
+ debug: (message: string, ...args: any[]) => {
5
+ if (process.env["DEBUG"]) {
6
+ console.debug(`[DEBUG] ${message}`, ...args);
7
+ }
8
+ },
9
+ info: (message: string, ...args: any[]) => {
10
+ if (IS_PREPARE_MODE) {
11
+ console.log(`[INFO] ${message}`, ...args);
12
+ }
13
+ },
14
+ error: (message: string, ...args: any[]) => {
15
+ console.error(`[ERROR] ${message}`, ...args);
16
+ },
17
+ warn: (message: string, ...args: any[]) => {
18
+ console.warn(`[WARN] ${message}`, ...args);
19
+ },
20
+ };
@@ -0,0 +1,12 @@
1
+ export function formatMCPResponse(data: any): {
2
+ content: Array<{ type: "text"; text: string }>;
3
+ } {
4
+ return {
5
+ content: [
6
+ {
7
+ type: "text",
8
+ text: typeof data === "string" ? data : JSON.stringify(data, null, 2),
9
+ },
10
+ ],
11
+ };
12
+ }