Self-Healing Context
Prerequisites
Install MCP Fusion before following this guide: npm install @vinkius-core/mcp-fusion @modelcontextprotocol/sdk zod — or scaffold a project with npx fusion create.
- Enriching Validation Errors
- Tool-Scoped Enhancer
- The
<contract_awareness>Block - Delta Filtering
- Full Setup Flow
- Configuration
- API Reference
When a Zod validation error occurs — the LLM sent malformed arguments — the default error response tells the model what went wrong but not why. If the tool's behavioral contract changed since the LLM was last calibrated (a new required field, a renamed action, a schema constraint), the LLM will repeat the same mistake on retry because it has no context about the change.
Contract-Aware Self-Healing enriches validation error responses with contract delta context from the Contract Diffing engine. The error XML includes which fields changed, what the previous contract looked like, and what the current contract requires. This gives the LLM enough context to self-correct on the next invocation instead of entering a retry loop.
When no contract changes exist, self-healing adds zero overhead. createToolEnhancer() checks for deltas at initialization and returns an identity function if none exist — no per-call filtering, no delta lookup, no XML generation.
Enriching Validation Errors
import {
enrichValidationError,
type SelfHealingConfig,
} from '@vinkius-core/mcp-fusion/introspection';
import { diffContracts } from '@vinkius-core/mcp-fusion/introspection';
// Compute deltas at startup (once)
const deltas = new Map<string, ContractDiffResult>();
for (const [toolName, current] of Object.entries(currentContracts)) {
const previous = previousContracts[toolName];
if (previous) {
deltas.set(toolName, diffContracts(previous, current));
}
}
const config: SelfHealingConfig = {
activeDeltas: deltas,
};
const result = enrichValidationError(
originalErrorXml,
'invoices',
'create',
config,
);
if (result.injected) {
console.log(`Injected ${result.deltaCount} contract deltas`);
}Contract deltas are computed once at server startup by diffing current contracts against the last known-good lockfile. The delta map is then frozen and shared across all request handlers.
Tool-Scoped Enhancer
createToolEnhancer() is the primary integration point. It returns a pre-scoped function optimized for a specific tool:
import { createToolEnhancer } from '@vinkius-core/mcp-fusion/introspection';
const enhance = createToolEnhancer('invoices', config);
const enrichedXml = enhance(originalErrorXml, 'create');If no deltas exist for the tool, createToolEnhancer() returns a literal identity function (x) => x. The JIT can inline this completely — the validation error path has zero additional cost when contracts are stable.
The <contract_awareness> Block
When deltas are injected, the enriched error XML includes a <contract_awareness> block before the closing </validation_error> tag:
<validation_error>
<tool>invoices</tool>
<action>create</action>
<error>Required field "currency" is missing</error>
<contract_awareness>
<system_note>
IMPORTANT: The behavioral contract for tool "invoices" has
changed since your last calibration.
</system_note>
<action>create</action>
<change_count>2</change_count>
<max_severity>BREAKING</max_severity>
<instructions>
Review the contract changes below and adjust your next
invocation accordingly. These changes may explain why
your previous arguments were rejected.
</instructions>
<contract_deltas>
<delta severity="BREAKING" field="actions.create.inputSchema">
<previous>{ amount: number, status: string }</previous>
<current>{ amount: number, status: string, currency: string }</current>
</delta>
<delta severity="RISKY" field="cognitiveGuardrails.agentLimitMax">
<previous>100</previous>
<current>50</current>
</delta>
</contract_deltas>
</contract_awareness>
</validation_error>The <contract_deltas> block is generated by formatDeltasAsXml() from the Contract Diffing module. Field values are sanitized to prevent XML injection.
Delta Filtering
Not all contract changes are relevant to a specific validation error. The module applies two filters:
Severity filter — By default, only BREAKING and RISKY deltas are injected. SAFE and COSMETIC changes don't cause validation failures and would add noise:
const config: SelfHealingConfig = {
activeDeltas: deltas,
includeAllSeverities: true, // include SAFE + COSMETIC too
};Action scope filter — Deltas are filtered by action relevance. Global deltas (e.g., description, tags) are always included. Action-specific deltas (e.g., actions.create.inputSchema) are included only if they match the failing action. A delta for actions.list.egressSchema won't be injected into a create validation error.
To prevent context flooding from large diffs, the number of injected deltas is capped:
const config: SelfHealingConfig = {
activeDeltas: deltas,
maxDeltasPerError: 3, // default: 5
};Full Setup Flow
The typical integration reads the lockfile (last known-good contracts), compiles current contracts, diffs each tool, and creates per-tool enhancers:
import { diffContracts } from '@vinkius-core/mcp-fusion/introspection';
import { createToolEnhancer } from '@vinkius-core/mcp-fusion/introspection';
import { readLockfile, compileContracts } from '@vinkius-core/mcp-fusion/introspection';
const lockfile = await readLockfile(cwd);
const currentContracts = compileContracts(builders);
const deltas = new Map();
for (const [toolName, current] of Object.entries(currentContracts)) {
const previous = lockfile.capabilities.tools[toolName];
if (previous) {
deltas.set(toolName, diffContracts(previous, current));
}
}
const config = { activeDeltas: deltas };
const enhancers = new Map();
for (const toolName of Object.keys(currentContracts)) {
enhancers.set(toolName, createToolEnhancer(toolName, config));
}Tools with no changes get identity enhancers. Tools with breaking changes get enhancers that inject contract context into every validation error. The cost is proportional to the number of changes — zero for stable tools.
Configuration
SelfHealingConfig:
| Field | Type | Default | Description |
|---|---|---|---|
activeDeltas | ReadonlyMap<string, ContractDiffResult> | — | Contract diff results keyed by tool name |
includeAllSeverities | boolean | false | When false, only BREAKING and RISKY deltas are injected |
maxDeltasPerError | number | 5 | Maximum deltas to inject per error response |
SelfHealingResult:
| Field | Type | Description |
|---|---|---|
originalError | string | The original validation error XML |
enrichedError | string | The enriched XML (same as original if no deltas) |
injected | boolean | Whether any contract context was injected |
deltaCount | number | Number of deltas injected |
toolName | string | The tool that failed validation |
API Reference
| Function | Description |
|---|---|
enrichValidationError(originalError, toolName, actionKey, config) | Enrich a validation error with contract change context |
createToolEnhancer(toolName, config) | Create a pre-scoped enhancer. Returns identity function if no deltas exist. |
Both functions are pure — no logging, no writes, no network calls. Given the same deltas and error, the enriched output is always identical.