@xano/developer-mcp 1.0.0 → 1.0.2

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 (46) hide show
  1. package/README.md +96 -31
  2. package/dist/index.js +335 -222
  3. package/dist/templates/init-workspace.d.ts +10 -0
  4. package/dist/templates/init-workspace.js +292 -0
  5. package/dist/templates/xanoscript-index.d.ts +9 -0
  6. package/dist/templates/xanoscript-index.js +61 -0
  7. package/package.json +4 -2
  8. package/xanoscript_docs/README.md +107 -1
  9. package/xanoscript_docs/agents.md +329 -0
  10. package/xanoscript_docs/apis.md +343 -0
  11. package/xanoscript_docs/database.md +417 -0
  12. package/xanoscript_docs/ephemeral.md +333 -0
  13. package/xanoscript_docs/frontend.md +291 -0
  14. package/xanoscript_docs/functions.md +232 -2035
  15. package/xanoscript_docs/integrations.md +439 -0
  16. package/xanoscript_docs/mcp-servers.md +190 -0
  17. package/xanoscript_docs/plan.md +192 -0
  18. package/xanoscript_docs/syntax.md +314 -0
  19. package/xanoscript_docs/tables.md +270 -0
  20. package/xanoscript_docs/tasks.md +254 -0
  21. package/xanoscript_docs/testing.md +335 -0
  22. package/xanoscript_docs/tools.md +305 -0
  23. package/xanoscript_docs/types.md +297 -0
  24. package/xanoscript_docs/version.json +2 -1
  25. package/xanoscript_docs/api_query_examples.md +0 -1255
  26. package/xanoscript_docs/api_query_guideline.md +0 -129
  27. package/xanoscript_docs/build_from_lovable.md +0 -715
  28. package/xanoscript_docs/db_query_guideline.md +0 -427
  29. package/xanoscript_docs/ephemeral_environment_guideline.md +0 -529
  30. package/xanoscript_docs/expression_guideline.md +0 -1086
  31. package/xanoscript_docs/frontend_guideline.md +0 -67
  32. package/xanoscript_docs/function_examples.md +0 -1406
  33. package/xanoscript_docs/function_guideline.md +0 -130
  34. package/xanoscript_docs/input_guideline.md +0 -227
  35. package/xanoscript_docs/mcp_server_examples.md +0 -36
  36. package/xanoscript_docs/mcp_server_guideline.md +0 -69
  37. package/xanoscript_docs/query_filter.md +0 -489
  38. package/xanoscript_docs/table_examples.md +0 -586
  39. package/xanoscript_docs/table_guideline.md +0 -137
  40. package/xanoscript_docs/task_examples.md +0 -511
  41. package/xanoscript_docs/task_guideline.md +0 -103
  42. package/xanoscript_docs/tips_and_tricks.md +0 -144
  43. package/xanoscript_docs/tool_examples.md +0 -69
  44. package/xanoscript_docs/tool_guideline.md +0 -139
  45. package/xanoscript_docs/unit_testing_guideline.md +0 -328
  46. package/xanoscript_docs/workspace.md +0 -17
package/dist/index.js CHANGED
@@ -1,108 +1,159 @@
1
1
  #!/usr/bin/env node
2
2
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
5
  import { readFileSync } from "fs";
6
6
  import { fileURLToPath } from "url";
7
7
  import { dirname, join } from "path";
8
+ import { minimatch } from "minimatch";
8
9
  import { xanoscriptParser } from "@xano/xanoscript-language-server/parser/parser.js";
9
10
  import { getSchemeFromContent } from "@xano/xanoscript-language-server/utils.js";
11
+ import { generateInitWorkspaceTemplate } from "./templates/init-workspace.js";
10
12
  const __filename = fileURLToPath(import.meta.url);
11
13
  const __dirname = dirname(__filename);
