From 544a6d3284874713a567d38c70d77ce17b3bae19 Mon Sep 17 00:00:00 2001 From: Alberto Spelta Date: Tue, 23 Jun 2026 11:36:15 +0200 Subject: [PATCH] Update MCP readme and tool instructions Expanded and reorganized README with step-by-step setup for multiple MCP clients, including advanced options and code snippets. Rewrote the DAX formatter tool description and input schema docs for explicit guidance and strict usage rules. Updated server to use the new tool description and removed legacy instructions. --- mcp/README.md | 72 +++++++++++++++++++++++++++++++++++------- mcp/src/format-tool.ts | 47 +++++++++++++++++++++++---- mcp/src/server.ts | 23 ++------------ 3 files changed, 103 insertions(+), 39 deletions(-) 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 },