Repository navigation
v0.13.0 — hardening for 1.0
From the 1.0 milestone: the AI/MCP security review and the bugs the API
reference turned up. Requires Node.js 22.
Breaking (the 1.0 upgrade guide covers each):
- The deprecated aliases from 0.12 are removed from
dendra:resolveProc,
invokeUnary,invokeStreamand theProc,AnyProc,CallSite,
TransportandProcedureKindtypes (import them fromdendra/adapter);
createMemoryStore,recallMemory,rememberFactsandformatMemoryBlock
(fromdendra/memory);toolSourceandToolSourceOptions(now
lazyToolSourceandLazyToolSourceOptions). enginesrequires Node.js 22 or later.- An
.ai()procedure whose input has no JSON Schema throws where it's defined,
andmaxBodyBytescaps JSON bodies (both below).
From the AI/MCP security review
(#42,
docs/security-review-ai-mcp.md).
- Unexpected errors are masked. An error that isn't a
DendraErrorused to
reach clients and the model with its own message, which can carry SQL,
hostnames or credentials. It now arrives asINTERNAL: Internal errorover
HTTP, streams, WebSockets, job snapshots, the MCP bridge and tool calls, and
the original is logged withconsole.error. After-hooks still get the
original. If clients relied on such a message, throw aDendraErrorinstead. createToolExecutoronly throwsDendraErrors, so a hand-written agent loop
can pass the message to the model. A tool without.ai()answers
NOT_FOUND, like a missing one, so the model can't probe for procedures.createMcpToolSourcethrows a remote tool's error as a newBAD_GATEWAY(502)
code, with the server's text.lazyToolsetthrows when two tools sanitize to the same name (a.band
a_b), which used to route calls to the wrong one.- Zod, ArkType and Valibot schemas reach OpenAPI and AI tools. Libraries
that implement Standard JSON Schema publish their JSON Schema to OpenAPI and
tool definitions, ass.*does: Zod 4 and ArkType, and Valibot through
toStandardJsonSchemafrom@valibot/to-json-schema
(#65). Before, a tool with a
Zod input showed the model no parameters. Breaking: an.ai()procedure
whose input has no JSON Schema (Zod 3, plain Valibot, az.date()) now throws
where it's defined. - Per-tool MCP annotations.
.ai({ annotations })sets MCP'stitle,
readOnlyHint,destructiveHint,idempotentHintandopenWorldHintover
the defaults dendra derives from.get()andconfirm, so a POST that only
reads, or a write that's safe to repeat, is described correctly
(#59).inspectRoutershows
each tool'sannotations, so a test can assert them. New type
ToolAnnotations. createMcpRoutermounts an external MCP server's tools in your router as
.ai()procedures ({ ...appRouter, gh: await createMcpRouter(client) }),
soconfirm(withcreateApprovals), your.use()guards, after-hooks and
inspectRoutercover them like local tools
(#58). Remote descriptions
are cut to 1,024 characters there and increateMcpToolSource
(maxDescriptionLength). The AI reference has a new section on treating
external servers as untrusted.- Streaming tools are bounded and cancellable. A
.stream()procedure
exposed with.ai()stops collecting after 1,000 chunks or 30 s, set per tool
with.ai({ maxItems, timeoutMs }). It answers with what arrived and aborts
the handler'ssignal, so a subscription can't hang an agent or grow without
limit (#57). Tool executors
takeexecute(name, args, { signal });runAgentpasses itssignal, so
aborting a run cancels the call in flight.mergeToolsets,ToolSourceand
createMcpBridge().callToolpass the signal through.createMcpToolSource
stops waiting but can't cancel the remote call. Behaviour change: a
streaming tool that ran longer than 30 s used to wait for its end; raise
timeoutMsif it needs longer. - A stream that yields
undefinedsendsnullover NDJSON and WebSockets, as
SSE already did. NDJSON used to write the lineundefined, which made the
client throw. A tool that returnsundefinedgives the MCP bridge and
runAgentthe textnullinstead of no text
(#64). maxBodyBytescaps JSON bodies too. RPC, batch and REST JSON bodies were
read whole with no limit; now, like uploads, they're refused past the cap
(default 10 MB) with413 PAYLOAD_TOO_LARGE, while reading
(#62). RaisemaxBodyBytes
if you accept larger JSON.approvals.list(ctx)only returns actions in the caller's exact scope. When
scope(ctx)returnedundefined(say, a user with no tenant), it used to
return every tenant's pending actions
(#63).- The example app's stdio MCP server only calls tools it listed and leaves out
confirm tools; its agent route builds ctx from the request instead of a user
id in the body.
Full docs: https://products.techfoundry.io/dendra/ · Upgrade guide: https://products.techfoundry.io/dendra/upgrading/