Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/Dax.Formatter.McpServer/Dax.Formatter.McpServer.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>Dax.Formatter.McpServer</RootNamespace>
<AssemblyName>Dax.Formatter.McpServer</AssemblyName>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ModelContextProtocol" Version="1.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.7" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Dax.Formatter\Dax.Formatter.csproj" />
</ItemGroup>

</Project>
84 changes: 84 additions & 0 deletions src/Dax.Formatter.McpServer/FormatDaxTool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
namespace Dax.Formatter.McpServer;

using Dax.Formatter;
using Dax.Formatter.Models;
using ModelContextProtocol.Server;
using System.ComponentModel;
using System.Reflection;

[McpServerToolType]
internal static class FormatDaxTool
{
[McpServerTool(
Name = "format_dax",
Title = "Format DAX expressions",
ReadOnly = true,
Idempotent = true,
OpenWorld = true,
Destructive = false)]
[Description("""
Formats DAX (Data Analysis Expressions) code. DAX comments are preserved. Supports both DAX queries and DAX expressions.

WHEN TO CALL:
- The user asks to format, beautify, prettify or normalize DAX code.

RETURNS:
An array of responses, one per input in the same order:
{
Formatted: string,
Errors: [{ Line: int, Column: int, Message: string }]
}
A format failure (invalid DAX) results in null `Formatted` and a non-empty `Errors` array.
A successful format results in a non-null `Formatted` and an empty or null `Errors` array.

NOTE:
This tool calls an external HTTP service. Always batch multiple snippets
in a single call — looping the tool wastes round-trips and is rate-limited upstream.
""")]
public static async Task<DaxFormatterResponse[]> FormatDax(
IDaxFormatterClient client,
[Description("""
The DAX expressions to format. Each array element must be a complete,
independent piece of DAX code — either a DAX query or a DAX expression.
""")]
string[] expressions,
[Description("List separator character.")]
char listSeparator = ',',
[Description("Decimal separator character.")]
char decimalSeparator = '.',
[Description("""
Controls how arguments and sub-expressions are wrapped across lines.
- 'LongLine': keeps arguments compact horizontally where readable.
- 'ShortLine': breaks each argument onto its own line for maximum vertical clarity.
""")]
DaxFormatterLineStyle lineStyle = DaxFormatterLineStyle.LongLine,
[Description($"""
Controls spacing between function names and their opening parentheses.
- 'SpaceAfterFunction': adds a space for readability, e.g. 'SUM (x)'.
- 'NoSpaceAfterFunction': removes the space for compactness, e.g. 'SUM(x)'.
""")]
DaxFormatterSpacingStyle spacingStyle = DaxFormatterSpacingStyle.SpaceAfterFunction,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(expressions);

if (expressions.Length == 0)
return [];

var request = new DaxFormatterMultipleRequest();
{
// Set formatting options
request.DecimalSeparator = decimalSeparator;
request.ListSeparator = listSeparator;
request.MaxLineLength = lineStyle;
request.SkipSpaceAfterFunctionName = spacingStyle;
// Add caller info
request.CallerApp = "Dax.Formatter.McpServer";
request.CallerVersion = typeof(Program).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
}
request.Dax.AddRange(expressions);

var responses = await client.FormatAsync(request, cancellationToken).ConfigureAwait(false);
return [.. responses];
}
}
57 changes: 57 additions & 0 deletions src/Dax.Formatter.McpServer/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Dax.Formatter;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Reflection;

var builder = Host.CreateApplicationBuilder(args);

builder.Logging.ClearProviders();
builder.Logging.AddConsole(options => options.LogToStandardErrorThreshold = LogLevel.Trace);

builder.Services.AddSingleton<IDaxFormatterClient, DaxFormatterClient>();
builder.Services
.AddMcpServer(ConfigureMcpServerOptions)
.WithStdioServerTransport()
.WithToolsFromAssembly();

await builder.Build().RunAsync();

static void ConfigureMcpServerOptions(ModelContextProtocol.Server.McpServerOptions options)
{
options.ServerInfo = new ModelContextProtocol.Protocol.Implementation
{
Name = "dax-formatter-mcp",
Version = typeof(Program).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "0.0.0",
WebsiteUrl = "https://www.daxformatter.com"
};
options.ServerInstructions = """
# DAX Formatter MCP Server

## Core Purpose

This server formats DAX (Data Analysis Expressions) source code for Microsoft
Power BI, Analysis Services, and Tabular models. It is the canonical wrapper
for the SQLBI daxformatter.com web service.

## Strict Behavioral Rules

### 1. Tool Selection

- No bypass: when DAX needs formatting, you MUST use this server's tools.
NEVER call the daxformatter.com HTTP endpoint directly.
This server is the single canonical channel for DAX formatting.

### 2. Code Integrity

- Pass input as-is: the user's DAX is the authoritative source. You MUST
send it to the tool exactly as provided without altering the string in any way.
You MAY propose or apply changes only when the user has explicitly asked you to do so.

### 3. Surface Errors

- Relay verbatim: if the formatter reports errors, you MUST pass them to
the user exactly as received including all details. They are diagnostic
information the user needs to fix the code.
""";
}
6 changes: 6 additions & 0 deletions src/Dax.Formatter.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dax.Formatter", "Dax.Format
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dax.Formatter.Tests", "Dax.Formatter.Tests\Dax.Formatter.Tests.csproj", "{44162C15-9AC7-406F-B1A4-1A49E73F962B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dax.Formatter.McpServer", "Dax.Formatter.McpServer\Dax.Formatter.McpServer.csproj", "{D839ACC9-427A-40AC-81FD-1DC4352FA019}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -21,6 +23,10 @@ Global
{44162C15-9AC7-406F-B1A4-1A49E73F962B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{44162C15-9AC7-406F-B1A4-1A49E73F962B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44162C15-9AC7-406F-B1A4-1A49E73F962B}.Release|Any CPU.Build.0 = Release|Any CPU
{D839ACC9-427A-40AC-81FD-1DC4352FA019}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D839ACC9-427A-40AC-81FD-1DC4352FA019}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D839ACC9-427A-40AC-81FD-1DC4352FA019}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D839ACC9-427A-40AC-81FD-1DC4352FA019}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Loading