Skip to main content

Python SDK

Protect your application in five lines. The Python SDK wraps your existing FastAPI / Starlette handler so AuthSec owns authentication, scope-level authorization, and tool inventory — your code keeps doing what it already does.

Install

pip install authsec-sdk

Requires Python 3.10+. The package is on PyPI.

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)

That's it. 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.

mount_mcp, Config, PolicyMode, ValidationMode, from_env, Runtime, and the error types are all importable from the top-level authsec_sdk package or from authsec_sdk.runtime (they're the same objects).

The five-step launch flow

The SDK is step 2 of a five-step product flow. The rest happens in the Applications screen of the admin UI.

1. Register the application

Open Applications → Install protection. Enter a name, your Public Base URL, and the Protected Path (defaults to /mcp). AuthSec generates a one-time introspection secret — copy it, you only see it once. The application's id becomes resource_server_id and introspection_client_id in your Config.

More: Register an application.

2. Install protection (this page)

Add the snippet above. Two values from step 1 land in Config:

  • resource_server_id — the application UUID
  • introspection_client_secret — read from a secret store, never hard-coded

Start your app. The SDK validates the config, fetches the scope policy, and (with publish_manifest=True) pushes your tool list to AuthSec in the background.

3. Publish the tool manifest

A tool manifest is the list of tools your application exposes, with each tool's required scopes. publish_manifest=True does this for you on every startup. The SDK runs initialize → notifications/initialized → tools/list against your unwrapped handler and PUTs the result to AuthSec. Your tools show up in the admin UI ready to be mapped to scopes.

If your handler can't service a synthetic tools/list (custom auth on initialize, dynamic registration, non-HTTP transport), use the escape hatch:

from authsec_sdk import ManifestTool

def my_tools():
return [
ManifestTool(
name="create_pr",
description="Open a pull request",
input_schema={"type": "object", "properties": {"repo": {"type": "string"}}},
suggested_scopes=["github.pr:write"],
),
]

cfg = Config(
# ...
publish_manifest=True,
tool_inventory_provider=my_tools,
)

4. Review tool access

In the admin UI, map each tool to its required scopes. Mark health-check / read-only tools public. Until every tool is mapped (or explicitly public), the application is held at "Needs review."

More: Map tools to scopes.

5. Launch

Hit Activate. From this moment the SDK's scope matrix flips to enforce-mode. Requests with the right scopes get 200s; everything else gets 401 invalid_token or 403 insufficient_scope.

The Applications screen now shows Readiness, Risk, Last Signal, and Next Action for your app.

More: Launch.

Configuration

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 5 min.

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

Protect a FastAPI MCP server

The five-line snippet at the top of this page. Full executable: sdk-authsec/packages/python-sdk/examples/protect_existing_mcp_server.py.

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 you've completed step 4.

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 /authsec/resource-servers/{id}/sdk-policy 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 5 min) or restart the app.
Manifest publish silently failsBest-effort; failures are logged at WARNINGCheck logs for authsec.manifest and confirm credentials.

Reference

The legacy decorator API (protected_by_AuthSec, run_mcp_server_with_oauth) still ships in authsec_sdk for backward compatibility but is in maintenance mode. New applications should use mount_mcp.