xoong
Menu · MCP server
DocsToolsMCP server

MCP server

The Model Context Protocol (MCP) is an open standard for connecting AI assistants to programs that offer them tools: the assistant starts the program, asks which tools it has, and calls them. xoong-mcp is that program for xoong. It gives an assistant the spec, the real operation names of your API, the checker and an explanation of every error, so the screens it writes pass xoong check instead of guessing attributes.

What the assistant gets

Six tools make a loop. The assistant reads the spec, looks up the operations, writes the screen, checks it and fixes what the message says, before it shows you any code.

  1. get_spec: read llms.txt, the whole public API in one short document.
  2. list_operations: the operation names of your API, so it never invents one.
  3. Write the screen.
  4. check: run xoong check on the HTML (or the script) it wrote. Each finding has a line, a column, a message and the fix.
  5. explain_error or explain_syntax when the message is not enough: what a code means, what an element or attribute does and which values it takes.
  6. search_docs for anything else in the documentation.

Back to step 3 until check says No findings. The server also sends this advice itself, in the instructions of its initialize reply.

Install and register

The server reads .xoong/operations.json, so run xoong sync in the project first. Then install the package, which has one program, xoong-mcp, and register it with your assistant as a server that runs over standard input and output (stdio).

npm install --save-dev @xoong/mcp@next
npx xoong sync openapi.json

# for you, in this project
claude mcp add xoong -- npx xoong-mcp

# for everyone who clones the project (writes .mcp.json)
claude mcp add --scope project xoong -- npx xoong-mcp

claude mcp list
{
  "mcpServers": {
    "xoong": {
      "command": "npx",
      "args": ["-y", "@xoong/mcp@next", "--operations", "/path/to/my-app/.xoong/operations.json"]
    }
  }
}
{
  "mcpServers": {
    "xoong": { "command": "npx", "args": ["xoong-mcp"] }
  }
}
{
  "mcpServers": {
    "xoong": {
      "command": "npx",
      "args": ["xoong-mcp", "--operations", ".xoong/operations.json", "--docs", "docs"]
    }
  }
}

Claude Desktop keeps its servers in claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/, Windows: %APPDATA%\Claude\); it does not know your project folder, so the path of operations.json is absolute and npx -y fetches the package. Cursor reads .cursor/mcp.json in the project, or ~/.cursor/mcp.json for every project. Other clients use the same mcpServers shape; look for the place in their documentation.

To see that it answers without an assistant, send it one message:

printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | npx xoong-mcp

Where it finds its files

The server looks for the table of operations and for the project's docs in the folder it is started from. Three options change that; none is required.

OptionDefaultWhat it does
--operations <file>.xoong/operations.jsonThe table that xoong sync wrote. It is read again on every call.
--llms <file>the spec packed with the packageA spec to give get_spec instead of the xoong one. If the packed one is missing, llms.txt in the folder is the last resort.
--docs <folder>a docs folder in the working folder, if there is oneThe project's own documentation. search_docs searches it first, then the xoong documentation.

If a file is missing, the tool that needs it answers with an error that says what to do (run `xoong sync <openapi>` first or pass --operations <file>), and the other tools keep working.

The tools

This table is made from the definitions in the server's source, so it is what tools/list returns.

ToolWhat it doesInput
get_specReturns llms.txt, the whole public API in one short document. Read it before writing xoong code.none
list_operationsThe operation names to use in xo-click, xo-submit and <xo-view get>, each with its method and path, query parameters and request fields (a * marks a required one). Without a filter it lists all. The table is read again on every call, so a new xoong sync needs no restart.filter (string): Only operations whose id or path contains this text.
checkRuns xoong check on the text it is given and returns the findings (code, line, column, message, fix), or No findings. Give html or script: one of the two is needed. It takes text, not a file name, so it checks what the assistant is about to write.html (string): The HTML source of a screen.
script (string): JavaScript or TypeScript source instead of html: checks html`...` templates and client.load/query/mutate/stream calls.
explain_errorWhat a code means and how to fix it: the XO2xx codes of xoong check and the XO1xx codes of xoong doctor and xoong sync. Capitals do not matter. An unknown code gets the list of known ones.code (string, required)
explain_syntaxWhat an element or attribute does, which attributes an element has and which values they take. Without a name it lists every element and attribute. A name that does not exist gets suggestions, so an assistant asks before it invents an attribute.name (string): An element (xo-table or <xo-table>), an attribute (xo-then), or an attribute of an element ("xo-table page-size").
search_docsA case-insensitive search of the .md files, line by line, up to 40 lines, with file names and line numbers. It searches the project's own docs folder first, then the xoong documentation (marked xoong/ when both are searched).query (string, required)

