A registry that changes at runtime
The tools an app serves live in its registry, and the registry can change at any time. Connected clients are told.
Where this fits: Build with TypeScript. The registry is what the transports serve; how Kervan works shows where it sits.
On this page
Change the tool set while a client is connected
import { createApp, z } from "@kervan/core"
import { createTestClient } from "@kervan/transport/testing"
const app = createApp({ name: "registry", version: "0.1.0" })
app.tool("add", {
description: "Adds two numbers.",
input: z.object({ a: z.number(), b: z.number() }),
handler: ({ a, b }) => String(a + b),
})
const client = await createTestClient(app)
let notified = 0
client.setNotificationHandler("notifications/tools/list_changed", () => {
notified++
})
// A 2026-07-28 client asks for change notifications on its subscriptions/listen stream.
await client.listen({ toolsListChanged: true })
const names = async () => (await client.listTools()).tools.map((tool) => tool.name).join(", ")
console.log(`before: ${await names()}`)
// While the client is connected: one tool more, one tool less, announced once.
app.tool("multiply", {
description: "Multiplies two numbers.",
input: z.object({ a: z.number(), b: z.number() }),
handler: ({ a, b }) => String(a * b),
})
app.removeTool("add")
await new Promise((resolve) => setTimeout(resolve, 100))
console.log(`after: ${await names()} (list_changed: ${notified})`)
await client.close()Adding multiply and removing add in the same tick is one change: the client gets one
notifications/tools/list_changed. A client of the 2025 revisions connected over HTTP gets none
(it has no session to receive it on) and sees the new list on its next tools/list.
The registry API
| API | Description |
|---|---|
app.registry | The app’s MutableToolRegistry (default InMemoryToolRegistry; pass createApp({ registry }) to use another) |
app.tool / app.replaceTool / app.removeTool | Change the tool set at runtime; connected clients get list_changed |
ToolRegistry | The read side transports use: list() and onChange(listener) |
app.createServer(registry?) | A fresh SDK server from a snapshot (per HTTP request) |
app.createLiveServer(registry?) | An SDK server that follows the registry (per stdio or in-memory connection) |
ServerResolver, FORBIDDEN | Types for the transport’s resolveServer hook |
Custom registries must return the same entry objects for unchanged tools: Kervan compares by
identity to avoid spurious notifications. Registries shared by several instances should fire
onChange on every instance. InMemoryToolRegistry coalesces changes made in one tick into a
single onChange call.