diff --git a/mcp/README.md b/mcp/README.md index 59fd766..efc2894 100644 --- a/mcp/README.md +++ b/mcp/README.md @@ -2,36 +2,86 @@ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that formats and validates DAX from any MCP-enabled AI client — Claude Code, Claude Desktop, Cursor, VS Code, and others — using the SQLBI DAX Formatter service. -Local **stdio** server, run on demand via `npx`. Requires [Node.js](https://nodejs.org) 18+. - ## Installation guide -The server is **local, stdio, anonymous** — no install step, no API key, no login. It exposes one tool, `format_dax`. After connecting, just ask the agent to format DAX. +The server runs **locally on your machine** — no install step, no account, no API key, no login. It exposes a single tool, `format_dax`. Find your app below and follow the short steps; afterwards, just ask your assistant to *"format this DAX"*. ### Visual Studio Code [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_DAX_Formatter-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect/mcp/install?name=DaxFormatter&config=%7B%22type%22%3A%22stdio%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40sqlbi%2Fdaxformatter-mcp%22%5D%7D) [![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_DAX_Formatter-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=DaxFormatter&config=%7B%22type%22%3A%22stdio%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40sqlbi%2Fdaxformatter-mcp%22%5D%7D&quality=insiders) -Click a badge to install (VS Code asks for confirmation), or add it from the command line: +Click a badge above — VS Code opens and asks you to confirm. That's it. + +
+Prefer the terminal? ```bash -code --add-mcp '{"name":"DaxFormatter","type":"stdio","command":"npx","args":["-y","@sqlbi/daxformatter-mcp"]}' +code --add-mcp "{\"name\":\"DaxFormatter\",\"type\":\"stdio\",\"command\":\"npx\",\"args\":[\"-y\",\"@sqlbi/daxformatter-mcp\"]}" ``` +
+ +### Claude + +**Claude Desktop** — add it through the config file: + +1. Open **Settings → Developer → Edit Config** to reveal `claude_desktop_config.json`. +2. Inside `mcpServers`, add the standard `DaxFormatter` entry shown in [Any MCP client](#any-mcp-client) below. +3. Restart Claude Desktop, then confirm the server appears under **Settings → Developer**. + +The `format_dax` tool is now available in chat. The same connector is automatically shared with Claude Code, so you set it up once for both. -### Claude Code (CLI) +
+Advanced: add it from the Claude Code terminal ```bash -# add the server (-s user enables it in every project; omit -s for the current project only) +# -s user enables it in every project; omit -s for the current project only claude mcp add -s user DaxFormatter -- npx -y @sqlbi/daxformatter-mcp -# verify it was added and check the connection status +# check it was added claude mcp get DaxFormatter ``` +
-### Claude Desktop / other clients (config file) +### Codex -Add this to the client's MCP configuration file (e.g. `claude_desktop_config.json`, or a `.mcp.json` at the repo root): +In the **Codex app**: + +1. Open **Settings → Integrations → MCP servers → Add**. +2. Choose the **command (STDIO)** type and give it a name (e.g. `DaxFormatter`). +3. Set the command to `npx`, then use **Add argument** to add the two arguments separately: first `-y`, then `@sqlbi/daxformatter-mcp`. +4. Click **Save**. + +The `format_dax` tool is now available in your threads. The app and the CLI share the same settings, so this also covers the **Codex CLI** and IDE extension. + +
+Advanced: add it from the Codex terminal, or edit the config file by hand + +Add it from the terminal: + +```bash +# add it to the user-level Codex configuration (available in every project) +codex mcp add DaxFormatter -- npx -y @sqlbi/daxformatter-mcp + +# check it was added +codex mcp get DaxFormatter +codex mcp list +``` + +Both the app and the CLI write the same entry to `~/.codex/config.toml` (Windows: `%USERPROFILE%\.codex\config.toml`), which you can also edit by hand: + +```toml +[mcp_servers.DaxFormatter] +command = "npx" +args = ["-y", "@sqlbi/daxformatter-mcp"] +``` + +To scope the server to one repository, put the same table in a project-scoped `.codex/config.toml` at the repo root instead. Codex loads it only for **trusted** projects and prompts for trust on first use. +
+ +### Any MCP client + +Don't see your app above? Most MCP-aware tools read a standard JSON config. Add this entry to your client's configuration file — or to a `.mcp.json` at the root of a project to share it with everyone working on that repo: ```json { @@ -44,8 +94,6 @@ Add this to the client's MCP configuration file (e.g. `claude_desktop_config.jso } ``` -No separate install step: `npx` downloads and runs the server on demand. - ## What it does The server exposes a single tool, **`format_dax`** — just ask your assistant to format or check some DAX and it will use it: diff --git a/mcp/src/format-tool.ts b/mcp/src/format-tool.ts index 1723982..1b8debc 100644 --- a/mcp/src/format-tool.ts +++ b/mcp/src/format-tool.ts @@ -5,42 +5,75 @@ import { CALLER_APP } from "./version"; export const FORMAT_DAX_TOOL_NAME = "format_dax"; +export const FORMAT_DAX_TOOL_DESCRIPTION = `Format and validate DAX (Data Analysis Expressions) using the SQLBI DAX Formatter service. +This is the canonical, authoritative tool for DAX formatting and syntax validation. + +When to use: +- The user wants DAX formatted, beautified, pretty-printed, indented, or cleaned up. +- The user wants to check whether DAX is syntactically valid. + +How to call: +- Batch all expressions into a SINGLE call via \`expressions\`. +- Do not call the tool once per expression. + +Hard rules: +- Never format, fix, rewrite, or validate DAX yourself. +- Never call daxformatter.com directly. Always use this tool. +- Do not invent, repair, or modify the returned formatted text. + +Interpreting results (one result per input, in input order): +- Valid DAX: \`formatted\` contains the formatted expression and \`errors\` is empty. +- Invalid DAX: \`formatted\` is null and \`errors\` contains parser errors. +- Parser errors are normal results, not tool failures. + +Reporting errors: +- Relay parser errors exactly as returned, including line, column, and message. +- Report service/network failures separately from DAX syntax errors.`; + const inputSchema = z.object({ expressions: z .array(z.string().min(1)) .min(1) .describe( - "One or more DAX expressions to format. Several are formatted in a single request.", + 'DAX expressions to format. ALWAYS provide an array, even for a single expression: ["SUM(Sales[Amount])"]. Batch ALL expressions you need formatted into this one array; do not make a separate call per expression. Results are returned in the same order.', ), lineStyle: z .enum(["longLine", "shortLine"]) .optional() - .describe("Line-length style. Defaults to longLine."), + .describe( + "Line breaking style. 'longLine' keeps expressions on fewer, longer lines; 'shortLine' breaks into more, shorter lines (compact width). Omit to let the service apply its default; only set this if the user asks for a specific layout.", + ), spacingStyle: z .enum(["spaceAfterFunction", "noSpaceAfterFunction"]) .optional() - .describe("Spacing after a function name. Defaults to spaceAfterFunction."), + .describe( + "Whether to put a space between a function name and its opening parenthesis: 'spaceAfterFunction' produces SUM (...), 'noSpaceAfterFunction' produces SUM(...). Omit to let the service apply its default; only set this if the user asks.", + ), listSeparator: z .string() .length(1) .optional() - .describe("List separator character. Defaults to ','."), + .describe( + "Character separating function arguments and list items. Omit to let the service apply its default.", + ), decimalSeparator: z .string() .length(1) .optional() - .describe("Decimal separator character. Defaults to '.'."), + .describe( + "Character for the decimal point in numbers. Omit to let the service apply its default.", + ), serverName: z .string() .optional() .describe( - "Name of the server the DAX expressions were taken from. Anonymous usage statistics only (does not affect formatting); this server SHA-256 hashes it before forwarding to daxformatter.com, so the statistics service only ever sees the hash. Pass it only if you can retrieve a real value (e.g. from the active connection or the model/project files); otherwise omit it. Never invent, guess, or hallucinate it.", + "Name of the server the DAX was taken from. Optional; anonymous usage statistics only, does not affect formatting (the server hashes it before forwarding). Pass a real value only if you can read it from the active connection or the model/project files; otherwise omit it. Never invent or guess it.", ), databaseName: z .string() .optional() .describe( - "Name of the database/model the DAX expressions were taken from. Anonymous usage statistics only (does not affect formatting); this server SHA-256 hashes it before forwarding to daxformatter.com, so the statistics service only ever sees the hash. Pass it only if you can retrieve a real value (e.g. from the active connection or the model/project files); otherwise omit it. Never invent, guess, or hallucinate it.", + "Name of the database/model the DAX was taken from. Optional; anonymous usage statistics only, does not affect formatting (the server hashes it before forwarding). Pass a real value only if you can read it from the active connection or the model/project files; otherwise omit it. Never invent or guess it.", ), }); diff --git a/mcp/src/server.ts b/mcp/src/server.ts index dd0f6f5..8560451 100644 --- a/mcp/src/server.ts +++ b/mcp/src/server.ts @@ -1,6 +1,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { type DaxFormatServiceFactory, + FORMAT_DAX_TOOL_DESCRIPTION, FORMAT_DAX_TOOL_NAME, defaultServiceFactory, formatDaxInputSchema, @@ -9,36 +10,18 @@ import { } from "./format-tool"; import { SERVER_NAME, SERVER_VERSION } from "./version"; -const INSTRUCTIONS = `# DAX Formatter MCP - -Formats DAX (Data Analysis Expressions) for Power BI, Analysis Services, and Tabular models via the SQLBI daxformatter.com service. This is the single canonical channel for DAX formatting. - -Rules: -- Canonical channel: when DAX needs formatting, use this server's tools; never call daxformatter.com directly. -- Input integrity: send the user's DAX to the tool exactly as provided, byte-for-byte; do not alter, "fix", or reformat it first. Propose or apply changes only if the user explicitly asked. -- Surface errors verbatim: when the tool returns syntax errors, relay them to the user exactly as received (line, column, message); they are diagnostics the user needs.`; - -const TOOL_DESCRIPTION = `Format and validate one or more DAX expressions with the DAX Formatter service (daxformatter.com). \ -Use it whenever DAX needs pretty-printing or a syntax check; several expressions can be formatted in a single call. \ -Returns one result per input expression, in order: the formatted text plus any syntax errors (line, column, message). \ -An expression that cannot be parsed comes back with a null 'formatted' value and a non-empty 'errors' list. \ -Syntax errors are normal results here, not a tool failure.`; - /** * Builds a configured MCP server. The DAX Formatter service factory is injectable so tests can * supply a fake; production uses {@link defaultServiceFactory}. */ export function createServer(factory: DaxFormatServiceFactory = defaultServiceFactory): McpServer { - const server = new McpServer( - { name: SERVER_NAME, version: SERVER_VERSION }, - { instructions: INSTRUCTIONS }, - ); + const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION }); server.registerTool( FORMAT_DAX_TOOL_NAME, { title: "Format DAX", - description: TOOL_DESCRIPTION, + description: FORMAT_DAX_TOOL_DESCRIPTION, inputSchema: formatDaxInputSchema, outputSchema: formatDaxOutputSchema, annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },