Skip to content

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.

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

typescript
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:

typescript
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:

xml
<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:

typescript
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:

typescript
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:

typescript
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:

FieldTypeDefaultDescription
activeDeltasReadonlyMap<string, ContractDiffResult>Contract diff results keyed by tool name
includeAllSeveritiesbooleanfalseWhen false, only BREAKING and RISKY deltas are injected
maxDeltasPerErrornumber5Maximum deltas to inject per error response

SelfHealingResult:

FieldTypeDescription
originalErrorstringThe original validation error XML
enrichedErrorstringThe enriched XML (same as original if no deltas)
injectedbooleanWhether any contract context was injected
deltaCountnumberNumber of deltas injected
toolNamestringThe tool that failed validation

API Reference

FunctionDescription
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.