Self-Healing Context
Prerequisites
Install MCP Fusion before following this recipe: npm install @vinkius-core/mcp-fusion @modelcontextprotocol/sdk zod — or scaffold a project with npx fusion create.
Introduction
When the AI makes a reasoning error — calling the wrong tool, using incorrect parameters, or misunderstanding data — a naive MCP server returns a generic error. The AI either gives up or retries blindly.
Self-Healing Context combines structured errors with agentic affordances to create a feedback loop: errors carry recovery instructions, and responses carry next-action hints. The AI corrects itself without human intervention.
Intent Recovery
When the AI's intent is correct but its method is wrong, guide it to the right tool:
const f = initFusion<AppContext>();
export const searchUsers = f.query('users.search')
.describe('Search users by name, email, or department')
.withString('query', 'Search query')
.handle(async (input, ctx) => {
const results = await ctx.db.users.search(input.query);
if (results.length === 0) {
return toolError('NoResults', {
message: `No users found matching "${input.query}".`,
suggestion: 'Try a broader search term, or use users.list to see all users.',
availableActions: ['users.list'],
});
}
if (results.length > 100) {
return toolError('TooManyResults', {
message: `${results.length} results for "${input.query}" — too broad.`,
suggestion: 'Narrow the search with a more specific term. Try department names or exact email addresses.',
});
}
return success(results);
});Context Injection
After a mutation, inject context about what changed so the AI doesn't operate on stale mental models:
export const updateProjectStatus = f.action('projects.update_status')
.describe('Update a project\'s status')
.withString('id', 'Project ID')
.withEnum('status', ['active', 'paused', 'archived'] as const, 'New status')
.handle(async (input, ctx) => {
const previous = await ctx.db.projects.findUnique({ where: { id: input.id } });
await ctx.db.projects.update({
where: { id: input.id },
data: { status: input.status },
});
return response({
updated: true,
previousStatus: previous.status,
newStatus: input.status,
})
.llmHint(
`Project "${input.id}" changed from "${previous.status}" to "${input.status}". ` +
`All cached project data is now stale.`
)
.build();
});The llmHint tells the AI that its mental model of this project is outdated — it should re-fetch before making further decisions.
Combined Pattern
The most powerful pattern combines structured errors, affordances, and context injection. Here's a complete billing workflow:
import { createPresenter, t, suggest, toolError, success } from '@vinkius-core/mcp-fusion';
const InvoicePresenter = createPresenter('Invoice')
.schema({
id: t.string,
amount_cents: t.number.describe('CENTS. Divide by 100.'),
status: t.enum('draft', 'pending', 'paid', 'overdue'),
})
.suggest((inv) => {
const hints = [];
if (inv.status === 'draft') hints.push(suggest('billing.finalize', 'Finalize and send'));
if (inv.status === 'pending') hints.push(suggest('billing.charge', 'Process payment'));
if (inv.status === 'overdue') hints.push(suggest('billing.escalate', 'Escalate to collections'));
return hints;
});
export const chargeInvoice = f.mutation('billing.charge')
.describe('Process payment for an invoice')
.withString('id', 'Invoice ID')
.handle(async (input, ctx) => {
const invoice = await ctx.db.invoices.findUnique({ where: { id: input.id } });
if (!invoice) {
return toolError('NotFound', {
message: `Invoice "${input.id}" not found.`,
suggestion: 'List invoices first.',
availableActions: ['billing.list'],
});
}
if (invoice.status === 'draft') {
return toolError('NotFinalized', {
message: 'Invoice is still a draft.',
suggestion: 'Finalize the invoice before charging.',
availableActions: ['billing.finalize'],
});
}
if (invoice.status === 'paid') {
return toolError('AlreadyPaid', {
message: 'Invoice is already paid.',
suggestion: 'No action needed.',
});
}
await ctx.db.payments.charge(invoice);
return success({ charged: true, amount: invoice.amount_cents });
});The AI navigates a complete state machine: draft → finalize → pending → charge → paid. At every step, it receives either suggested actions (what to do next) or structured errors (how to recover). No blind retries. No hallucinated tool names.