Error Handling
Prerequisites
Install Vurb.ts before following this guide: npm install @vurb/core @modelcontextprotocol/sdk zod — or scaffold a project with vurb create.
error() — Simple Errors
For straightforward failures:
import { initVurb, error, success } from '@vurb/core';
const f = initVurb<AppContext>();
export const getProject = f.query('projects.get')
.describe('Get a project by ID')
.withString('id', 'Project ID')
.handle(async (input, ctx) => {
const project = await ctx.db.projects.findUnique({ where: { id: input.id } });
if (!project) return error(`Project "${input.id}" not found`);
return success(project);
});This works, but the AI only sees text — no recovery path. For guidance, use toolError() or f.error().
required() — Missing Parameters
Tells the agent exactly which parameter to provide:
import { required } from '@vurb/core';
.handle(async (input, ctx) => {
if (!input.workspace_id) return required('workspace_id');
// ...
})<tool_error code="MISSING_REQUIRED_FIELD">
<message>Required field "workspace_id" is missing.</message>
<recovery>Provide the "workspace_id" parameter and retry.</recovery>
</tool_error>toolError() — Self-Healing Errors
Rich error envelope with everything the AI needs to self-correct:
The agent reads <available_actions> and calls billing.list_invoices instead of retrying with the same invalid ID.
TIP
Use domain-specific codes (InvoiceNotFound, AlreadyPaid) instead of generic ones. They make error logs self-documenting.
ErrorBuilder — Fluent Error Chain
For maximum readability, use the fluent f.error():
ErrorBuilder Methods
| Method | Purpose |
|---|---|
.suggest(text) | Recovery instruction for the LLM agent |
.actions(...names) | Tool names the agent should try instead |
.warning() | Non-fatal advisory (isError: false) |
.critical() | System-level failure requiring escalation |
.severity(level) | 'error' (default), 'warning', or 'critical' |
.details(data) | Structured metadata (Record<string, string>) |
.retryAfter(seconds) | Suggest delay for transient errors |
Architect's Check
When your AI agent generates error handlers, verify that every NOT_FOUND error includes an availableActions array or .actions() call. Without recovery paths, the agent falls back to "I encountered an error" — the worst possible UX.
Severity Levels
// Warning — non-fatal advisory (isError: false)
return f.error('DEPRECATED', 'This endpoint is deprecated')
.suggest('Use billing.invoices_v2 instead.')
.actions('billing.invoices_v2')
.warning();
// Critical — system failure requiring escalation
return f.error('INTERNAL_ERROR', 'Database connection pool exhausted')
.suggest('Retry after 30 seconds or contact support.')
.retryAfter(30)
.critical();Automatic Validation Errors
Invalid Zod arguments auto-generate per-field corrections — no code needed:
<validation_error action="users/create">
<field name="email">Invalid email. You sent: 'bad-email'. Expected: a valid email address.</field>
<field name="role">Invalid enum value. Expected 'admin' | 'user', received 'superadmin'.</field>
<recovery>Fix the fields above and call the tool again.</recovery>
</validation_error>Automatic Routing Errors
Missing or misspelled discriminators produce structured corrections:
<tool_error code="UNKNOWN_ACTION">
<message>The action "destory" does not exist.</message>
<available_actions>list, create, delete</available_actions>
<recovery>Choose a valid action from available_actions.</recovery>
</tool_error>Composing Errors with Result
For multi-step operations, use the Result monad:
import { succeed, fail, error, success, type Result } from '@vurb/core';
function findUser(db: Database, id: string): Result<User> {
const user = db.users.get(id);
return user ? succeed(user) : fail(error(`User "${id}" not found`));
}
.handle(async (input, ctx) => {
const user = findUser(ctx.db, input.user_id);
if (!user.ok) return user.response;
const authorized = checkPermission(user.value, 'delete');
if (!authorized.ok) return authorized.response;
await ctx.db.projects.delete({ where: { id: input.project_id } });
return success('Deleted');
})The Error Protocol
| Error Type | Source | Root Element | Trigger |
|---|---|---|---|
error() | Handler | <tool_error> | Generic failures |
required() | Handler | <tool_error code="MISSING_REQUIRED_FIELD"> | Missing arguments |
toolError() | Handler | <tool_error code="..."> | Recoverable business errors |
f.error() | Handler | <tool_error code="..."> | Fluent builder chain |
| Validation | Automatic | <validation_error> | Invalid arguments |
| Routing | Automatic | <tool_error code="MISSING_DISCRIMINATOR"> | Bad discriminator |
All user-controlled data is XML-escaped automatically.