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
[](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)
[](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 },