@rendobar/mcp

April 30, 2026 · View on GitHub

Local stdio MCP server. Single ESM bundle. ~6 tools. Public, MIT.

CRITICAL RULES

1. NEVER write to stdout

stdout is reserved for JSON-RPC framing. Any byte kills the connection.

  • console.log, process.stdout.write, print — banned.
  • Pipe subprocess stdout to stderr or /dev/null.
  • The boot-time global console.* patch redirects to stderr (see src/logger.ts). Don't disable it.

--help and --version are the ONLY commands that write to stdout. Both exit before the MCP transport is wired.

2. NEVER silence Zod-version overload errors with as any

@modelcontextprotocol/sdk 1.x uses zod/v4 internally. Two Zod copies break registerTool overloads. Fix: pnpm why zod shows one version. pnpm.overrides.zod pinned. Don't reach for as any.

3. Use registerTool, not .tool() shorthand

The legacy shorthand is removed in v2-alpha of the SDK. registerTool is the only one in v2.

4. Tool errors via isError: true, not throws

Recoverable failures (bad credits, rate limit, not found) → { isError: true, content: [...] }. Programmer bugs → throw, surface in stderr as -32603 InternalError.

The withErrorMapping wrapper in src/errors.ts handles this. Use it for every tool.

5. Cold-start budget < 2 s

CI asserts time node dist/bin.js --version < 2000ms. No network calls at module load. Lazy SDK init in tools, not at boot.

6. Bundle budget < 100 KB

CI asserts dist/bin.js < 100KB. If pino blows the budget, swap to the 30-line custom logger.

Build & test

pnpm install
pnpm typecheck
pnpm lint
pnpm test
pnpm build
pnpm inspector   # interactive MCP testing

Layout

src/
├── bin.ts          # entry: parse args, init logger + sdk, connect StdioServerTransport
├── server.ts       # createRendobarMcpServer factory
├── config.ts       # parseConfig: --flag → env → creds.json → fail
├── context.ts      # RendobarContext type
├── logger.ts       # JSON to stderr
├── errors.ts       # withErrorMapping using ApiError from @rendobar/sdk
├── paths.ts        # resolveSafe (realpath + roots check)
├── instructions.ts # SERVER_INSTRUCTIONS string
├── version.ts      # built-time injected
└── tools/
    ├── index.ts    # registerTools(server, ctx)
    ├── util.ts     # defineTool helper
    ├── uploads.ts  # upload_file
    ├── jobs.ts     # submit_job, get_job, list_jobs, cancel_job
    └── account.ts  # get_account

Auth chain

--api-key flag → RENDOBAR_API_KEY env → ~/.config/rendobar/credentials.json (CLI-written) → fail.

Conventions

  • Conventional commits — release-please reads them.
  • TDD — test before implementation for new tools.
  • One tool file per resource. Tools are pure objects via defineTool().
  • Reshape SDK responses to drop fields the LLM doesn't read. Token cost is real cost.
  • Honest annotations — destructive tools must declare destructiveHint: true.
  • Trust Zod-validated inputs. Don't double-validate inside handlers.
  • Errors flow through withErrorMapping. Never try/catch and swallow.

Don't

  • Don't reach for pino if it blows the bundle budget. Use the 30-line custom logger.
  • Don't add --debug-stdio, --toolsets, --read-only flags. Those are deferred until customers ask.
  • Don't add OAuth flow. The CLI handles auth (rb login writes the creds file).
  • Don't bundle MCPB (.mcpb) in v1. Deferred to v1.1.
  • Don't open PRs without running pnpm typecheck && pnpm test && pnpm build clean locally.