Ashlar.Mcp.Server.Host 0.4.2

{
  "inputs": [
    {
      "type": "promptString",
      "id": "Ashlar__Mcp__Bricks__ExposedBrickIds__0",
      "description": "Brick to expose, e.g. github.blockers. Nothing is exposed until at least one id is listed."
    },
    {
      "type": "promptString",
      "id": "Ashlar__Mcp__Bricks__ExposedBrickIds__1",
      "description": "Optional second brick, e.g. github.pr-status or github.ci-status."
    }
  ],
  "servers": {
    "Ashlar.Mcp.Server.Host": {
      "type": "stdio",
      "command": "dnx",
      "args": ["Ashlar.Mcp.Server.Host@0.4.2", "--yes"],
      "env": {
        "Ashlar__Mcp__Bricks__ExposedBrickIds__0": "${input:Ashlar__Mcp__Bricks__ExposedBrickIds__0}",
        "Ashlar__Mcp__Bricks__ExposedBrickIds__1": "${input:Ashlar__Mcp__Bricks__ExposedBrickIds__1}"
      }
    }
  }
}
                    
This package contains an MCP Server. The server can be used in VS Code by copying the generated JSON to your VS Code workspace's .vscode/mcp.json settings file.
dotnet tool install --global Ashlar.Mcp.Server.Host --version 0.4.2
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local Ashlar.Mcp.Server.Host --version 0.4.2
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Ashlar.Mcp.Server.Host&version=0.4.2
                    
nuke :add-package Ashlar.Mcp.Server.Host --version 0.4.2
                    

Ashlar.Mcp.Server.Host

An MCP server over stdio that exposes Ashlar bricks (typed, deterministic .NET units of work) and read-only repository tools to any MCP client: VS Code / GitHub Copilot, Claude Code, Cursor, Visual Studio. Every brick call writes one audit record you can match to the exact request and response.

Nothing is exposed by default. You list the bricks (and tools) a client may call.

Run it

Needs the .NET 10 SDK, which ships dnx.

Ashlar__Mcp__Bricks__ExposedBrickIds__0=github.blockers dnx Ashlar.Mcp.Server.Host --yes

VS Code (.vscode/mcp.json):

{
  "servers": {
    "ashlar": {
      "type": "stdio",
      "command": "dnx",
      "args": ["Ashlar.Mcp.Server.Host", "--yes"],
      "env": {
        "Ashlar__Mcp__Bricks__ExposedBrickIds__0": "github.blockers",
        "Ashlar__Mcp__Bricks__ExposedBrickIds__1": "github.ci-status"
      }
    }
  }
}

Pin a version with Ashlar.Mcp.Server.Host@<version> in args.

From a clone

The package has been on nuget.org since 0.4.0. To run unreleased changes, or a version older than 0.4.0 (where dnx answers ashlar.mcp.server.host is not found in NuGet feeds), run the server from a clone:

dotnet build src/Ashlar.Mcp.Server.Host -c Release
Ashlar__Mcp__Bricks__ExposedBrickIds__0=github.blockers \
  dotnet src/Ashlar.Mcp.Server.Host/bin/Release/net10.0/Ashlar.Mcp.Server.Host.dll

In an MCP client configuration, set "command": "dotnet" and pass the .dll path as the only args entry. Build once and start the built .dll, so a client does not wait for a build on every start.

Built-in bricks

