Skip to content

Kizuna's MCP server supports three authentication modes for inbound requests from MCP clients (Claude Desktop, Cursor, Opencode, etc.):

ModeTransportUse Case
OAuth 2.1Streamable HTTPProduction, multi-user, auto-discovery
Static tokenstdio / HTTPSelf-hosted, single-user, development
Nonelocalhost HTTP onlyLocal development

How It Works ​

MCP clients authenticate automatically using the OAuth 2.1 discovery flow (RFC 9728):

  1. Client sends a request without a token
  2. Server returns 401 with WWW-Authenticate header pointing to discovery metadata
  3. Client fetches /.well-known/oauth-protected-resource to discover the authorization server
  4. Client performs OAuth Authorization Code + PKCE flow via browser
  5. Client receives a JWT and uses it for all subsequent requests

Zero configuration required on the client side — the discovery protocol handles everything.

Setup with WorkOS AuthKit ​

WorkOS AuthKit is the recommended OAuth provider. It handles user authentication, SSO, and token issuance.

Prerequisites ​

  1. Create a WorkOS account and project
  2. Note your AuthKit domain (e.g., https://your-project.authkit.app)
  3. Enable Client ID Metadata Documents (CIMD) in Dashboard > Connect > Configuration
  4. Optionally enable Dynamic Client Registration (DCR) for older MCP clients

Configure Kizuna MCP Server ​

toml
# ~/.config/kizuna-mcp/config.toml

kizuna_url = "https://kizuna.example.com"  # your Kizuna instance
api_token = "kzn_service_..."  # Your Kizuna backend API token

[http]
bind = "0.0.0.0"  # Allow remote connections
port = 3100

[auth]
mode = "oauth"
resource_uri = "https://mcp.your-domain.com"

[auth.oauth]
issuer = "https://your-project.authkit.app"
audience = "https://mcp.your-domain.com"
strict_audience = false  # AuthKit doesn't support RFC 8707 audience binding

Or via environment variables:

bash
export KIZUNA_AUTH_MODE=oauth
export KIZUNA_AUTH_ISSUER=https://your-project.authkit.app
export KIZUNA_AUTH_AUDIENCE=https://mcp.your-domain.com
export KIZUNA_AUTH_RESOURCE_URI=https://mcp.your-domain.com

Start the Server ​

bash
kizuna-mcp serve-http --bind 0.0.0.0 --port 3100

Connect from MCP Clients ​

MCP clients auto-discover authentication. Just point them at your server:

Claude Desktop / Claude Code:

json
{
  "mcpServers": {
    "kizuna": {
      "url": "https://mcp.your-domain.com/mcp"
    }
  }
}

Cursor: Add the server URL in Settings > MCP Servers.

The client will handle the OAuth flow automatically — a browser window opens for consent on first use.

WorkOS Client Credentials (CI/CD, Agents) ​

For machine-to-machine authentication (CI/CD pipelines, automated agents) without a browser flow:

Prerequisites ​

  1. In WorkOS Dashboard, create an API key with client credentials grant
  2. Note the client_id and client_secret

Token Request ​

bash
curl -X POST https://your-project.authkit.app/oauth2/token \
  -d grant_type=client_credentials \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET \
  -d scope="kizuna:repos:read kizuna:issues:read"

The returned JWT will be classified as a ServiceAccount in Kizuna's audit logs (distinct from human OAuth tokens).

Scopes ​

Request only the scopes your automation needs:

ScopeAccess
kizuna:repos:readList and view repositories
kizuna:repos:writeCreate repositories
kizuna:issues:readList and view issues
kizuna:issues:writeCreate and close issues
kizuna:prs:readList and view pull requests
kizuna:prs:writeOpen, close, and merge PRs
kizuna:agents:readList and view agents
kizuna:agents:writeSpawn and assign agents
kizuna:adminAdministrative operations

Insufficient scopes return 403 Forbidden with a WWW-Authenticate header indicating what's needed:

http
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope", scope="kizuna:repos:write"

Generic OAuth 2.1 AS Setup ​

Any OAuth 2.1 authorization server that supports RS256 JWT signing works. Requirements:

  • Serves /.well-known/oauth-authorization-server metadata (RFC 8414)
  • Includes jwks_uri in metadata pointing to a JWKS endpoint
  • Issues RS256-signed JWTs with kid header
  • Supports Authorization Code + PKCE grant

Configuration ​

toml
[auth]
mode = "oauth"
resource_uri = "https://mcp.your-domain.com"

[auth.oauth]
issuer = "https://your-auth-server.com"
audience = "https://mcp.your-domain.com"
strict_audience = true  # Enable if your AS supports RFC 8707
max_token_lifetime_secs = 300  # Reject tokens valid for longer than 5 minutes
clock_skew_tolerance_secs = 60

Token Claims ​

Kizuna classifies tokens based on these JWT claims:

ClaimPresentIdentity Type
subYesOAuth (human user)
client_id (no sub)YesServiceAccount (M2M)
Neither--Rejected

Additional claims used: email, scope (space-delimited), exp, iat, nbf, iss, aud.

Static Token Mode ​

For stdio transport (Claude Desktop local servers) or simple self-hosted deployments:

toml
[auth]
mode = "static"

[auth.static_token]
token = "your-secret-token-here"

Or via environment:

bash
export KIZUNA_MCP_TOKEN=your-secret-token-here

Static tokens bypass scope enforcement — any authenticated request can access all tools.

Migration from Static Tokens to OAuth ​

  1. Keep static tokens working — set mode = "static" initially. Existing clients continue to work.
  2. Deploy with HTTP transport — run serve-http alongside stdio. OAuth clients connect via HTTP, static clients via stdio.
  3. Configure OAuth — add [auth.oauth] section. Set mode = "oauth".
  4. Test — verify MCP clients auto-discover and authenticate.
  5. Deprecate static tokens — once all clients support OAuth, remove [auth.static_token] section.

Audit Logging ​

All authentication events and tool invocations are logged as structured JSON to stderr:

json
{
  "timestamp": "2026-04-01T12:34:56.789Z",
  "event": "tool_call",
  "identity": "user:usr_123 (alice@example.com)",
  "auth_mode": "oauth",
  "tool": "kizuna/list_repositories",
  "arguments": {"org": "acme"},
  "result": "success",
  "duration_ms": 42
}

Event types: auth_success, auth_failure, tool_call, scope_denied, token_lifetime_exceeded

Sensitive arguments (token, password, secret, api_key) are automatically redacted.

Security Considerations ​

  • HTTPS required in production. The server rejects http:// resource URIs unless explicitly allowed.
  • Origin validation — configure allowed_origins to prevent DNS rebinding attacks.
  • Token lifetime — max_token_lifetime_secs (default 300s) rejects long-lived tokens for revocation protection.
  • No token forwarding — client OAuth tokens are never forwarded to the Kizuna backend API.
  • Static tokens hashed — raw tokens are never stored or logged, only SHA-256 hashes.

Configuration Reference ​

Environment Variables ​

VariableMaps toDefault
KIZUNA_AUTH_MODEauth.modestatic
KIZUNA_AUTH_ISSUERauth.oauth.issuer--
KIZUNA_AUTH_AUDIENCEauth.oauth.audience--
KIZUNA_AUTH_RESOURCE_URIauth.resource_uri--
KIZUNA_MCP_TOKENauth.static_token.token--
KIZUNA_AUTH_STRICT_AUDIENCEauth.oauth.strict_audiencefalse
KIZUNA_AUTH_ALLOWED_ORIGINSauth.allowed_origins--
KIZUNA_AUTH_CLOCK_SKEW_SECSauth.oauth.clock_skew_tolerance_secs60
KIZUNA_AUTH_MAX_TOKEN_LIFETIMEauth.oauth.max_token_lifetime_secs300

Discovery Endpoint ​

GET /.well-known/oauth-protected-resource

Returns:

json
{
  "resource": "https://mcp.your-domain.com",
  "authorization_servers": ["https://your-project.authkit.app"],
  "scopes_supported": ["openid", "profile", "email", "kizuna:repos:read", "..."],
  "bearer_methods_supported": ["header"]
}