Software Testing  

Testing MCP Servers for Schema Drift and Tool Compatibility

An MCP server can be healthy, reachable, and still be incompatible with the client consuming it.

A tool may have the same name but a different input schema. A required property may disappear. An enum may change. A server upgrade may introduce a response field that an older client does not understand. Documentation can also become stale while the implementation continues to evolve.

These problems are examples of schema drift.

For MCP systems, schema drift is especially important because tools are contracts between an AI client and an external server. The model uses tool descriptions and schemas to decide what arguments to generate. If those contracts change unexpectedly, failures can occur before the underlying business logic is even reached.

The official MCP C# SDK has continued to expand its conformance and protocol-validation coverage as the protocol evolves. Its 2.0 release aligns with the 2026-07-28 MCP specification and includes JSON Schema 2020-12 support, while the SDK repository also maintains conformance testing as part of its development process.

This makes contract testing an important part of an MCP deployment pipeline.

What Is MCP Schema Drift?

Consider an MCP tool initially defined as:

{
  "name": "get_customer",
  "inputSchema": {
    "type": "object",
    "properties": {
      "customerId": {
        "type": "string"
      }
    },
    "required": ["customerId"]
  }
}

A client builds its tool-calling behavior around that contract.

Later, the server changes the property:

{
  "name": "get_customer",
  "inputSchema": {
    "type": "object",
    "properties": {
      "id": {
        "type": "string"
      }
    },
    "required": ["id"]
  }
}

The implementation may still work perfectly from the server's perspective.

The client, however, is now sending:

{
  "customerId": "C1001"
}

The server expects:

{
  "id": "C1001"
}

Nothing is wrong with the network connection.

Nothing is necessarily wrong with the server process.

The contract changed.

Why Schema Drift Is Dangerous for AI Agents

Traditional APIs are usually called by application code written against a known contract.

AI agents add another layer.

The model receives tool metadata and uses it to construct arguments dynamically.

The flow looks like this:

MCP Server
    |
    | Tool definition
    v
MCP Client
    |
    | Tool schema
    v
LLM
    |
    | Generated arguments
    v
MCP Tool

If the schema is incorrect, incomplete, stale, or incompatible, the model can generate invalid requests.

There are therefore at least three contracts to consider:

  1. Discovery contract — what the server advertises.

  2. Input contract — what arguments the tool accepts.

  3. Output contract — what the tool returns.

A good test strategy validates all three.

What Should an MCP Compatibility Test Validate?

A useful test suite should check:

AreaExample Validation
Tool nameExpected tool exists
DescriptionRequired metadata exists
Input schemaValid JSON Schema
Required propertiesExpected fields remain required
Property typesString remains string
Enum valuesSupported values remain compatible
Output schemaResponse contract remains valid
Tool invocationValid request succeeds
Invalid inputServer rejects invalid arguments
Error behaviorErrors follow expected structure
Protocol behaviorClient and server negotiate correctly

Do not rely only on an end-to-end happy-path test.

A tool can successfully return a result while still having a contract that will break another client.

Creating a Contract Snapshot

One practical approach is to capture the server's advertised tool definitions and store them as versioned test artifacts.

For example:

{
  "name": "get_customer",
  "description": "Returns customer information.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "customerId": {
        "type": "string"
      }
    },
    "required": [
      "customerId"
    ]
  }
}

Store this snapshot in your test project:

tests/
└── Contracts/
    ├── get-customer.json
    ├── create-order.json
    └── search-products.json

When the MCP server changes, the CI pipeline can retrieve the current definitions and compare them with the approved contract.

This does not mean every difference should fail the build.

Some changes are compatible.

Others are breaking.

Breaking vs Non-Breaking Schema Changes

A useful compatibility policy looks like this:

ChangeCompatibility
Fix description typoUsually safe
Add optional propertyUsually safe
Add required propertyBreaking
Rename propertyBreaking
Remove propertyPotentially breaking
Change string to integerBreaking
Narrow accepted enum valuesPotentially breaking
Add enum valueUsually compatible
Remove enum valuePotentially breaking
Change output property typeBreaking

The exact policy should be defined by your client ecosystem.

For example, adding an optional field is normally less risky than changing a required field, but an especially strict client may still enforce an exact schema.

Testing the MCP Server with .NET

A basic integration test can use the official C# SDK.

A simplified test structure might look like:

public sealed class McpContractTests
{
    private readonly IMcpClient _client;

    public McpContractTests(IMcpClient client)
    {
        _client = client;
    }

    [Fact]
    public async Task GetCustomer_Tool_ShouldExist()
    {
        var tools = await _client.ListToolsAsync();

        var tool = tools.FirstOrDefault(
            x => x.Name == "get_customer");

        Assert.NotNull(tool);
    }
}

The exact API surface should be matched to the SDK version used by the project. The official C# SDK provides client and server APIs and is maintained in collaboration with Microsoft.

The important testing principle is that the test should consume the server through MCP rather than directly inspecting its internal C# classes.

That makes the test a contract test rather than an implementation test.

Validating Tool Input

After confirming that a tool exists, validate its schema.

A contract test can verify that:

get_customer
    |
    +-- customerId
        |
        +-- type: string
        +-- required: true

The test can fail if the server changes it to:

get_customer
    |
    +-- id
        |
        +-- type: string
        +-- required: true

The test output should explain the difference clearly:

MCP contract violation

Tool: get_customer

Expected required property:
customerId

Actual required property:
id

This is much easier to diagnose than discovering the problem through an agent failure in production.

Testing JSON Schema

MCP tool schemas use JSON Schema. The current C# SDK 2.0 work includes support aligned with JSON Schema 2020-12.

That makes schema validation a useful independent test layer.

For example, a test can validate that a schema contains:

{
  "type": "object",
  "properties": {
    "customerId": {
      "type": "string"
    }
  },
  "required": ["customerId"]
}

The test should verify both structure and semantics.

For example:

Assert.Equal(
    "object",
    schema["type"]?.ToString());

Assert.True(
    schema["properties"]?["customerId"] is not null);

For more complete validation, use a JSON Schema validator rather than manually checking every property.

The objective is to validate the schema as a schema, not merely as a JSON document.

Testing Valid and Invalid Tool Calls

Schema validation alone is not enough.

The server should also be tested with representative inputs.

For a customer lookup:

{
  "customerId": "C1001"
}

Then test invalid requests:

{}

and:

{
  "customerId": 1001
}

The expected behavior should be explicitly defined.

For example:

Valid input
    -> Tool executes

Missing customerId
    -> Validation error

customerId has wrong type
    -> Validation error

This verifies that the advertised schema and actual server behavior agree.

That distinction matters.

A server can advertise one schema while its implementation behaves differently.

Detecting Output Contract Drift

Input schemas receive most of the attention, but output schemas can drift too.

Suppose the expected result is:

{
  "customerId": "C1001",
  "name": "John",
  "status": "active"
}

A later version returns:

{
  "customer": "C1001",
  "name": "John",
  "status": "active"
}

An agent or downstream application that expects customerId can fail even though the tool invocation itself succeeds.

Therefore, contract tests should validate important output fields as well.

A useful test might assert:

Assert.True(result.ContainsKey("customerId"));
Assert.True(result.ContainsKey("name"));
Assert.True(result.ContainsKey("status"));

For strongly structured applications, use a JSON Schema validator or deserialize into a known DTO and validate it.

Testing Tool Compatibility Across Versions

Schema testing becomes especially valuable during server upgrades.

Use a matrix:

                 MCP Server Version
                 v1       v2       v3
Client A          OK       OK       ?
Client B          OK       ?        ?
Client C          OK       ?        ?

A compatibility pipeline can test:

Client Version
      +
Server Version
      +
Protocol Version
      +
Tool Contract

The MCP C# SDK 2.0 supports negotiation with earlier protocol versions, and the SDK's release work explicitly tracks interoperability and conformance behavior across protocol revisions.

This is important when an organization cannot upgrade every MCP client simultaneously.

Protocol Compatibility Is Different from Tool Compatibility

These two problems should not be confused.

Protocol compatibility

Can the client and server communicate using the expected MCP protocol behavior?

Tool compatibility

Once communication succeeds, do the available tools and their schemas match what the client expects?

The architecture is:

             Protocol Contract
Client <----------------------> Server
  |
  |
  +-------- Tool Contract --------+
                                   |
                              Tool Schema
                                   |
                              Tool Output

A successful protocol handshake does not guarantee tool compatibility.

This distinction should appear in your test reports.

A CI Pipeline for MCP Contract Testing

A production pipeline can look like:

Developer Pull Request
          |
          v
Build MCP Server
          |
          v
Start Test Server
          |
          v
Discover Tools
          |
          v
Validate Schemas
          |
          v
Run Valid Calls
          |
          v
Run Invalid Calls
          |
          v
Compare Approved Contracts
          |
          v
Compatibility Tests
          |
          v
Publish Artifact

A simplified GitHub Actions workflow could be:

name: MCP Contract Tests

on:
  pull_request:
  push:

jobs:
  contract-tests:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.x'

      - name: Restore
        run: dotnet restore

      - name: Build
        run: dotnet build --no-restore

      - name: Run MCP contract tests
        run: dotnet test --no-build

The important part is not the workflow syntax.

It is the policy:

A tool contract change should become visible during CI rather than after deployment.

Golden Contract Files

A useful technique is to maintain approved "golden" contracts.

For example:

contracts/
├── customer-service/
│   ├── get_customer.json
│   ├── update_customer.json
│   └── search_customer.json

During CI:

Current Server Contract
          |
          v
       Compare
          |
          v
Approved Contract

If the difference is compatible, the pipeline can report it as informational.

If it is breaking, the pipeline should fail.

This provides a clear review process for API-like changes to MCP tools.

Handling Intentional Breaking Changes

Not every breaking change is bad.

Sometimes the business contract genuinely needs to change.

The problem is making that change without acknowledging its impact.

A better process is:

Breaking Change
      |
      v
Create New Tool Version
      |
      v
Update Consumers
      |
      v
Compatibility Period
      |
      v
Deprecate Old Tool
      |
      v
Retire Old Tool

For example:

get_customer_v1
get_customer_v2

The exact versioning model can vary, but the principle remains the same: make breaking changes explicit.

Detecting Description Drift

Tool descriptions are also part of the agent-facing contract.

Consider:

Searches customer orders by customer ID.

changing to:

Searches all customer records and may modify order state.

That is not merely documentation.

It can influence how an agent decides to use the tool.

Recent MCP ecosystem research has specifically examined how server descriptions change over time, highlighting that catalog descriptions can become stale and that drift needs continuous validation rather than a one-time audit.

For important tools, review:

  • Description

  • Tool name

  • Input schema

  • Output schema

  • Security requirements

  • Side-effect behavior

as one contract.

Common Mistakes

Testing Only Tool Availability

A test that says "the tool exists" does not prove compatibility.

Comparing Raw JSON Strings

JSON property ordering should not normally determine compatibility.

Compare parsed structures and semantic differences instead.

Treating Every Difference as Breaking

Adding an optional property should not automatically block every deployment.

Define compatibility rules.

Ignoring Descriptions

Descriptions are consumed by agents and can affect tool selection.

Testing Only Successful Calls

Invalid input behavior is part of the contract.

Relying Only on End-to-End Agent Tests

End-to-end tests are valuable but difficult to diagnose.

Contract tests isolate the problem earlier.

Troubleshooting Schema Test Failures

When a contract test fails, identify which layer changed.

Tool disappeared

Check:

  • Tool registration

  • Assembly scanning

  • Feature flags

  • Deployment configuration

  • Tool deprecation status

Property disappeared

Check whether the change was intentional and whether consumers have been migrated.

Type changed

Check the DTO and serialization configuration.

Output validation failed

Capture the actual response and compare it against the expected schema.

Old client stopped working

Check protocol negotiation separately from tool schema compatibility.

For MCP SDK upgrades, also review the SDK release notes and migration guidance because protocol and transport behavior can change independently of application-level tool contracts.

Conclusion

MCP tools should be treated as contracts, not simply methods exposed to an AI model.

As MCP servers evolve, schema drift can silently break clients, change agent behavior, or create failures that are difficult to diagnose from production logs alone.

A strong testing strategy therefore validates the complete contract:

Tool Discovery
     +
Input Schema
     +
Input Validation
     +
Output Schema
     +
Protocol Compatibility
     +
Client Compatibility

The official C# SDK's continued focus on protocol alignment and conformance testing reinforces the importance of this approach. MCP 2.0 introduces substantial protocol changes, including discovery-first negotiation, stateless HTTP behavior, and JSON Schema 2020-12 alignment, making explicit compatibility testing even more valuable.

The goal is not to prevent every schema change.

The goal is to ensure that every meaningful schema change is detected, classified, reviewed, and tested before it reaches the agents depending on it.

That turns MCP compatibility from a production surprise into a normal part of the software delivery pipeline.

Frequently Asked Questions

What is MCP schema drift?

Schema drift occurs when the contract exposed by an MCP server changes over time, such as renamed properties, changed types, removed fields, or modified tool behavior.

Should MCP tool schemas be stored in source control?

For production systems, maintaining approved contract snapshots in source control can provide a useful baseline for automated compatibility testing.

Are MCP protocol tests and tool contract tests the same?

No. Protocol tests verify communication and protocol behavior. Tool contract tests verify the tools, schemas, inputs, outputs, and metadata exposed by the server.

Should every schema change fail CI?

No. Teams should define compatibility rules. Adding an optional property may be acceptable, while removing a required property or changing its type may be breaking.

Why test tool descriptions?

Tool descriptions are part of the agent-facing interface. Changes can affect how an AI system understands and selects tools, even when the underlying API still accepts the same arguments.