fix(mcp): require the auth choice startMcpHttpServer serves under - #933
Merged
Merged
Conversation
`token` was optional with no default, so a host that omitted it published an
unauthenticated endpoint that executes every registered task — and nothing
logged or threw, because `authorizeBearer(header, undefined)` allows the
request without reading the header, which is also the right answer for a
deliberate opt-out. A wildcard bind made it worse: `resolveAllowedHosts`
returns no `Host` allow-list for one, so `{ port, host: "0.0.0.0",
createServer }` type-checked and served task execution to the network with
neither a token nor a rebinding guard.
`token` is now required and `string | null`: omitting it is a type error, an
untyped caller is refused at run time, and `null` — the written opt-out — is
refused outright together with a wildcard bind, where nothing else is left to
decide who may run a task.
BREAKING CHANGE: `StartMcpHttpServerArgs.token` is required. Pass the token, or
`null` to serve unauthenticated on a named interface.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T8DU24G9GWuRUJncc5TJFZ
sroussey
added a commit
that referenced
this pull request
Sep 13, 2026
## @workglow/task-graph ### Features #### task-graph,ai - a tool call's whole life on the wire (#936) ## @workglow/web-search ### Bug Fixes #### web-search - reduce domain entries on every route, not just site: (#931) ## @workglow/ai ### Features #### task-graph,ai - a tool call's whole life on the wire (#936) #### ai - AgentTask: the tool-calling turn loop as a task, with the CLI and its console on it (#935) ### Bug Fixes #### pricing - stop reporting an unpriced rate as free (#930) ## workglow ### Updated Dependencies - `tslog`: ^5.2.0 ## @workglow/storage ### Bug Fixes #### storage - decide the join pushdown on enlistment, not the instance flag (#934) ## @workglow/mcp ### Breaking Changes - **bug fixes(mcp)**: require the auth choice startMcpHttpServer serves under (#933) ### Bug Fixes #### mcp - require the auth choice startMcpHttpServer serves under (#933) ## @workglow/test ### Features #### task-graph,ai - a tool call's whole life on the wire (#936) #### ai - AgentTask: the tool-calling turn loop as a task, with the CLI and its console on it (#935) ### Bug Fixes #### storage - decide the join pushdown on enlistment, not the instance flag (#934) #### pricing - stop reporting an unpriced rate as free (#930) ### Updated Dependencies - `miniflare`: ^5.20260911.0-alpha ## @workglow/postgres ### Bug Fixes #### storage - decide the join pushdown on enlistment, not the instance flag (#934) ## @workglow/openrouter ### Bug Fixes #### pricing - stop reporting an unpriced rate as free (#930) ## @workglow/eval ### Updated Dependencies - `hyparquet`: ^1.30.1 ## @workglow/cli ### Breaking Changes - **bug fixes(mcp)**: require the auth choice startMcpHttpServer serves under (#933) ### Features #### ai - AgentTask: the tool-calling turn loop as a task, with the CLI and its console on it (#935) ### Bug Fixes #### mcp - require the auth choice startMcpHttpServer serves under (#933) ## @workglow/web ### Updated Dependencies - `react-dom`: ^19.3.0 - `tailwind-merge`: ^3.7.0 - `@types/react-dom`: ^19.3.0 - `vite`: ^8.3.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
StartMcpHttpServerArgs.tokenwasreadonly token?: string | undefined— optional, with no runtime default and no warning.authorizeBearer(header, undefined)returns "allowed" without reading the header (bearerAuth.ts:87). That is the correct answer for an explicit opt-out, and it is also exactly what a plain omission produces. Nothing logged, nothing threw.The exposure is not abstract.
createTaskMcpServerpublishes every registered task outsideFlow Control/Hiddenas a callable tool, which for a typical host includesFetchUrlTask,JavaScriptTask, andFileGrepTask/FileSedTask/FileLoaderTaskunderregisterCommonTasks({ fileSystemTasks: true }).resolveAllowedHostsalso returnsundefined(noHostcheck at all) for a wildcard bind with noallowedHosts— deliberately, since nothing about0.0.0.0names the addresses a machine answers on. Combined with the optional token, this type-checked and ran:…serving task execution to the network with neither a token nor a DNS-rebinding guard.
The concrete failure
A downstream host of
@workglow/mcp/server— builder and embarc are the two named consumers — writesawait startMcpHttpServer({ port, host, createServer }), forgetstoken, and ships. Every request is authorized.workglow mcp servegets this right (examples/cli/src/commands/mcpServe.tsgenerates a token and requires--no-authto drop it), so the "requires a bearer token by default" property held for exactly one caller — not for the downstream hosts the library exists to serve.What changed
StartMcpHttpServerArgs.tokenis nowreadonly token: string | null. Omitting it is a type error;nullis the written opt-out.nullreads the same as before at the request layer (the handle'stokenand the internal request context staystring | undefined, soauthorizeBeareris untouched).assertAuthChoice(token, host), called before the listener is created:token === undefinedthrows. Unreachable through the types, checked anyway — this is a published package and an untyped caller still arrives with the field omitted, which used to mean "serve unauthenticated".token === nulltogether with a wildcard bind ("",0.0.0.0,::,[::]) throws. A wildcard is reachable under every name the machine answers to andresolveAllowedHostsderives noHostallow-list from it, so nothing at all would be left deciding who may run a task. Binding a named interface unauthenticated is still allowed — that is the deliberate escape, and it is stated in the error message.examples/cli'sresolveServeTokenreturnsstring | null;--no-authreachesnullinstead ofundefined. No behavior change for the CLI.@workglow/mcp/serverparagraph in.claude/CLAUDE.mdupdated, since the contract they describe changed.Breaking change
This is a breaking API change to a published package.
StartMcpHttpServerArgs.tokenis required. Callers pass their token, ornullto serve unauthenticated on a named interface. The only in-repo caller (examples/cli) is updated here; downstream hosts will get a compile error, which is the point.Verified
Run in a clean worktree after
bun install, withbun run use-dist(realdist) for the type checks:bunx vitest run packages/mcp/src/server/__tests__/— 62 passed.null → undefinednormalization — 4 failed / 17 passed. With the fix, all pass.bunx vitest run examples/cli/src/commands/mcpServe.test.ts— 8 passed.bunx tsc --noEmitinpackages/mcp(src) andpackages/mcp -p tsconfig.test.json(tests) andexamples/cli— all clean.bunx oxlint --type-aware packages/mcp/src examples/cli/src/commands/— clean.bunx oxfmt --checkon the touched files — clean. (examples/cli/src/web/client/index.htmlreports a pre-existing format issue onmain; untouched here.)Not verified here
🤖 Generated with Claude Code
https://claude.ai/code/session_01T8DU24G9GWuRUJncc5TJFZ
Generated by Claude Code