Skip to content

feat: wire policy enforcement env vars into connect()#26

Merged
beonde merged 4 commits into
mainfrom
feat/wire-pdp-to-guard
May 11, 2026
Merged

feat: wire policy enforcement env vars into connect()#26
beonde merged 4 commits into
mainfrom
feat/wire-pdp-to-guard

Conversation

@beonde
Copy link
Copy Markdown
Member

@beonde beonde commented May 11, 2026

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.py

  • api_key parameter changed from str to Optional[str] = None with CAPISCIO_API_KEY env var fallback
  • Extracts orgId from the registration response and persists it to org_id.txt alongside the cache
  • On cache hit, loads org_id from file
  • Sets CAPISCIO_BUNDLE_URL and CAPISCIO_API_KEY env vars before Go subprocess spawns
  • Added org_id: Optional[str] = None field to MCPServerIdentity dataclass
  • from_env() no longer raises for missing api_key (delegated to connect())

capiscio_mcp/_core/version.py

  • CORE_MIN_VERSION bumped from 2.4.0 to 2.7.0 (requires core with PDP wiring)

tests/test_core_health.py

  • Mock core version updated from 2.5.0 to 2.7.0 (4 occurrences)

How it works

  1. connect() resolves api_key from parameter or CAPISCIO_API_KEY env var
  2. During registration, extracts orgId from server response
  3. Constructs bundle URL: {server_url}/v1/bundles/{org_id}
  4. Sets env vars in os.environ before asyncio.create_subprocess_exec() spawns the Go binary
  5. Go binary's initLocalPDP() reads these env vars and starts OPA bundle polling

The subprocess inherits the parent's environment (no env= kwarg on create_subprocess_exec), so os.environ mutations are picked up automatically.

Testing

  • All 384 tests pass
  • Version pin verified against core release schedule

Related

  • capiscio-core#73 — Go-side PDP wiring (companion PR, must ship first as part of core v2.7.0)
  • capiscio-server#85 — multi-tenant server-side PDP (future work)

beonde added 2 commits May 11, 2026 10:15
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.
Copilot AI review requested due to automatic review settings May 11, 2026 16:18
@github-actions
Copy link
Copy Markdown

✅ Integration tests passed! capiscio-core gRPC tests working.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 optional api_key with env-var fallback, persist/load org_id, and set CAPISCIO_BUNDLE_URL / CAPISCIO_API_KEY for 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 be None now that connect() allows api_key: Optional[str] = None. This will break badge renewal or cause type errors; pass effective_api_key instead 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,

Comment thread capiscio_mcp/connect.py Outdated
Comment on lines +490 to +491
if not os.environ.get("CAPISCIO_API_KEY"):
os.environ["CAPISCIO_API_KEY"] = effective_api_key
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8434fefos.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.

Comment thread capiscio_mcp/connect.py Outdated
)
server_id = resolved

# Try loading cached org_id (avoids network call if keys already exist)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — reworded to: "provides fallback if registration response omits orgId, e.g. on 409 idempotent re-registration".

Comment thread capiscio_mcp/connect.py Outdated
Comment on lines 543 to 544
- ``CAPISCIO_API_KEY`` (required — via env or ``api_key`` kwarg)
- ``CAPISCIO_SERVER_URL`` (optional, default: production)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread capiscio_mcp/connect.py
Comment on lines 236 to +237
private_key_pem: Optional[str] = None
org_id: Optional[str] = None
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — added org_id to the Attributes docstring on MCPServerIdentity.

Comment thread capiscio_mcp/connect.py Outdated
Comment on lines +485 to +489
# 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}"
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

beonde added 2 commits May 11, 2026 15:35
- 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
Copilot AI review requested due to automatic review settings May 11, 2026 19:35
@github-actions
Copy link
Copy Markdown

✅ Integration tests passed! capiscio-core gRPC tests working.

1 similar comment
@github-actions
Copy link
Copy Markdown

✅ Integration tests passed! capiscio-core gRPC tests working.

@beonde beonde merged commit 0ee359e into main May 11, 2026
12 checks passed
@beonde beonde deleted the feat/wire-pdp-to-guard branch May 11, 2026 19:38
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • ServerBadgeKeeper is initialized with api_key=api_key (the original argument), which can be None when 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 from effective_api_key used for registration/badge issuance. Pass effective_api_key into ServerBadgeKeeper instead.
                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,
                )

Comment thread capiscio_mcp/connect.py
Comment on lines +498 to +503
# 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}"

Comment thread capiscio_mcp/connect.py
Comment on lines +339 to +346
# ------------------------------------------------------------------
# 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

Comment thread capiscio_mcp/connect.py
Comment on lines +324 to +330
# 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."
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants