A .NET MCP control-gate project that discovers live tools, validates contracts, and blocks risky tool changes before promotion.
nl-mcp-tool-contract-gates
An educational local C# project showing how to gate a real MCP server profile by launching it over STDIO, discovering its live tools and JSON schemas, probing those tools through the MCP client, and blocking risky tool surface drift before promotion.
This version uses a support operations scenario with one baseline MCP profile, one candidate MCP profile, a live MCP client, frozen role cases, and file-backed JSON gate reports.
Overview
This repository demonstrates a practical live MCP gate:
- Launch a real MCP server profile over STDIO.
- Discover the live MCP tools exposed by that profile.
- Extract actual tool names, JSON schemas, and tool annotations from the MCP protocol response.
- Compare a baseline profile and a candidate profile for added tools, removed tools, schema breakage, read-only hint regressions, and newly added destructive tools.
- Replay frozen role cases by calling the live MCP tools with structured arguments.
- Apply deterministic promotion rules to decide
Promote,Hold, orRollback. - Persist the report as JSON for later review.
The result is a real MCP control loop rather than a simulated contract comparison over static snapshot files.
What This Project Demonstrates
- How to host a real MCP server in .NET with STDIO transport
- How to expose tools with
[McpServerTool]names and annotations - How to launch that server from a C# MCP client
- How to discover live MCP tools and inspect their actual JSON schemas
- How to compare baseline and candidate MCP profiles using live discovery
- How to probe discovered tools through MCP calls before promotion
- How to block newly exposed destructive tools from leaking into a narrower role surface
Prerequisites
- .NET 10 SDK or later
No external model endpoint or cloud MCP host is required. The sample uses a local MCP server process started by the gate client itself.
Quick Start
From the project root:
dotnet run --project McpToolContractGates
The app:
- launches the baseline MCP profile
- discovers its live tools
- launches the candidate MCP profile
- discovers its live tools
- compares both live MCP surfaces
- probes the required tools through real MCP calls
- prints the promotion decision
- saves a JSON report under
McpToolContractGates/bin/Debug/net10.0/data/reports/
Configuration
Default settings are in McpToolContractGates/appsettings.json under
Experiment and Promotion.
Example:
{
"Experiment": {
"ServerCommand": "dotnet",
"BaselineProfile": "baseline",
"CandidateProfile": "candidate",
"DatasetPath": "data/mcp_tool_gate_eval.json",
"ReportDirectory": "data/reports"
},
"Promotion": {
"BlockOnRemovedTools": true,
"BlockOnSchemaBreakage": true,
"BlockOnReadOnlyHintRegression": true,
"BlockOnNewDestructiveTools": true,
"MaxNewDestructiveTools": 0,
"MaxBrokenCases": 0
}
}
Environment variable overrides use prefix MCPGATE_.
How It Works
SupportOpsMcpServeris a real MCP server host usingWithStdioServerTransport().- The server accepts one profile argument:
baselineorcandidate. ActualMcpSessionlaunches that server withdotnet <server.dll> <profile>.- The MCP client discovers live tools with
ListToolsAsync(). DiscoveredToolSurfaceextracts real tool names, JSON schemas, andReadOnlyHint/DestructiveHintmetadata from the MCP response.LiveToolSurfaceComparercompares the baseline and candidate profiles.LiveToolProbeRunnercalls the required MCP tools with structured arguments from the frozen role dataset.PromotionGateturns the findings into an explicitPromote,Hold, orRollbackdecision.
Project Structure
.
+-- McpToolContractGates.slnx
+-- SupportOpsMcpServer/
| +-- SupportOpsMcpServer.csproj
| +-- Program.cs
| +-- ServerAssemblyMarker.cs
| +-- BaselineSupportOpsTools.cs
| +-- CandidateSupportOpsTools.cs
+-- McpToolContractGates/
| +-- McpToolContractGates.csproj
| +-- Program.cs
| +-- appsettings.json
| +-- App/
| | +-- ExperimentConfig.cs
| | +-- PromotionGateConfig.cs
| +-- Contracts/
| | +-- DiscoveredToolContract.cs
| | +-- RequiredToolExpectation.cs
| | +-- ToolGateEvalCase.cs
| +-- Data/
| | +-- ToolGateDatasetLoader.cs
| | +-- mcp_tool_gate_eval.json
| +-- Evaluation/
| | +-- Actual discovery + diff + probe + gate types
| +-- Mcp/
| | +-- ActualMcpSession.cs
| +-- Persistence/
| +-- JsonGateReportStore.cs
+-- McpToolContractGates.Tests/
| +-- McpToolContractGates.Tests.csproj
| +-- ActualMcpDiscoveryTests.cs
| +-- LiveToolSurfaceComparerTests.cs
| +-- EndToEndGateTests.cs
Control Guarantees
- The server is a real MCP process, not a simulated adapter
- Tool discovery comes from actual MCP
ListToolsAsync()results - Required arguments come from the live MCP JSON schemas
- Tool hints come from live MCP tool annotations
- Role-case probes call real MCP tools over STDIO
- New destructive tools can be blocked before promotion
- Full reports are persisted so decisions remain inspectable
Tests
Run the test suite from the repo root:
dotnet test McpToolContractGates.slnx
The tests cover:
- live MCP discovery of the baseline profile
- detection of live schema breakage and added destructive tools
- end-to-end rollback of the candidate profile against the real MCP server
License
See the LICENSE file for details.
Contributing
Contributions are welcome for improvements within current project scope.
Suggested areas:
- Add streamable HTTP transport support alongside STDIO so the same gate can evaluate remote MCP deployments
- Extend the sample from one support role to multiple role surfaces with different allowed and forbidden tools
- Persist longitudinal diff history so tool-surface drift can be inspected across multiple promotion attempts
- Add policy exceptions for explicitly approved tool additions while keeping the rest of the surface gated
- Expand the probe dataset to cover more schema edge cases, hint regressions, and destructive-tool scenarios