@xano/developer-mcp 1.0.0
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/LICENSE +21 -0
- package/README.md +261 -0
- package/api_docs/addon.md +193 -0
- package/api_docs/agent.md +154 -0
- package/api_docs/api_group.md +236 -0
- package/api_docs/authentication.md +68 -0
- package/api_docs/file.md +190 -0
- package/api_docs/function.md +217 -0
- package/api_docs/history.md +263 -0
- package/api_docs/index.md +104 -0
- package/api_docs/mcp_server.md +139 -0
- package/api_docs/middleware.md +205 -0
- package/api_docs/realtime.md +153 -0
- package/api_docs/table.md +151 -0
- package/api_docs/task.md +191 -0
- package/api_docs/tool.md +216 -0
- package/api_docs/triggers.md +344 -0
- package/api_docs/workspace.md +246 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +495 -0
- package/package.json +49 -0
- package/xanoscript_docs/README.md +1 -0
- package/xanoscript_docs/api_query_examples.md +1255 -0
- package/xanoscript_docs/api_query_guideline.md +129 -0
- package/xanoscript_docs/build_from_lovable.md +715 -0
- package/xanoscript_docs/db_query_guideline.md +427 -0
- package/xanoscript_docs/ephemeral_environment_guideline.md +529 -0
- package/xanoscript_docs/expression_guideline.md +1086 -0
- package/xanoscript_docs/frontend_guideline.md +67 -0
- package/xanoscript_docs/function_examples.md +1406 -0
- package/xanoscript_docs/function_guideline.md +130 -0
- package/xanoscript_docs/functions.md +2155 -0
- package/xanoscript_docs/input_guideline.md +227 -0
- package/xanoscript_docs/mcp_server_examples.md +36 -0
- package/xanoscript_docs/mcp_server_guideline.md +69 -0
- package/xanoscript_docs/query_filter.md +489 -0
- package/xanoscript_docs/table_examples.md +586 -0
- package/xanoscript_docs/table_guideline.md +137 -0
- package/xanoscript_docs/task_examples.md +511 -0
- package/xanoscript_docs/task_guideline.md +103 -0
- package/xanoscript_docs/tips_and_tricks.md +144 -0
- package/xanoscript_docs/tool_examples.md +69 -0
- package/xanoscript_docs/tool_guideline.md +139 -0
- package/xanoscript_docs/unit_testing_guideline.md +328 -0
- package/xanoscript_docs/version.json +3 -0
- package/xanoscript_docs/workspace.md +17 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { dirname, join } from "path";
|
|
8
|
+
import { xanoscriptParser } from "@xano/xanoscript-language-server/parser/parser.js";
|
|
9
|
+
import { getSchemeFromContent } from "@xano/xanoscript-language-server/utils.js";
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
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"],
|
|
42
|
+
};
|
|
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",
|
|
104
|
+
};
|
|
105
|
+
// Map of object names to their documentation files
|
|
106
|
+
const DOCS_MAP = {
|
|
107
|
+
workspace: "workspace.md",
|
|
108
|
+
table: "table.md",
|
|
109
|
+
api_group: "api_group.md",
|
|
110
|
+
function: "function.md",
|
|
111
|
+
task: "task.md",
|
|
112
|
+
middleware: "middleware.md",
|
|
113
|
+
addon: "addon.md",
|
|
114
|
+
agent: "agent.md",
|
|
115
|
+
tool: "tool.md",
|
|
116
|
+
mcp_server: "mcp_server.md",
|
|
117
|
+
realtime: "realtime.md",
|
|
118
|
+
triggers: "triggers.md",
|
|
119
|
+
file: "file.md",
|
|
120
|
+
history: "history.md",
|
|
121
|
+
authentication: "authentication.md",
|
|
122
|
+
};
|
|
123
|
+
// Get the api_docs directory path
|
|
124
|
+
function getDocsPath() {
|
|
125
|
+
// In development, look relative to src
|
|
126
|
+
// In production (after build), look relative to dist
|
|
127
|
+
const possiblePaths = [
|
|
128
|
+
join(__dirname, "..", "api_docs"),
|
|
129
|
+
join(__dirname, "..", "..", "api_docs"),
|
|
130
|
+
];
|
|
131
|
+
for (const p of possiblePaths) {
|
|
132
|
+
try {
|
|
133
|
+
readFileSync(join(p, "index.md"));
|
|
134
|
+
return p;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return join(__dirname, "..", "api_docs");
|
|
141
|
+
}
|
|
142
|
+
const DOCS_PATH = getDocsPath();
|
|
143
|
+
// Get the xanoscript_docs directory path
|
|
144
|
+
function getXanoscriptDocsPath() {
|
|
145
|
+
const possiblePaths = [
|
|
146
|
+
join(__dirname, "..", "xanoscript_docs"),
|
|
147
|
+
join(__dirname, "..", "..", "xanoscript_docs"),
|
|
148
|
+
];
|
|
149
|
+
for (const p of possiblePaths) {
|
|
150
|
+
try {
|
|
151
|
+
readFileSync(join(p, "version.json"));
|
|
152
|
+
return p;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return join(__dirname, "..", "xanoscript_docs");
|
|
159
|
+
}
|
|
160
|
+
const XANOSCRIPT_DOCS_PATH = getXanoscriptDocsPath();
|
|
161
|
+
function readDocumentation(object) {
|
|
162
|
+
try {
|
|
163
|
+
if (!object) {
|
|
164
|
+
// Return index documentation
|
|
165
|
+
return readFileSync(join(DOCS_PATH, "index.md"), "utf-8");
|
|
166
|
+
}
|
|
167
|
+
const normalizedObject = object.toLowerCase().trim();
|
|
168
|
+
// Check if the object exists in our map
|
|
169
|
+
if (normalizedObject in DOCS_MAP) {
|
|
170
|
+
const filePath = join(DOCS_PATH, DOCS_MAP[normalizedObject]);
|
|
171
|
+
return readFileSync(filePath, "utf-8");
|
|
172
|
+
}
|
|
173
|
+
// Try to find a partial match
|
|
174
|
+
const matchingKey = Object.keys(DOCS_MAP).find((key) => key.includes(normalizedObject) || normalizedObject.includes(key));
|
|
175
|
+
if (matchingKey) {
|
|
176
|
+
const filePath = join(DOCS_PATH, DOCS_MAP[matchingKey]);
|
|
177
|
+
return readFileSync(filePath, "utf-8");
|
|
178
|
+
}
|
|
179
|
+
// Return error message with available options
|
|
180
|
+
const availableObjects = Object.keys(DOCS_MAP).join(", ");
|
|
181
|
+
return `Error: Unknown object "${object}". Available objects: ${availableObjects}
|
|
182
|
+
|
|
183
|
+
Use api_docs() without parameters to see the full documentation index.`;
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
187
|
+
return `Error reading documentation: ${errorMessage}`;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
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";
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return "unknown";
|
|
198
|
+
}
|
|
199
|
+
}
|
|
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);
|
|
208
|
+
}
|
|
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
|
+
`;
|
|
263
|
+
}
|
|
264
|
+
// Read XanoScript documentation for a keyword
|
|
265
|
+
function readXanoscriptDocs(keyword) {
|
|
266
|
+
try {
|
|
267
|
+
if (!keyword) {
|
|
268
|
+
return generateXanoscriptIndex();
|
|
269
|
+
}
|
|
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);
|
|
279
|
+
}
|
|
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.`;
|
|
284
|
+
}
|
|
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`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return contents.join("\n");
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
306
|
+
return `Error reading XanoScript documentation: ${errorMessage}`;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Create the MCP server
|
|
310
|
+
const server = new Server({
|
|
311
|
+
name: "xano-developer-mcp",
|
|
312
|
+
version: "1.0.0",
|
|
313
|
+
}, {
|
|
314
|
+
capabilities: {
|
|
315
|
+
tools: {},
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
// List available tools
|
|
319
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
320
|
+
return {
|
|
321
|
+
tools: [
|
|
322
|
+
{
|
|
323
|
+
name: "api_docs",
|
|
324
|
+
description: "Get Xano Headless API documentation. Returns documentation for interacting with the Xano Headless API using XanoScript. Use without parameters for an overview, or specify an object for detailed documentation.",
|
|
325
|
+
inputSchema: {
|
|
326
|
+
type: "object",
|
|
327
|
+
properties: {
|
|
328
|
+
object: {
|
|
329
|
+
type: "string",
|
|
330
|
+
description: `Optional: The specific API object to get documentation for. Available values: ${Object.keys(DOCS_MAP).join(", ")}`,
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
required: [],
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: "validate_xanoscript",
|
|
338
|
+
description: "Validate XanoScript code for syntax errors. Returns a list of errors with line/column positions, or confirms the code is valid. The language server auto-detects the object type from the code syntax.",
|
|
339
|
+
inputSchema: {
|
|
340
|
+
type: "object",
|
|
341
|
+
properties: {
|
|
342
|
+
code: {
|
|
343
|
+
type: "string",
|
|
344
|
+
description: "The XanoScript code to validate",
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
required: ["code"],
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: "xanoscript_docs",
|
|
352
|
+
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).",
|
|
355
|
+
inputSchema: {
|
|
356
|
+
type: "object",
|
|
357
|
+
properties: {
|
|
358
|
+
keyword: {
|
|
359
|
+
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.",
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
required: [],
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
],
|
|
371
|
+
};
|
|
372
|
+
});
|
|
373
|
+
// Handle tool calls
|
|
374
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
375
|
+
if (request.params.name === "api_docs") {
|
|
376
|
+
const args = request.params.arguments;
|
|
377
|
+
const object = args?.object;
|
|
378
|
+
const documentation = readDocumentation(object);
|
|
379
|
+
return {
|
|
380
|
+
content: [
|
|
381
|
+
{
|
|
382
|
+
type: "text",
|
|
383
|
+
text: documentation,
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
if (request.params.name === "validate_xanoscript") {
|
|
389
|
+
const args = request.params.arguments;
|
|
390
|
+
if (!args?.code) {
|
|
391
|
+
return {
|
|
392
|
+
content: [
|
|
393
|
+
{
|
|
394
|
+
type: "text",
|
|
395
|
+
text: "Error: 'code' parameter is required",
|
|
396
|
+
},
|
|
397
|
+
],
|
|
398
|
+
isError: true,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
try {
|
|
402
|
+
const text = args.code;
|
|
403
|
+
const scheme = getSchemeFromContent(text);
|
|
404
|
+
const parser = xanoscriptParser(text, scheme);
|
|
405
|
+
if (parser.errors.length === 0) {
|
|
406
|
+
return {
|
|
407
|
+
content: [
|
|
408
|
+
{
|
|
409
|
+
type: "text",
|
|
410
|
+
text: "✓ XanoScript is valid. No syntax errors found.",
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
// Convert parser errors to diagnostics with line/column info
|
|
416
|
+
const diagnostics = parser.errors.map((error) => {
|
|
417
|
+
const startOffset = error.token?.startOffset ?? 0;
|
|
418
|
+
const endOffset = error.token?.endOffset ?? 5;
|
|
419
|
+
// Calculate line and character positions from offset
|
|
420
|
+
const lines = text.substring(0, startOffset).split("\n");
|
|
421
|
+
const line = lines.length - 1;
|
|
422
|
+
const character = lines[lines.length - 1].length;
|
|
423
|
+
const endLines = text.substring(0, endOffset + 1).split("\n");
|
|
424
|
+
const endLine = endLines.length - 1;
|
|
425
|
+
const endCharacter = endLines[endLines.length - 1].length;
|
|
426
|
+
return {
|
|
427
|
+
range: {
|
|
428
|
+
start: { line, character },
|
|
429
|
+
end: { line: endLine, character: endCharacter },
|
|
430
|
+
},
|
|
431
|
+
message: error.message,
|
|
432
|
+
source: error.name || "XanoScript Parser",
|
|
433
|
+
};
|
|
434
|
+
});
|
|
435
|
+
// Format errors for readable output
|
|
436
|
+
const errorMessages = diagnostics.map((d, i) => {
|
|
437
|
+
const location = `Line ${d.range.start.line + 1}, Column ${d.range.start.character + 1}`;
|
|
438
|
+
return `${i + 1}. [${location}] ${d.message}`;
|
|
439
|
+
});
|
|
440
|
+
return {
|
|
441
|
+
content: [
|
|
442
|
+
{
|
|
443
|
+
type: "text",
|
|
444
|
+
text: `Found ${diagnostics.length} error(s):\n\n${errorMessages.join("\n")}`,
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
isError: true,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
452
|
+
return {
|
|
453
|
+
content: [
|
|
454
|
+
{
|
|
455
|
+
type: "text",
|
|
456
|
+
text: `Validation error: ${errorMessage}`,
|
|
457
|
+
},
|
|
458
|
+
],
|
|
459
|
+
isError: true,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (request.params.name === "xanoscript_docs") {
|
|
464
|
+
const args = request.params.arguments;
|
|
465
|
+
const keyword = args?.keyword;
|
|
466
|
+
const documentation = readXanoscriptDocs(keyword);
|
|
467
|
+
return {
|
|
468
|
+
content: [
|
|
469
|
+
{
|
|
470
|
+
type: "text",
|
|
471
|
+
text: documentation,
|
|
472
|
+
},
|
|
473
|
+
],
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
content: [
|
|
478
|
+
{
|
|
479
|
+
type: "text",
|
|
480
|
+
text: `Unknown tool: ${request.params.name}`,
|
|
481
|
+
},
|
|
482
|
+
],
|
|
483
|
+
isError: true,
|
|
484
|
+
};
|
|
485
|
+
});
|
|
486
|
+
// Start the server
|
|
487
|
+
async function main() {
|
|
488
|
+
const transport = new StdioServerTransport();
|
|
489
|
+
await server.connect(transport);
|
|
490
|
+
console.error("Xano Developer MCP server running on stdio");
|
|
491
|
+
}
|
|
492
|
+
main().catch((error) => {
|
|
493
|
+
console.error("Fatal error:", error);
|
|
494
|
+
process.exit(1);
|
|
495
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xano/developer-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Xano Headless API documentation and XanoScript code validation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"xano-developer-mcp": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"api_docs",
|
|
13
|
+
"xanoscript_docs"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"start": "node dist/index.js",
|
|
18
|
+
"dev": "tsc && node dist/index.js",
|
|
19
|
+
"prepublishOnly": "npm run build",
|
|
20
|
+
"sync-docs": "npx ts-node scripts/sync-xanoscript-docs.ts"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"xano",
|
|
24
|
+
"mcp",
|
|
25
|
+
"model-context-protocol",
|
|
26
|
+
"xanoscript",
|
|
27
|
+
"documentation",
|
|
28
|
+
"api",
|
|
29
|
+
"claude",
|
|
30
|
+
"ai"
|
|
31
|
+
],
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/xano-inc/xano-developer-mcp.git"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/xano-inc/xano-developer-mcp#readme",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/xano-inc/xano-developer-mcp/issues"
|
|
39
|
+
},
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
43
|
+
"@xano/xanoscript-language-server": "^11.6.3"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^22.0.0",
|
|
47
|
+
"typescript": "^5.9.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# xanoscript-ai-documentation
|