@plane/sdk
Four layers, each usable on its own.
npm i @plane/sdk| Import | What it gives you |
|---|---|
@plane/sdk/client | The Plane API as ordinary promises. |
@plane/sdk/verify | Proof that an incoming request really came from Plane. |
@plane/sdk/browser | A resuming SSE reader, and one layer per stream format. |
@plane/sdk/dev | The outbound channel client. Behind npx plane dev. |
@plane/sdk | Co-definition: definePlane, tool, providerTool, handlers, sync. |
Everything on the public surface is plain TypeScript. Effect is not a dependency; zod v4 is a peer dependency for tool input schemas.
The root export
definePlane is the whole point of the package: the agent definition and the code its
tools run, in one object, so neither half can drift from the other. It is covered in
its own guide.
Defining an agent
definePlane, tool, plane.handlers, plane.sync().
Provider-executed tools
providerTool("web_search", …).
import { definePlane, tool, providerTool, createPlaneClient } from "@plane/sdk"
export const plane = definePlane({ agent: { … }, tools: [ … ], hooks: { … } })
plane.definition() // the definition, without talking to Plane
plane.handlers // { GET, POST, DELETE } — Web Request/Response
await plane.sync({ publicUrl }) // connections.upsert + agents.upsert
await plane.sessions.start({ message, metadata, identity, overrides })
plane.sessions.stream(id) // async iterable of SessionEvent
await plane.sessions.children(id)
await plane.approvals.pending(sessionId)
await plane.approvals.allow(sessionId, toolCallId, { actor })
await plane.approvals.deny(sessionId, toolCallId, { message })
await plane.authorizations.pending(sessionId)
await plane.dev({ planeUrl, name })The client is generated
Plane's API is an OpenAPI 3.1 document, itself generated from the contract the server
implements. This package's transport is generated from that, with
openapi-typescript for the types and
openapi-fetch — about two kilobytes — for
the runtime. Every path the client names is checked against the document at compile
time, so an endpoint renamed upstream is a build failure here rather than a 404 in
your logs.
What is not generated is the shape of client.agents.create(...). A generated
client's ergonomics are the generator's opinion; the namespaces, the option objects,
the PlaneError subclasses and the async-iterable stream are hand-written over a
generated transport.
A client in another language comes from the same document and nothing else — see Generating your own client.
Notes and limits
- The MCP server is stateless and built per request. A tool handler needs the verified claims of the request it is answering, so the server closes over exactly one verified context.
- A handler's return value is JSON-serialised into the MCP result. Return
{ content: [...] }to speak MCP directly — rich blocks,isError. verify: { enabled: false }exists for local development against a fake Plane. An endpoint with verification off is an open tool-execution endpoint. Never ship it.