Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,68 @@ async with CapiscioMCPClient(
print(result)
```

## MCPServerIdentity.connect() — "Let's Encrypt" Style Setup

Register your MCP server and get a badge with a single call:

```python
from capiscio_mcp import MCPServerIdentity

identity = await MCPServerIdentity.connect(
server_id="550e8400-...", # From the dashboard
api_key="sk_live_...",
)

print(identity.did) # did:web:registry.capisc.io:servers:550e8400-...
print(identity.badge) # Current badge JWS (auto-issued)
```

### Using Environment Variables

```python
identity = await MCPServerIdentity.from_env()
```

| Variable | Required | Description |
|----------|----------|-------------|
| `CAPISCIO_SERVER_ID` | Yes | Server UUID from dashboard |
| `CAPISCIO_API_KEY` | Yes | Registry API key |
| `CAPISCIO_SERVER_URL` | No | Registry URL (default: production) |
| `CAPISCIO_SERVER_DOMAIN` | No | Domain for badge issuance |
| `CAPISCIO_SERVER_PRIVATE_KEY_PEM` | No | PEM-encoded Ed25519 private key for ephemeral environments |

### Deploying to Containers / Serverless

In ephemeral environments (Docker, Lambda, Cloud Run) the local `~/.capiscio/` directory
doesn't survive restarts. On first run the SDK generates a keypair and logs a capture hint:

```
╔══════════════════════════════════════════════════════════╗
║ New server identity generated — save key for persistence ║
╚══════════════════════════════════════════════════════════╝

Add to your secrets manager / .env:

CAPISCIO_SERVER_PRIVATE_KEY_PEM='-----BEGIN PRIVATE KEY-----\nMC4C...\n-----END PRIVATE KEY-----\n'
```

Copy that value into your secrets manager and set it as an environment variable.
Comment on lines +232 to +245
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

This "Deploying to Containers / Serverless" section describes a flow where the SDK logs the full PEM-encoded server private key so it can be copied into CAPISCIO_SERVER_PRIVATE_KEY_PEM. Emitting a long‑term private key in cleartext logs is dangerous, because log streams are often aggregated and broadly accessible, allowing anyone with log access to recover the key and impersonate the MCP server. The recommended flow should avoid logging private keys entirely and instead rely on a more controlled key export mechanism or non-sensitive fingerprints, and the documentation should be updated to stop encouraging copying keys from logs.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

Same as the deployment.md thread — the code now uses print(file=sys.stderr) instead of logger.warning(), preventing the PEM from entering log aggregation. The capture hint is a deliberate first-run-only operator UX for local terminal sessions. See commit b0b457d.

On subsequent starts the SDK will recover the same DID without generating a new identity.

**Key resolution priority:** env var → local file → generate new.

```yaml
# docker-compose.yml
services:
mcp-server:
environment:
CAPISCIO_SERVER_ID: "550e8400-..."
CAPISCIO_API_KEY: "sk_live_..."
CAPISCIO_SERVER_PRIVATE_KEY_PEM: "${MCP_SERVER_KEY}" # from secrets
```

See the [Deployment Guide](https://docs.capisc.io/mcp-guard/guides/deployment/) for full examples.

## Core Connection Modes

MCP Guard connects to capiscio-core for cryptographic operations:
Expand Down Expand Up @@ -299,6 +361,11 @@ config = VerifyConfig(

| Variable | Description | Default |
|----------|-------------|---------|
| `CAPISCIO_SERVER_ID` | Server UUID (for `MCPServerIdentity`) | — |
| `CAPISCIO_API_KEY` | Registry API key (for `MCPServerIdentity`) | — |
| `CAPISCIO_SERVER_URL` | Registry server URL | `https://registry.capisc.io` |
| `CAPISCIO_SERVER_DOMAIN` | Domain for badge issuance | (derived from server URL) |
| `CAPISCIO_SERVER_PRIVATE_KEY_PEM` | PEM-encoded Ed25519 private key (ephemeral envs) | — |
| `CAPISCIO_CORE_ADDR` | External core address | (embedded mode) |
| `CAPISCIO_SERVER_ORIGIN` | Server origin for guard | (auto-detect) |
| `CAPISCIO_LOG_LEVEL` | Logging verbosity | `info` |
Expand Down
26 changes: 24 additions & 2 deletions capiscio_mcp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,30 @@
- Server identity registration for MCP servers
- PoP (Proof of Possession) handshake for server key verification
- Evidence logging for audit and forensics
- One-line server identity setup via MCPServerIdentity.connect()

Installation:
pip install capiscio-mcp # Standalone
pip install capiscio-mcp[mcp] # With MCP SDK integration
pip install capiscio-mcp[crypto] # With PoP signing/verification

Quickstart (Server-side):
Quickstart ("Let's Encrypt" style — recommended):
from capiscio_mcp import MCPServerIdentity
from capiscio_mcp.integrations.mcp import CapiscioMCPServer

identity = await MCPServerIdentity.connect(
server_id=os.environ["CAPISCIO_SERVER_ID"],
api_key=os.environ["CAPISCIO_API_KEY"],
)
server = CapiscioMCPServer(identity=identity)

@server.tool(min_trust_level=2)
async def read_file(path: str) -> str:
...

server.run()

Quickstart (@guard decorator):
from capiscio_mcp import guard

@guard(min_trust_level=2)
Expand All @@ -33,7 +50,7 @@ async def read_database(query: str) -> list[dict]:
if result.state == ServerState.VERIFIED_PRINCIPAL:
print(f"Trusted at level {result.trust_level}")

Quickstart (Server Registration):
Quickstart (Server Registration, manual):
from capiscio_mcp import setup_server_identity

result = await setup_server_identity(
Expand Down Expand Up @@ -95,6 +112,8 @@ async def read_database(query: str) -> list[dict]:
RegistrationError,
KeyGenerationError,
)
from capiscio_mcp.keeper import ServerBadgeKeeper
from capiscio_mcp.connect import MCPServerIdentity
from capiscio_mcp._core.version import (
MCP_VERSION,
CORE_MIN_VERSION,
Expand Down Expand Up @@ -154,4 +173,7 @@ async def read_database(query: str) -> list[dict]:
"setup_server_identity_sync",
"RegistrationError",
"KeyGenerationError",
# One-liner identity setup (MCPServerIdentity.connect())
"MCPServerIdentity",
"ServerBadgeKeeper",
]
Loading
Loading