Errors of the tools are not protocol errors: the reply has isError: true and a text the assistant can act on, such as the closest name for an unknown attribute. explain_error knows two families of codes: XO2xx from xoong check and XO1xx from xoong doctor and xoong sync. They are all on the Error codes page, which is made from the same table.

A session, recorded

This is a real exchange. When the page is generated, the site starts xoong-mcp, sends it the messages below over standard input (JSON-RPC 2.0, one message per line) and prints what comes back, on the site's own demo API. The first message is shown as it goes over the wire; the others are shortened to the tool and its arguments, and a tool's reply is the text of its content. An assistant does all of this for you; what you see is its answer.

> {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"assistant","version":"1"}}}
< {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "tools": {}
    },
    "serverInfo": {
      "name": "xoong",
      "version": "0.0.54"
    },
    "instructions": "Call get_spec first. Write xoong screens, then call check on the HTML and fix every error from its message. Never invent an xo-* attribute or an operation: explain_syntax says what an element or attribute does, list_operations gives the real operation names, explain_error explains a code."
  }

> tools/list
< get_spec, list_operations, check, explain_error, explain_syntax, search_docs   (each with its description and input schema)

> tools/call list_operations {"filter":"delete"}
< [
    {
      "id": "deletePerson",
      "call": "DELETE /api/people/{id}"
    }
  ]

> tools/call check {"html":"<xo-view get=\"GET /api/people\">\n  <button xo-click=\"deletePersn\">Delete</button>\n</xo-view>"}
< [
    {
      "level": "error",
      "code": "XO201",
      "file": "screen.html",
      "line": 2,
      "column": 11,
      "message": "unknown operation \"deletePersn\" (closest: \"deletePerson\")",
      "fix": "Use \"deletePerson\""
    }
  ]

> tools/call explain_error {"code":"XO201"}
< XO201: An operation name in xo-click, xo-submit, <xo-view get> or <xo-form op> does not exist in the synced API. Use the closest name the message gives, or run `xoong sync` if the API changed.

> tools/call explain_syntax {"name":"xo-dialog size"}
< <xo-dialog size>: `sm`, `wide` or `full` (the default is 40rem).
  Values: sm, wide, full.

> tools/call check {"html":"<xo-view get=\"GET /api/people\">\n  <button xo-click=\"deletePerson\">Delete</button>\n</xo-view>"}
< No findings.

The assistant sent a misspelled operation, deletePersn. check answered with the code, the place and the closest name; explain_error said what XO201 means; the second check came back clean.

What it does not do yet

The spec and the docs come with the package

The npm tarball may hold src/ only, so before the package is packed a script (prepack.mjs) copies llms.txt and the docs/*.md files into src/; the copies are generated, not committed, and removed again afterwards. That is why get_spec and search_docs work in a project that installed @xoong/mcp from npm. In the xoong repository the server reads the files at the root. A test packs a copy of the package into a node_modules layout and asks it for the spec from an empty project.

Without MCP

An assistant that cannot run a server can still work from two things. llms.txt, at the root of the repository, is what get_spec returns: attach it or paste it. And npx xoong check --format json gives it the same findings that the check tool does; paste them back. Without list_operations, give it the operation names from .xoong/operations.json. A test keeps llms.txt honest: it stays within a token budget, names only commands that exist and documents every attribute the code defines.

See also: Command line · Editor support · Error codes · How it is built