feat(mcp): add stdio host for local clients

This commit is contained in:
Aakash Kattelu 2026-08-29 13:31:44 -04:00
parent 82a92429b8
commit 5c43b7e7f4
5 changed files with 76 additions and 2 deletions

View File

@ -45,6 +45,7 @@ Every workspace-scoped tool takes a `workspace_id` argument. If you set `X-Honch
```
src/
index.ts # Worker entry point — parse config, delegate to MCP handler
stdio.ts # Local stdio host (bun run stdio)
server.ts # createServer() — registers all tools on an McpServer
config.ts # HonchoConfig, parseConfig(), createClientFactory()
types.ts # ToolContext, result helpers
@ -84,6 +85,25 @@ wrangler secret put HONCHO_API_URL
When `HONCHO_API_URL` is unset the Worker routes to `https://api.honcho.dev`,
so this change is backward-compatible.
## Local stdio
For a local Honcho instance, or any MCP client that spawns a process, run the
stdio host instead of the Worker. Point the client at `src/stdio.ts` directly
`bun run stdio` writes lifecycle output to stdout and breaks the protocol.
```bash
cd mcp && bun install
claude mcp add honcho -- \
-e HONCHO_API_KEY=hch-your-key-here \
-e HONCHO_API_URL=http://127.0.0.1:28000 \
-e HONCHO_WORKSPACE_ID=my-workspace \
bun "$(pwd)/src/stdio.ts"
```
`HONCHO_API_URL` defaults to `https://api.honcho.dev`. `HONCHO_WORKSPACE_ID` is
optional; without it, pass `workspace_id` on each tool call.
## Development
### Setup

2
mcp/bunfig.toml Normal file
View File

@ -0,0 +1,2 @@
[loader]
".md" = "text"

View File

@ -11,6 +11,7 @@
"scripts": {
"preinstall": "node -e \"const ua=process.env.npm_config_user_agent||'';if(ua.includes('npm')&&!ua.includes('bun')){console.error('❌ Please use bun instead of npm!\\n📦 Run: bun install\\n🌐 Install bun: https://bun.sh/');process.exit(1)}\"",
"dev": "wrangler dev",
"stdio": "bun src/stdio.ts",
"deploy": "wrangler deploy",
"deploy:staging": "wrangler deploy --env staging"
},

View File

@ -3,7 +3,7 @@ import { Honcho } from "@honcho-ai/sdk";
export interface HonchoConfig {
apiKey: string;
baseUrl: string;
/** From X-Honcho-Workspace-ID when set. */
/** From X-Honcho-Workspace-ID (HTTP) or HONCHO_WORKSPACE_ID (stdio). */
workspaceId?: string;
}
@ -12,6 +12,12 @@ export interface Env {
ALERT_WEBHOOK_URL?: string;
}
export interface EnvConfig {
HONCHO_API_KEY?: string;
HONCHO_API_URL?: string;
HONCHO_WORKSPACE_ID?: string;
}
/**
* Parse configuration from request headers and Worker env bindings.
* Throws only when the Authorization bearer token is missing/empty.
@ -48,8 +54,23 @@ export function parseConfig(request: Request, env: Env = {}): HonchoConfig {
};
}
/** Parse configuration from process env for the stdio host. */
export function parseEnvConfig(env: EnvConfig): HonchoConfig {
const apiKey = env.HONCHO_API_KEY?.trim();
if (!apiKey) {
throw new Error(
"Missing HONCHO_API_KEY. Set HONCHO_API_KEY to your Honcho API key.",
);
}
return {
apiKey,
baseUrl: env.HONCHO_API_URL?.trim() || "https://api.honcho.dev",
workspaceId: env.HONCHO_WORKSPACE_ID?.trim() || undefined,
};
}
export const MISSING_WORKSPACE_ID_MESSAGE =
"Missing workspace_id. Pass workspace_id on the next tool call, or set the X-Honcho-Workspace-ID header on the connection so it is used automatically.";
"Missing workspace_id. Pass workspace_id on the next tool call, or set X-Honcho-Workspace-ID (HTTP) / HONCHO_WORKSPACE_ID (stdio).";
export function resolveWorkspaceId(
config: HonchoConfig,

30
mcp/src/stdio.ts Normal file
View File

@ -0,0 +1,30 @@
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
createClientFactory,
createUnscopedClient,
parseEnvConfig,
} from "./config.js";
import { createServer } from "./server.js";
declare const process: {
env: Record<string, string | undefined>;
exit(code?: number): never;
};
try {
const config = parseEnvConfig({
HONCHO_API_KEY: process.env.HONCHO_API_KEY,
HONCHO_API_URL: process.env.HONCHO_API_URL,
HONCHO_WORKSPACE_ID: process.env.HONCHO_WORKSPACE_ID,
});
const server = createServer({
config,
clientFor: createClientFactory(config),
unscoped: createUnscopedClient(config),
});
await server.connect(new StdioServerTransport());
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
console.error(message);
process.exit(1);
}