Skip to content
Prism

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.

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”
  1. Extract a plain service function (listCards, moveCard, …) that takes data in and returns data out.
  2. Point existing routes at the service (REST behavior unchanged).
  3. Add Prism operations that call the same service.
  4. 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 Prism
async 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 function
const 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;

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.