Error Handling
MCP Fusion provides a layered error handling system designed for both human debugging and LLM agent self-correction.
The Error Hierarchy
error() → Simple error message (human-readable)
required() → Missing field shortcut
toolError() → Self-healing error with recovery instructions
Result<T> → Pipeline-oriented success/failure compositionSimple Errors
Use error() for straightforward failures:
import { error } from '@vinkius-core/mcp-fusion';
handler: async (ctx, args) => {
const project = await ctx.db.projects.findUnique(args.id);
if (!project) return error(`Project "${args.id}" not found`);
return success(project);
}Missing Field Errors
required() is a convenience shortcut for validation:
import { required } from '@vinkius-core/mcp-fusion';
handler: async (ctx, args) => {
if (!args.workspace_id) return required('workspace_id');
// ...
}Self-Healing Errors (Agent Experience)
toolError() provides structured recovery instructions so LLM agents can self-correct instead of hallucinating:
import { toolError } from '@vinkius-core/mcp-fusion';
handler: async (ctx, args) => {
const project = await ctx.db.get(args.project_id);
if (!project) {
return toolError('ProjectNotFound', {
message: `Project '${args.project_id}' does not exist.`,
suggestion: 'Call projects.list first to get valid IDs, then retry.',
availableActions: ['projects.list'],
});
}
return success(project);
}What the LLM sees:
[ProjectNotFound] Project 'proj_xyz' does not exist.
💡 Suggestion: Call projects.list first to get valid IDs, then retry.
📋 Try: projects.listThis structured format helps the agent:
- Understand the error — via the error code
- Know what to do — via the suggestion
- Know which actions exist — via the available actions list
When to use each
| Helper | Use Case | LLM Benefit |
|---|---|---|
error() | Generic failures, auth errors | Basic retry trigger |
required() | Missing arguments | Tells LLM which field to add |
toolError() | Recoverable failures | Full self-healing with next steps |
Agentic Error Presenter (Automatic)
The framework automatically formats validation and routing errors for LLM agents. You don't need to write any error formatting code — these are generated by the execution pipeline.
Validation Errors
When the LLM sends arguments that fail Zod validation, it receives a structured, actionable correction prompt:
⚠️ VALIDATION FAILED — ACTION 'USERS/CREATE'
• email — Invalid email. You sent: 'bad-email'. Expected: a valid email address (e.g. user@example.com).
• role — Invalid enum value. Expected 'admin' | 'user', received 'superadmin'. You sent: 'superadmin'. Valid options: 'admin', 'user'.
💡 Fix the fields above and call the tool again. Do not explain the error.Key design decisions:
- Uppercase header — visually distinct for the LLM to parse
- Per-field
You sent:values — the LLM sees exactly what it passed - Expected types/formats — tells the LLM what to send instead
- Anti-apology footer — instructs the LLM to retry immediately, not explain
Unknown Fields (.strict())
When the LLM sends fields not declared in the schema, they are explicitly rejected (not silently stripped):
⚠️ VALIDATION FAILED — ACTION 'BILLING/CREATE'
• — Unrecognized key(s) in object: 'hallucinated_param', 'admin_override'.
💡 Check for typos. Only declared fields are accepted.
💡 Fix the fields above and call the tool again. Do not explain the error.This teaches the LLM which fields are valid, enabling self-correction on retry.
Routing Errors
Missing discriminator field:
❌ ROUTING ERROR: The required field 'action' is missing.
You must specify which action to perform. Available: [list, create, delete].
💡 Add the 'action' field to your JSON and call the tool again.Unknown action value:
❌ UNKNOWN ACTION: The action 'destory' does not exist.
Available actions: [list, create, delete].
💡 Choose a valid action from the list above and call the tool again.Error Helper Summary
| Error Type | Source | Format | When |
|---|---|---|---|
error() | Your handler | Custom message | Generic failures |
toolError() | Your handler | Structured with recovery hints | Recoverable business errors |
| Validation | Automatic | ⚠️ VALIDATION FAILED | Bad args or unknown fields |
| Routing | Automatic | ❌ ROUTING ERROR / ❌ UNKNOWN ACTION | Missing or invalid action |
Result Monad — Pipeline Composition
For complex multi-step operations, use the Result<T> monad to compose error handling without try/catch:
import { succeed, fail, error, type Result } from '@vinkius-core/mcp-fusion';
function findUser(id: string): Result<User> {
const user = db.users.get(id);
return user ? succeed(user) : fail(error(`User "${id}" not found`));
}
function checkPermission(user: User, action: string): Result<User> {
return user.can(action)
? succeed(user)
: fail(error(`User "${user.id}" cannot ${action}`));
}
// Pipeline composition
handler: async (ctx, args) => {
const user = findUser(args.user_id);
if (!user.ok) return user.response; // Early exit
const authorized = checkPermission(user.value, 'delete');
if (!authorized.ok) return authorized.response; // Early exit
await ctx.db.projects.delete(args.project_id);
return success('Deleted');
}Pipeline Pattern Summary
const step1 = someOperation();
if (!step1.ok) return step1.response; // ← Failure short-circuits
const step2 = nextOperation(step1.value); // ← Success narrows type
if (!step2.ok) return step2.response;
return success(step2.value); // ← Final successSee also: Result Monad for the complete API reference and advanced patterns.
Combining with Middleware
Middleware can handle cross-cutting error concerns:
const errorBoundary: MiddlewareFn<AppContext> = async (ctx, args, next) => {
try {
return await next();
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
return toolError('UnhandledException', {
message,
suggestion: 'This is an unexpected error. Please report it.',
});
}
};
const tool = createTool<AppContext>('projects')
.use(errorBoundary)
.action({ name: 'list', handler: listProjects });Best Practices
- Prefer
toolError()overerror()for any failure the LLM could recover from - Use
Result<T>for multi-step pipelines to avoid nested try/catch - Include
availableActionsso the LLM knows which tool calls can fix the issue - Keep error messages concise — LLMs process shorter text more accurately
- Never expose internal stack traces — use error codes like
'DatabaseError', not raw SQL errors
