Ch7. Tools, Approval, Connections
Design Eve authored tools, human approval, MCP/OpenAPI connections, and OAuth boundaries as enterprise security surfaces.
Written and reviewed by Eric Han · Co-founder & CTO, reopt
Key takeaways
- Authored tool
executeruns in the app runtime, not in the sandbox, so it is a privileged surface. toModelOutput,approval, and auth scope control data exposure and risky actions.- MCP/OpenAPI connections expand capabilities quickly, but require allow-lists, approval, and OAuth boundaries.
Requester, approver, and execution identity in 0.78–0.82
defineTool now uses approval. Approved calls execute with the requester's authority. Approval does not automatically transfer the approver's privileges. Workflow and agent calls also recheck requester access before dispatch.
In approval: { request: always(), response: ... }, a response policy replaces the default requester-only policy. Compare composite identity, including authenticator, principalType, and tenant, rather than principalId alone. Only server-side approval.settled or input.resolved confirms the final decision; a submission or approval.candidate event does not.
An executing defineTool may inspect ctx.approval.responder; using that person's token requires an explicit design. auto() uses a decision model and may send inputs to its provider. It does not replace authentication or authorization. Multiple already-pending requests under once() must each be resolved.
Catalog calls and real names
Use eve__search for discovery and eve__tool({ name, input }) for deferred or connection tools. A connection tool is named <connection>__<tool>. Load skills with eve__skill({ name }). Traces and evals record the resolved tool or skill rather than the catalog wrapper.
Tools are typed actions the model can request. The most important operational fact is that authored tool execute runs in the app runtime. If it can access secrets or internal APIs, treat it as privileged code.
Tool Structure
import { defineTool } from "eve/tools";
import { z } from "zod";
export default defineTool({
description: "Return mock weather data for a city.",
inputSchema: z.object({ city: z.string().min(1) }),
async execute({ city }) {
return { city, condition: "Sunny", temperatureF: 72 };
},
});Standards:
descriptionis model-facing routing textinputSchemais a control surfaceexecutemust tolerate replay and retries- returned data may enter history and telemetry, so minimize it
Minimize Model-visible Output
Use toModelOutput when hooks/channels need rich output but the model should see only a summary.
// Spread this projection into defineTool({ ...outputProjection, ... }).
const outputProjection = {
toModelOutput(output: { id: string; riskLevel: string }) {
return { type: "text" as const, value: `Report ${output.id}: risk ${output.riskLevel}.` };
},
};This prevents CRM rows, audit payloads, pricing tables, and PII from being copied into later model context.
Approval Policy
| Helper | Behavior |
|---|---|
never() | no approval; omission has the same effect |
once() | approve first execution per session |
always() | approve every execution |
| predicate | decide from tool input |
import { defineTool } from "eve/tools";
import { always } from "eve/tools/approval";
import { z } from "zod";
export default defineTool({
description: "Refund a customer charge.",
inputSchema: z.object({
chargeId: z.string(),
amount: z.number().positive(),
}),
approval: always(),
async execute(input) {
return refundCharge(input);
},
});Input-based Approval
const approval = ({ toolInput }: { toolInput?: { amount?: number } }) => {
const amount = Number(toolInput?.amount ?? 0);
return amount > 1000;
};Keep business policy in pure functions under agent/lib/ and unit test it. Do not hide large policy logic inside tool definitions.
HITL Is Durable Parking
When approval is required, Eve emits input.requested and parks the session in turn.waiting. The process is not held open. The session resumes when the answer arrives.
Connections
Connections expose external MCP or OpenAPI tools that you do not author.
| Connection | Helper | Model-visible tool name |
|---|---|---|
| MCP | defineMcpClientConnection | <connection>__<tool> |
| OpenAPI | defineOpenAPIConnection | <connection>__<operationId> |
The model discovers them through eve__search and calls qualified names.
import { defineMcpClientConnection } from "eve/connections";
import { once } from "eve/tools/approval";
export default defineMcpClientConnection({
url: "https://mcp.linear.app/mcp",
description: "Linear workspace: issues, projects, cycles, and comments.",
auth: {
getToken: async () => ({ token: process.env.LINEAR_API_TOKEN! }),
},
tools: { allow: ["search_issues", "get_issue"] },
approval: once(),
});Tokens Stay Out Of The Model
Connection tokens are resolved in the app runtime and injected into outbound requests. They do not become model context. This is a major safety property, but scope still matters.
| Risk | Response |
|---|---|
| shared app token reaches all tenant data | use user or tenant-scoped token |
| too many MCP tools exposed | use allow-list |
| write operation has no approval | split tools or use approval: always() |
| token revoked mid-call | map provider 401 to ctx.requireAuth() |
Tool Auth vs Connection Auth vs Route Auth
| Surface | Purpose |
|---|---|
| route auth | who may call Eve routes |
| tool auth | one custom tool needs OAuth |
| connection auth | an external MCP/OpenAPI server needs credentials |
Route auth does not automatically make tool or connection scopes safe.
ask_question vs Approval
| Purpose | Eve feature |
|---|---|
| permission to execute | approval |
| missing information | built-in ask_question |
| OAuth consent | tool or connection auth |
All use the same durable input.requested pause protocol, but their intent is different.
Tool Design Checklist
| Item | Standard |
|---|---|
| input schema | strict validation and limits |
| output minimization | model sees only what it needs |
| idempotency | external writes use key or ledger |
| approval | irreversible/sensitive actions gated |
| auth | token scope and principal type explicit |
| observability | action.result hook can audit |
| eval | calledTool/noFailedActions/output checks |
Ch6. Context, Skills, Dynamic Capabilities
Design context, skills, dynamic tools, dynamic instructions, and dynamic skills for high-quality Eve agents.
Ch8. Sandbox Secure Runtime
Turn Eve sandbox trust boundaries, backends, network policy, credential brokering, and workspace lifecycle into operating standards.