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.