12
- // XanoScript docs mapping - keyword to files
13
- // Run `npm run sync-docs` to regenerate this from the docs directory
14
- const XANOSCRIPT_DOCS = {
15
- // Core concepts (guideline + examples)
16
- agent: ["agent_guideline.md", "agent_examples.md"],
17
- api_query: ["api_query_guideline.md", "api_query_examples.md"],
18
- function: ["function_guideline.md", "function_examples.md"],
19
- mcp_server: ["mcp_server_guideline.md", "mcp_server_examples.md"],
20
- table: ["table_guideline.md", "table_examples.md"],
21
- task: ["task_guideline.md", "task_examples.md"],
22
- tool: ["tool_guideline.md", "tool_examples.md"],
23
- // Guideline only
24
- db_query: ["db_query_guideline.md"],
25
- ephemeral: ["ephemeral_environment_guideline.md"],
26
- expressions: ["expression_guideline.md"],
27
- frontend: ["frontend_guideline.md"],
28
- input: ["input_guideline.md"],
29
- testing: ["unit_testing_guideline.md"],
30
- // Workflows (AI agent development guides)
31
- workflow: ["AGENTS.md"],
32
- api_workflow: ["API_AGENTS.md"],
33
- function_workflow: ["FUNCTION_AGENTS.md"],
34
- table_workflow: ["TABLE_AGENTS.md"],
35
- task_workflow: ["TASK_AGENTS.md"],
36
- // Standalone reference docs
37
- lovable: ["build_from_lovable.md"],
38
- syntax: ["functions.md"],
39
- query_filter: ["query_filter.md"],
40
- tips: ["tips_and_tricks.md"],
41
- workspace: ["workspace.md"],
14
+ const XANOSCRIPT_DOCS_V2 = {
15
+ readme: {
16
+ file: "README.md",
17
+ applyTo: [],
18
+ description: "XanoScript overview, workspace structure, and quick reference",
19
+ },
20
+ syntax: {
21
+ file: "syntax.md",
22
+ applyTo: ["**/*.xs"],
23
+ description: "Expressions, operators, and filters for all XanoScript code",
24
+ },
25
+ types: {
26
+ file: "types.md",
27
+ applyTo: ["functions/**/*.xs", "apis/**/*.xs", "tools/**/*.xs", "agents/**/*.xs"],
28
+ description: "Data types, input blocks, and validation",
29
+ },
30
+ tables: {
31
+ file: "tables.md",
32
+ applyTo: ["tables/*.xs"],
33
+ description: "Database schema definitions with indexes and relationships",
34
+ },
35
+ functions: {
36
+ file: "functions.md",
37
+ applyTo: ["functions/**/*.xs"],
38
+ description: "Reusable function stacks with inputs and responses",
39
+ },
40
+ apis: {
41
+ file: "apis.md",
42
+ applyTo: ["apis/**/*.xs"],
43
+ description: "HTTP endpoint definitions with authentication and CRUD patterns",
44
+ },
45
+ tasks: {
46
+ file: "tasks.md",
47
+ applyTo: ["tasks/*.xs"],
48
+ description: "Scheduled and cron jobs",
49
+ },
50
+ database: {
51
+ file: "database.md",
52
+ applyTo: ["functions/**/*.xs", "apis/**/*.xs", "tasks/*.xs", "tools/**/*.xs"],
53
+ description: "All db.* operations: query, get, add, edit, patch, delete",
54
+ },
55
+ agents: {
56
+ file: "agents.md",
57
+ applyTo: ["agents/**/*.xs"],
58
+ description: "AI agent configuration with LLM providers and tools",
59
+ },
60
+ tools: {
61
+ file: "tools.md",
62
+ applyTo: ["tools/**/*.xs"],
63
+ description: "AI tools for agents and MCP servers",
64
+ },
65
+ "mcp-servers": {
66
+ file: "mcp-servers.md",
67
+ applyTo: ["mcp_servers/**/*.xs"],
68
+ description: "MCP server definitions exposing tools",
69
+ },
70
+ testing: {
71
+ file: "testing.md",
72
+ applyTo: ["functions/**/*.xs", "apis/**/*.xs"],
73
+ description: "Unit tests, mocks, and assertions",
74
+ },
75
+ integrations: {
76
+ file: "integrations.md",
77
+ applyTo: ["functions/**/*.xs", "apis/**/*.xs", "tasks/*.xs"],
78
+ description: "Cloud storage, Redis, security, and external APIs",
79
+ },
80
+ frontend: {
81
+ file: "frontend.md",
82
+ applyTo: ["static/**/*"],
83
+ description: "Static frontend development and deployment",
84
+ },
85
+ ephemeral: {
86
+ file: "ephemeral.md",
87
+ applyTo: ["ephemeral/**/*.xs"],
88
+ description: "Temporary test environments",
89
+ },
42
90
  };
43
- // Keyword aliases for convenience
44
- const KEYWORD_ALIASES = {
45
- // api_query
46
- api: "api_query",
47
- apis: "api_query",
48
- endpoint: "api_query",
49
- endpoints: "api_query",
50
- query: "api_query",
51
- // function
52
- func: "function",
53
- functions: "function",
54
- // table
55
- tables: "table",
56
- schema: "table",
57
- schemas: "table",
58
- // task
59
- tasks: "task",
60
- cron: "task",
61
- scheduled: "task",
62
- // tool
63
- tools: "tool",
64
- // agent
65
- agents: "agent",
66
- ai_agent: "agent",
67
- // mcp_server
68
- mcp: "mcp_server",
69
- // syntax
70
- reference: "syntax",
71
- ref: "syntax",
72
- statements: "syntax",
73
- stack: "syntax",
74
- // expressions
75
- expr: "expressions",
76
- expression: "expressions",
77
- filters: "expressions",
78
- pipes: "expressions",
79
- operators: "expressions",
80
- // input
81
- inputs: "input",
82
- params: "input",
83
- parameters: "input",
84
- // db_query
85
- db: "db_query",
86
- database: "db_query",
87
- // query_filter
88
- filter: "query_filter",
89
- where: "query_filter",
90
- // workflow
91
- workflows: "workflow",
92
- dev: "workflow",
93
- development: "workflow",
94
- // testing
95
- test: "testing",
96
- tests: "testing",
97
- unit_test: "testing",
98
- // tips
99
- tip: "tips",
100
- tricks: "tips",
101
- // frontend
102
- ui: "frontend",
103
- static: "frontend",
91
+ const XANO_OBJECT_TYPES = {
92
+ function: {
93
+ path: "functions",
94
+ endpoint: "function",
95
+ extension: ".xs",
96
+ hasXanoscript: true,
97
+ },
98
+ table: {
99
+ path: "tables",
100
+ endpoint: "table",
101
+ extension: ".xs",
102
+ hasXanoscript: true,
103
+ },
104
+ task: {
105
+ path: "tasks",
106
+ endpoint: "task",
107
+ extension: ".xs",
108
+ hasXanoscript: true,
109
+ },
110
+ api_group: {
111
+ path: "apis",
112
+ endpoint: "api-group",
113
+ extension: ".xs",
114
+ hasXanoscript: true,
115
+ supportsNesting: true,
116
+ },
117
+ tool: {
118
+ path: "tools",
119
+ endpoint: "tool",
120
+ extension: ".xs",
121
+ hasXanoscript: true,
122
+ },
123
+ agent: {
124
+ path: "agents",
125
+ endpoint: "agent",
126
+ extension: ".xs",
127
+ hasXanoscript: true,
128
+ },
129
+ middleware: {
130
+ path: "middlewares",
131
+ endpoint: "middleware",
132
+ extension: ".xs",
133
+ hasXanoscript: true,
134
+ },
135
+ addon: {
136
+ path: "addons",
137
+ endpoint: "addon",
138
+ extension: ".xs",
139
+ hasXanoscript: true,
140
+ },
141
+ mcp_server: {
142
+ path: "mcp_servers",
143
+ endpoint: "mcp-server",
144
+ extension: ".xs",
145
+ hasXanoscript: true,
146
+ },
147
+ realtime_channel: {
148
+ path: "realtime",
149
+ endpoint: "realtime-channel",
150
+ extension: ".xs",
151
+ hasXanoscript: true,
152
+ },
104
153
  };
105
- // Map of object names to their documentation files
154
+ // =============================================================================
155
+ // API Documentation Configuration
156
+ // =============================================================================
106
157
  const DOCS_MAP = {
107
158
  workspace: "workspace.md",
108
159
  table: "table.md",
@@ -120,10 +171,10 @@ const DOCS_MAP = {
120
171
  history: "history.md",
121
172
  authentication: "authentication.md",
122
173
  };
123
- // Get the api_docs directory path
174
+ // =============================================================================
175
+ // Path Resolution
176
+ // =============================================================================
124
177
  function getDocsPath() {
125
- // In development, look relative to src
126
- // In production (after build), look relative to dist
127
178
  const possiblePaths = [
128
179
  join(__dirname, "..", "api_docs"),
129
180
  join(__dirname, "..", "..", "api_docs"),
@@ -139,8 +190,6 @@ function getDocsPath() {
139
190
  }
140
191
  return join(__dirname, "..", "api_docs");
141
192
  }
142
- const DOCS_PATH = getDocsPath();
143
- // Get the xanoscript_docs directory path
144
193
  function getXanoscriptDocsPath() {
145
194
  const possiblePaths = [
146
195
  join(__dirname, "..", "xanoscript_docs"),
@@ -157,26 +206,35 @@ function getXanoscriptDocsPath() {
157
206
  }
158
207
  return join(__dirname, "..", "xanoscript_docs");
159
208
  }
209
+ const DOCS_PATH = getDocsPath();
160
210
  const XANOSCRIPT_DOCS_PATH = getXanoscriptDocsPath();
211
+ // =============================================================================
212
+ // Documentation Helpers
213
+ // =============================================================================
214
+ function getXanoscriptDocsVersion() {
215
+ try {
216
+ const versionFile = readFileSync(join(XANOSCRIPT_DOCS_PATH, "version.json"), "utf-8");
217
+ return JSON.parse(versionFile).version || "unknown";
218
+ }
219
+ catch {
220
+ return "unknown";
221
+ }
222
+ }
161
223
  function readDocumentation(object) {
162
224
  try {
163
225
  if (!object) {
164
- // Return index documentation
165
226
  return readFileSync(join(DOCS_PATH, "index.md"), "utf-8");
166
227
  }
167
228
  const normalizedObject = object.toLowerCase().trim();
168
- // Check if the object exists in our map
169
229
  if (normalizedObject in DOCS_MAP) {
170
230
  const filePath = join(DOCS_PATH, DOCS_MAP[normalizedObject]);
171
231
  return readFileSync(filePath, "utf-8");
172
232
  }
173
- // Try to find a partial match
174
233
  const matchingKey = Object.keys(DOCS_MAP).find((key) => key.includes(normalizedObject) || normalizedObject.includes(key));
175
234
  if (matchingKey) {
176
235
  const filePath = join(DOCS_PATH, DOCS_MAP[matchingKey]);
177
236
  return readFileSync(filePath, "utf-8");
178
237
  }
179
- // Return error message with available options
180
238
  const availableObjects = Object.keys(DOCS_MAP).join(", ");
181
239
  return `Error: Unknown object "${object}". Available objects: ${availableObjects}
182
240
 
@@ -187,135 +245,159 @@ Use api_docs() without parameters to see the full documentation index.`;
187
245
  return `Error reading documentation: ${errorMessage}`;
188
246
  }
189
247
  }
190
- // Read XanoScript documentation version
191
- function getXanoscriptDocsVersion() {
192
- try {
193
- const versionFile = readFileSync(join(XANOSCRIPT_DOCS_PATH, "version.json"), "utf-8");
194
- return JSON.parse(versionFile).version || "unknown";
248
+ // =============================================================================
249
+ // XanoScript Documentation v2 Functions
250
+ // =============================================================================
251
+ /**
252
+ * Get list of topics that apply to a given file path based on applyTo patterns
253
+ */
254
+ function getDocsForFilePath(filePath) {
255
+ const matches = [];
256
+ for (const [topic, config] of Object.entries(XANOSCRIPT_DOCS_V2)) {
257
+ if (topic === "readme")
258
+ continue; // Don't auto-include readme
259
+ for (const pattern of config.applyTo) {
260
+ if (minimatch(filePath, pattern)) {
261
+ matches.push(topic);
262
+ break;
263
+ }
264
+ }
195
265
  }
196
- catch {
197
- return "unknown";
266
+ // Always include syntax as foundation (if not already matched)
267
+ if (!matches.includes("syntax")) {
268
+ matches.unshift("syntax");
198
269
  }
270
+ return matches;
199
271
  }
200
- // Generate the XanoScript documentation index
201
- function generateXanoscriptIndex() {
202
- const version = getXanoscriptDocsVersion();
203
- // Build alias lookup (keyword -> aliases)
204
- const aliasLookup = {};
205
- for (const [alias, keyword] of Object.entries(KEYWORD_ALIASES)) {
206
- aliasLookup[keyword] = aliasLookup[keyword] || [];
207
- aliasLookup[keyword].push(alias);
272
+ /**
273
+ * Extract just the Quick Reference section from a doc
274
+ */
275
+ function extractQuickReference(content, topic) {
276
+ const lines = content.split("\n");
277
+ const startIdx = lines.findIndex((l) => l.startsWith("## Quick Reference"));
278
+ if (startIdx === -1) {
279
+ // Fallback: return first 50 lines or up to first ## section
280
+ const firstSection = lines.findIndex((l, i) => i > 0 && l.startsWith("## "));
281
+ return lines.slice(0, firstSection > 0 ? firstSection : 50).join("\n");
208
282
  }
209
- const formatRow = (keyword, description) => {
210
- const aliases = aliasLookup[keyword]?.slice(0, 3).join(", ") || "";
211
- return `| \`${keyword}\` | ${aliases ? aliases : "-"} | ${description} |`;
212
- };
213
- return `# XanoScript Documentation Index
214
- Version: ${version}
215
-
216
- Use \`xanoscript_docs\` with a keyword to retrieve documentation.
217
-
218
- ## Core Concepts
219
- These return guidelines + examples for writing XanoScript code.
220
-
221
- | Keyword | Aliases | Description |
222
- |---------|---------|-------------|
223
- ${formatRow("function", "Custom reusable functions in `functions/`")}
224
- ${formatRow("api_query", "HTTP API endpoints in `apis/`")}
225
- ${formatRow("table", "Database table schemas in `tables/`")}
226
- ${formatRow("task", "Scheduled background tasks in `tasks/`")}
227
- ${formatRow("tool", "AI-callable tools in `tools/`")}
228
- ${formatRow("agent", "AI agents in `agents/`")}
229
- ${formatRow("mcp_server", "MCP servers in `mcp_servers/`")}
230
-
231
- ## Language Reference
232
- Core syntax and operators.
233
-
234
- | Keyword | Aliases | Description |
235
- |---------|---------|-------------|
236
- ${formatRow("syntax", "Complete XanoScript syntax (stack, var, conditional, foreach, etc.)")}
237
- ${formatRow("expressions", "Pipe operators and filters (string, math, array, date)")}
238
- ${formatRow("input", "Input definition syntax (types, filters, validation)")}
239
- ${formatRow("db_query", "Database query patterns (query, add, edit, delete)")}
240
- ${formatRow("query_filter", "WHERE clause and filter syntax")}
241
-
242
- ## Development Workflows
243
- AI agent development strategies and phases.
244
-
245
- | Keyword | Aliases | Description |
246
- |---------|---------|-------------|
247
- ${formatRow("workflow", "Overall XanoScript development workflow")}
248
- ${formatRow("function_workflow", "AI workflow for creating functions")}
249
- ${formatRow("api_workflow", "AI workflow for creating API endpoints")}
250
- ${formatRow("table_workflow", "AI workflow for creating tables")}
251
- ${formatRow("task_workflow", "AI workflow for creating tasks")}
252
-
253
- ## Specialized Topics
254
-
255
- | Keyword | Aliases | Description |
256
- |---------|---------|-------------|
257
- ${formatRow("frontend", "Frontend development with Xano")}
258
- ${formatRow("lovable", "Building from Lovable-generated websites")}
259
- ${formatRow("testing", "Unit testing XanoScript code")}
260
- ${formatRow("tips", "Tips and tricks")}
261
- ${formatRow("ephemeral", "Ephemeral environment setup")}
262
- `;
283
+ // Find the next ## section after Quick Reference
284
+ let endIdx = lines.findIndex((l, i) => i > startIdx && l.startsWith("## "));
285
+ if (endIdx === -1)
286
+ endIdx = lines.length;
287
+ // Include topic header for context
288
+ const header = `# ${topic}\n\n`;
289
+ return header + lines.slice(startIdx, endIdx).join("\n");
263
290
  }
264
- // Read XanoScript documentation for a keyword
265
- function readXanoscriptDocs(keyword) {
291
+ /**
292
+ * Read XanoScript documentation with new v2 structure
293
+ */
294
+ function readXanoscriptDocsV2(args) {
295
+ const mode = args?.mode || "full";
296
+ const version = getXanoscriptDocsVersion();
266
297
  try {
267
- if (!keyword) {
268
- return generateXanoscriptIndex();
298
+ // Default: return README
299
+ if (!args?.topic && !args?.file_path) {
300
+ const readme = readFileSync(join(XANOSCRIPT_DOCS_PATH, "README.md"), "utf-8");
301
+ return `${readme}\n\n---\nDocumentation version: ${version}`;
269
302
  }
270
- const normalizedKeyword = keyword.toLowerCase().trim();
271
- // Check for alias first
272
- const resolvedKeyword = KEYWORD_ALIASES[normalizedKeyword] || normalizedKeyword;
273
- // Check if keyword exists
274
- if (!(resolvedKeyword in XANOSCRIPT_DOCS)) {
275
- // Try partial match
276
- const matchingKey = Object.keys(XANOSCRIPT_DOCS).find((key) => key.includes(resolvedKeyword) || resolvedKeyword.includes(key));
277
- if (matchingKey) {
278
- return readXanoscriptDocs(matchingKey);
303
+ // Context-aware: return docs matching file pattern
304
+ if (args?.file_path) {
305
+ const topics = getDocsForFilePath(args.file_path);
306
+ if (topics.length === 0) {
307
+ return `No documentation found for file pattern: ${args.file_path}\n\nAvailable topics: ${Object.keys(XANOSCRIPT_DOCS_V2).join(", ")}`;
279
308
  }
280
- const availableKeywords = Object.keys(XANOSCRIPT_DOCS).join(", ");
281
- return `Error: Unknown keyword "${keyword}". Available keywords: ${availableKeywords}
282
-
283
- Use xanoscript_docs() without parameters to see the full documentation index.`;
309
+ const docs = topics.map((t) => {
310
+ const config = XANOSCRIPT_DOCS_V2[t];
311
+ const content = readFileSync(join(XANOSCRIPT_DOCS_PATH, config.file), "utf-8");
312
+ return mode === "quick_reference"
313
+ ? extractQuickReference(content, t)
314
+ : content;
315
+ });
316
+ const header = `# XanoScript Documentation for: ${args.file_path}\n\nMatched topics: ${topics.join(", ")}\nMode: ${mode}\nVersion: ${version}\n\n---\n\n`;
317
+ return header + docs.join("\n\n---\n\n");
284
318
  }
285
- const files = XANOSCRIPT_DOCS[resolvedKeyword];
286
- const version = getXanoscriptDocsVersion();
287
- // Read and concatenate all files for this keyword
288
- const contents = [];
289
- contents.push(`# XanoScript: ${resolvedKeyword}`);
290
- contents.push(`Documentation version: ${version}\n`);
291
- for (const file of files) {
292
- const filePath = join(XANOSCRIPT_DOCS_PATH, file);
293
- try {
294
- const content = readFileSync(filePath, "utf-8");
295
- contents.push(`---\n## Source: ${file}\n---\n`);
296
- contents.push(content);
297
- }
298
- catch (err) {
299
- contents.push(`\n[Error reading ${file}: file not found]\n`);
319
+ // Topic-based: return specific doc
320
+ if (args?.topic) {
321
+ const config = XANOSCRIPT_DOCS_V2[args.topic];
322
+ if (!config) {
323
+ const availableTopics = Object.keys(XANOSCRIPT_DOCS_V2).join(", ");
324
+ return `Error: Unknown topic "${args.topic}".\n\nAvailable topics: ${availableTopics}`;
300
325
  }
326
+ const content = readFileSync(join(XANOSCRIPT_DOCS_PATH, config.file), "utf-8");
327
+ const doc = mode === "quick_reference"
328
+ ? extractQuickReference(content, args.topic)
329
+ : content;
330
+ return `${doc}\n\n---\nDocumentation version: ${version}`;
301
331
  }
302
- return contents.join("\n");
332
+ return "Error: Invalid parameters";
303
333
  }
304
334
  catch (error) {
305
335
  const errorMessage = error instanceof Error ? error.message : String(error);
306
336
  return `Error reading XanoScript documentation: ${errorMessage}`;
307
337
  }
308
338
  }
309
- // Create the MCP server
339
+ // =============================================================================
340
+ // Init Workspace Documentation
341
+ // =============================================================================
342
+ function generateInitWorkspaceDoc() {
343
+ const objectTypes = Object.entries(XANO_OBJECT_TYPES).map(([type, config]) => ({
344
+ type,
345
+ path: config.path,
346
+ endpoint: config.endpoint,
347
+ }));
348
+ return generateInitWorkspaceTemplate(objectTypes);
349
+ }
350
+ // =============================================================================
351
+ // MCP Server Setup
352
+ // =============================================================================
310
353
  const server = new Server({
311
354
  name: "xano-developer-mcp",
312
355
  version: "1.0.0",
356
+ description: "MCP server for Xano Headless API documentation and XanoScript code validation",
313
357
  }, {
314
358
  capabilities: {
315
359
  tools: {},
360
+ resources: {},
316
361
  },
317
362
  });
318
- // List available tools
363
+ // =============================================================================
364
+ // Resource Handlers (MCP Resources)
365
+ // =============================================================================
366
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
367
+ const resources = Object.entries(XANOSCRIPT_DOCS_V2).map(([key, config]) => ({
368
+ uri: `xanoscript://docs/${key}`,
369
+ name: key,
370
+ description: config.description,
371
+ mimeType: "text/markdown",
372
+ }));
373
+ return { resources };
374
+ });
375
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
376
+ const { uri } = request.params;
377
+ const match = uri.match(/^xanoscript:\/\/docs\/(.+)$/);
378
+ if (!match) {
379
+ throw new Error(`Unknown resource URI: ${uri}`);
380
+ }
381
+ const topic = match[1];
382
+ const config = XANOSCRIPT_DOCS_V2[topic];
383
+ if (!config) {
384
+ throw new Error(`Unknown topic: ${topic}. Available: ${Object.keys(XANOSCRIPT_DOCS_V2).join(", ")}`);
385
+ }
386
+ const content = readFileSync(join(XANOSCRIPT_DOCS_PATH, config.file), "utf-8");
387
+ const version = getXanoscriptDocsVersion();
388
+ return {
389
+ contents: [
390
+ {
391
+ uri,
392
+ mimeType: "text/markdown",
393
+ text: `${content}\n\n---\nDocumentation version: ${version}`,
394
+ },
395
+ ],
396
+ };
397
+ });
398
+ // =============================================================================
399
+ // Tool Handlers
400
+ // =============================================================================
319
401
  server.setRequestHandler(ListToolsRequestSchema, async () => {
320
402
  return {
321
403
  tools: [
@@ -350,27 +432,49 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
350
432
  {
351
433
  name: "xanoscript_docs",
352
434
  description: "Get XanoScript programming language documentation for AI code generation. " +
353
- "Call without a keyword to see the full index of available topics. " +
354
- "Use a keyword to retrieve specific documentation (guidelines + examples).",
435
+ "Call without parameters for overview (README). " +
436
+ "Use 'topic' for specific documentation, or 'file_path' for context-aware docs based on the file you're editing. " +
437
+ "Use mode='quick_reference' for compact syntax cheatsheet (recommended for context efficiency).",
355
438
  inputSchema: {
356
439
  type: "object",
357
440
  properties: {
358
- keyword: {
441
+ topic: {
359
442
  type: "string",
360
- description: "Documentation topic to retrieve. " +
361
- "Core: function, api_query (or 'api'), table, task, tool, agent, mcp_server. " +
362
- "Reference: syntax (or 'ref'), expressions (or 'expr'), input, db_query (or 'db'). " +
363
- "Workflow: workflow, function_workflow, api_workflow, table_workflow, task_workflow. " +
364
- "Omit for the full documentation index.",
443
+ description: "Documentation topic. Available: " +
444
+ Object.entries(XANOSCRIPT_DOCS_V2)
445
+ .map(([k, v]) => `${k} (${v.description.split(".")[0]})`)
446
+ .join(", "),
447
+ },
448
+ file_path: {
449
+ type: "string",
450
+ description: "File path being edited (e.g., 'apis/users/create.xs', 'functions/utils/format.xs'). " +
451
+ "Returns all relevant docs based on file type using applyTo pattern matching.",
452
+ },
453
+ mode: {
454
+ type: "string",
455
+ enum: ["full", "quick_reference"],
456
+ description: "full = complete documentation, quick_reference = compact Quick Reference sections only. " +
457
+ "Use quick_reference for smaller context window usage.",
365
458
  },
366
459
  },
367
460
  required: [],
368
461
  },
369
462
  },
463
+ {
464
+ name: "init_workspace",
465
+ description: "Get comprehensive instructions for initializing a local Xano development workspace. " +
466
+ "Returns documentation on directory structure, file naming conventions, registry format for tracking changes, " +
467
+ "and workflows for pulling/pushing XanoScript files via the Headless API. " +
468
+ "Use this when setting up local development for Xano projects.",
469
+ inputSchema: {
470
+ type: "object",
471
+ properties: {},
472
+ required: [],
473
+ },
474
+ },
370
475
  ],
371
476
  };
372
477
  });
373
- // Handle tool calls
374
478
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
375
479
  if (request.params.name === "api_docs") {
376
480
  const args = request.params.arguments;
@@ -407,16 +511,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
407
511
  content: [
408
512
  {
409
513
  type: "text",
410
- text: "XanoScript is valid. No syntax errors found.",
514
+ text: "XanoScript is valid. No syntax errors found.",
411
515
  },
412
516
  ],
413
517
  };
414
518
  }
415
- // Convert parser errors to diagnostics with line/column info
416
519
  const diagnostics = parser.errors.map((error) => {
417
520
  const startOffset = error.token?.startOffset ?? 0;
418
521
  const endOffset = error.token?.endOffset ?? 5;
419
- // Calculate line and character positions from offset
420
522
  const lines = text.substring(0, startOffset).split("\n");
421
523
  const line = lines.length - 1;
422
524
  const character = lines[lines.length - 1].length;
@@ -432,7 +534,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
432
534
  source: error.name || "XanoScript Parser",
433
535
  };
434
536
  });
435
- // Format errors for readable output
436
537
  const errorMessages = diagnostics.map((d, i) => {
437
538
  const location = `Line ${d.range.start.line + 1}, Column ${d.range.start.character + 1}`;
438
539
  return `${i + 1}. [${location}] ${d.message}`;
@@ -462,8 +563,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
462
563
  }
463
564
  if (request.params.name === "xanoscript_docs") {
464
565
  const args = request.params.arguments;
465
- const keyword = args?.keyword;
466
- const documentation = readXanoscriptDocs(keyword);
566
+ const documentation = readXanoscriptDocsV2(args);
567
+ return {
568
+ content: [
569
+ {
570
+ type: "text",
571
+ text: documentation,
572
+ },
573
+ ],
574
+ };
575
+ }
576
+ if (request.params.name === "init_workspace") {
577
+ const documentation = generateInitWorkspaceDoc();
467
578
  return {
468
579
  content: [
469
580
  {
@@ -483,7 +594,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
483
594
  isError: true,
484
595
  };
485
596
  });
486
- // Start the server
597
+ // =============================================================================
598
+ // Start Server
599
+ // =============================================================================
487
600
  async function main() {
488
601
  const transport = new StdioServerTransport();
489
602
  await server.connect(transport);