@nimbus-dev/sdk
v2.1.0
Published
[](https://www.npmjs.com/package/@nimbus-dev/sdk) [](./LICENSE)
Readme
@nimbus-dev/sdk
The MIT-licensed, dependency-free TypeScript authoring contract for Nimbus MCP (Model Context Protocol) connectors and extensions. It ships types, small pure helpers, and test utilities — no runtime dependencies, no I/O, no credentials.
The gateway, Vault, HITL (human-in-the-loop) gate, and connector sandbox all live in the Nimbus monorepo. This package is just the stable surface you compile against.
Install
npm install @nimbus-dev/sdk # or: bun add @nimbus-dev/sdkQuickstart
Scaffold a connector that performs the contract-version handshake and then serves MCP tools over the same two streams. Full walkthrough: quickstart-typescript.md (or quickstart-python.md).
npm create @nimbus-dev/connector@latest weather-connectorUsage: npx @nimbus-dev/create-connector@latest <name> [--lang ts|python] [--dir <path>]
<name> lowercase kebab-case, starting with a letter (e.g. weather-connector)
--lang ts (default) or python
--dir where to write it (default: ./<name>)<name> has to be an npm package name, a Python module name, and a directory name at
once, so the CLI takes the intersection of all three: lowercase kebab-case starting with
a letter. my_connector, MyConnector and 2fa-connector are refused rather than
quietly rewritten. Then:
cd ~/src/weather-connector
npm install
npm test # typechecks, builds, then runs unit + acceptance tests
npm start # node dist/main.jsYou get ten files, five of them source. manifest.ts is the contract the gateway reads:
import type { ExtensionManifest } from "@nimbus-dev/sdk";
export const manifest: ExtensionManifest = {
id: "nimbus-quickstart-connector",
displayName: "Nimbus Quickstart Connector",
version: "0.1.0",
description: "A Nimbus connector that echoes what it is given.",
author: "you",
entrypoint: "./dist/main.js",
runtime: "node",
permissions: ["read"],
hitlRequired: [],
// …
contractVersions: ["1"],
minNimbusVersion: "0.1.0",
};
export const TOOLS = [{ name: "echo", description: "Echoes its input" }] as const;handlers.ts holds your logic and imports no protocol. main.ts is the only file that
knows a protocol exists — it handshakes, then serves MCP over what the handshake did not
consume. It is shown here as plain text, not as a checked snippet: it imports
@modelcontextprotocol/sdk and zod, which this dependency-free repository does not
install, so nothing here could compile it. The generated project is where it is
typechecked, built and executed, on every CI run.
import { Readable } from "node:stream";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CONTRACT_HANDSHAKE_EXIT } from "@nimbus-dev/sdk";
import { createRegisterSimpleTool, mcpJsonResult } from "@nimbus-dev/sdk/connector-kit";
import { NdjsonLineReader, performHandshake } from "@nimbus-dev/sdk/ipc";
import { z } from "zod";
import { echo } from "./handlers.js";
import { manifest, TOOLS } from "./manifest.js";
// …
async function run(): Promise<void> {
const reader = new NdjsonLineReader();
const result = await performHandshake(
{
read: readChunk,
write: async (chunk) => {
process.stdout.write(chunk);
},
},
{ localVersions: manifest.contractVersions ?? ["1"], reader },
);
if (!result.ok) {
process.stderr.write(`handshake refused: ${result.reason}\n`);
process.exitCode = CONTRACT_HANDSHAKE_EXIT;
// Release stdin so the process can exit on its own. `process.exit()` here would risk
// truncating the hello we just wrote to a pipe.
await stdinChunks.return?.(undefined);
return;
}
// …
const replay = Readable.from(
(async function* stream(): AsyncGenerator<Uint8Array> {
for (const frame of result.pending) {
yield frameOf(frame);
}
for (;;) {
const next = await stdinChunks.next();
if (next.done === true) {
break;
}
for (const frame of reader.push(new Uint8Array(next.value))) {
yield frameOf(frame);
}
}
for (const frame of reader.flushFrames().frames) {
yield frameOf(frame);
}
})(),
{ objectMode: false },
);
await connectTransport(createMcpServer(), replay);
}The transport is deliberately not given process.stdin. Both peers announce
unprompted, so the gateway's hello and its first MCP request routinely arrive in one
read: performHandshake returns the complete frames it read past the hello as
result.pending, and leaves a half-written one inside the NdjsonLineReader it was
given. Serving on raw stdin drops both, silently. The generated main.test.ts guards
each half with its own test — keep them.
Public surface (the exports map)
@nimbus-dev/sdk— the main contract:NimbusExtensionServer, the plugin API types,ExtensionManifest/NimbusItem, HITL requests, distribution-channel resolution, the scoped audit logger (its free-form payload is@deprecatedin favor of@nimbus-dev/sdk/diagnosticsbelow), iCalendar + JMAP helpers, and thecrypto/data-profile/agentshelper modules.@nimbus-dev/sdk/testing—MockGateway+ contract-test / sandbox-probe utilities for connector test suites.@nimbus-dev/sdk/ipc— the NDJSON line-reader + IPC framing helpers.@nimbus-dev/sdk/connector-kit— helpers for hand-rolled MCP connectors:createRegisterSimpleTool/registerZodToolfor Zod-validated tool registration,mcpJsonResultand friends for MCP tool results, andmakeRestFetcher— a Bearer-auth JSON fetcher with origin-locked URL resolution. Still dependency-free:ZodObjectSchemais a structural type, not an import ofzod. This is the entry point the generatedmain.tsabove imports from.@nimbus-dev/sdk/diagnostics— the diagnostics / telemetry contract v0:encodeDiagnostic/parseDiagnostic/isDiagnosticEvent/meetsLevel, the closedDiagnosticEventenvelope, andcreateEmitterfor building a sink-backedDiagnosticEmitter. Validated rather than trusted — afieldsmember holds only numbers and booleans, so there is nowhere in afieldsvalue for a secret or a row of user data to go. That guarantee coversfields, not every string on the envelope:extensionId,event, anderror.codeare still caller-controlled strings this contract does not length-bound (spec §8). The redaction-safe replacement for the scoped audit logger's free-form payload above.@nimbus-dev/sdk/signing— manifest signing:canonicalize/canonicalizeManifest, and the detached JWS envelope (signManifest,verifyManifestSignature,generateSigningKey), which rejects with oneSignatureError. The envelope functions are asynchronous because they run on WebCrypto (crypto.subtle), which keeps them usable in a browser, Deno or an edge worker. This entry point replaces the deprecated signing helpers incrypto/, which sign different bytes.
Changing an exported type is a semver-relevant change.
Documentation
- Documentation index — every module, every public export, and the runnable examples.
- Roadmap — the 9 pillars and the phased plan to make the SDK a language-neutral, batteries-included authoring contract for all of Nimbus.
- Architecture — how the SDK is structured today and the spec-first / polyglot target.
- Releasing — how each language SDK is published — npm, PyPI, and, for Go, a tag the module proxy serves — under one set of release-parity guarantees.
- Security — reporting, supply-chain posture, and the trust model as the SDK grows.
- Governance — how contract-affecting decisions are made (the RFC process, how a language becomes official).
- Inclusion policy — the bar a new battery must clear.
- Deprecation policy — how an export is marked deprecated and how long it survives before removal.
- Glossary — the shared vocabulary (narrow waist, binding, conformance suite, …).
- Contract spec — the
versioned v1 JSON Schemas for
ExtensionManifest/NimbusItemand the language-neutral conformance fixtures every binding validates against.
Contributing
See CONTRIBUTING.md.
In short: Bun v1.2+, TypeScript strict,
Biome, no any, and no runtime dependencies — the published surface stays
dependency-free.
See also
- Nimbus — the local-first AI agent gateway
- Model Context Protocol
License
MIT © Nimbus Contributors
