@xano/developer-mcp 1.0.56 → 1.0.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -10
- package/dist/cli_docs/index.d.ts +10 -0
- package/dist/cli_docs/index.js +10 -0
- package/dist/index.js +2 -18
- package/dist/lib.d.ts +2 -2
- package/dist/lib.js +1 -1
- package/dist/meta_api_docs/index.d.ts +10 -0
- package/dist/meta_api_docs/index.js +10 -0
- package/dist/tools/cli_docs.js +1 -0
- package/dist/tools/index.d.ts +138 -2
- package/dist/tools/index.js +60 -8
- package/dist/tools/index.test.d.ts +1 -0
- package/dist/tools/index.test.js +179 -0
- package/dist/tools/mcp_version.d.ts +10 -0
- package/dist/tools/mcp_version.js +13 -1
- package/dist/tools/meta_api_docs.js +1 -0
- package/dist/tools/types.d.ts +15 -6
- package/dist/tools/types.js +10 -4
- package/dist/tools/validate_xanoscript.d.ts +14 -0
- package/dist/tools/validate_xanoscript.js +22 -5
- package/dist/tools/xanoscript_docs.d.ts +32 -1
- package/dist/tools/xanoscript_docs.js +66 -2
- package/dist/tools/xanoscript_docs.test.d.ts +1 -0
- package/dist/tools/xanoscript_docs.test.js +197 -0
- package/dist/xanoscript.d.ts +17 -1
- package/dist/xanoscript.js +99 -189
- package/dist/xanoscript.test.js +75 -1
- package/dist/xanoscript_docs/docs_index.json +131 -60
- package/package.json +2 -2
|
@@ -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
|
+
});
|
package/dist/xanoscript.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/xanoscript.js
CHANGED
|
@@ -7,191 +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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
applyTo: ["functions/**/*.xs", "apis/**/*.xs", "tools/**/*.xs", "agents/**/*.xs"],
|
|
47
|
-
description: "Data types, input blocks, and validation",
|
|
48
|
-
},
|
|
49
|
-
tables: {
|
|
50
|
-
file: "tables.md",
|
|
51
|
-
applyTo: ["tables/*.xs"],
|
|
52
|
-
description: "Database schema definitions with indexes and relationships",
|
|
53
|
-
},
|
|
54
|
-
functions: {
|
|
55
|
-
file: "functions.md",
|
|
56
|
-
applyTo: ["functions/**/*.xs"],
|
|
57
|
-
description: "Reusable function stacks with inputs and responses",
|
|
58
|
-
},
|
|
59
|
-
apis: {
|
|
60
|
-
file: "apis.md",
|
|
61
|
-
applyTo: ["apis/**/*.xs"],
|
|
62
|
-
description: "HTTP endpoint definitions with authentication and CRUD patterns",
|
|
63
|
-
},
|
|
64
|
-
tasks: {
|
|
65
|
-
file: "tasks.md",
|
|
66
|
-
applyTo: ["tasks/*.xs"],
|
|
67
|
-
description: "Scheduled and cron jobs",
|
|
68
|
-
},
|
|
69
|
-
triggers: {
|
|
70
|
-
file: "triggers.md",
|
|
71
|
-
applyTo: ["triggers/**/*.xs"],
|
|
72
|
-
description: "Event-driven handlers (table, realtime, workspace, agent, MCP)",
|
|
73
|
-
},
|
|
74
|
-
database: {
|
|
75
|
-
file: "database.md",
|
|
76
|
-
applyTo: ["functions/**/*.xs", "apis/**/*.xs", "tasks/*.xs", "tools/**/*.xs"],
|
|
77
|
-
description: "All db.* operations: query, get, add, edit, patch, delete",
|
|
78
|
-
},
|
|
79
|
-
agents: {
|
|
80
|
-
file: "agents.md",
|
|
81
|
-
applyTo: ["agents/**/*.xs"],
|
|
82
|
-
description: "AI agent configuration with LLM providers and tools",
|
|
83
|
-
},
|
|
84
|
-
tools: {
|
|
85
|
-
file: "tools.md",
|
|
86
|
-
applyTo: ["tools/**/*.xs"],
|
|
87
|
-
description: "AI tools for agents and MCP servers",
|
|
88
|
-
},
|
|
89
|
-
"mcp-servers": {
|
|
90
|
-
file: "mcp-servers.md",
|
|
91
|
-
applyTo: ["mcp_servers/**/*.xs"],
|
|
92
|
-
description: "MCP server definitions exposing tools",
|
|
93
|
-
},
|
|
94
|
-
"unit-testing": {
|
|
95
|
-
file: "unit-testing.md",
|
|
96
|
-
applyTo: ["functions/**/*.xs", "apis/**/*.xs", "middleware/**/*.xs"],
|
|
97
|
-
description: "Unit tests, mocks, and assertions within functions, APIs, and middleware",
|
|
98
|
-
},
|
|
99
|
-
"workflow-tests": {
|
|
100
|
-
file: "workflow-tests.md",
|
|
101
|
-
applyTo: ["workflow_test/**/*.xs"],
|
|
102
|
-
description: "End-to-end workflow tests with data source selection and tags",
|
|
103
|
-
},
|
|
104
|
-
integrations: {
|
|
105
|
-
file: "integrations.md",
|
|
106
|
-
applyTo: [],
|
|
107
|
-
description: "External service integrations index - see sub-topics for details",
|
|
108
|
-
},
|
|
109
|
-
"integrations/cloud-storage": {
|
|
110
|
-
file: "integrations/cloud-storage.md",
|
|
111
|
-
applyTo: [],
|
|
112
|
-
description: "AWS S3, Azure Blob, and GCP Storage operations",
|
|
113
|
-
},
|
|
114
|
-
"integrations/search": {
|
|
115
|
-
file: "integrations/search.md",
|
|
116
|
-
applyTo: [],
|
|
117
|
-
description: "Elasticsearch, OpenSearch, and Algolia search operations",
|
|
118
|
-
},
|
|
119
|
-
"integrations/redis": {
|
|
120
|
-
file: "integrations/redis.md",
|
|
121
|
-
applyTo: [],
|
|
122
|
-
description: "Redis caching, rate limiting, and queue operations",
|
|
123
|
-
},
|
|
124
|
-
"integrations/external-apis": {
|
|
125
|
-
file: "integrations/external-apis.md",
|
|
126
|
-
applyTo: [],
|
|
127
|
-
description: "HTTP requests with api.request patterns",
|
|
128
|
-
},
|
|
129
|
-
"integrations/utilities": {
|
|
130
|
-
file: "integrations/utilities.md",
|
|
131
|
-
applyTo: [],
|
|
132
|
-
description: "Local storage, email, zip, and Lambda utilities",
|
|
133
|
-
},
|
|
134
|
-
frontend: {
|
|
135
|
-
file: "frontend.md",
|
|
136
|
-
applyTo: ["static/**/*"],
|
|
137
|
-
description: "Static frontend development and deployment",
|
|
138
|
-
},
|
|
139
|
-
run: {
|
|
140
|
-
file: "run.md",
|
|
141
|
-
applyTo: ["run/**/*.xs"],
|
|
142
|
-
description: "Run job and service configurations for the Xano Job Runner",
|
|
143
|
-
},
|
|
144
|
-
addons: {
|
|
145
|
-
file: "addons.md",
|
|
146
|
-
applyTo: ["addons/*.xs"],
|
|
147
|
-
description: "Reusable subqueries for fetching related data",
|
|
148
|
-
},
|
|
149
|
-
debugging: {
|
|
150
|
-
file: "debugging.md",
|
|
151
|
-
applyTo: ["**/*.xs"],
|
|
152
|
-
description: "Logging, inspecting, and debugging XanoScript execution",
|
|
153
|
-
},
|
|
154
|
-
performance: {
|
|
155
|
-
file: "performance.md",
|
|
156
|
-
applyTo: ["functions/**/*.xs", "apis/**/*.xs"],
|
|
157
|
-
description: "Performance optimization best practices",
|
|
158
|
-
},
|
|
159
|
-
realtime: {
|
|
160
|
-
file: "realtime.md",
|
|
161
|
-
applyTo: ["triggers/**/*.xs"],
|
|
162
|
-
description: "Real-time channels and events for push updates",
|
|
163
|
-
},
|
|
164
|
-
schema: {
|
|
165
|
-
file: "schema.md",
|
|
166
|
-
applyTo: [],
|
|
167
|
-
description: "Runtime schema parsing and validation",
|
|
168
|
-
},
|
|
169
|
-
security: {
|
|
170
|
-
file: "security.md",
|
|
171
|
-
applyTo: ["functions/**/*.xs", "apis/**/*.xs"],
|
|
172
|
-
description: "Security best practices for authentication and authorization",
|
|
173
|
-
},
|
|
174
|
-
streaming: {
|
|
175
|
-
file: "streaming.md",
|
|
176
|
-
applyTo: [],
|
|
177
|
-
description: "Streaming data from files, requests, and responses",
|
|
178
|
-
},
|
|
179
|
-
middleware: {
|
|
180
|
-
file: "middleware.md",
|
|
181
|
-
applyTo: ["middleware/**/*.xs"],
|
|
182
|
-
description: "Request/response interceptors for functions, queries, tasks, and tools",
|
|
183
|
-
},
|
|
184
|
-
branch: {
|
|
185
|
-
file: "branch.md",
|
|
186
|
-
applyTo: ["branch.xs"],
|
|
187
|
-
description: "Branch-level settings: middleware, history retention, visual styling",
|
|
188
|
-
},
|
|
189
|
-
workspace: {
|
|
190
|
-
file: "workspace.md",
|
|
191
|
-
applyTo: ["workspace/**/*.xs"],
|
|
192
|
-
description: "Workspace-level settings: environment variables, preferences, realtime",
|
|
193
|
-
},
|
|
194
|
-
};
|
|
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
|
+
}
|
|
195
47
|
// =============================================================================
|
|
196
48
|
// Core Functions
|
|
197
49
|
// =============================================================================
|
|
@@ -239,9 +91,14 @@ export function extractQuickReference(content, topic) {
|
|
|
239
91
|
* Get the documentation version from the version.json file
|
|
240
92
|
*/
|
|
241
93
|
export function getXanoscriptDocsVersion(docsPath) {
|
|
94
|
+
if (_versionCache && _versionCache.path === docsPath) {
|
|
95
|
+
return _versionCache.version;
|
|
96
|
+
}
|
|
242
97
|
try {
|
|
243
|
-
const versionFile =
|
|
244
|
-
|
|
98
|
+
const versionFile = cachedReadFile(join(docsPath, "version.json"));
|
|
99
|
+
const version = JSON.parse(versionFile).version || "unknown";
|
|
100
|
+
_versionCache = { path: docsPath, version };
|
|
101
|
+
return version;
|
|
245
102
|
}
|
|
246
103
|
catch {
|
|
247
104
|
return "unknown";
|
|
@@ -251,13 +108,39 @@ export function getXanoscriptDocsVersion(docsPath) {
|
|
|
251
108
|
* Read XanoScript documentation with v2 structure
|
|
252
109
|
*/
|
|
253
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
|
+
}
|
|
254
138
|
// Default to quick_reference for file_path mode (loads many topics),
|
|
255
139
|
// full for topic mode (loads single topic)
|
|
256
140
|
const mode = args?.mode || (args?.file_path ? "quick_reference" : "full");
|
|
257
|
-
const version = getXanoscriptDocsVersion(docsPath);
|
|
258
141
|
// Default: return README
|
|
259
142
|
if (!args?.topic && !args?.file_path) {
|
|
260
|
-
const readme =
|
|
143
|
+
const readme = cachedReadFile(join(docsPath, "README.md"));
|
|
261
144
|
return `${readme}\n\n---\nDocumentation version: ${version}`;
|
|
262
145
|
}
|
|
263
146
|
// Context-aware: return docs matching file pattern
|
|
@@ -272,7 +155,7 @@ export function readXanoscriptDocsV2(docsPath, args) {
|
|
|
272
155
|
}
|
|
273
156
|
const docs = topics.map((t) => {
|
|
274
157
|
const config = XANOSCRIPT_DOCS_V2[t];
|
|
275
|
-
const content =
|
|
158
|
+
const content = cachedReadFile(join(docsPath, config.file));
|
|
276
159
|
return mode === "quick_reference"
|
|
277
160
|
? extractQuickReference(content, t)
|
|
278
161
|
: content;
|
|
@@ -286,12 +169,39 @@ export function readXanoscriptDocsV2(docsPath, args) {
|
|
|
286
169
|
const availableTopics = Object.keys(XANOSCRIPT_DOCS_V2).join(", ");
|
|
287
170
|
throw new Error(`Unknown topic "${args.topic}".\n\nAvailable topics: ${availableTopics}`);
|
|
288
171
|
}
|
|
289
|
-
const content =
|
|
172
|
+
const content = cachedReadFile(join(docsPath, config.file));
|
|
290
173
|
const doc = mode === "quick_reference"
|
|
291
174
|
? extractQuickReference(content, args.topic)
|
|
292
175
|
: content;
|
|
293
176
|
return `${doc}\n\n---\nDocumentation version: ${version}`;
|
|
294
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
|
+
// =============================================================================
|
|
295
205
|
/**
|
|
296
206
|
* Get available topic names
|
|
297
207
|
*/
|
package/dist/xanoscript.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import { join, dirname } from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
-
import { XANOSCRIPT_DOCS_V2, getDocsForFilePath, extractQuickReference, getXanoscriptDocsVersion, readXanoscriptDocsV2, getTopicNames, getTopicDescriptions, } from "./xanoscript.js";
|
|
4
|
+
import { XANOSCRIPT_DOCS_V2, getDocsForFilePath, extractQuickReference, getXanoscriptDocsVersion, readXanoscriptDocsV2, readXanoscriptDocsStructured, getTopicNames, getTopicDescriptions, } from "./xanoscript.js";
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = dirname(__filename);
|
|
7
7
|
const DOCS_PATH = join(__dirname, "xanoscript_docs");
|
|
@@ -316,6 +316,30 @@ Even more content.
|
|
|
316
316
|
it("should throw for invalid docs path", () => {
|
|
317
317
|
expect(() => readXanoscriptDocsV2("/nonexistent/path", { topic: "syntax" })).toThrow();
|
|
318
318
|
});
|
|
319
|
+
it("should return compact index with mode: index", () => {
|
|
320
|
+
const result = readXanoscriptDocsV2(DOCS_PATH, { mode: "index" });
|
|
321
|
+
expect(result).toContain("# XanoScript Documentation Index");
|
|
322
|
+
expect(result).toContain("Version:");
|
|
323
|
+
expect(result).toContain("Topics:");
|
|
324
|
+
expect(result).toContain("| Topic | Description | Size |");
|
|
325
|
+
// Should contain some known topics
|
|
326
|
+
expect(result).toContain("| syntax |");
|
|
327
|
+
expect(result).toContain("| essentials |");
|
|
328
|
+
expect(result).toContain("| database |");
|
|
329
|
+
// Should contain KB size indicators
|
|
330
|
+
expect(result).toContain("KB |");
|
|
331
|
+
});
|
|
332
|
+
it("should return index mode without requiring topic or file_path", () => {
|
|
333
|
+
const result = readXanoscriptDocsV2(DOCS_PATH, { mode: "index" });
|
|
334
|
+
// Index mode ignores topic/file_path — just returns the listing
|
|
335
|
+
expect(result).toContain("# XanoScript Documentation Index");
|
|
336
|
+
});
|
|
337
|
+
it("should return index that is significantly smaller than full docs", () => {
|
|
338
|
+
const index = readXanoscriptDocsV2(DOCS_PATH, { mode: "index" });
|
|
339
|
+
const full = readXanoscriptDocsV2(DOCS_PATH, { topic: "syntax", mode: "full" });
|
|
340
|
+
// Index should be compact — smaller than even a single full topic
|
|
341
|
+
expect(index.length).toBeLessThan(full.length);
|
|
342
|
+
});
|
|
319
343
|
});
|
|
320
344
|
describe("getTopicNames", () => {
|
|
321
345
|
it("should return all topic names", () => {
|
|
@@ -347,4 +371,54 @@ Even more content.
|
|
|
347
371
|
expect(descriptions).toContain("Expressions, operators");
|
|
348
372
|
});
|
|
349
373
|
});
|
|
374
|
+
describe("readXanoscriptDocsStructured", () => {
|
|
375
|
+
it("should return array of TopicDoc objects", () => {
|
|
376
|
+
const result = readXanoscriptDocsStructured(DOCS_PATH, {
|
|
377
|
+
file_path: "apis/users/create.xs",
|
|
378
|
+
});
|
|
379
|
+
expect(Array.isArray(result)).toBe(true);
|
|
380
|
+
expect(result.length).toBeGreaterThan(0);
|
|
381
|
+
for (const doc of result) {
|
|
382
|
+
expect(doc).toHaveProperty("topic");
|
|
383
|
+
expect(doc).toHaveProperty("content");
|
|
384
|
+
expect(typeof doc.topic).toBe("string");
|
|
385
|
+
expect(typeof doc.content).toBe("string");
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
it("should match the same topics as getDocsForFilePath", () => {
|
|
389
|
+
const filePath = "apis/users/create.xs";
|
|
390
|
+
const expected = getDocsForFilePath(filePath);
|
|
391
|
+
const result = readXanoscriptDocsStructured(DOCS_PATH, { file_path: filePath });
|
|
392
|
+
const resultTopics = result.map((d) => d.topic);
|
|
393
|
+
expect(resultTopics).toEqual(expected);
|
|
394
|
+
});
|
|
395
|
+
it("should respect exclude_topics", () => {
|
|
396
|
+
const result = readXanoscriptDocsStructured(DOCS_PATH, {
|
|
397
|
+
file_path: "apis/users/create.xs",
|
|
398
|
+
exclude_topics: ["syntax", "essentials"],
|
|
399
|
+
});
|
|
400
|
+
const topics = result.map((d) => d.topic);
|
|
401
|
+
expect(topics).not.toContain("syntax");
|
|
402
|
+
expect(topics).not.toContain("essentials");
|
|
403
|
+
});
|
|
404
|
+
it("should throw when all topics are excluded", () => {
|
|
405
|
+
expect(() => readXanoscriptDocsStructured(DOCS_PATH, {
|
|
406
|
+
file_path: "branch.xs",
|
|
407
|
+
exclude_topics: ["syntax", "essentials", "debugging", "branch"],
|
|
408
|
+
})).toThrow("No documentation found");
|
|
409
|
+
});
|
|
410
|
+
it("should use quick_reference mode by default", () => {
|
|
411
|
+
const quickResult = readXanoscriptDocsStructured(DOCS_PATH, {
|
|
412
|
+
file_path: "tables/users.xs",
|
|
413
|
+
});
|
|
414
|
+
const fullResult = readXanoscriptDocsStructured(DOCS_PATH, {
|
|
415
|
+
file_path: "tables/users.xs",
|
|
416
|
+
mode: "full",
|
|
417
|
+
});
|
|
418
|
+
// Quick reference content should be shorter than full
|
|
419
|
+
const quickTotal = quickResult.reduce((sum, d) => sum + d.content.length, 0);
|
|
420
|
+
const fullTotal = fullResult.reduce((sum, d) => sum + d.content.length, 0);
|
|
421
|
+
expect(quickTotal).toBeLessThanOrEqual(fullTotal);
|
|
422
|
+
});
|
|
423
|
+
});
|
|
350
424
|
});
|