One framework, multi-faced agentic access

A service usually gives an agent one way in. AgentBack gives the same authored surface many faces — REST, MCP, an in-app agent's own tools, an operator CLI, and a read-only introspection feed — each sharing one validation, authorization, and metering pipeline.

The recurring claim on this blog is that a service should have one source of truth — a Zod schema on a decorator — and that everything an agent or a human touches should be a projection of it, not a re-description of it. REST was the first projection. MCP was the second. The v0.7.0 release adds three more, and the point isn't the count. It's that they all reach the domain through the same door.

You already wrote the hard part — a registered @tool class with an input schema, an output schema, and a body:

@tool('forecast', {input: ForecastIn, output: ForecastOut})
async forecast(input: z.infer<typeof ForecastIn>) { … }

That one declaration is a REST route and an MCP tool. In 0.7 it is also an AI agent's tool, a command-line command, and a node in a live introspection feed. Five faces, one author.

The agent runs your tools

The usual way to "add an agent" bolts a model onto your service from the outside — it calls your REST API like any other client, and now you have two authorization stories to keep in sync. @agentback/agents inverts that. It projects your registered @tool classes into the agent as host-executed tools:

const tools = toHostTools(app, {include: ['forecast', 'book_trip']});
// each tool's execute() routes through MCPServer.callTool —
// Zod validation, @authorize voters, metering, output validation

The model chooses; the framework executes through the identical seam your REST and MCP callers already use. Identity is read per turn (a transport's authenticated principal always wins), quota is preflighted before the model spends a token, and each turn emits one usage event with its tool calls correlated underneath. You bring the model and the credentials; AgentBack brings the pipeline. There is no auto-projected /agent/run — where a turn runs is your call.

The same tool, from a shell

A human operator doesn't want an agent — they want to run the thing once, by hand, and read the result. @agentback/command makes the @tool a command:

my-svc forecast --city Tokyo --days 3
my-svc forecast --city Tokyo --format json   # for scripts and pipes

There is no .command() builder to learn. The flags are the input schema — argvToBundle coerces the string argv off the emitted JSON Schema (booleans become --verbose/--no-verbose, arrays repeat) before callTool re-validates. The bare result goes to stdout, the exit code carries success or failure, and an AgentError envelope goes to stderr — a well-behaved Unix citizen that still ran through the same voters and meter as everything else.

The face that only reads

The fifth face doesn't invoke anything. @agentback/introspection is a read-only MCP server that projects the live app — what's bound, the real schema graph, the routes and tools — so a coding agent grounds itself in the running instance instead of guessing from stale source:

app.service(IntrospectionTools);  // inventory(), get(), get_okf_bundle()
await installMcpHttp(app);         // point your agent's MCP client at /mcp

It never invokes a route or tool and never resolves a secret-bearing binding — the one thing it reads is a schema-tagged binding's Zod object, because schemas are not secrets. It's the "see" half of a see-and-evolve loop; the new console agent dock (@agentback/console-chat) wires it to an ACP coding agent so the agent reads the live app before it edits the source.

Why the shared pipeline is the whole point

It would be easy to build these as five adapters, each with its own argument parsing, its own auth check, its own idea of an error. That is how surfaces normally drift: the CLI forgets a permission the REST layer enforces; the agent path skips the meter; the third client validates a field the other two don't. Every face becomes a place a bug can hide.

AgentBack routes all of them through one function — MCPServer.callTool. Validation, @authorize voters, metering, and output validation happen once, in one place, and every face inherits them. Add a voter and it guards the REST route, the MCP tool, the agent's tool call, and the CLI command in the same edit. The faces are thin on purpose: argv in, or a model's tool call, or an HTTP request — different skins over the same muscle.

That's what "multi-faced agentic access" means here. Not five ways to write the same logic five times, but one authored surface an agent can reach however it happens to be shaped today — and a promise that the contract it depends on is the same one every other caller sees.

Read the agents guide Try the CLI example