JCI-MCP uses Bio-ID as its authorization server. All identity — bioId, orgSlug, and
roles — flows from a Bio-ID JWT. That JWT drives which JCI role a user gets, which
pipeline audiences they can access, and how jci-brain scopes returned data.
There are two credential paths:
| Path | Credential | Use case |
|---|---|---|
| OAuth popup | Bio-ID Bearer JWT (auto-managed) | claude.ai, Cursor, Windsurf, any MCP-OAuth client |
| JCI API key | jci_* static key | Claude Code stdio, CLI, programmatic tools |
MCP clients that implement the MCP OAuth spec discover the authorization server automatically via two well-known endpoints that jci-mcp exposes:
GET /.well-known/oauth-protected-resource
→ { authorization_servers: ["https://bio.tawa.pro"], bearer_methods_supported: ["header"], ... }
GET /.well-known/oauth-authorization-server
→ { issuer: "https://bio.tawa.pro",
authorization_endpoint: "https://bio.tawa.pro/oauth/authorize",
token_endpoint: "https://bio.tawa.pro/api/oauth/token",
code_challenge_methods_supported: ["S256"], ... }
https://mcp.askjefferson.com (current: https://jefferson.daryx.com)/.well-known/oauth-protected-resource → discovers Bio-ID is the AS/.well-known/oauth-authorization-serverAuthorization: Bearer <bio-id-jwt>The user does not configure any client ID or secret manually. The MCP client handles the full PKCE flow with Bio-ID directly.
On each request, jci-mcp's bioAuthMiddleware verifies the Bearer token against Bio-ID's
JWKS endpoint (https://bio.tawa.pro/.well-known/jwks.json). The JWKS response is cached
and refreshed automatically on key rotation. No shared secret is needed — tokens are RS256.
Request arrives with Authorization: Bearer <jwt>
→ bioAuthMiddleware verifies via JWKS
→ extracts { bioId, orgSlug, roles, email } from JWT payload
→ sets req.userContext
→ request proceeds to tool handler
JCI API keys are generated from the askJefferson admin portal. They carry a pre-assigned role and pipeline audience set and are stored in jci-brain as SHA-256 hashes (the raw key is shown once at generation time).
Key format: jci_<12-char-prefix>_<36-char-random>
The key is sent in the Authorization header:
Authorization: Bearer jci_abc123456789_<rest-of-key>
Or in the x-api-key header:
x-api-key: jci_abc123456789_<rest-of-key>
jci-brain detects the jci_ prefix and routes to API key verification instead of JWT
verification. The key is hashed on arrival and looked up against the jci_api_keys
collection. Each key stores:
bio_id — the Bio-ID of the key ownerrole — the JCI role (developer, devops, business, admin)pipeline_audiences — the audiences this key can accesspersona_id — which persona (context profile) this key belongs tolast_used — updated on each successful verificationdecoded.sub (or decoded.bioId) → jciUser.bio_id
decoded.orgSlug → jciUser.org_slug (company scoping)
decoded.roles[] → mapped to JCI role (see table below)
decoded.email → jciUser.email
After JWT verification, jci-brain looks up the user in jci_users by bio_id to find
their active persona. The persona carries the definitive role and pipeline_audiences
for this session. If no persona exists yet, defaults to role: business /
pipeline_audiences: ['business', 'public'].
Role and pipeline audiences are stored directly on the key record — no persona lookup
needed. Identity is the key's bio_id.
| Bio-ID roles | JCI role | Pipeline audiences |
|---|---|---|
super_admin, admin | admin | business, developer, infra, internal, public |
developer | developer | developer, public |
devops, infra | devops | developer, infra, public |
| (all others / default) | business | business, public |
The mapping is performed by jci-brain's authMiddleware._verifyBioJwt. Note: for Bio-ID JWT
logins, persona_id is set to null — the persona system is defined in jci_users but the
middleware does not currently perform a persona lookup on JWT authentication. Persona role
overrides are only active for JCI API key sessions (where persona_id is stored on the key record).
Every data record in jci-brain carries one or more pipeline_audiences tags. Queries are
filtered to only return records whose audience overlaps with the authenticated user's
pipeline_audiences.
| JCI role | Sees |
|---|---|
admin | All data (business + developer + infra + internal + public) |
developer | Developer + public data |
devops | Developer + infra + public data |
business | Business + public data |
This means a business user querying pipeline logs will only see records tagged
business or public — they cannot see developer build logs or infra metrics.
| Variable | Required by | Purpose |
|---|---|---|
BIO_ID_URL | jci-mcp, jci-brain | Bio-ID base URL. Default: https://bio.tawa.pro |
MCP_SERVER_URL | jci-mcp | Included in oauth-protected-resource response. Current: https://jefferson.daryx.com. Target: https://mcp.askjefferson.com |
BIO_AUTH_REQUIRED | jci-mcp | Set true to reject requests without a valid JWT (disables legacy fallback) |
OWNER_BIO_ID | jci-brain | bio_id that gets is_owner: true (admin bypass) |
https://mcp.askjefferson.com (current: https://jefferson.daryx.com)In .cursor/mcp.json or the MCP settings panel:
{
"mcpServers": {
"jci-mcp": {
"url": "https://mcp.askjefferson.com",
"transport": "http"
}
}
}
Cursor will pop the Bio-ID OAuth flow on first connection.
In ~/.claude/settings.json or the project .claude/settings.json:
{
"mcpServers": {
"jci-mcp": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://jci-mcp.tawa.pro/mcp"],
"env": {
"BEARER_TOKEN": "jci_your_api_key_here"
}
}
}
}
Or using environment headers with mcp-remote:
MCP_SERVER_HEADERS='{"Authorization":"Bearer jci_your_key"}' \
npx mcp-remote https://mcp.askjefferson.com
Claude Code in remote mode will follow the same OAuth popup flow as claude.ai when the server URL is used directly without a pre-configured key:
{
"mcpServers": {
"jci-mcp": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://jci-mcp.tawa.pro/mcp"]
}
}
}
jci-brain's authMiddleware checks credentials in this order:
jci_ → JCI API key pathx-api-key header starting with jci_ → JCI API key path/mcp/jci-brain/:username) → legacy fallbackx-bio-id, x-role) → localhost only, development modeIn production with BIO_AUTH_REQUIRED=true, steps 3-5 are rejected with 401.
BIO_ID_URL defaults to https://bio.tawa.pro — no env var needed in standard deployments.orgSlug from the JWT becomes the company_id for data scoping in jci-brain.iecHash field on InsureBio entities is the Vault chain address for insurance entities
— it is a separate concept from JCI user identity (bioId). Do not conflate the two.bio_id to carry different roles in different contexts (e.g., a user who
acts as both developer and business). The default persona is used unless a specific
persona_id is passed.x-api-key header → only jci_* keys are accepted thereiecHash (InsureBio Vault entity address) with bioId (JCI user identity)orgSlug in the JWT without the user having an org in Bio-IDBIO_AUTH_REQUIRED=true in dev before you have a JCI API key configuredinternalDependencies: bio-id in catalog-info.yaml to enable OAuth — wrong; use spec.auth: mode: ssoLast updated: July 15, 2026