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

ts
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

APIDescription
app.registryThe app’s MutableToolRegistry (default InMemoryToolRegistry; pass createApp({ registry }) to use another)
app.tool / app.replaceTool / app.removeToolChange the tool set at runtime; connected clients get list_changed
ToolRegistryThe 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, FORBIDDENTypes 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.