feat: wire policy enforcement env vars into connect()#26
Conversation
Make api_key optional (falls back to CAPISCIO_API_KEY env var). Extract org_id from registration response and persist to org_id.txt. Set CAPISCIO_BUNDLE_URL and CAPISCIO_API_KEY in os.environ before the Go core subprocess spawns, so NewLocalPDPFromEnv() activates OPA bundle polling and local policy evaluation (sub-ms). Add org_id field to MCPServerIdentity dataclass. Update from_env() to delegate api_key validation to connect().
The local OPA policy evaluation (initLocalPDP) ships in core v2.7.0. Pin the SDK to require it so the downloaded binary includes the PDP wiring. Update test mocks to use 2.7.0.
|
✅ Integration tests passed! capiscio-core gRPC tests working. |
There was a problem hiding this comment.
Pull request overview
Plumbs policy-enforcement configuration (bundle URL + API key) into the embedded capiscio-core subprocess so local OPA policy evaluation can be enabled when the core starts, aligning MCP Guard behavior with the Go core’s PDP wiring requirements.
Changes:
- Update
MCPServerIdentity.connect()to accept an optionalapi_keywith env-var fallback, persist/loadorg_id, and setCAPISCIO_BUNDLE_URL/CAPISCIO_API_KEYfor the embedded core. - Bump embedded core minimum compatible version to
2.7.0. - Update mocked core version in health-check tests to
2.7.0.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
capiscio_mcp/connect.py |
Resolves API key with env fallback, caches org ID, and sets policy-related env vars intended for the embedded core subprocess. |
capiscio_mcp/_core/version.py |
Raises CORE_MIN_VERSION to require a core release with PDP wiring. |
tests/test_core_health.py |
Adjusts mocked core version strings to match the new minimum. |
Comments suppressed due to low confidence (1)
capiscio_mcp/connect.py:504
- ServerBadgeKeeper is still being initialized with
api_key=api_key(the original argument), which can beNonenow thatconnect()allowsapi_key: Optional[str] = None. This will break badge renewal or cause type errors; passeffective_api_keyinstead so the keeper always has a valid key.
keeper = ServerBadgeKeeper(
server_id=server_id,
api_key=api_key,
initial_badge=badge,
ca_url=server_url,
| if not os.environ.get("CAPISCIO_API_KEY"): | ||
| os.environ["CAPISCIO_API_KEY"] = effective_api_key |
There was a problem hiding this comment.
Fixed in 8434fef — os.environ["CAPISCIO_API_KEY"] is now always set to effective_api_key unconditionally, so the Go subprocess always sees the same key used for registration regardless of whether an explicit api_key arg was passed.
| ) | ||
| server_id = resolved | ||
|
|
||
| # Try loading cached org_id (avoids network call if keys already exist) |
There was a problem hiding this comment.
Fixed — reworded to: "provides fallback if registration response omits orgId, e.g. on 409 idempotent re-registration".
| - ``CAPISCIO_API_KEY`` (required — via env or ``api_key`` kwarg) | ||
| - ``CAPISCIO_SERVER_URL`` (optional, default: production) |
There was a problem hiding this comment.
Fixed — removed the mention of api_key kwarg from from_env() docstring. It now states api_key is always read from CAPISCIO_API_KEY env var, with a note not to pass it as a kwarg.
| private_key_pem: Optional[str] = None | ||
| org_id: Optional[str] = None |
There was a problem hiding this comment.
Fixed — added org_id to the Attributes docstring on MCPServerIdentity.
| # Configure policy enforcement for Go core. | ||
| # Must be set BEFORE the first @guard call triggers CoreClient.get_instance() | ||
| # which spawns the Go binary (inherits parent env). | ||
| if org_id: | ||
| os.environ["CAPISCIO_BUNDLE_URL"] = f"{server_url}/v1/bundles/{org_id}" |
There was a problem hiding this comment.
Good catch — fixed. Env vars are now set in Step 1.5 (right after resolving effective_api_key and keys dir), before any code path that can start the Go core subprocess. CAPISCIO_API_KEY is always set, and CAPISCIO_BUNDLE_URL is set from cached org_id.txt if available. If registration returns a different org_id, the bundle URL is updated post-registration.
- Always set CAPISCIO_API_KEY to effective_api_key so Go subprocess matches the key used for registration (fixes arg vs env divergence) - Move env var setup (API key + cached bundle URL) before Step 2 key generation, which can start Go core via CoreClient.get_instance() - Fix misleading 'avoids network call' comment on cached org_id - Fix from_env() docstring — api_key is always read from env, not kwarg - Add org_id to MCPServerIdentity Attributes docstring
|
✅ Integration tests passed! capiscio-core gRPC tests working. |
1 similar comment
|
✅ Integration tests passed! capiscio-core gRPC tests working. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
capiscio_mcp/connect.py:518
ServerBadgeKeeperis initialized withapi_key=api_key(the original argument), which can beNonewhen callers rely on the new CAPISCIO_API_KEY env fallback. This will break badge auto-renewal (keeper expects a real API key header) and can also diverge fromeffective_api_keyused for registration/badge issuance. Passeffective_api_keyintoServerBadgeKeeperinstead.
keeper = ServerBadgeKeeper(
server_id=server_id,
api_key=api_key,
initial_badge=badge,
ca_url=server_url,
renewal_threshold=renewal_threshold,
on_renew=on_badge_renew,
)
| # Update bundle URL if registration yielded a new/different org_id. | ||
| # (API key and cached bundle URL were already set in Step 1.5 before | ||
| # the Go core could be started.) | ||
| if org_id and org_id != cached_org_id: | ||
| os.environ["CAPISCIO_BUNDLE_URL"] = f"{server_url}/v1/bundles/{org_id}" | ||
|
|
| # ------------------------------------------------------------------ | ||
| # Step 1.5: Pre-set policy env vars BEFORE any code path that may | ||
| # start the Go core subprocess (generate_server_keypair calls | ||
| # CoreClient.get_instance). The subprocess inherits the parent env, | ||
| # so these must be in os.environ before it spawns. | ||
| # ------------------------------------------------------------------ | ||
| os.environ["CAPISCIO_API_KEY"] = effective_api_key | ||
|
|
| # Resolve api_key: argument > env var | ||
| effective_api_key = api_key or os.environ.get("CAPISCIO_API_KEY") | ||
| if not effective_api_key: | ||
| raise ValueError( | ||
| "api_key argument or CAPISCIO_API_KEY environment variable is required." | ||
| ) | ||
|
|
Summary
Plumbs policy enforcement environment variables (
CAPISCIO_BUNDLE_URL,CAPISCIO_API_KEY) into the Go core subprocess so the embedded OPA evaluator activates on startup.Changes
capiscio_mcp/connect.pyapi_keyparameter changed fromstrtoOptional[str] = NonewithCAPISCIO_API_KEYenv var fallbackorgIdfrom the registration response and persists it toorg_id.txtalongside the cacheorg_idfrom fileCAPISCIO_BUNDLE_URLandCAPISCIO_API_KEYenv vars before Go subprocess spawnsorg_id: Optional[str] = Nonefield toMCPServerIdentitydataclassfrom_env()no longer raises for missingapi_key(delegated toconnect())capiscio_mcp/_core/version.pyCORE_MIN_VERSIONbumped from2.4.0to2.7.0(requires core with PDP wiring)tests/test_core_health.py2.5.0to2.7.0(4 occurrences)How it works
connect()resolvesapi_keyfrom parameter orCAPISCIO_API_KEYenv varorgIdfrom server response{server_url}/v1/bundles/{org_id}os.environbeforeasyncio.create_subprocess_exec()spawns the Go binaryinitLocalPDP()reads these env vars and starts OPA bundle pollingThe subprocess inherits the parent's environment (no
env=kwarg oncreate_subprocess_exec), soos.environmutations are picked up automatically.Testing
Related