Skip to content

feat: support bearer tokens and OpenChamber sign-in - #34

Merged
AlaeddineMessadi merged 2 commits into
AlaeddineMessadi:mainfrom
Rahulsharma0810:feat/openchamber-auth
Oct 29, 2026
Merged

AlaeddineMessadi merged 2 commits into
AlaeddineMessadi:mainfrom
Rahulsharma0810:feat/openchamber-auth

Conversation

@Rahulsharma0810

Copy link
Copy Markdown
Contributor

Closes #33

Problem

OpenCode servers fronted by OpenChamber (proxied under /api) reject HTTP Basic auth, and opencode-mcp has no other way to authenticate.

Change

  • OPENCODE_SERVER_TOKEN: static bearer token sent as Authorization: Bearer <token>.
  • OPENCODE_OPENCHAMBER_PASSWORD (+ optional OPENCODE_OPENCHAMBER_URL, default: origin of OPENCODE_BASE_URL): on first use, POST /auth/session with issueClientToken: true, trustDevice: true and a per-host dedupeKey, so repeated sign-ins replace one client entry instead of piling up. The token stays in memory only. A 401 triggers one shared re-login and a single replay of the request. The rejected request never reached OpenCode, so writes can't be duplicated. Wrong-password (401) and rate-limit (429) responses produce clear errors that never include the password.
  • New src/auth.ts (AuthProvider) is passed to the SDK client as its fetch, so SDK calls, subscribeSSE and the /global/health probe (new optional authorization argument) all use one auth path. Precedence: OpenChamber > bearer token > Basic. Existing Basic setups behave the same, and their job-store scope hash doesn't change.

Request fields were checked against @openchamber/web 2.4.0 (server/lib/ui-auth/ui-auth.js, client-auth/remote-clients.js).

Validation

  • npm test: 18 files, 515 passed / 3 skipped. New tests/openchamber-auth.test.ts runs the real SDK transport against a local OpenChamber stand-in. It covers lazy login, the bearer header, renewal and body replay, concurrent renewal, no retry loop, bad password, 429, URL override, SSE, health probe and static token. There's also a new header-precedence test in server-manager.test.ts.
  • npm run docs:check, npm audit, git diff --check: clean.
  • Docs: README config table, docs/configuration.md (new OpenChamber section) and CHANGELOG [Unreleased].

Add OPENCODE_SERVER_TOKEN and OPENCODE_OPENCHAMBER_PASSWORD/URL so
opencode-mcp can reach OpenCode behind OpenChamber, which rejects Basic
auth. The client token is cached in memory and renewed once on 401.

Closes AlaeddineMessadi#33

@AlaeddineMessadi AlaeddineMessadi left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a useful addition. The shared authentication provider is a sensible place for this behavior, and the change adds no dependencies. I am requesting changes for the two integration issues described inline:

  1. [P1] Independent MCP clients on the same host replace each other's OpenChamber tokens. This is reproducible with OpenChamber v2.4.0's actual remote-client authentication runtime, including a concurrent request failing with 401 after its renewal replay.
  2. [P2] The actual event-polling tool loses the /api prefix. The new test manually includes the prefix, hiding an existing URL-joining bug that leaves the advertised OpenChamber event support incomplete.

Please also address this small integration omission:

  1. [P2] Forward the new credentials through the smoke runner. smokeEnvironment() in scripts/mcp-smoke-test.mjs:44-49 builds the child environment explicitly but only forwards OPENCODE_SERVER_USERNAME and OPENCODE_SERVER_PASSWORD. It drops OPENCODE_SERVER_TOKEN, OPENCODE_OPENCHAMBER_PASSWORD, and OPENCODE_OPENCHAMBER_URL, so the smoke child cannot authenticate using either new mode. Add the three settings to the existing allowlist and extend the existing environment test. I confirmed that all three are absent from the returned child environment even when supplied to the runner.

Validation at 060c918: build and docs checks pass; 515 tests pass and 3 skip with --maxWorkers=1. Earlier parallel runs hit timing failures. The base commit also passes with one worker. The findings above were checked with targeted local reproductions using the real SDK and, for token replacement, OpenChamber's actual v2.4.0 token-store implementation. A full live OpenChamber deployment was not tested.

Comment thread src/auth.ts Outdated
trustDevice: true,
clientLabel: `opencode-mcp (${host})`,
clientKind: "desktop",
dedupeKey: `opencode-mcp:${host}`,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Use a distinct sign-in key for independent MCP instances

dedupeKey is derived only from hostname(), so two MCP processes on the same computer send the same key to the same OpenChamber server. OpenChamber does not keep both tokens valid: a mint with the same key removes the previous client record (v2.4.0 implementation). Starting a second editor/MCP connection therefore invalidates the first one's cached token; renewals then invalidate the other connection in turn.

