@xano/developer-mcp 1.0.54 → 1.0.57

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 (43) hide show
  1. package/README.md +42 -10
  2. package/dist/cli_docs/index.d.ts +10 -0
  3. package/dist/cli_docs/index.js +10 -0
  4. package/dist/index.js +2 -18
  5. package/dist/lib.d.ts +2 -2
  6. package/dist/lib.js +1 -1
  7. package/dist/meta_api_docs/index.d.ts +10 -0
  8. package/dist/meta_api_docs/index.js +10 -0
  9. package/dist/tools/cli_docs.js +1 -0
  10. package/dist/tools/index.d.ts +138 -2
  11. package/dist/tools/index.js +60 -8
  12. package/dist/tools/index.test.d.ts +1 -0
  13. package/dist/tools/index.test.js +179 -0
  14. package/dist/tools/mcp_version.d.ts +10 -0
  15. package/dist/tools/mcp_version.js +13 -1
  16. package/dist/tools/meta_api_docs.js +1 -0
  17. package/dist/tools/types.d.ts +15 -6
  18. package/dist/tools/types.js +10 -4
  19. package/dist/tools/validate_xanoscript.d.ts +14 -0
  20. package/dist/tools/validate_xanoscript.js +22 -5
  21. package/dist/tools/xanoscript_docs.d.ts +32 -1
  22. package/dist/tools/xanoscript_docs.js +69 -5
  23. package/dist/tools/xanoscript_docs.test.d.ts +1 -0
  24. package/dist/tools/xanoscript_docs.test.js +197 -0
  25. package/dist/xanoscript.d.ts +17 -1
  26. package/dist/xanoscript.js +99 -179
  27. package/dist/xanoscript.test.js +84 -8
  28. package/dist/xanoscript_docs/README.md +7 -7
  29. package/dist/xanoscript_docs/cheatsheet.md +4 -1
  30. package/dist/xanoscript_docs/database.md +2 -2
  31. package/dist/xanoscript_docs/docs_index.json +186 -108
  32. package/dist/xanoscript_docs/essentials.md +665 -0
  33. package/dist/xanoscript_docs/functions.md +1 -1
  34. package/dist/xanoscript_docs/middleware.md +5 -18
  35. package/dist/xanoscript_docs/quickstart.md +15 -6
  36. package/dist/xanoscript_docs/security.md +18 -43
  37. package/dist/xanoscript_docs/syntax/array-filters.md +238 -0
  38. package/dist/xanoscript_docs/syntax/functions.md +136 -0
  39. package/dist/xanoscript_docs/syntax/string-filters.md +188 -0
  40. package/dist/xanoscript_docs/syntax.md +92 -900
  41. package/dist/xanoscript_docs/triggers.md +1 -1
  42. package/dist/xanoscript_docs/types.md +1 -1
  43. package/package.json +1 -1
@@ -0,0 +1,197 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { join, dirname } from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { getXanoscriptDocsPath, setXanoscriptDocsPath, xanoscriptDocs, xanoscriptDocsTool, } from "./xanoscript_docs.js";
5
+ import { toMcpResponse } from "./types.js";
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+ const DOCS_PATH = join(__dirname, "..", "xanoscript_docs");
9
+ afterEach(() => {
10
+ // Reset to default so tests don't interfere with each other
11
+ setXanoscriptDocsPath(DOCS_PATH);
12
+ });
13
+ describe("getXanoscriptDocsPath", () => {
14
+ it("should return a string path", () => {
15
+ const path = getXanoscriptDocsPath();
16
+ expect(typeof path).toBe("string");
17
+ expect(path.length).toBeGreaterThan(0);
18
+ });
19
+ it("should return a path ending with xanoscript_docs", () => {
20
+ const path = getXanoscriptDocsPath();
21
+ expect(path).toMatch(/xanoscript_docs$/);
22
+ });
23
+ it("should return a consistent path on repeated calls", () => {
24
+ const path1 = getXanoscriptDocsPath();
25
+ const path2 = getXanoscriptDocsPath();
26
+ expect(path1).toBe(path2);
27
+ });
28
+ });
29
+ describe("setXanoscriptDocsPath", () => {
30
+ it("should override the docs path", () => {
31
+ const customPath = "/custom/docs/path";
32
+ setXanoscriptDocsPath(customPath);
33
+ expect(getXanoscriptDocsPath()).toBe(customPath);
34
+ });
35
+ it("should be used by xanoscriptDocs when set", () => {
36
+ setXanoscriptDocsPath(DOCS_PATH);
37
+ const result = xanoscriptDocs();
38
+ expect(result.documentation).toContain("Documentation version:");
39
+ });
40
+ });
41
+ describe("xanoscriptDocs", () => {
42
+ it("should return README when called with no args", () => {
43
+ const result = xanoscriptDocs();
44
+ expect(result).toHaveProperty("documentation");
45
+ expect(typeof result.documentation).toBe("string");
46
+ expect(result.documentation).toContain("Documentation version:");
47
+ });
48
+ it("should return specific topic documentation", () => {
49
+ const result = xanoscriptDocs({ topic: "syntax" });
50
+ expect(result.documentation).toContain("Documentation version:");
51
+ });
52
+ it("should return context-aware docs for file_path", () => {
53
+ const result = xanoscriptDocs({ file_path: "apis/users/create.xs" });
54
+ expect(result.documentation).toContain("XanoScript Documentation for:");
55
+ expect(result.documentation).toContain("Matched topics:");
56
+ });
57
+ it("should support quick_reference mode", () => {
58
+ const full = xanoscriptDocs({ topic: "syntax", mode: "full" });
59
+ const quick = xanoscriptDocs({ topic: "syntax", mode: "quick_reference" });
60
+ expect(quick.documentation.length).toBeLessThanOrEqual(full.documentation.length);
61
+ });
62
+ it("should throw for unknown topic", () => {
63
+ expect(() => xanoscriptDocs({ topic: "nonexistent" })).toThrow('Unknown topic "nonexistent"');
64
+ });
65
+ it("should throw for invalid docs path", () => {
66
+ setXanoscriptDocsPath("/nonexistent/path");
67
+ expect(() => xanoscriptDocs({ topic: "syntax" })).toThrow();
68
+ });
69
+ });
70
+ describe("xanoscriptDocsTool", () => {
71
+ it("should return success with data for valid call", () => {
72
+ const result = xanoscriptDocsTool();
73
+ expect(result.success).toBe(true);
74
+ expect(result.data).toBeDefined();
75
+ expect(typeof result.data).toBe("string");
76
+ expect(result.error).toBeUndefined();
77
+ });
78
+ it("should return success with topic docs", () => {
79
+ const result = xanoscriptDocsTool({ topic: "syntax" });
80
+ expect(result.success).toBe(true);
81
+ expect(result.data).toContain("Documentation version:");
82
+ });
83
+ it("should return error for unknown topic", () => {
84
+ const result = xanoscriptDocsTool({ topic: "nonexistent" });
85
+ expect(result.success).toBe(false);
86
+ expect(result.error).toBeDefined();
87
+ expect(result.error).toContain("Unknown topic");
88
+ });
89
+ it("should return error for invalid docs path", () => {
90
+ setXanoscriptDocsPath("/nonexistent/path");
91
+ const result = xanoscriptDocsTool({ topic: "syntax" });
92
+ expect(result.success).toBe(false);
93
+ expect(result.error).toBeDefined();
94
+ expect(result.error).toContain("Error retrieving XanoScript documentation");
95
+ });
96
+ it("should return multi-content array for file_path mode", () => {
97
+ const result = xanoscriptDocsTool({ file_path: "apis/users/create.xs" });
98
+ expect(result.success).toBe(true);
99
+ expect(Array.isArray(result.data)).toBe(true);
100
+ const data = result.data;
101
+ // First element is the header
102
+ expect(data[0]).toContain("XanoScript Documentation for: apis/users/create.xs");
103
+ expect(data[0]).toContain("Matched topics:");
104
+ expect(data[0]).toContain("Version:");
105
+ // Remaining elements are per-topic content
106
+ expect(data.length).toBeGreaterThan(1);
107
+ });
108
+ it("should return single string for topic mode", () => {
109
+ const result = xanoscriptDocsTool({ topic: "syntax" });
110
+ expect(result.success).toBe(true);
111
+ expect(typeof result.data).toBe("string");
112
+ expect(Array.isArray(result.data)).toBe(false);
113
+ });
114
+ it("should return single string for no-args mode", () => {
115
+ const result = xanoscriptDocsTool();
116
+ expect(result.success).toBe(true);
117
+ expect(typeof result.data).toBe("string");
118
+ expect(Array.isArray(result.data)).toBe(false);
119
+ });
120
+ it("should produce multiple MCP content blocks for file_path via toMcpResponse", () => {
121
+ const result = xanoscriptDocsTool({ file_path: "apis/users/create.xs" });
122
+ const mcpResponse = toMcpResponse(result);
123
+ expect(mcpResponse.isError).toBeUndefined();
124
+ // Should have multiple content blocks (header + N topics)
125
+ expect(mcpResponse.content.length).toBeGreaterThan(1);
126
+ // All blocks should be text type
127
+ for (const block of mcpResponse.content) {
128
+ expect(block.type).toBe("text");
129
+ expect(typeof block.text).toBe("string");
130
+ }
131
+ });
132
+ it("should produce single MCP content block for topic mode via toMcpResponse", () => {
133
+ const result = xanoscriptDocsTool({ topic: "syntax" });
134
+ const mcpResponse = toMcpResponse(result);
135
+ expect(mcpResponse.content).toHaveLength(1);
136
+ expect(mcpResponse.content[0].type).toBe("text");
137
+ });
138
+ it("should include structuredContent for file_path mode", () => {
139
+ const result = xanoscriptDocsTool({ file_path: "apis/users/create.xs" });
140
+ expect(result.success).toBe(true);
141
+ expect(result.structuredContent).toBeDefined();
142
+ expect(result.structuredContent).toHaveProperty("file_path", "apis/users/create.xs");
143
+ expect(result.structuredContent).toHaveProperty("mode", "quick_reference");
144
+ expect(result.structuredContent).toHaveProperty("version");
145
+ expect(result.structuredContent).toHaveProperty("topics");
146
+ expect(Array.isArray(result.structuredContent.topics)).toBe(true);
147
+ });
148
+ it("should include structuredContent for topic mode", () => {
149
+ const result = xanoscriptDocsTool({ topic: "syntax" });
150
+ expect(result.success).toBe(true);
151
+ expect(result.structuredContent).toBeDefined();
152
+ expect(result.structuredContent).toHaveProperty("documentation");
153
+ });
154
+ it("should include structuredContent in MCP response", () => {
155
+ const result = xanoscriptDocsTool({ file_path: "apis/users/create.xs" });
156
+ const mcpResponse = toMcpResponse(result);
157
+ expect(mcpResponse.structuredContent).toBeDefined();
158
+ expect(mcpResponse.structuredContent).toHaveProperty("file_path");
159
+ expect(mcpResponse.structuredContent).toHaveProperty("topics");
160
+ });
161
+ });
162
+ describe("xanoscriptDocs structured output", () => {
163
+ it("should include topics array for file_path mode", () => {
164
+ const result = xanoscriptDocs({ file_path: "apis/users/create.xs" });
165
+ expect(result.topics).toBeDefined();
166
+ expect(Array.isArray(result.topics)).toBe(true);
167
+ expect(result.topics.length).toBeGreaterThan(0);
168
+ });
169
+ it("should have topic and content fields in each TopicDoc", () => {
170
+ const result = xanoscriptDocs({ file_path: "apis/users/create.xs" });
171
+ for (const doc of result.topics) {
172
+ expect(doc).toHaveProperty("topic");
173
+ expect(doc).toHaveProperty("content");
174
+ expect(typeof doc.topic).toBe("string");
175
+ expect(typeof doc.content).toBe("string");
176
+ expect(doc.content.length).toBeGreaterThan(0);
177
+ }
178
+ });
179
+ it("should not include topics array for topic mode", () => {
180
+ const result = xanoscriptDocs({ topic: "syntax" });
181
+ expect(result.topics).toBeUndefined();
182
+ });
183
+ it("should not include topics array for no-args mode", () => {
184
+ const result = xanoscriptDocs();
185
+ expect(result.topics).toBeUndefined();
186
+ });
187
+ it("should respect exclude_topics in structured output", () => {
188
+ const result = xanoscriptDocs({
189
+ file_path: "apis/users/create.xs",
190
+ exclude_topics: ["syntax", "essentials"],
191
+ });
192
+ expect(result.topics).toBeDefined();
193
+ const topicNames = result.topics.map((d) => d.topic);
194
+ expect(topicNames).not.toContain("syntax");
195
+ expect(topicNames).not.toContain("essentials");
196
+ });
197
+ });
@@ -12,10 +12,15 @@ export interface DocConfig {
12
12
  export interface XanoscriptDocsArgs {
13
13
  topic?: string;
14
14
  file_path?: string;
15
- mode?: "full" | "quick_reference";
15
+ mode?: "full" | "quick_reference" | "index";
16
16
  exclude_topics?: string[];
17
17
  }
18
18
  export declare const XANOSCRIPT_DOCS_V2: Record<string, DocConfig>;
19
+ /**
20
+ * Clear all cached documentation content and version data.
21
+ * Useful for testing or when docs files change at runtime.
22
+ */
23
+ export declare function clearDocsCache(): void;
19
24
  /**
20
25
  * Get list of topics that apply to a given file path based on applyTo patterns
21
26
  */
@@ -32,6 +37,17 @@ export declare function getXanoscriptDocsVersion(docsPath: string): string;
32
37
  * Read XanoScript documentation with v2 structure
33
38
  */
34
39
  export declare function readXanoscriptDocsV2(docsPath: string, args?: XanoscriptDocsArgs): string;
40
+ export interface TopicDoc {
41
+ topic: string;
42
+ content: string;
43
+ }
44
+ /**
45
+ * Read documentation as structured per-topic entries for file_path mode.
46
+ * Returns each matched topic as a separate object for multi-content MCP responses.
47
+ */
48
+ export declare function readXanoscriptDocsStructured(docsPath: string, args: XanoscriptDocsArgs & {
49
+ file_path: string;
50
+ }): TopicDoc[];
35
51
  /**
36
52
  * Get available topic names
37
53
  */
@@ -7,181 +7,43 @@
7
7
  import { readFileSync } from "fs";
8
8
  import { join } from "path";
9
9
  import { minimatch } from "minimatch";
10
+ import docsIndex from "./xanoscript_docs/docs_index.json" with { type: "json" };
10
11
  // =============================================================================
11
- // Documentation Configuration
12
+ // Documentation Configuration (loaded from docs_index.json)
12
13
  // =============================================================================
13
- export const XANOSCRIPT_DOCS_V2 = {
14
- readme: {
15
- file: "README.md",
16
- applyTo: [],
17
- description: "XanoScript overview, workspace structure, and quick reference",
18
- },
19
- cheatsheet: {
20
- file: "cheatsheet.md",
21
- applyTo: ["**/*.xs"],
22
- description: "Quick reference for 20 most common XanoScript patterns",
23
- },
24
- syntax: {
25
- file: "syntax.md",
26
- applyTo: ["**/*.xs"],
27
- description: "Expressions, operators, and filters for all XanoScript code",
28
- },
29
- quickstart: {
30
- file: "quickstart.md",
31
- applyTo: ["**/*.xs"],
32
- description: "Common patterns, quick reference, and common mistakes to avoid",
33
- },
34
- types: {
35
- file: "types.md",
36
- applyTo: ["functions/**/*.xs", "apis/**/*.xs", "tools/**/*.xs", "agents/**/*.xs"],
37
- description: "Data types, input blocks, and validation",
38
- },
39
- tables: {
40
- file: "tables.md",
41
- applyTo: ["tables/*.xs"],
42
- description: "Database schema definitions with indexes and relationships",
43
- },
44
- functions: {
45
- file: "functions.md",
46
- applyTo: ["functions/**/*.xs"],
47
- description: "Reusable function stacks with inputs and responses",
48
- },
49
- apis: {
50
- file: "apis.md",
51
- applyTo: ["apis/**/*.xs"],
52
- description: "HTTP endpoint definitions with authentication and CRUD patterns",
53
- },
54
- tasks: {
55
- file: "tasks.md",
56
- applyTo: ["tasks/*.xs"],
57
- description: "Scheduled and cron jobs",
58
- },
59
- triggers: {
60
- file: "triggers.md",
61
- applyTo: ["triggers/**/*.xs"],
62
- description: "Event-driven handlers (table, realtime, workspace, agent, MCP)",
63
- },
64
- database: {
65
- file: "database.md",
66
- applyTo: ["functions/**/*.xs", "apis/**/*.xs", "tasks/*.xs", "tools/**/*.xs"],
67
- description: "All db.* operations: query, get, add, edit, patch, delete",
68
- },
69
- agents: {
70
- file: "agents.md",
71
- applyTo: ["agents/**/*.xs"],
72
- description: "AI agent configuration with LLM providers and tools",
73
- },
74
- tools: {
75
- file: "tools.md",
76
- applyTo: ["tools/**/*.xs"],
77
- description: "AI tools for agents and MCP servers",
78
- },
79
- "mcp-servers": {
80
- file: "mcp-servers.md",
81
- applyTo: ["mcp_servers/**/*.xs"],
82
- description: "MCP server definitions exposing tools",
83
- },
84
- "unit-testing": {
85
- file: "unit-testing.md",
86
- applyTo: ["functions/**/*.xs", "apis/**/*.xs", "middleware/**/*.xs"],
87
- description: "Unit tests, mocks, and assertions within functions, APIs, and middleware",
88
- },
89
- "workflow-tests": {
90
- file: "workflow-tests.md",
91
- applyTo: ["workflow_test/**/*.xs"],
92
- description: "End-to-end workflow tests with data source selection and tags",
93
- },
94
- integrations: {
95
- file: "integrations.md",
96
- applyTo: [],
97
- description: "External service integrations index - see sub-topics for details",
98
- },
99
- "integrations/cloud-storage": {
100
- file: "integrations/cloud-storage.md",
101
- applyTo: [],
102
- description: "AWS S3, Azure Blob, and GCP Storage operations",
103
- },
104
- "integrations/search": {
105
- file: "integrations/search.md",
106
- applyTo: [],
107
- description: "Elasticsearch, OpenSearch, and Algolia search operations",
108
- },
109
- "integrations/redis": {
110
- file: "integrations/redis.md",
111
- applyTo: [],
112
- description: "Redis caching, rate limiting, and queue operations",
113
- },
114
- "integrations/external-apis": {
115
- file: "integrations/external-apis.md",
116
- applyTo: [],
117
- description: "HTTP requests with api.request patterns",
118
- },
119
- "integrations/utilities": {
120
- file: "integrations/utilities.md",
121
- applyTo: [],
122
- description: "Local storage, email, zip, and Lambda utilities",
123
- },
124
- frontend: {
125
- file: "frontend.md",
126
- applyTo: ["static/**/*"],
127
- description: "Static frontend development and deployment",
128
- },
129
- run: {
130
- file: "run.md",
131
- applyTo: ["run/**/*.xs"],
132
- description: "Run job and service configurations for the Xano Job Runner",
133
- },
134
- addons: {
135
- file: "addons.md",
136
- applyTo: ["addons/*.xs"],
137
- description: "Reusable subqueries for fetching related data",
138
- },
139
- debugging: {
140
- file: "debugging.md",
141
- applyTo: ["**/*.xs"],
142
- description: "Logging, inspecting, and debugging XanoScript execution",
143
- },
144
- performance: {
145
- file: "performance.md",
146
- applyTo: ["functions/**/*.xs", "apis/**/*.xs"],
147
- description: "Performance optimization best practices",
148
- },
149
- realtime: {
150
- file: "realtime.md",
151
- applyTo: ["triggers/**/*.xs"],
152
- description: "Real-time channels and events for push updates",
153
- },
154
- schema: {
155
- file: "schema.md",
156
- applyTo: [],
157
- description: "Runtime schema parsing and validation",
158
- },
159
- security: {
160
- file: "security.md",
161
- applyTo: ["functions/**/*.xs", "apis/**/*.xs"],
162
- description: "Security best practices for authentication and authorization",
163
- },
164
- streaming: {
165
- file: "streaming.md",
166
- applyTo: [],
167
- description: "Streaming data from files, requests, and responses",
168
- },
169
- middleware: {
170
- file: "middleware.md",
171
- applyTo: ["middleware/**/*.xs"],
172
- description: "Request/response interceptors for functions, queries, tasks, and tools",
173
- },
174
- branch: {
175
- file: "branch.md",
176
- applyTo: ["branch.xs"],
177
- description: "Branch-level settings: middleware, history retention, visual styling",
178
- },
179
- workspace: {
180
- file: "workspace.md",
181
- applyTo: ["workspace/**/*.xs"],
182
- description: "Workspace-level settings: environment variables, preferences, realtime",
183
- },
184
- };
14
+ function buildDocsConfig() {
15
+ const config = {};
16
+ for (const [key, topic] of Object.entries(docsIndex.topics)) {
17
+ config[key] = {
18
+ file: topic.file,
19
+ applyTo: topic.applyTo,
20
+ description: topic.description,
21
+ };
22
+ }
23
+ return config;
24
+ }
25
+ export const XANOSCRIPT_DOCS_V2 = buildDocsConfig();
26
+ // =============================================================================
27
+ // Content Cache
28
+ // =============================================================================
29
+ const _fileCache = new Map();
30
+ let _versionCache = null;
31
+ /**
32
+ * Clear all cached documentation content and version data.
33
+ * Useful for testing or when docs files change at runtime.
34
+ */
35
+ export function clearDocsCache() {
36
+ _fileCache.clear();
37
+ _versionCache = null;
38
+ }
39
+ function cachedReadFile(filePath) {
40
+ const cached = _fileCache.get(filePath);
41
+ if (cached !== undefined)
42
+ return cached;
43
+ const content = readFileSync(filePath, "utf-8");
44
+ _fileCache.set(filePath, content);
45
+ return content;
46
+ }
185
47
  // =============================================================================
186
48
  // Core Functions
187
49
  // =============================================================================
@@ -229,9 +91,14 @@ export function extractQuickReference(content, topic) {
229
91
  * Get the documentation version from the version.json file
230
92
  */
231
93
  export function getXanoscriptDocsVersion(docsPath) {
94
+ if (_versionCache && _versionCache.path === docsPath) {
95
+ return _versionCache.version;
96
+ }
232
97
  try {
233
- const versionFile = readFileSync(join(docsPath, "version.json"), "utf-8");
234
- return JSON.parse(versionFile).version || "unknown";
98
+ const versionFile = cachedReadFile(join(docsPath, "version.json"));
99
+ const version = JSON.parse(versionFile).version || "unknown";
100
+ _versionCache = { path: docsPath, version };
101
+ return version;
235
102
  }
236
103
  catch {
237
104
  return "unknown";
@@ -241,13 +108,39 @@ export function getXanoscriptDocsVersion(docsPath) {
241
108
  * Read XanoScript documentation with v2 structure
242
109
  */
243
110
  export function readXanoscriptDocsV2(docsPath, args) {
111
+ const version = getXanoscriptDocsVersion(docsPath);
112
+ // Index mode: return compact topic listing with byte sizes
113
+ if (args?.mode === "index") {
114
+ const rows = Object.entries(XANOSCRIPT_DOCS_V2).map(([name, config]) => {
115
+ let size;
116
+ try {
117
+ size = cachedReadFile(join(docsPath, config.file)).length;
118
+ }
119
+ catch {
120
+ size = 0;
121
+ }
122
+ const sizeKb = (size / 1024).toFixed(1);
123
+ return `| ${name} | ${config.description} | ${sizeKb} KB |`;
124
+ });
125
+ return [
126
+ `# XanoScript Documentation Index`,
127
+ ``,
128
+ `Version: ${version}`,
129
+ `Topics: ${rows.length}`,
130
+ ``,
131
+ `| Topic | Description | Size |`,
132
+ `|-------|-------------|------|`,
133
+ ...rows,
134
+ ``,
135
+ `Use topic='<name>' to load a specific topic. Use mode='quick_reference' for compact output.`,
136
+ ].join("\n");
137
+ }
244
138
  // Default to quick_reference for file_path mode (loads many topics),
245
139
  // full for topic mode (loads single topic)
246
140
  const mode = args?.mode || (args?.file_path ? "quick_reference" : "full");
247
- const version = getXanoscriptDocsVersion(docsPath);
248
141
  // Default: return README
249
142
  if (!args?.topic && !args?.file_path) {
250
- const readme = readFileSync(join(docsPath, "README.md"), "utf-8");
143
+ const readme = cachedReadFile(join(docsPath, "README.md"));
251
144
  return `${readme}\n\n---\nDocumentation version: ${version}`;
252
145
  }
253
146
  // Context-aware: return docs matching file pattern
@@ -262,7 +155,7 @@ export function readXanoscriptDocsV2(docsPath, args) {
262
155
  }
263
156
  const docs = topics.map((t) => {
264
157
  const config = XANOSCRIPT_DOCS_V2[t];
265
- const content = readFileSync(join(docsPath, config.file), "utf-8");
158
+ const content = cachedReadFile(join(docsPath, config.file));
266
159
  return mode === "quick_reference"
267
160
  ? extractQuickReference(content, t)
268
161
  : content;
@@ -276,12 +169,39 @@ export function readXanoscriptDocsV2(docsPath, args) {
276
169
  const availableTopics = Object.keys(XANOSCRIPT_DOCS_V2).join(", ");
277
170
  throw new Error(`Unknown topic "${args.topic}".\n\nAvailable topics: ${availableTopics}`);
278
171
  }
279
- const content = readFileSync(join(docsPath, config.file), "utf-8");
172
+ const content = cachedReadFile(join(docsPath, config.file));
280
173
  const doc = mode === "quick_reference"
281
174
  ? extractQuickReference(content, args.topic)
282
175
  : content;
283
176
  return `${doc}\n\n---\nDocumentation version: ${version}`;
284
177
  }
178
+ /**
179
+ * Read documentation as structured per-topic entries for file_path mode.
180
+ * Returns each matched topic as a separate object for multi-content MCP responses.
181
+ */
182
+ export function readXanoscriptDocsStructured(docsPath, args) {
183
+ const mode = args.mode || "quick_reference";
184
+ let topics = getDocsForFilePath(args.file_path);
185
+ if (args.exclude_topics && args.exclude_topics.length > 0) {
186
+ topics = topics.filter((t) => !args.exclude_topics.includes(t));
187
+ }
188
+ if (topics.length === 0) {
189
+ throw new Error(`No documentation found for file pattern: ${args.file_path}\n\nAvailable topics: ${Object.keys(XANOSCRIPT_DOCS_V2).join(", ")}`);
190
+ }
191
+ return topics.map((t) => {
192
+ const config = XANOSCRIPT_DOCS_V2[t];
193
+ const content = cachedReadFile(join(docsPath, config.file));
194
+ return {
195
+ topic: t,
196
+ content: mode === "quick_reference"
197
+ ? extractQuickReference(content, t)
198
+ : content,
199
+ };
200
+ });
201
+ }
202
+ // =============================================================================
203
+ // Topic Metadata
204
+ // =============================================================================
285
205
  /**
286
206
  * Get available topic names
287
207
  */