Skip to content

fix(mcp/oauth): coerce client_id_issued_at to number for Postgres - #381

Open
10x-smitty wants to merge 1 commit into
CoreBunch:mainfrom
10x-smitty:fix/oauth-client-id-issued-at-pg-bigint
Open

fix(mcp/oauth): coerce client_id_issued_at to number for Postgres#381
10x-smitty wants to merge 1 commit into
CoreBunch:mainfrom
10x-smitty:fix/oauth-client-id-issued-at-pg-bigint

Conversation

@10x-smitty

Copy link
Copy Markdown

Problem

Adding the Instatic MCP connector to a client that validates the RFC 7591 registration response fails on Postgres deployments:

SDK auth failed: expected number, received string
path: ["client_id_issued_at"]

The registration endpoint returns the timestamp quoted:

{"client_id":"imcp_client_...","client_id_issued_at":"1786813381", ...}

Cause

ai_mcp_oauth_clients.client_id_issued_at is declared bigint (server/db/migrations-pg.ts:1085), and the driver returns int8 as a string to avoid precision loss. OAuthClientRow typed the field as number, so rowToClient passed the raw string through to the registration response unchanged.

SQLite declares the same column as integer (server/db/migrations-sqlite.ts:1149) and hands back a real number, so this is invisible in the default SQLite deployment — only Postgres installs are affected.

Fix

Type the row field as number | string and coerce in the mapper. This matches the existing convention for bigint columns elsewhere in the codebase:

  • server/repositories/mediaAssetMapping.ts:59,154size_bytes: number | stringNumber(row.size_bytes)
  • server/ai/conversations/store.ts:41,82prompt_tokens_total: number | stringtoNumber(...)

Testing

Added a case to server/ai/mcp/oauth/store.test.ts. The existing harness is in-memory SQLite and therefore cannot reproduce a Postgres-only bug, so the test stubs a DbClient returning a Postgres-shaped row and asserts findOAuthClient reads it back as a number.

Verified it fails without the fix:

expect(received).toBe(expected)
Expected: 1786813381
Received: "1786813381"

and passes with it. bun test server/ai/mcp/ is green (79 pass, 0 fail), as are tsc -b and eslint on the changed files.

Reproduced and verified against a Postgres deployment built from compose.prod.yml + compose.build.yml.

ai_mcp_oauth_clients.client_id_issued_at is `bigint` in the Postgres
schema, and the driver returns int8 as a string to avoid precision loss.
OAuthClientRow typed the field as `number`, so rowToClient passed the raw
string straight through to the dynamic client registration response.

RFC 7591 requires client_id_issued_at to be a number, so strict clients
reject the payload. Claude Code fails to add the connector with:

    SDK auth failed: expected number, received string
    path: ["client_id_issued_at"]

SQLite declares the column as `integer` and returns a real number, so
this only affects Postgres deployments.

Matches the existing convention for bigint columns elsewhere in the
codebase, e.g. mediaAssetMapping.ts and ai/conversations/store.ts, which
type the row field as `number | string` and coerce in the mapper.
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.

1 participant