I reproduced this with the actual upstream remote-client runtime: starting client B invalidates A, and when both clients renew concurrently, one of the two requests still fails with HTTP 401 after its single replay. Please scope the key to an independent MCP instance and reuse that key for renewals within that instance. Add a two-client regression test whose login fixture actually replaces the previous token for a repeated key; the current fixture only adds tokens to a set and cannot catch this.

Comment thread tests/openchamber-auth.test.ts Outdated
it("authenticates event streams", async () => {
const client = openchamberClient();
const events = [];
for await (const event of client.subscribeSSE("/api/event")) events.push(event);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Exercise the event tool without manually adding /api

This test supplies /api/event, but the real opencode_events_poll handler calls subscribeSSE('/event') or subscribeSSE('/global/event'). With the documented base URL ending in /api, new URL(path, this.baseUrl) in src/client.ts:219 discards that prefix and requests /event or /global/event at the origin instead of OpenChamber's API proxy.

I reproduced failures through the registered tool for both project and global scope, while the explicitly prefixed control succeeds. The URL-joining behavior predates this PR, but the new OpenChamber integration needs it corrected to provide the event support described here. Please preserve the base URL prefix when constructing SSE URLs and add coverage through the actual event tool for both scopes.

- Scope the OpenChamber dedupeKey to a per-process instance ID
  (OPENCODE_OPENCHAMBER_CLIENT_ID override) instead of hostname alone,
  so independent MCP instances on the same host no longer replace
  each other's client token in OpenChamber v2.4.0's client store.
- Preserve the OPENCODE_BASE_URL path prefix (e.g. OpenChamber's /api
  proxy) when building SSE URLs in subscribeSSE, fixing event
  streaming through opencode_events_poll.
- Forward OPENCODE_SERVER_TOKEN, OPENCODE_OPENCHAMBER_PASSWORD,
  OPENCODE_OPENCHAMBER_URL and OPENCODE_OPENCHAMBER_CLIENT_ID through
  the smoke-test runner's child environment allowlist.
- Add regression tests: two independent OpenChamber clients with
  distinct dedupeKeys, concurrent renewal of both without spurious
  401s, event-poll tool exercised end-to-end for project and global
  scope, and smoke-environment forwarding of the new credentials.
- Update README, docs/configuration.md and CHANGELOG for the new
  OPENCODE_OPENCHAMBER_CLIENT_ID variable.
@Rahulsharma0810

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review! Addressed in 3d62cee:

  1. [P1] Independent instances replacing each other's tokens — dedupeKey is now opencode-mcp:<host>:<instanceId>, where instanceId is <pid>-<randomUUID> generated once per provider (stable across renewals, unique per process). New optional OPENCODE_OPENCHAMBER_CLIENT_ID overrides it when instances should intentionally share one client record. Added a test with two independent providers against a mock that revokes the previous token on dedupeKey reuse (OpenChamber v2.4.0 behaviour).
  2. [P2] Event polling losing the /api prefix — URL joining now appends to the base URL string instead of new URL('/event', baseUrl), preserving the base path for both per-directory and global events. The test now goes through the real code path instead of hand-building the prefixed URL.
  3. [P2] Smoke runner env — smokeEnvironment() now forwards OPENCODE_SERVER_TOKEN, OPENCODE_OPENCHAMBER_PASSWORD, OPENCODE_OPENCHAMBER_URL and OPENCODE_OPENCHAMBER_CLIENT_ID; extended the existing env test.

README, docs/configuration.md and CHANGELOG updated. npm run build, tsc --noEmit, and npm test (520 passed, 3 skipped) all pass.

@AlaeddineMessadi AlaeddineMessadi left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three requested changes are addressed. Approving.

@AlaeddineMessadi

Copy link
Copy Markdown
Owner

Thank you so much, @Rahulsharma0810! 🎉

I re-checked all three points from my review and each one is fixed properly:

  • The per-instance dedupeKey (<pid>-<uuid>), with OPENCODE_OPENCHAMBER_CLIENT_ID as an opt-in override, is a nice solution. Independent clients no longer revoke each other's tokens, and teams that want a shared client record can still have one.
  • Event polling now keeps the /api base path, and the test now exercises the real code path.
  • The smoke runner forwards the new credentials.

Build, typecheck, the full test suite, docs check and CI are all green, and it merges cleanly. I also appreciate the care that went into the details: the token is kept in memory only, the rejected request is replayed only once, error messages never include the password, and the docs and changelog are updated. That made this easy to review.

Merging now. This will be in the next release.

A heads-up on what comes next: #35 ports the server to the OpenCode 2.x /api surface, so the auth layer will need to fit into that. I'll handle the integration when the two meet. If you run OpenChamber in front of OpenCode 2.x, your feedback (or a follow-up PR) would be really welcome there.

Thanks again for contributing, and I hope to see more from you!

@AlaeddineMessadi
AlaeddineMessadi merged commit 5a3ea80 into AlaeddineMessadi:main Oct 29, 2026
4 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.

Support OpenChamber authentication (Bearer client token) for remote OpenCode servers

2 participants