feat(mcp): read HONCHO_API_URL env var to support self-hosted Honcho (#575)

The MCP Worker hardcoded https://api.honcho.dev for every request, forcing
anyone running a self-hosted Honcho instance to patch the source before
deploying their own Worker alongside it.

Route the baseUrl through the Worker env so operators can set
HONCHO_API_URL (via .dev.vars for local development or wrangler secret for
deployed Workers) and point the Worker at their instance. The variable is
intentionally not exposed as a request header: that would let public
clients steer traffic to internal URLs, which is a latency and security
regression.

When HONCHO_API_URL is unset, the Worker falls back to
https://api.honcho.dev, so existing deployments are unaffected.

Closes #508
This commit is contained in:
Alfredo Colangelo 2026-04-20 22:13:16 +02:00 committed by GitHub
parent 3dbf0e66fc
commit 51497f174f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 43 additions and 6 deletions

4
mcp/.dev.vars.example Normal file
View File

@ -0,0 +1,4 @@
# Uncomment and point at a self-hosted Honcho to bypass the managed API.
# wrangler dev reads this file automatically when it exists as `.dev.vars`.
# For deployed Workers, use: wrangler secret put HONCHO_API_URL
# HONCHO_API_URL=http://127.0.0.1:28000

1
mcp/.gitignore vendored
View File

@ -12,6 +12,7 @@ dist/
.env
.env.local
.env.production
.dev.vars
# IDE files
.vscode/

View File

@ -69,6 +69,28 @@ Built on:
- **[@modelcontextprotocol/sdk](https://www.npmjs.com/package/@modelcontextprotocol/sdk)** — `McpServer` for tool registration
- **[@honcho-ai/sdk](https://www.npmjs.com/package/@honcho-ai/sdk)** v2 — Honcho TypeScript SDK
## Self-Hosted Honcho
If you run Honcho yourself (for privacy, latency, or offline use), deploy the
MCP Worker alongside your instance and set `HONCHO_API_URL` in its
environment.
**Local dev (`bun run dev`):** create `mcp/.dev.vars`:
```
HONCHO_API_URL=http://127.0.0.1:28000
```
**Deployed Worker:**
```bash
wrangler secret put HONCHO_API_URL
# paste your URL when prompted
```
When `HONCHO_API_URL` is unset the Worker routes to `https://api.honcho.dev`,
so this change is backward-compatible.
## Development
### Setup

View File

@ -8,11 +8,21 @@ export interface HonchoConfig {
workspaceId: string;
}
export interface Env {
HONCHO_API_URL?: string;
}
/**
* Parse configuration from request headers.
* Parse configuration from request headers and Worker env bindings.
* Throws on missing required fields so callers get clear errors.
*
* The Honcho API URL is read from the `HONCHO_API_URL` env var when set,
* allowing operators to run this Worker alongside a self-hosted Honcho
* instance (see the "Self-Hosted Honcho" section in README.md). It is
* intentionally not exposed as a request header: routing public requests
* to an internal URL would be a latency and security regression.
*/
export function parseConfig(request: Request): HonchoConfig {
export function parseConfig(request: Request, env: Env = {}): HonchoConfig {
const authHeader = request.headers.get("Authorization");
const trimmedAuthHeader = authHeader?.trim();
if (!trimmedAuthHeader?.startsWith("Bearer ")) {
@ -37,7 +47,7 @@ export function parseConfig(request: Request): HonchoConfig {
apiKey,
userName,
assistantName: request.headers.get("X-Honcho-Assistant-Name")?.trim() || "Assistant",
baseUrl: "https://api.honcho.dev",
baseUrl: env.HONCHO_API_URL?.trim() || "https://api.honcho.dev",
workspaceId: request.headers.get("X-Honcho-Workspace-ID")?.trim() || "default",
};
}

View File

@ -1,5 +1,5 @@
import { createMcpHandler } from "agents/mcp";
import { parseConfig, createClient } from "./config.js";
import { parseConfig, createClient, type Env } from "./config.js";
import { createServer } from "./server.js";
const CORS_ORIGIN = "*";
@ -16,7 +16,7 @@ const CORS_HEADERS = {
export default {
async fetch(
request: Request,
env: unknown,
env: Env,
executionCtx: ExecutionContext,
): Promise<Response> {
if (request.method === "OPTIONS") {
@ -25,7 +25,7 @@ export default {
let config;
try {
config = parseConfig(request);
config = parseConfig(request, env);
} catch (e) {
const message =
e instanceof Error ? e.message : "Invalid request";