Server SDK
Framework-neutral discovery, pairing, authentication, and sessions.
@openprinter/server fits inside an existing application. It
validates discovery and pairing, verifies the initial Ed25519
challenge, and runs each protocol session. The application owns
HTTP/WebSocket routing, durable stores, rate limiting, live-session
routing, cluster coordination, and durable jobs.
const server = createOpenPrinterServer({
brand: { name: 'Acme Print Service' },
serverId: 'acme-print-v1',
serverVersion: '1.0.0',
pairingCodeStore,
credentialStore,
checkPairingRateLimit: ({ remoteAddress }) =>
limiter.check(remoteAddress),
onAgentConnected: ({ agent, session }) =>
sessions.register(agent.agentId, session),
onJobReceived: ({ agent, message }) =>
jobs.markReceived(agent.agentId, message.payload.jobId),
});Discovery and pairing routes
app.get(server.paths.discovery, async (_request, response) => {
response.json(await server.discover());
});
app.post(server.paths.pairing, async (request, response) => {
response.json(
await server.pair(request.body, { remoteAddress: request.ip }),
);
});createPairingCode creates a secure, short-lived, single-use grant.
Protect code creation with application authorization. Use the
in-memory stores only for tests and local examples.
Gateway
wss.handleUpgrade(request, socket, head, (webSocket) => {
server.handleGatewayConnection(webSocket);
});The convenience adapter is structural rather than tied to ws. Custom
transports call server.accept({ transport }), forward frames to
session.receive, and report closure through
session.transportClosed.
accept() starts unauthenticated. The SDK sends and verifies the
initial challenge before accepting agent.hello. The authenticated
identity comes from the stored public credential, not from
host-supplied socket metadata.
Delivery ownership
Store application jobs durably before selecting a live session and
calling sendJob. If no worker owns the session, keep the job queued.
Two sessions for one agent are independent; host code or a backplane
decides affinity, replacement, and distributed delivery.
agent.job_received confirms local durable receipt.
agent.job_submitted confirms backend acceptance, not universal
physical completion.