Skip to content

fix(binding-llm): llm server options.authorization guard support - #2591

Merged
jfallows merged 2 commits into
developfrom
claude/wonderful-cerf-k273d3
Sep 30, 2026
Merged

jfallows merged 2 commits into
developfrom
claude/wonderful-cerf-k273d3

Conversation

@jfallows

@jfallows jfallows commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Description

Wires llm server into the existing options.authorization convention (guard name + credentials template) to validate inbound request credentials, matching binding-mcp's server-side pattern (options.authorization on the mcp(server) binding, McpOptionsConfigAdapter/McpServerFactory).

  • Added LlmAuthorizationConfig/LlmAuthorizationConfigBuilder and wired authorization into LlmOptionsConfig/LlmOptionsConfigBuilder/LlmOptionsConfigAdapter, mirroring McpAuthorizationConfig's shape (guard name as JSON key, credentials template defaulting to Bearer {credentials}).
  • Extended llm.schema.patch.json with options.authorization under the kind: server branch, following the same patternProperties-by-guard-name shape binding-mcp uses.
  • Added LlmDialect.credentialsHeader() and LlmDialect.unauthorizedBody() extension points (default methods), overridden per dialect: LlmOpenaiDialect expects credentials in authorization and returns an OpenAI-shaped invalid_api_key error body; LlmAnthropicDialect expects x-api-key and returns an Anthropic-shaped authentication_error body.
  • LlmBindingConfig resolves the configured guard and compiles the credentials template into a matcher (same idiom as McpBindingConfig), exposing authorize(...) to check a request's dialect-selected header against it and call GuardHandler.reauthorize/deauthorize directly — no new guard mechanism.
  • LlmServerFactory calls authorize(...) once the dialect is resolved; a rejected request never reaches app0 and instead gets a 401 response shaped in the resolved dialect's own JSON error envelope via a new LlmUnauthorizedResponder. An accepted request's guard session is deauthorized when the stream closes.

Test plan

  • Config adapter unit tests: extended LlmOptionsConfigAdapterTest with read/write coverage for options.authorization (default and explicit credentials templates); added LlmAuthorizationConfig/Builder factory coverage (builder()/inject()) to LlmOptionsConfigTest for 100% instruction coverage.
  • k3po IT coverage mirroring binding-mcp's authorization IT pattern — new paired client.rpt/server.rpt scenarios under incubator/binding-llm.spec for both dialects: openai.request.guarded, anthropic.request.guarded (accept), openai.request.rejected.authorization, anthropic.request.rejected.authorization (reject), plus a server.guarded.yaml fixture using type: test guard per repo convention.
  • New scenarios added to NetworkIT (peer-to-peer self-consistency, required per specs/AGENTS.md) and LlmServerIT (live-engine).
  • ./mvnw verify -pl incubator/binding-llm — full module green including LlmClientIT (its earlier failures were fixed upstream in fix(binding-llm): coherent, extensible dialect schema for cross-dialect translation #2590).
  • ./mvnw verify -pl incubator/binding-llm.spec -Dit.test=NetworkIT,ApplicationIT — 26/26 and 19/19 pass.
  • ./mvnw checkstyle:check / license:check — clean on changed files.

Fixes #2498


🤖 Generated with Claude Code

https://claude.ai/code/session_017zzRpze4nXEv4dbNjkRSVk


Generated by Claude Code

Wires `llm server` into the options.authorization convention (guard name +
credentials template), mirroring binding-mcp's server-side pattern. The
dialect resolved for a request selects which header carries credentials
(Authorization for openai, x-api-key for anthropic) via a new
LlmDialect.credentialsHeader() extension point; a request whose header
doesn't match the configured template, or whose extracted token the guard
rejects, is turned away with a 401 response shaped in the resolved
dialect's own JSON error envelope (LlmDialect.unauthorizedBody()).

Adds LlmAuthorizationConfig/Builder (config surface), LlmBindingConfig
guard resolution + authorize(), and LlmServerFactory's
LlmUnauthorizedResponder for the reject path, plus config adapter tests
and IT coverage (accept/reject, both dialects) mirroring binding-mcp's
authorization IT pattern.

Fixes #2498

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017zzRpze4nXEv4dbNjkRSVk
jacoco flagged binding-llm.conf at 0.97 instruction coverage (rule
requires 1.00): LlmAuthorizationConfig.builder() (no-arg) and
LlmAuthorizationConfigBuilder.thisType() were never exercised, since
every existing usage goes through LlmOptionsConfigBuilder.authorization()
(the mapper-taking overload). Adds the same builder()/inject() coverage
pair LlmOptionsConfigTest already uses for LlmOptionsConfig and
LlmServerConfig.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017zzRpze4nXEv4dbNjkRSVk
@jfallows
jfallows force-pushed the claude/wonderful-cerf-k273d3 branch from 986ec91 to 8ebc29c Compare September 30, 2026 14:16
@jfallows
jfallows merged commit 01e4c0d into develop Sep 30, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

llm server: options.authorization guard support (inbound)

2 participants