From d8eabca92b941893c6b9146f07f4eb16a38a24d9 Mon Sep 17 00:00:00 2001 From: Zdenek Srejber <77669032+zdeneksrejber@users.noreply.github.com> Date: Thu, 28 May 2026 14:45:09 +0200 Subject: [PATCH 1/3] docs: avoid dotnet run build output on MCP stdio --- src/TALXIS.CLI.MCP/README.md | 530 +++++++++++++++++------------------ 1 file changed, 260 insertions(+), 270 deletions(-) diff --git a/src/TALXIS.CLI.MCP/README.md b/src/TALXIS.CLI.MCP/README.md index 1745f288..0478aebf 100644 --- a/src/TALXIS.CLI.MCP/README.md +++ b/src/TALXIS.CLI.MCP/README.md @@ -1,270 +1,260 @@ -# TALXIS CLI MCP Server - -A [Model Context Protocol](https://modelcontextprotocol.io/) server for Power Platform and Dataverse development. Works with GitHub Copilot, Claude Code, VS Code, and any MCP-compatible client. - -## Prerequisites - -- [**.NET 10 SDK**](https://dotnet.microsoft.com/download/dotnet/10.0) or later - -## Setup - -### GitHub Copilot CLI - -Add to `~/.copilot/mcp-config.json`: - -```json -{ - "mcpServers": { - "TXC": { - "type": "stdio", - "command": "dnx", - "args": ["TALXIS.CLI.MCP", "--yes"] - } - } -} -``` - -### VS Code / GitHub Copilot Chat - -Add to `.vscode/mcp.json` in your project: - -```json -{ - "servers": { - "TALXIS CLI": { - "type": "stdio", - "command": "dnx", - "args": ["TALXIS.CLI.MCP", "--yes"] - } - } -} -``` - -### Claude Code - -```sh -claude mcp add --transport stdio txc -- dnx TALXIS.CLI.MCP --yes -``` - - -No install needed — [`dnx`](https://learn.microsoft.com/dotnet/core/tools/dotnet-tool-exec) downloads and runs the server on demand. - -[![NuGet](https://img.shields.io/nuget/v/TALXIS.CLI.MCP)](https://www.nuget.org/packages/TALXIS.CLI.MCP) - -## Features - -- **Tool Discovery & Execution** — Dynamically discovers and exposes all TALXIS CLI commands as MCP tools with typed input schemas -- **Task-Augmented Execution** — Long-running tools (deploy, import) support the MCP tasks protocol for async "call-now, fetch-later" execution with cancellation support -- **Structured Logging** — Streams real-time log output from CLI subprocesses to MCP clients via `notifications/message` -- **Progress Notifications** — Emits `notifications/progress` during long-running tool calls when the client provides a `progressToken` -- **Workspace Roots** — Requests the client's workspace roots via `roots/list` and uses the primary root as the subprocess working directory -- **Log Redaction** — Automatically redacts passwords, tokens, and filesystem paths from log output - -## Developing and Debugging Locally - -When developing the MCP server locally, you can run it directly from source and configure VS Code to use your local build. In your `.vscode/mcp.json`, set the server command to launch the project via `dotnet run`: - -```json -{ - "inputs": [], - "servers": { - "TALXIS CLI Dev": { - "type": "stdio", - "command": "dotnet", - "args": [ - "run", - "--project", - "${workspaceFolder}/src/TALXIS.CLI.MCP/TALXIS.CLI.MCP.csproj" - ] - } - } -} -``` - -- Adjust the path in `args` to match your local project location if needed. -- This setup allows you to test changes without reinstalling the global tool. - -## Testing and Debugging - -### Interactive Manual Testing - -You can use the [Model Context Protocol Inspector](https://www.npmjs.com/package/@modelcontextprotocol/inspector) for interactive inspection: - -```sh -npx @modelcontextprotocol/inspector dotnet run --project src/TALXIS.CLI.MCP -``` - -> **Note:** The Inspector is an interactive web browser application designed for manual testing and exploration. It is not suitable for automated testing scenarios. - -### Command Line Debugging & Automated Testing - -For debugging or automated testing, you can interact with the MCP server using JSON-RPC messages over stdin/stdout: - -```sh -# Start the server -dotnet run --project src/TALXIS.CLI.MCP - -# Then send JSON-RPC messages via stdin (one per line): -# 1. Initialize the connection (required by MCP protocol) -{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "test-client", "version": "1.0.0"}}} - -# 2. List available tools -{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}} - -# 3. Call a specific tool (example) -{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "tool-name", "arguments": {}}} - -# 4. Call a tool with progress tracking -{"jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": {"name": "environment_deploy", "arguments": {"Package": "MyPackage"}, "_meta": {"progressToken": "my-progress-1"}}} - -# 5. Call a long-running tool as a task (returns immediately with task ID) -{"jsonrpc": "2.0", "id": 5, "method": "tools/call", "params": {"name": "environment_deploy", "arguments": {"Package": "MyPackage"}, "task": {}}} - -# 6. Poll task status -{"jsonrpc": "2.0", "id": 6, "method": "tasks/get", "params": {"taskId": ""}} - -# 7. Get task result (blocks until complete) -{"jsonrpc": "2.0", "id": 7, "method": "tasks/result", "params": {"taskId": ""}} -``` - - -#### Example JSON-RPC Messages - -To list available tools: - -```json -{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}} -``` - -To call a tool (replace arguments as needed): - -```json -{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "workspace_component_create", "arguments": {"ShortName": "pp-entity", "name": "TestEntity", "Param": ["EntityType=InvalidType"]}}} -``` - ---- - -## Auth contract (stdio) - -**The MCP server never runs an interactive auth flow.** Every `txc` tool -subprocess spawned by this server is forced headless — stdout is reserved -for JSON-RPC frames and the child has stdin/stdout redirected, so a -browser/device-code/masked-prompt attempt would either hang the session -or corrupt the transport. `CliSubprocessRunner` sets -`TXC_NON_INTERACTIVE=1` unconditionally on every spawn. - -Prerequisites, before invoking any tool that touches a Dataverse -environment (or any other Connection-bound tool): - -1. On the human's machine, run `txc config auth login` (interactive - browser) or `txc config auth add-service-principal` once to register - a credential and prime the MSAL token cache. -2. Run `txc config connection create --provider dataverse ...` - to register the endpoint. -3. Run `txc config profile create --auth --connection ` - to bind them, then `txc config profile select ` (or pin the - workspace via `txc config profile pin`). - -After that, MCP tool calls resolve the active profile silently via the -acquired token cache or stored SPN secret. If resolution fails (expired -refresh token, missing credential, broken config), the subprocess exits -non-zero and the MCP server surfaces the error through the tool-call -result; the structured log line includes the fail-fast remedy string -(`txc config profile validate `). - -### Per-call profile override - -Any Connection-touching MCP tool accepts an optional `profile` argument -which is forwarded to the child as `--profile `. This lets a -single MCP session switch between profiles per call without restarting -the server — e.g. an agent can target `customer-a-dev` for one tool and -`customer-b-prod` for the next. - -### Client env allow-list (`mcpServers..env`) - -The MCP server inherits the client's environment and the child CLI -subprocess inherits that. The following are meaningful to `txc`; anything -else is ignored: - -| Variable | Purpose | -|---|---| -| `TXC_PROFILE` | Select active profile for every call in this MCP session (overridden by per-call `profile` argument when supplied). | -| `TXC_CONFIG_DIR` | Override entire config directory (total CI isolation). | -| `TXC_NON_INTERACTIVE` | Redundant here — MCP already forces this to `1`. | -| `TXC_LOG_FORMAT` | Redundant here — MCP already forces `json`. | -| `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_TENANT_ID` | Service-principal fallback for CI / headless runners. | -| `AZURE_FEDERATED_TOKEN_FILE` | Kubernetes / Azure workload-identity federation. | -| `ACTIONS_ID_TOKEN_REQUEST_URL`, `ACTIONS_ID_TOKEN_REQUEST_TOKEN` | GitHub Actions OIDC federation. | -| `TXC_ADO_ID_TOKEN_REQUEST_URL`, `TXC_ADO_ID_TOKEN_REQUEST_TOKEN` | Azure DevOps pipelines workload-identity federation (legacy `PAC_ADO_*` also honored). | - -Secrets (client secrets, PATs, certificate passwords) are **never** -accepted as plain MCP tool arguments — they are stored in the OS-level -secret vault via `txc config auth add-service-principal` and referenced -from config by `SecretRef` handle only. - -### Log redaction - -`McpLogForwarder` runs every child stderr JSON log line through -`LogRedactionFilter` before emitting `notifications/message` to the MCP -client. Patterns covered: `Bearer `, `Authorization:` headers, -bare JWTs, connection-string secret keys (`Password`, `ClientSecret`, -`AccessToken`, `RefreshToken`, etc.) and URL query-param secrets -(`code=`, `token=`, `access_token=`). This is the last-chance belt for -accidental `logger.LogError(ex, ...)` leaks where the Dataverse or -Microsoft.Identity SDKs embed tokens inside exception messages. - -### Forbidden patterns (forward-compat for HTTP transport) - -When the HTTP/SSE transport ships, `txc-mcp` will be an **OAuth resource -server only** — Entra ID remains the authorization server. The following -patterns are forbidden by the MCP spec and will not be implemented: - -- **Token passthrough**: never forward a client's bearer token directly - to Dataverse — always use on-behalf-of or a separate service account. -- **Tokens in URIs**: never embed access tokens in query strings. -- **Non-HTTPS redirects** (except loopback for local dev). -- **Missing PKCE**: all OAuth flows must use PKCE. -- **Missing audience check**: tokens must be audience-bound to the - `txc-mcp` resource URI (RFC 8707). - -See `docs/mcp-http-auth-notes.md` (design notes, no code in v1). - ---- - -## Progressive Disclosure Architecture - -### The Problem - -MCP clients like GitHub Copilot have practical limits on the number of tools they can handle effectively. With 128+ CLI commands registered as MCP tools, the full catalog causes context bloat — the client's LLM spends tokens parsing tool schemas instead of reasoning about the user's task. - -### The Solution - -`txc-mcp` uses **progressive disclosure** to expose only a small set of always-on tools at startup, then dynamically injects additional tools as needed: - -**Always-on tools (9 total):** -- **6 guide tools** — `guide`, `guide_workspace`, `guide_environment`, `guide_deployment`, `guide_data`, `guide_config` — domain-scoped tool discovery via LLM sampling -- **`execute_operation`** — bridges same-turn execution for tools discovered by a guide -- **`get_skill_details`** — retrieves public developer-facing skills (documentation, best practices) -- **`copilot-instructions`** — manages `.github/copilot-instructions.md` for AI assistants - -### The Workflow - -1. **Guide call** — The client calls a guide tool (e.g., `guide_workspace`) with a natural language query describing the task. -2. **Sampling** — The guide sends a `sampling/createMessage` request to the client's LLM with the full internal catalog, asking it to select the most relevant tools. Internal reasoning skills are injected for domain-specific expertise. -3. **Same-turn execution** — The guide returns structured results with tool names and schemas. The client can immediately call `execute_operation` with the selected tool name and arguments — no round-trip needed. -4. **Direct calls (next turn)** — Matched tools are injected into the `ActiveToolSet`. Clients discover injected tools by re-fetching tools/list on subsequent turns, and can then call them directly by name. - -### Skills Architecture - -`txc-mcp` has a two-tier skills system: - -- **Internal skills** (proprietary) — Embedded `.md` files in `Skills/Internal/` containing Power Platform development expertise, decision trees, and workflow patterns. These are loaded by `GuideReasoningEngine` and injected into sampling prompts. They are **never exposed to clients** — they guide the server's own reasoning. -- **Public skills** (developer-facing) — Loaded from `TALXIS.CLI.Features.Docs` embedded resources. Accessible via the `get_skill_details` MCP tool and the `txc docs` CLI command. These contain documentation, best practices, and how-to guides for developers. - -### Local-First Development Philosophy - -Guide tools and internal skills encode a **local-first** philosophy: prefer local workspace operations (scaffolding, validation, schema inspection) over live environment operations (API calls to Dataverse). Environment operations take 30 seconds to 5 minutes and should only be used for inspection, troubleshooting, or deployment after local validation is complete. - ---- - -For more details, see the main [TALXIS CLI README](../../README.md). +# TALXIS CLI MCP Server + +A [Model Context Protocol](https://modelcontextprotocol.io/) server for Power Platform and Dataverse development. Works with GitHub Copilot, Claude Code, VS Code, and any MCP-compatible client. + +## Prerequisites + +- [**.NET 10 SDK**](https://dotnet.microsoft.com/download/dotnet/10.0) or later + +## Setup + +### GitHub Copilot CLI + +Add to `~/.copilot/mcp-config.json`: + +```json +{ + "mcpServers": { + "TXC": { + "type": "stdio", + "command": "dnx", + "args": ["TALXIS.CLI.MCP", "--yes"] + } + } +} +``` + +### VS Code / GitHub Copilot Chat + +Add to `.vscode/mcp.json` in your project: + +```json +{ + "servers": { + "TALXIS CLI": { + "type": "stdio", + "command": "dnx", + "args": ["TALXIS.CLI.MCP", "--yes"] + } + } +} +``` + +### Claude Code + +```sh +claude mcp add --transport stdio txc -- dnx TALXIS.CLI.MCP --yes +``` + + +No install needed — [`dnx`](https://learn.microsoft.com/dotnet/core/tools/dotnet-tool-exec) downloads and runs the server on demand. + +[![NuGet](https://img.shields.io/nuget/v/TALXIS.CLI.MCP)](https://www.nuget.org/packages/TALXIS.CLI.MCP) + +## Features + +- **Tool Discovery & Execution** — Dynamically discovers and exposes all TALXIS CLI commands as MCP tools with typed input schemas +- **Task-Augmented Execution** — Long-running tools (deploy, import) support the MCP tasks protocol for async "call-now, fetch-later" execution with cancellation support +- **Structured Logging** — Streams real-time log output from CLI subprocesses to MCP clients via `notifications/message` +- **Progress Notifications** — Emits `notifications/progress` during long-running tool calls when the client provides a `progressToken` +- **Workspace Roots** — Requests the client's workspace roots via `roots/list` and uses the primary root as the subprocess working directory +- **Log Redaction** — Automatically redacts passwords, tokens, and filesystem paths from log output + +## Developing and Debugging Locally + +When developing the MCP server locally, avoid plain `dotnet run` for stdio MCP sessions. `dotnet run` performs a build first, and build warnings can be written before the MCP transport is ready. MCP clients may then try to parse those warning lines as protocol messages. + +Build the project separately, then configure VS Code to start the already-built server with `--no-build`: + +```sh +dotnet build src/TALXIS.CLI.MCP/TALXIS.CLI.MCP.csproj +``` + +In your `.vscode/mcp.json`, set the server command like this: + +```json +{ + "inputs": [], + "servers": { + "TALXIS CLI Dev": { + "type": "stdio", + "command": "dotnet", + "args": [ + "run", + "--no-build", + "--project", + "${workspaceFolder}/src/TALXIS.CLI.MCP/TALXIS.CLI.MCP.csproj" + ] + } + } +} +``` + +- Adjust the path in `args` to match your local project location if needed. +- Rebuild after code changes before restarting the MCP server. +- For non-local usage, prefer the packaged server via `dnx TALXIS.CLI.MCP --yes`. + +## Testing and Debugging + +### Interactive Manual Testing + +You can use the [Model Context Protocol Inspector](https://www.npmjs.com/package/@modelcontextprotocol/inspector) for interactive inspection: + +```sh +dotnet build src/TALXIS.CLI.MCP/TALXIS.CLI.MCP.csproj +npx @modelcontextprotocol/inspector dotnet run --no-build --project src/TALXIS.CLI.MCP +``` + +> **Note:** The Inspector is an interactive web browser application designed for manual testing and exploration. It is not suitable for automated testing scenarios. + +### Command Line Debugging & Automated Testing + +For debugging or automated testing, interact with the MCP server using JSON-RPC messages over stdin/stdout. Build first, then run with `--no-build` so stdout stays reserved for MCP traffic: + +```sh +# Build once before starting the server +dotnet build src/TALXIS.CLI.MCP/TALXIS.CLI.MCP.csproj + +# Start the server without rebuilding +dotnet run --no-build --project src/TALXIS.CLI.MCP + +# Then send JSON-RPC messages via stdin (one per line): +# 1. Initialize the connection (required by MCP protocol) +{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "test-client", "version": "1.0.0"}}} + +# 2. List available tools +{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}} + +# 3. Call a specific tool (example) +{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "tool-name", "arguments": {}}} + +# 4. Call a tool with progress tracking +{"jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": {"name": "environment_deploy", "arguments": {"Package": "MyPackage"}, "_meta": {"progressToken": "my-progress-1"}}} + +# 5. Call a long-running tool as a task (returns immediately with task ID) +{"jsonrpc": "2.0", "id": 5, "method": "tools/call", "params": {"name": "environment_deploy", "arguments": {"Package": "MyPackage"}, "task": {}}} + +# 6. Poll task status +{"jsonrpc": "2.0", "id": 6, "method": "tasks/get", "params": {"taskId": ""}} + +# 7. Get task result (blocks until complete) +{"jsonrpc": "2.0", "id": 7, "method": "tasks/result", "params": {"taskId": ""}} +``` + + +#### Example JSON-RPC Messages + +To list available tools: + +```json +{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}} +``` + +To call a tool (replace arguments as needed): + +```json +{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "workspace_component_create", "arguments": {"ShortName": "pp-entity", "name": "TestEntity", "Param": ["EntityType=InvalidType"]}}} +``` + +--- + +## Auth contract (stdio) + +**The MCP server never runs an interactive auth flow.** Every `txc` tool +subprocess spawned by this server is forced headless — stdout is reserved +for JSON-RPC frames and the child has stdin/stdout redirected, so a +browser/device-code/masked-prompt attempt would either hang the session +or corrupt the transport. `CliSubprocessRunner` sets +`TXC_NON_INTERACTIVE=1` unconditionally on every spawn. + +Prerequisites, before invoking any tool that touches a Dataverse +environment (or any other Connection-bound tool): + +1. On the human's machine, run `txc config auth login` (interactive + browser) or `txc config auth add-service-principal` once to register + a credential and prime the MSAL token cache. +2. Run `txc config connection create --provider dataverse ...` + to register the endpoint. +3. Run `txc config profile create --auth --connection ` + to bind them, then `txc config profile select ` (or pin the + workspace via `txc config profile pin`). + +After that, MCP tool calls resolve the active profile silently via the +acquired token cache or stored SPN secret. If resolution fails (expired +refresh token, missing credential, broken config), the subprocess exits +non-zero and the MCP server surfaces the error through the tool-call +result; the structured log line includes the fail-fast remedy string +(`txc config profile validate `). + +### Per-call profile override + +Any Connection-touching MCP tool accepts an optional `profile` argument +which is forwarded to the child as `--profile `. This lets a +single MCP session switch between profiles per call without restarting +the server — e.g. an agent can target `customer-a-dev` for one tool and +`customer-b-prod` for the next. + +### Client env allow-list (`mcpServers..env`) + +The MCP server inherits the client's environment and the child CLI +subprocess inherits that. The following are meaningful to `txc`; anything +else is ignored: + +| Variable | Purpose | +|---|---| +| `TXC_PROFILE` | Select active profile for every call in this MCP session (overridden by per-call `profile` argument when supplied). | +| `TXC_CONFIG_DIR` | Override entire config directory (total CI isolation). | +| `TXC_NON_INTERACTIVE` | Redundant here — MCP already forces this to `1`. | +| `TXC_LOG_FORMAT` | Redundant here — MCP already forces `json`. | +| `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_TENANT_ID` | Service-principal fallback for CI / headless runners. | +| `AZURE_FEDERATED_TOKEN_FILE` | Kubernetes / Azure workload-identity federation. | +| `ACTIONS_ID_TOKEN_REQUEST_URL`, `ACTIONS_ID_TOKEN_REQUEST_TOKEN` | GitHub Actions OIDC federation. | +| `TXC_ADO_ID_TOKEN_REQUEST_URL`, `TXC_ADO_ID_TOKEN_REQUEST_TOKEN` | Azure DevOps pipelines workload-identity federation (legacy `PAC_ADO_*` also honored). | + +Secrets (client secrets, PATs, certificate passwords) are **never** +accepted as plain MCP tool arguments — they are stored in the OS-level +secret vault via `txc config auth add-service-principal` and referenced +from config by `SecretRef` handle only. + +### Log redaction + +`McpLogForwarder` runs every child stderr JSON log line through +`LogRedactionFilter` before emitting `notifications/message` to the MCP +client. Patterns covered: `Bearer `, `Authorization:` headers, +bare JWTs, connection-string secret keys (`Password`, `ClientSecret`, +`AccessToken`, `RefreshToken`, etc.) and URL query-param secrets +(`code=`, `token=`, `access_token=`). This is the last-chance belt for +accidental `logger.LogError(ex, ...)` leaks where the Dataverse or +Microsoft.Identity SDKs embed tokens inside exception messages. + +### Forbidden patterns (forward-compat for HTTP transport) + +When the HTTP/SSE transport ships, `txc-mcp` will be an **OAuth resource +server only** — Entra ID remains the authorization server. The following +patterns are forbidden by the MCP spec and will not be implemented: + +- **Token passthrough**: never forward a client's bearer token directly + to Dataverse — always use on-behalf-of or a separate service account. +- **Tokens in URIs**: never embed access tokens in query strings. +- **Non-HTTPS redirects** (except loopback for local dev). +- **Missing PKCE**: all OAuth flows must use PKCE. +- **Missing audience check**: tokens must be audience-bound to the + `txc-mcp` resource URI (RFC 8707). + +See `docs/mcp-http-auth-notes.md` (design notes, no code in v1). + +--- + +## Progressive Disclosure Architecture + +### The Problem + +MCP clients like GitHub Copilot have practical limits on the number of tools they can handle effectively. With 128+ CLI commands registered as MCP tools, the full catalog causes context bloat — the client's LLM spends tokens parsing tool schemas instead of reasoning about the user's task. + +### The Solution + +`txc-mcp` uses **progressive disclosure** to expose only a small set of always-on tools at startup, then dynamically injects additional tools as needed: + +**Always-on tools (9 total):** +- **6 guide tools** — `guide`, `guide_workspace`, `guide_environment`, `guide_deployment`, `guide_data`, `guide_config` — domain-scoped tool discovery via LLM sampling +- **`execute_operation`** — bridges same-turn execution for tools discovered by a guide \ No newline at end of file From 377f228c05788cdafe2a5e888d5c5dbaa00bf4c5 Mon Sep 17 00:00:00 2001 From: Zdenek Srejber <77669032+zdeneksrejber@users.noreply.github.com> Date: Thu, 28 May 2026 15:03:19 +0200 Subject: [PATCH 2/3] docs: normalize MCP README line endings --- src/TALXIS.CLI.MCP/README.md | 520 +++++++++++++++++------------------ 1 file changed, 260 insertions(+), 260 deletions(-) diff --git a/src/TALXIS.CLI.MCP/README.md b/src/TALXIS.CLI.MCP/README.md index 0478aebf..223d0154 100644 --- a/src/TALXIS.CLI.MCP/README.md +++ b/src/TALXIS.CLI.MCP/README.md @@ -1,260 +1,260 @@ -# TALXIS CLI MCP Server - -A [Model Context Protocol](https://modelcontextprotocol.io/) server for Power Platform and Dataverse development. Works with GitHub Copilot, Claude Code, VS Code, and any MCP-compatible client. - -## Prerequisites - -- [**.NET 10 SDK**](https://dotnet.microsoft.com/download/dotnet/10.0) or later - -## Setup - -### GitHub Copilot CLI - -Add to `~/.copilot/mcp-config.json`: - -```json -{ - "mcpServers": { - "TXC": { - "type": "stdio", - "command": "dnx", - "args": ["TALXIS.CLI.MCP", "--yes"] - } - } -} -``` - -### VS Code / GitHub Copilot Chat - -Add to `.vscode/mcp.json` in your project: - -```json -{ - "servers": { - "TALXIS CLI": { - "type": "stdio", - "command": "dnx", - "args": ["TALXIS.CLI.MCP", "--yes"] - } - } -} -``` - -### Claude Code - -```sh -claude mcp add --transport stdio txc -- dnx TALXIS.CLI.MCP --yes -``` - - -No install needed — [`dnx`](https://learn.microsoft.com/dotnet/core/tools/dotnet-tool-exec) downloads and runs the server on demand. - -[![NuGet](https://img.shields.io/nuget/v/TALXIS.CLI.MCP)](https://www.nuget.org/packages/TALXIS.CLI.MCP) - -## Features - -- **Tool Discovery & Execution** — Dynamically discovers and exposes all TALXIS CLI commands as MCP tools with typed input schemas -- **Task-Augmented Execution** — Long-running tools (deploy, import) support the MCP tasks protocol for async "call-now, fetch-later" execution with cancellation support -- **Structured Logging** — Streams real-time log output from CLI subprocesses to MCP clients via `notifications/message` -- **Progress Notifications** — Emits `notifications/progress` during long-running tool calls when the client provides a `progressToken` -- **Workspace Roots** — Requests the client's workspace roots via `roots/list` and uses the primary root as the subprocess working directory -- **Log Redaction** — Automatically redacts passwords, tokens, and filesystem paths from log output - -## Developing and Debugging Locally - -When developing the MCP server locally, avoid plain `dotnet run` for stdio MCP sessions. `dotnet run` performs a build first, and build warnings can be written before the MCP transport is ready. MCP clients may then try to parse those warning lines as protocol messages. - -Build the project separately, then configure VS Code to start the already-built server with `--no-build`: - -```sh -dotnet build src/TALXIS.CLI.MCP/TALXIS.CLI.MCP.csproj -``` - -In your `.vscode/mcp.json`, set the server command like this: - -```json -{ - "inputs": [], - "servers": { - "TALXIS CLI Dev": { - "type": "stdio", - "command": "dotnet", - "args": [ - "run", - "--no-build", - "--project", - "${workspaceFolder}/src/TALXIS.CLI.MCP/TALXIS.CLI.MCP.csproj" - ] - } - } -} -``` - -- Adjust the path in `args` to match your local project location if needed. -- Rebuild after code changes before restarting the MCP server. -- For non-local usage, prefer the packaged server via `dnx TALXIS.CLI.MCP --yes`. - -## Testing and Debugging - -### Interactive Manual Testing - -You can use the [Model Context Protocol Inspector](https://www.npmjs.com/package/@modelcontextprotocol/inspector) for interactive inspection: - -```sh -dotnet build src/TALXIS.CLI.MCP/TALXIS.CLI.MCP.csproj -npx @modelcontextprotocol/inspector dotnet run --no-build --project src/TALXIS.CLI.MCP -``` - -> **Note:** The Inspector is an interactive web browser application designed for manual testing and exploration. It is not suitable for automated testing scenarios. - -### Command Line Debugging & Automated Testing - -For debugging or automated testing, interact with the MCP server using JSON-RPC messages over stdin/stdout. Build first, then run with `--no-build` so stdout stays reserved for MCP traffic: - -```sh -# Build once before starting the server -dotnet build src/TALXIS.CLI.MCP/TALXIS.CLI.MCP.csproj - -# Start the server without rebuilding -dotnet run --no-build --project src/TALXIS.CLI.MCP - -# Then send JSON-RPC messages via stdin (one per line): -# 1. Initialize the connection (required by MCP protocol) -{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "test-client", "version": "1.0.0"}}} - -# 2. List available tools -{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}} - -# 3. Call a specific tool (example) -{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "tool-name", "arguments": {}}} - -# 4. Call a tool with progress tracking -{"jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": {"name": "environment_deploy", "arguments": {"Package": "MyPackage"}, "_meta": {"progressToken": "my-progress-1"}}} - -# 5. Call a long-running tool as a task (returns immediately with task ID) -{"jsonrpc": "2.0", "id": 5, "method": "tools/call", "params": {"name": "environment_deploy", "arguments": {"Package": "MyPackage"}, "task": {}}} - -# 6. Poll task status -{"jsonrpc": "2.0", "id": 6, "method": "tasks/get", "params": {"taskId": ""}} - -# 7. Get task result (blocks until complete) -{"jsonrpc": "2.0", "id": 7, "method": "tasks/result", "params": {"taskId": ""}} -``` - - -#### Example JSON-RPC Messages - -To list available tools: - -```json -{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}} -``` - -To call a tool (replace arguments as needed): - -```json -{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "workspace_component_create", "arguments": {"ShortName": "pp-entity", "name": "TestEntity", "Param": ["EntityType=InvalidType"]}}} -``` - ---- - -## Auth contract (stdio) - -**The MCP server never runs an interactive auth flow.** Every `txc` tool -subprocess spawned by this server is forced headless — stdout is reserved -for JSON-RPC frames and the child has stdin/stdout redirected, so a -browser/device-code/masked-prompt attempt would either hang the session -or corrupt the transport. `CliSubprocessRunner` sets -`TXC_NON_INTERACTIVE=1` unconditionally on every spawn. - -Prerequisites, before invoking any tool that touches a Dataverse -environment (or any other Connection-bound tool): - -1. On the human's machine, run `txc config auth login` (interactive - browser) or `txc config auth add-service-principal` once to register - a credential and prime the MSAL token cache. -2. Run `txc config connection create --provider dataverse ...` - to register the endpoint. -3. Run `txc config profile create --auth --connection ` - to bind them, then `txc config profile select ` (or pin the - workspace via `txc config profile pin`). - -After that, MCP tool calls resolve the active profile silently via the -acquired token cache or stored SPN secret. If resolution fails (expired -refresh token, missing credential, broken config), the subprocess exits -non-zero and the MCP server surfaces the error through the tool-call -result; the structured log line includes the fail-fast remedy string -(`txc config profile validate `). - -### Per-call profile override - -Any Connection-touching MCP tool accepts an optional `profile` argument -which is forwarded to the child as `--profile `. This lets a -single MCP session switch between profiles per call without restarting -the server — e.g. an agent can target `customer-a-dev` for one tool and -`customer-b-prod` for the next. - -### Client env allow-list (`mcpServers..env`) - -The MCP server inherits the client's environment and the child CLI -subprocess inherits that. The following are meaningful to `txc`; anything -else is ignored: - -| Variable | Purpose | -|---|---| -| `TXC_PROFILE` | Select active profile for every call in this MCP session (overridden by per-call `profile` argument when supplied). | -| `TXC_CONFIG_DIR` | Override entire config directory (total CI isolation). | -| `TXC_NON_INTERACTIVE` | Redundant here — MCP already forces this to `1`. | -| `TXC_LOG_FORMAT` | Redundant here — MCP already forces `json`. | -| `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_TENANT_ID` | Service-principal fallback for CI / headless runners. | -| `AZURE_FEDERATED_TOKEN_FILE` | Kubernetes / Azure workload-identity federation. | -| `ACTIONS_ID_TOKEN_REQUEST_URL`, `ACTIONS_ID_TOKEN_REQUEST_TOKEN` | GitHub Actions OIDC federation. | -| `TXC_ADO_ID_TOKEN_REQUEST_URL`, `TXC_ADO_ID_TOKEN_REQUEST_TOKEN` | Azure DevOps pipelines workload-identity federation (legacy `PAC_ADO_*` also honored). | - -Secrets (client secrets, PATs, certificate passwords) are **never** -accepted as plain MCP tool arguments — they are stored in the OS-level -secret vault via `txc config auth add-service-principal` and referenced -from config by `SecretRef` handle only. - -### Log redaction - -`McpLogForwarder` runs every child stderr JSON log line through -`LogRedactionFilter` before emitting `notifications/message` to the MCP -client. Patterns covered: `Bearer `, `Authorization:` headers, -bare JWTs, connection-string secret keys (`Password`, `ClientSecret`, -`AccessToken`, `RefreshToken`, etc.) and URL query-param secrets -(`code=`, `token=`, `access_token=`). This is the last-chance belt for -accidental `logger.LogError(ex, ...)` leaks where the Dataverse or -Microsoft.Identity SDKs embed tokens inside exception messages. - -### Forbidden patterns (forward-compat for HTTP transport) - -When the HTTP/SSE transport ships, `txc-mcp` will be an **OAuth resource -server only** — Entra ID remains the authorization server. The following -patterns are forbidden by the MCP spec and will not be implemented: - -- **Token passthrough**: never forward a client's bearer token directly - to Dataverse — always use on-behalf-of or a separate service account. -- **Tokens in URIs**: never embed access tokens in query strings. -- **Non-HTTPS redirects** (except loopback for local dev). -- **Missing PKCE**: all OAuth flows must use PKCE. -- **Missing audience check**: tokens must be audience-bound to the - `txc-mcp` resource URI (RFC 8707). - -See `docs/mcp-http-auth-notes.md` (design notes, no code in v1). - ---- - -## Progressive Disclosure Architecture - -### The Problem - -MCP clients like GitHub Copilot have practical limits on the number of tools they can handle effectively. With 128+ CLI commands registered as MCP tools, the full catalog causes context bloat — the client's LLM spends tokens parsing tool schemas instead of reasoning about the user's task. - -### The Solution - -`txc-mcp` uses **progressive disclosure** to expose only a small set of always-on tools at startup, then dynamically injects additional tools as needed: - -**Always-on tools (9 total):** -- **6 guide tools** — `guide`, `guide_workspace`, `guide_environment`, `guide_deployment`, `guide_data`, `guide_config` — domain-scoped tool discovery via LLM sampling -- **`execute_operation`** — bridges same-turn execution for tools discovered by a guide \ No newline at end of file +# TALXIS CLI MCP Server + +A [Model Context Protocol](https://modelcontextprotocol.io/) server for Power Platform and Dataverse development. Works with GitHub Copilot, Claude Code, VS Code, and any MCP-compatible client. + +## Prerequisites + +- [**.NET 10 SDK**](https://dotnet.microsoft.com/download/dotnet/10.0) or later + +## Setup + +### GitHub Copilot CLI + +Add to `~/.copilot/mcp-config.json`: + +```json +{ + "mcpServers": { + "TXC": { + "type": "stdio", + "command": "dnx", + "args": ["TALXIS.CLI.MCP", "--yes"] + } + } +} +``` + +### VS Code / GitHub Copilot Chat + +Add to `.vscode/mcp.json` in your project: + +```json +{ + "servers": { + "TALXIS CLI": { + "type": "stdio", + "command": "dnx", + "args": ["TALXIS.CLI.MCP", "--yes"] + } + } +} +``` + +### Claude Code + +```sh +claude mcp add --transport stdio txc -- dnx TALXIS.CLI.MCP --yes +``` + + +No install needed — [`dnx`](https://learn.microsoft.com/dotnet/core/tools/dotnet-tool-exec) downloads and runs the server on demand. + +[![NuGet](https://img.shields.io/nuget/v/TALXIS.CLI.MCP)](https://www.nuget.org/packages/TALXIS.CLI.MCP) + +## Features + +- **Tool Discovery & Execution** — Dynamically discovers and exposes all TALXIS CLI commands as MCP tools with typed input schemas +- **Task-Augmented Execution** — Long-running tools (deploy, import) support the MCP tasks protocol for async "call-now, fetch-later" execution with cancellation support +- **Structured Logging** — Streams real-time log output from CLI subprocesses to MCP clients via `notifications/message` +- **Progress Notifications** — Emits `notifications/progress` during long-running tool calls when the client provides a `progressToken` +- **Workspace Roots** — Requests the client's workspace roots via `roots/list` and uses the primary root as the subprocess working directory +- **Log Redaction** — Automatically redacts passwords, tokens, and filesystem paths from log output + +## Developing and Debugging Locally + +When developing the MCP server locally, avoid plain `dotnet run` for stdio MCP sessions. `dotnet run` performs a build first, and build warnings can be written before the MCP transport is ready. MCP clients may then try to parse those warning lines as protocol messages. + +Build the project separately, then configure VS Code to start the already-built server with `--no-build`: + +```sh +dotnet build src/TALXIS.CLI.MCP/TALXIS.CLI.MCP.csproj +``` + +In your `.vscode/mcp.json`, set the server command like this: + +```json +{ + "inputs": [], + "servers": { + "TALXIS CLI Dev": { + "type": "stdio", + "command": "dotnet", + "args": [ + "run", + "--no-build", + "--project", + "${workspaceFolder}/src/TALXIS.CLI.MCP/TALXIS.CLI.MCP.csproj" + ] + } + } +} +``` + +- Adjust the path in `args` to match your local project location if needed. +- Rebuild after code changes before restarting the MCP server. +- For non-local usage, prefer the packaged server via `dnx TALXIS.CLI.MCP --yes`. + +## Testing and Debugging + +### Interactive Manual Testing + +You can use the [Model Context Protocol Inspector](https://www.npmjs.com/package/@modelcontextprotocol/inspector) for interactive inspection: + +```sh +dotnet build src/TALXIS.CLI.MCP/TALXIS.CLI.MCP.csproj +npx @modelcontextprotocol/inspector dotnet run --no-build --project src/TALXIS.CLI.MCP +``` + +> **Note:** The Inspector is an interactive web browser application designed for manual testing and exploration. It is not suitable for automated testing scenarios. + +### Command Line Debugging & Automated Testing + +For debugging or automated testing, interact with the MCP server using JSON-RPC messages over stdin/stdout. Build first, then run with `--no-build` so stdout stays reserved for MCP traffic: + +```sh +# Build once before starting the server +dotnet build src/TALXIS.CLI.MCP/TALXIS.CLI.MCP.csproj + +# Start the server without rebuilding +dotnet run --no-build --project src/TALXIS.CLI.MCP + +# Then send JSON-RPC messages via stdin (one per line): +# 1. Initialize the connection (required by MCP protocol) +{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "test-client", "version": "1.0.0"}}} + +# 2. List available tools +{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}} + +# 3. Call a specific tool (example) +{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "tool-name", "arguments": {}}} + +# 4. Call a tool with progress tracking +{"jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": {"name": "environment_deploy", "arguments": {"Package": "MyPackage"}, "_meta": {"progressToken": "my-progress-1"}}} + +# 5. Call a long-running tool as a task (returns immediately with task ID) +{"jsonrpc": "2.0", "id": 5, "method": "tools/call", "params": {"name": "environment_deploy", "arguments": {"Package": "MyPackage"}, "task": {}}} + +# 6. Poll task status +{"jsonrpc": "2.0", "id": 6, "method": "tasks/get", "params": {"taskId": ""}} + +# 7. Get task result (blocks until complete) +{"jsonrpc": "2.0", "id": 7, "method": "tasks/result", "params": {"taskId": ""}} +``` + + +#### Example JSON-RPC Messages + +To list available tools: + +```json +{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}} +``` + +To call a tool (replace arguments as needed): + +```json +{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "workspace_component_create", "arguments": {"ShortName": "pp-entity", "name": "TestEntity", "Param": ["EntityType=InvalidType"]}}} +``` + +--- + +## Auth contract (stdio) + +**The MCP server never runs an interactive auth flow.** Every `txc` tool +subprocess spawned by this server is forced headless — stdout is reserved +for JSON-RPC frames and the child has stdin/stdout redirected, so a +browser/device-code/masked-prompt attempt would either hang the session +or corrupt the transport. `CliSubprocessRunner` sets +`TXC_NON_INTERACTIVE=1` unconditionally on every spawn. + +Prerequisites, before invoking any tool that touches a Dataverse +environment (or any other Connection-bound tool): + +1. On the human's machine, run `txc config auth login` (interactive + browser) or `txc config auth add-service-principal` once to register + a credential and prime the MSAL token cache. +2. Run `txc config connection create --provider dataverse ...` + to register the endpoint. +3. Run `txc config profile create --auth --connection ` + to bind them, then `txc config profile select ` (or pin the + workspace via `txc config profile pin`). + +After that, MCP tool calls resolve the active profile silently via the +acquired token cache or stored SPN secret. If resolution fails (expired +refresh token, missing credential, broken config), the subprocess exits +non-zero and the MCP server surfaces the error through the tool-call +result; the structured log line includes the fail-fast remedy string +(`txc config profile validate `). + +### Per-call profile override + +Any Connection-touching MCP tool accepts an optional `profile` argument +which is forwarded to the child as `--profile `. This lets a +single MCP session switch between profiles per call without restarting +the server — e.g. an agent can target `customer-a-dev` for one tool and +`customer-b-prod` for the next. + +### Client env allow-list (`mcpServers..env`) + +The MCP server inherits the client's environment and the child CLI +subprocess inherits that. The following are meaningful to `txc`; anything +else is ignored: + +| Variable | Purpose | +|---|---| +| `TXC_PROFILE` | Select active profile for every call in this MCP session (overridden by per-call `profile` argument when supplied). | +| `TXC_CONFIG_DIR` | Override entire config directory (total CI isolation). | +| `TXC_NON_INTERACTIVE` | Redundant here — MCP already forces this to `1`. | +| `TXC_LOG_FORMAT` | Redundant here — MCP already forces `json`. | +| `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_TENANT_ID` | Service-principal fallback for CI / headless runners. | +| `AZURE_FEDERATED_TOKEN_FILE` | Kubernetes / Azure workload-identity federation. | +| `ACTIONS_ID_TOKEN_REQUEST_URL`, `ACTIONS_ID_TOKEN_REQUEST_TOKEN` | GitHub Actions OIDC federation. | +| `TXC_ADO_ID_TOKEN_REQUEST_URL`, `TXC_ADO_ID_TOKEN_REQUEST_TOKEN` | Azure DevOps pipelines workload-identity federation (legacy `PAC_ADO_*` also honored). | + +Secrets (client secrets, PATs, certificate passwords) are **never** +accepted as plain MCP tool arguments — they are stored in the OS-level +secret vault via `txc config auth add-service-principal` and referenced +from config by `SecretRef` handle only. + +### Log redaction + +`McpLogForwarder` runs every child stderr JSON log line through +`LogRedactionFilter` before emitting `notifications/message` to the MCP +client. Patterns covered: `Bearer `, `Authorization:` headers, +bare JWTs, connection-string secret keys (`Password`, `ClientSecret`, +`AccessToken`, `RefreshToken`, etc.) and URL query-param secrets +(`code=`, `token=`, `access_token=`). This is the last-chance belt for +accidental `logger.LogError(ex, ...)` leaks where the Dataverse or +Microsoft.Identity SDKs embed tokens inside exception messages. + +### Forbidden patterns (forward-compat for HTTP transport) + +When the HTTP/SSE transport ships, `txc-mcp` will be an **OAuth resource +server only** — Entra ID remains the authorization server. The following +patterns are forbidden by the MCP spec and will not be implemented: + +- **Token passthrough**: never forward a client's bearer token directly + to Dataverse — always use on-behalf-of or a separate service account. +- **Tokens in URIs**: never embed access tokens in query strings. +- **Non-HTTPS redirects** (except loopback for local dev). +- **Missing PKCE**: all OAuth flows must use PKCE. +- **Missing audience check**: tokens must be audience-bound to the + `txc-mcp` resource URI (RFC 8707). + +See `docs/mcp-http-auth-notes.md` (design notes, no code in v1). + +--- + +## Progressive Disclosure Architecture + +### The Problem + +MCP clients like GitHub Copilot have practical limits on the number of tools they can handle effectively. With 128+ CLI commands registered as MCP tools, the full catalog causes context bloat — the client's LLM spends tokens parsing tool schemas instead of reasoning about the user's task. + +### The Solution + +`txc-mcp` uses **progressive disclosure** to expose only a small set of always-on tools at startup, then dynamically injects additional tools as needed: + +**Always-on tools (9 total):** +- **6 guide tools** — `guide`, `guide_workspace`, `guide_environment`, `guide_deployment`, `guide_data`, `guide_config` — domain-scoped tool discovery via LLM sampling +- **`execute_operation`** — bridges same-turn execution for tools discovered by a guide From 9749bd7133019977ee012b59d9175a06ad49f674 Mon Sep 17 00:00:00 2001 From: Zdenek Srejber <77669032+zdeneksrejber@users.noreply.github.com> Date: Thu, 28 May 2026 15:15:35 +0200 Subject: [PATCH 3/3] docs: restore truncated MCP README sections --- src/TALXIS.CLI.MCP/README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/TALXIS.CLI.MCP/README.md b/src/TALXIS.CLI.MCP/README.md index 223d0154..66bfa7cd 100644 --- a/src/TALXIS.CLI.MCP/README.md +++ b/src/TALXIS.CLI.MCP/README.md @@ -258,3 +258,27 @@ MCP clients like GitHub Copilot have practical limits on the number of tools the **Always-on tools (9 total):** - **6 guide tools** — `guide`, `guide_workspace`, `guide_environment`, `guide_deployment`, `guide_data`, `guide_config` — domain-scoped tool discovery via LLM sampling - **`execute_operation`** — bridges same-turn execution for tools discovered by a guide +- **`get_skill_details`** — retrieves public developer-facing skills (documentation, best practices) +- **`copilot-instructions`** — manages `.github/copilot-instructions.md` for AI assistants + +### The Workflow + +1. **Guide call** — The client calls a guide tool (e.g., `guide_workspace`) with a natural language query describing the task. +2. **Sampling** — The guide sends a `sampling/createMessage` request to the client's LLM with the full internal catalog, asking it to select the most relevant tools. Internal reasoning skills are injected for domain-specific expertise. +3. **Same-turn execution** — The guide returns structured results with tool names and schemas. The client can immediately call `execute_operation` with the selected tool name and arguments — no round-trip needed. +4. **Direct calls (next turn)** — Matched tools are injected into the `ActiveToolSet`. Clients discover injected tools by re-fetching tools/list on subsequent turns, and can then call them directly by name. + +### Skills Architecture + +`txc-mcp` has a two-tier skills system: + +- **Internal skills** (proprietary) — Embedded `.md` files in `Skills/Internal/` containing Power Platform development expertise, decision trees, and workflow patterns. These are loaded by `GuideReasoningEngine` and injected into sampling prompts. They are **never exposed to clients** — they guide the server's own reasoning. +- **Public skills** (developer-facing) — Loaded from `TALXIS.CLI.Features.Docs` embedded resources. Accessible via the `get_skill_details` MCP tool and the `txc docs` CLI command. These contain documentation, best practices, and how-to guides for developers. + +### Local-First Development Philosophy + +Guide tools and internal skills encode a **local-first** philosophy: prefer local workspace operations (scaffolding, validation, schema inspection) over live environment operations (API calls to Dataverse). Environment operations take 30 seconds to 5 minutes and should only be used for inspection, troubleshooting, or deployment after local validation is complete. + +--- + +For more details, see the main [TALXIS CLI README](../../README.md).