D365FSC Security MCP
A .NET based MCP server that connects to Dynamics 365 Finance & Supply Chain and allows to for queries focused around user security
Ask AI about D365FSC Security MCP
Powered by Claude Β· Grounded in docs
I know everything about D365FSC Security MCP. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
D365 F&SC Security Analysis via MCP + Programmatic Tool Calling
Companion code for the blog post series "Using Claude AI to Analyze D365 F&SC Security Data".
| Post | Approach | Best For |
|---|---|---|
| Part 1 | Manual copy/paste into Claude chat | One-off analysis, no setup |
| Part 2 | Claude API + OData via C# console app | Automated, repeatable queries |
| Part 3 (this repo) | MCP Server + Programmatic Tool Calling | Interactive analysis + batch efficiency |
Solution Structure
D365ClaudeIntegration.sln
β
βββ D365McpServer/ # The MCP server
β βββ D365McpServer.csproj
β βββ appsettings.json # D365 connection config
β βββ Program.cs # Host setup, stdio transport, DI wiring
β βββ Models/
β β βββ D365Config.cs # Config binding model
β β βββ D365Models.cs # OData response models
β βββ Services/
β β βββ D365ODataService.cs # MSAL auth + OData query execution
β βββ Tools/
β βββ D365SecurityTools.cs # [McpServerTool] definitions
β
βββ D365ProgrammaticClient/ # Programmatic tool calling demo
β βββ D365ProgrammaticClient.csproj
β βββ appsettings.json # Anthropic API key + MCP server path
β βββ Program.cs # Batch user analysis with shared role schema
β
βββ claude_desktop_config_snippet.json # Claude Desktop wiring
Package Versions
D365McpServer
| Package | Version |
|---|---|
ModelContextProtocol | 1.1.0 |
Microsoft.Extensions.Hosting | 9.0.0 |
Microsoft.Extensions.Http | 9.0.0 |
Microsoft.Identity.Client | 4.67.2 |
Microsoft.Extensions.Configuration.Json | 9.0.0 |
Microsoft.Extensions.Configuration.Binder | 9.0.0 |
D365ProgrammaticClient
| Package | Version |
|---|---|
Anthropic.SDK | 5.10.0 |
ModelContextProtocol | 1.1.0 |
Microsoft.Extensions.Configuration.Json | 9.0.0 |
Microsoft.Extensions.Configuration.Binder | 9.0.0 |
Note on Anthropic.SDK vs the official Anthropic package:
Anthropic.SDK(by tghamm) is the community-built SDK used throughout this series for continuity with Parts 1 and 2. As of package version 10+, a separateAnthropicNuGet package became the official Anthropic C# SDK with a different API surface. This repo intentionally stays onAnthropic.SDK5.x.
Prerequisites
- .NET 9 SDK
- A D365 F&SC environment (sandbox is fine)
- An Azure AD App Registration with D365 API permissions (same setup as Part 2)
- Claude Desktop β for interactive MCP use
- An Anthropic API key β for the programmatic client
Setup: D365McpServer
1. Configure D365 Connection
Edit D365McpServer/appsettings.json:
{
"D365": {
"TenantId": "your-azure-ad-tenant-id",
"ClientId": "your-app-registration-client-id",
"ClientSecret": "your-client-secret",
"ResourceUrl": "https://your-env.sandbox.operations.dynamics.com",
"ApiVersion": "7.0"
}
}
2. Build and Publish
cd D365McpServer
dotnet build -c Release
dotnet publish -c Release -o ./publish
3. Configure Claude Desktop
Add to your Claude Desktop config file:
- Windows:
%APPDATA%\Claude\claude_desktop_config.json - macOS:
~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"d365-security": {
"command": "C:\\path\\to\\D365McpServer\\publish\\D365McpServer.exe",
"args": [],
"env": {}
}
}
}
Fully restart Claude Desktop after saving. Verify the server connected via Settings β Developer β MCP Servers β it should show a green status indicator. MCP server logs are written to stderr and can be found in %APPDATA%\Claude\logs\ on Windows.
4. Test Interactively
Open a new Claude Desktop conversation and try:
Show me all active users in the USMF company, then pull the role
assignments for the first 3 users you find.
Claude will discover and call your MCP tools automatically.
Setup: D365ProgrammaticClient
1. Configure
Edit D365ProgrammaticClient/appsettings.json:
{
"Anthropic": {
"ApiKey": "sk-ant-...",
"Model": "claude-sonnet-4-6"
},
"McpServer": {
"ExecutablePath": "C:\\path\\to\\D365McpServer\\publish\\D365McpServer.exe"
}
}
2. Update the User List
In D365ProgrammaticClient/Program.cs, update the usersToAnalyze array with real user IDs from your environment.
3. Run
cd D365ProgrammaticClient
dotnet run
Results are written to analysis-results.json in the build output directory.
MCP Tools Exposed
| Tool | Description |
|---|---|
GetUsers | List D365 users, optionally filtered by company and/or enabled status |
GetSecurityRoles | Retrieve the full security role catalog |
GetUserRoleAssignments | Get all role assignments for a specific user |
RunODataQuery | Execute any arbitrary OData query (power user escape hatch) |
How Programmatic Tool Calling Saves Tokens
When Claude calls tools interactively via Claude Desktop, the full tool schema is sent on every API call. For batch operations this adds up fast.
The programmatic client avoids this by:
- Fetching the tool schema once via
ListToolsAsync()and reusing it across all calls - Pre-fetching the role catalog once directly via
CallToolAsync("GetSecurityRoles"), then injecting it as prompt context rather than letting Claude call the tool on each iteration - Registering only the tools Claude needs for the batch β omitting
GetUsersandGetSecurityRolesfrom the schema entirely reduces per-call input tokens
Rough token comparison for a 20-user batch:
| Approach | Estimated Input Tokens |
|---|---|
| Interactive (schema on every call) | ~16,000 |
| Programmatic (schema once + pre-fetched catalog) | ~4,000 |
The savings grow linearly with user count and schema complexity.
Enterprise Option: HTTP/SSE Transport
The blog post uses stdio for simplicity β Claude Desktop spawns the server as a local process. For a centrally hosted team deployment, switch to HTTP/SSE by replacing .WithStdioServerTransport() in D365McpServer/Program.cs with:
.WithHttpServerTransport(options => options.Port = 5050)
Then point Claude Desktop at the URL instead of an executable:
{
"mcpServers": {
"d365-security": {
"url": "http://your-server:5050/sse"
}
}
}
Host in Azure Container Apps behind Azure API Management for auth, rate limiting, and centralized audit logging β giving your entire D365 security team access through a single managed integration without needing local credentials.
Build Troubleshooting
This section documents every breaking change encountered building this solution against the current package versions. Hopefully this saves you from the same debugging loop.
Solution Structure (CS8802)
Both projects must be in their own subdirectories under the .sln file. If you see CS8802: Only one compilation unit can have top-level statements, the most likely cause is stale files from a previous session being picked up by the compiler. Ensure only one Program.cs exists per project folder.
ModelContextProtocol 1.1.0 β Breaking Changes from 0.2.x
| Area | Old API | New API |
|---|---|---|
| Client creation | McpClientFactory.CreateAsync() | McpClient.CreateAsync() |
| Transport namespace | ModelContextProtocol.Protocol.Transport | ModelContextProtocol.Client |
| Client type | IMcpClient (interface) | McpClient (concrete class) |
| Tool schema access | tool.InputSchema | tool.ProtocolTool.InputSchema (returns JsonElement) |
| Tool result text | ContentBlock.Text | Cast to TextContentBlock first (see below) |
Reading text from a tool result:
CallToolAsync returns IList<ContentBlock>. The base ContentBlock class does not expose .Text β you must cast to the TextContentBlock subtype. Use the fully-qualified name to avoid ambiguity with Anthropic.SDK.Messaging.TextContent:
var text = result.Content
.OfType<ModelContextProtocol.Protocol.TextContentBlock>()
.FirstOrDefault()?.Text ?? "{}";
Converting MCP tool definitions to Anthropic.SDK tools:
McpClientTool.ProtocolTool.InputSchema is a JsonElement. Parse it to JsonNode for the Function constructor:
var schemaNode = JsonNode.Parse(t.ProtocolTool.InputSchema.GetRawText()) ?? new JsonObject();
var tool = (Common.Tool) new Function(t.Name, t.Description ?? string.Empty, schemaNode);
Anthropic.SDK 5.x β Breaking Changes from 3.x
| Area | Old type | New type |
|---|---|---|
| Text response content | TextBlock | TextContent |
| Tool call request from Claude | ToolUseBlock | ToolUseContent |
| Tool result back to Claude | ToolResultBlock | ToolResultContent |
| Tool list parameter | IList<Messaging.Tool> | IList<Common.Tool> |
| Tool definition | new Tool { Name, Description, InputSchema } | new Function(name, description, JsonNode) |
ToolResultContent.Content is List<ContentBase>, not string:
Even though the Anthropic API accepts a plain string for tool results at the protocol level, the C# SDK requires a typed list:
// Incorrect β does not compile
new ToolResultContent { ToolUseId = id, Content = "result text" }
// Correct
new ToolResultContent
{
ToolUseId = id,
Content = new List<ContentBase> { new TextContent { Text = "result text" } }
}
Message.Content is List<ContentBase>, not string:
// Incorrect β does not compile
new Message { Role = RoleType.User, Content = "your prompt" }
// Correct β use the constructor overload
new Message(RoleType.User, "your prompt")
Anthropic.SDK 5.x β NU1015 Package Error
If you see NU1015: The following PackageReference item(s) do not have a version specified, bump Anthropic.SDK to 5.10.0 or later. Earlier 3.x versions have a transitive dependency conflict with .NET 9 that triggers this error.
DotnetToolReference Error
If you see Package 'mcpclient x.x.x' has a package type 'DotnetTool' that is not supported, your .csproj has a stale <DotnetToolReference> entry from a previous session. Remove it β the .csproj files in this repo do not contain one.
