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:
parent
3dbf0e66fc
commit
51497f174f
|
|
@ -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
|
||||
|
|
@ -12,6 +12,7 @@ dist/
|
|||
.env
|
||||
.env.local
|
||||
.env.production
|
||||
.dev.vars
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
Loading…
Reference in New Issue