Brownfield adoption
Extract shared logic, then project both ways — do not wrap Hono handlers.
Most teams already have a Hono (or Express) app. The first instinct is to wrap existing route handlers as Prism operations. That does not work.
Why wrapping fails
Section titled “Why wrapping fails”| Shape | Signature |
|---|---|
| Hono handler | (c) => Response — reads c.req, writes c.json(...) |
| Prism handler | ({ input, context, errors }) => output |
There is no adapter between those shapes. Calling a Hono handler from an operation means synthesizing a full HTTP request — absurd. Calling an operation from a Hono route is fine once the logic is not trapped inside either closure.
What works: extract, then project both ways
Section titled “What works: extract, then project both ways”- Extract a plain service function (
listCards,moveCard, …) that takes data in and returns data out. - Point existing routes at the service (REST behavior unchanged).
- Add Prism operations that call the same service.
- Project MCP / CLI / AI SDK from the new router alongside the existing REST app.
import { agent, http, scopes } from "@prism/agent";import { implement, op } from "@prism/core";import { z } from "zod";
// Plain service — no Hono, no Prismasync function archiveCard( cardId: string,): Promise<{ ok: true; id: string } | { ok: false; reason: "not_found" }> { void cardId; return { ok: true, id: cardId };}
// Existing route keeps calling the service:// app.post("/cards/:id/archive", async (c) => { ... archiveCard(...) })
// Prism op calls the same functionconst archive = implement( op() .input(z.object({ cardId: z.string() })) .output(z.object({ id: z.string() })) .errors({ NOT_FOUND_CARD: { status: 404, title: "Card not found." }, }) .meta( http({ method: "POST", path: "/cards/{cardId}/archive" }), scopes("card:archive"), agent({ description: "Archive a card. Requires confirmation.", confirm: true, }), ),).handler(async ({ input, errors }) => { const result = await archiveCard(input.cardId); if (!result.ok) { return errors.NOT_FOUND_CARD(); } return { id: result.id };});
void archive;Worked instance: kanban-api
Section titled “Worked instance: kanban-api”A committed brownfield trial lives at kanban-api (sibling of this repo). Results, honestly:
| Area | Cost |
|---|---|
| Original app logic modified | 1 file (src/app.ts) — net −86 lines of duplicated DB logic |
| New Prism surface | ops + toMCP + CLI (~weekend for three endpoints) |
| Original REST tests | untouched |
Scope that worked: list / move / archive over MCP + CLI alongside existing REST. Scope that would not be weekend work: migrating every board/card endpoint onto Prism first.
Agent surfaces next to an existing API is weekend work. Full migration is a project — do not pretend otherwise.
Prism projections call z.toJSONSchema() — zod v4 only. A zod v3 pin fails at runtime with a cryptic Cannot read properties of undefined (reading 'def'). Bump to zod ^4 before wiring MCP.