Skip to main content

Python SDK

One package, two sides of the MCP auth protocol:

You are buildingYou needStart here
An MCP server that must be protectedToken validation + per-tool RBACProtect your MCP server
An agent/service that calls a protected serverA token — as itself (M2M) or on behalf of a userM2M auth · ID-JAG delegation

Install

pip install authsec-sdk

Latest release: 4.7.0 (PyPI) — everything in this section (the three M2M auth methods, AgentIdentity, ID-JAG delegation, the module layout) requires 4.7.0 or newer. Already installed? Upgrade with pip install -U authsec-sdk.

Requires Python ≥ 3.10.11. Fully typed (py.typed ships with the package).

Package layout (v4.7+)

The package is organized by function; everything commonly needed is also re-exported at the top level:

authsec_sdk/
├── identity/ # AGENT SIDE — AgentIdentity (M2M + ID-JAG), browser_login,
│ # ClientSecretAuth, PrivateKeyJwtAuth, SpiffeSvidAuth,
│ # SpiffeWorkloadIdentity
├── runtime/ # SERVER SIDE — mount_mcp, Config, from_env, validation,
│ # scope matrix, manifest, RFC 9728 metadata
├── client/ # typed errors for parsing AuthSec 401/403 responses
├── integrations/ # framework glue (LangGraph)
├── spiffe/ # SPIFFE Workload API — X.509-SVIDs, mTLS material
└── _legacy/ # original decorator API (back-compat only)

Old flat module paths (authsec_sdk.agent_identity, .core, .ciba_sdk, .delegation_sdk, .spire_sdk, .spiffe_workload_api) still work but emit a DeprecationWarning — they're removed in v5.

Protect a server: drop in the wrapper

from fastapi import FastAPI
from authsec_sdk import from_env, mount_mcp

app = FastAPI()
cfg = from_env()

mount_mcp(app, "/mcp", your_existing_handler, cfg)

mount_mcp registers two routes:

  • /mcp — your handler, wrapped: every request has its bearer token validated and tools/call checked against the scope policy you configured in AuthSec.
  • /.well-known/oauth-protected-resource/mcp — the RFC 9728 metadata that lets OAuth clients discover where to authenticate.

handler accepts a FastMCP instance (auto-detected), a plain async request handler, or any ASGI app via wrap_asgi_handler.

Full walkthrough with dashboard screenshots: Protect your MCP server — from pip install to a launched, protected application.

Call a server: get a token

Machine-to-machine (no user) — three interchangeable credential types:

from authsec_sdk import AgentIdentity, ClientSecretAuth, PrivateKeyJwtAuth, SpiffeSvidAuth

agent = AgentIdentity(ISSUER, CLIENT_ID, auth=ClientSecretAuth("sec_...")) # shared secret
agent = AgentIdentity(ISSUER, CLIENT_ID, auth=PrivateKeyJwtAuth("key.pem", kid="key-1")) # RFC 7523
agent = AgentIdentity(ISSUER, CLIENT_ID, auth=SpiffeSvidAuth(svid)) # SPIFFE SVID

async with agent:
token = await agent.access_for(MCP_URL, requested_scopes=["myapp:read"])

On behalf of a signed-in user (XAA / ID-JAG):

from authsec_sdk import AgentIdentity, browser_login

id_token = await browser_login(issuer=ISSUER, client_id=CLIENT_ID, resource=MCP_URL)
agent = AgentIdentity(ISSUER, CLIENT_ID, client_secret=SECRET, idp_issuer=ISSUER)
async with agent:
token = await agent.access_for(
MCP_URL,
user_session={"subject_token": id_token},
requested_scopes=["myapp:read"],
)
# token: sub = the user, act.client_id = this agent — auditable delegation

Guides: M2M auth (all three methods) · ID-JAG delegation

Configuration (server side)

All fields are documented inline in authsec_sdk.runtime.config.Config. The required ones for a production deploy:

FieldSourceNotes
issuerAuthSec base URLe.g. https://api.authsec.dev
authorization_serverAuthSec API originDefaults to issuer when omitted
jwks_urlJWKS endpointRequired for JWT validation modes
introspection_urlRFC 7662 endpointRequired for introspection validation modes
resource_server_idApplication detail panel, field idUUID
introspection_client_idSame value as resource_server_idUsername for Basic auth
introspection_client_secretOne-time secret from registrationStore in a secret manager
resource_uriApplication detail panel, field resource_urlMust match the aud claim of issued tokens
publish_manifestTrue recommendedPushes tool inventory at startup

Optional but useful:

FieldPurpose
policy_modeREMOTE_REQUIRED (default), REMOTE_WITH_LOCAL_FALLBACK, LOCAL_ONLY, OPEN.
validation_modeJWT_AND_INTROSPECT (default), JWT_ONLY, INTROSPECTION_ONLY, JWT_OR_INTROSPECT.
tool_scopesLocal fallback policy. Required if policy_mode=REMOTE_WITH_LOCAL_FALLBACK.
tool_scope_suggestionsPer-tool scope hints sent in the manifest payload.
scope_matrix_ttlHow long fetched policy is cached. Default 30 s — dashboard changes reach a running server within that window.

Environment-variable config

For container deployments, use from_env():

from authsec_sdk import from_env, mount_mcp

cfg = from_env() # reads AUTHSEC_* env vars
cfg.validate() # raises ValueError on misconfiguration

mount_mcp(app, "/mcp", your_handler, cfg)

Supported env vars (all prefixed AUTHSEC_):

AUTHSEC_ISSUER
AUTHSEC_AUTHORIZATION_SERVER
AUTHSEC_JWKS_URL
AUTHSEC_INTROSPECTION_URL
AUTHSEC_INTROSPECTION_CLIENT_ID
AUTHSEC_INTROSPECTION_CLIENT_SECRET
AUTHSEC_RESOURCE_URI
AUTHSEC_RESOURCE_NAME
AUTHSEC_RESOURCE_SERVER_ID
AUTHSEC_SUPPORTED_SCOPES # space-separated
AUTHSEC_POLICY_MODE # remote_required | remote_with_local_fallback | local_only | open
AUTHSEC_VALIDATION_MODE # jwt_and_introspect | jwt_only | introspection_only | jwt_or_introspect
AUTHSEC_PUBLISH_MANIFEST # true | false

Older aliases such as AUTHSEC_RESOURCE, AUTHSEC_JWKS_URI, AUTHSEC_INTROSPECTION_ENDPOINT, AUTHSEC_INTROSPECTION_ID, and AUTHSEC_INTROSPECTION_SECRET are accepted for compatibility, but new deploys should use the canonical names above.

Common scenarios

Run in observe-only mode

You want the SDK to validate tokens but not block anything while you map scopes. Use PolicyMode.OPEN:

from authsec_sdk import Config, PolicyMode

cfg = Config(
# ... regular fields ...
policy_mode=PolicyMode.OPEN,
)

Open mode logs every request, attaches the validated Principal to the request, but allows all tool calls regardless of scope. Switch back to REMOTE_REQUIRED once every tool is mapped.

Keep serving when AuthSec is briefly unreachable

Use REMOTE_WITH_LOCAL_FALLBACK and ship a baseline tool_scopes map:

from authsec_sdk import Config, PolicyMode

cfg = Config(
# ... regular fields ...
policy_mode=PolicyMode.REMOTE_WITH_LOCAL_FALLBACK,
tool_scopes={
"list_repos": ["github.repo:read"],
"create_issue": ["github.issue:write"],
"health_check": [], # empty list = public
},
)

The SDK prefers remote policy; the local map is used only when the AuthSec scope-matrix fetch fails.

Read the authenticated principal in a downstream handler

# Via request state (FastAPI / Starlette route)
async def my_tool(request: Request):
principal = request.state.authsec_principal
log.info("subject=%s scopes=%s", principal.subject, principal.scopes)

# Or via contextvar in deeply-nested async code
from authsec_sdk import principal_from_context

async def deep_helper():
p = principal_from_context()
if p:
...

The Principal carries subject, issuer, audience (list), scopes (list), claims (raw dict), active (bool).

Troubleshooting

SymptomLikely causeFix
ValueError: introspection client credentials are requiredintrospection_url set with no client ID/secretSet both. They come from the application registration response.
App refuses to start with REMOTE_REQUIRED initial scope matrix fetch failedThe SDK can't reach the policy endpoint and manifest publishing is not enabledCheck the AuthSec URLs and credentials. For first launch, set AUTHSEC_PUBLISH_MANIFEST=true so the SDK can start, publish tools, and deny tool calls until policy is complete.
Every request returns 401 invalid_token with audience mismatchToken's aud claim doesn't match cfg.resource_uriThe OAuth client must request resource=<your resource_uri>. MCP clients with proper discovery do this automatically.
403 insufficient_scope on a tool you mappedScope matrix cached pre-mapping; or the tool name in tools/call doesn't match the manifestWait for scope_matrix_ttl to expire (default 30 s) or restart the app.
Manifest publish silently failsBest-effort; failures are logged at WARNINGCheck logs for authsec.manifest and confirm credentials.

Agent-side errors (PendingApprovalError, CredentialInvalidError, …) are covered in the M2M and delegation guides.

Reference

The legacy decorator API (protected_by_AuthSec, run_mcp_server_with_oauth) now lives in authsec_sdk._legacy and remains importable from the top level for backward compatibility. New applications should use mount_mcp.