Brick id MCP tool name What it does
github.blockers github_blockers What is blocking a pull request or release: a verdict, each blocker with an evidence link, and every fact it could not read (with the reason). Input: url (https://github.com/<owner>/<repo>/pull/<n> or .../releases/tag/<tag>); optional githubToken.
github.pr-status github_pr-status Pull request state, mergeability, reviews, checks and the blockers they add up to. Input: prUrl; optional githubToken.
github.ci-status github_ci-status Workflow runs and check runs for a ref. Inputs: owner, repo, ref; optional githubToken.

All three call api.github.com and accept only https://github.com URLs. Without a token GitHub allows 60 requests an hour; pass githubToken for more, and to read private repositories.

What a call returns

A successful call returns a JSON text block (also sent as structured content):

{"brickId":"github.pr-status","brickVersion":"1.0.0","auditId":"3f2c…","summary":"…","outputs":{ … }}

Missing required inputs and arguments that are not a JSON object come back as tool errors (isError: true) before the brick runs, and also carry an auditId. A brick that throws comes back the same way. A tool name the server does not list (including a registered brick that is not allowlisted) is an MCP protocol error, not a tool result; it is recorded too, and the error message carries its auditId. A tools/call whose arguments is not a JSON object, or a frame that is not valid JSON-RPC, is rejected by the MCP SDK before any Ashlar code runs, and is not recorded.

Audit records

One JSON line per brick call (refusals included) in <LocalApplicationData>/ashlar/mcp-audit/mcp-brick-calls-<start time>.ndjson (on Linux ~/.local/share/ashlar/mcp-audit), one file per server start, none deleted by the server. Set Ashlar__Audit__File__Directory to move it. To find the record for one call: grep <auditId> ~/.local/share/ashlar/mcp-audit/mcp-brick-calls-*.ndjson. Each record has eventType MCP_BRICK_INVOKED, correlationId = the auditId the caller saw, and a detail holding the brick id and version, the outcome (succeeded, failed, refused), the duration, and the SHA-256 of the arguments as received and of the text returned. A refused unknown tool name has agentName mcp.unknown-tool, a null brickId, and the name as requestedToolSha256 plus requestedTool (cut to 64 characters, anything outside [A-Za-z0-9_.-] replaced by _). Argument and output values are not written, so a token passed as githubToken does not land on disk.

inputSha256 is the hash of the arguments as the server re-serializes them: compact JSON, properties in the order sent, strings escaped the System.Text.Json default way (non-ASCII and <>&'+ as \uXXXX), not the bytes the client sent. resultSha256 is the hash of the exact text returned. These are plain SHA-256, not keyed: an argument with few possible values (a public pull request URL) can be recovered from its hash by trying candidates. A token is safe from that; which pull request was asked about is not.

The records of one server process form a hash chain. Each detail also holds the auditId, occurredAt, a session id, seq (1, 2, …) and prevSha256: the SHA-256 of the previous record's detail string exactly as written (64 zeros for a session's first record). To verify a file, walk it per session and check that seq increases and each prevSha256 matches; McpBrickAuditChain.Verify in Ashlar.Mcp.Bricks does exactly that. An edited, removed or reordered record breaks the chain. A gap in seq with the chain intact is a record the server could not write. The chain cannot show records cut from the end of a session, a deleted file, or a session rewritten from the edited record on: for the first, the server also logs each record's hash (RecordSha256) to stderr as it writes it, so compare the last one logged with the last record; for the rest, keep a copy of the files elsewhere.

Configuration

Variable Meaning
Ashlar__Mcp__Bricks__ExposedBrickIds__N Bricks to expose. An id with no registered brick stops the server at startup.
Ashlar__Mcp__Bricks__MaxArgumentBytes Largest brick-call arguments object, in UTF-8 bytes (default 65536; 0 removes the cap). A larger call is refused, recorded, and not echoed back.
Ashlar__Mcp__Server__ExposedToolIds__N Tools to expose (repo.fs.read, repo.fs.list).
Ashlar__Mcp__Server__RepoRoot Root the repo tools read from (default: working directory).
Ashlar__Audit__File__Directory Audit directory.
Ashlar__Audit__File__MaxRotatedFiles How many audit files to keep; the oldest are deleted when a server starts. 0 (default here) keeps all of them. Each server start is one file, so a count also deletes old sessions' records.
Ashlar__Audit__Sinks__0 File (default here), StructuredLog, or NoOp.

Logs go to stderr; stdout carries only MCP JSON-RPC.

Known limits

  • Host-registered bricks are not certificate-checked. The built-in bricks are admitted because this host registers them, like any RegisterBrick<T>() host. Use the certification gate for bricks you did not build.
  • No record, no result. A call returns only after its record is written and flushed. If the record cannot be written (a full disk, an unwritable directory), the call comes back as a tool error saying so, with its auditId and without the brick's output, even when the brick ran. Flushed means handed to the operating system, not fsynced: a power loss can still take the last records.
  • A token passed as githubToken travels in the tool arguments, so the MCP client sees it. The audit record holds only its hash. The GitHub bricks send through Ashlar's egress guard (EG-HTTP-08), and the server refuses to start under the AirGapped profile.
  • The caller identity is not in the audit record. Over stdio there is one local caller; the record holds what was asked and answered, not who asked.
  • No local GGUF inference in this package. The local model provider's native backend is left out of the tool package to keep the download small; build the host from source to get it.
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
0.4.2 124 10/28/2026
0.4.2-rc1 104 10/28/2026
0.4.1 104 10/27/2026
0.4.1-rc1 101 10/27/2026
0.4.0 112 10/26/2026
0.4.0-rc1 102 10/26/2026