capiscio-sdk 2.3.1__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/.github/workflows/integration-tests.yml +1 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/CHANGELOG.md +12 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/PKG-INFO +1 -1
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/_rpc/client.py +406 -4
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/badge.py +85 -15
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/integrations/fastapi.py +18 -6
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/message.py +4 -3
- capiscio_sdk-2.4.0/docs/api-reference.md +208 -0
- capiscio_sdk-2.4.0/docs/guides/mcp.md +283 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/guides/scoring.md +1 -1
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/mkdocs.yml +5 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/pyproject.toml +1 -1
- capiscio_sdk-2.4.0/tests/integration/test_mcp_service.py +377 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_badge.py +79 -5
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_fastapi_integration.py +41 -0
- capiscio_sdk-2.3.1/docs/api-reference.md +0 -79
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/.github/copilot-instructions.md +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/.github/markdown-link-check-config.json +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/.github/workflows/docs.yml +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/.github/workflows/pr-checks.yml +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/.github/workflows/publish.yml +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/.gitignore +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/.python-version +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/CONTRIBUTING.md +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/Dockerfile.test +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/LICENSE +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/QUICK_REFERENCE.md +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/README.md +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/RELEASE_GUIDE.md +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/SECURITY.md +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/__init__.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/_rpc/__init__.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/_rpc/gen/__init__.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/_rpc/process.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/badge_keeper.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/config.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/dv.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/errors.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/executor.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/infrastructure/__init__.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/infrastructure/cache.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/infrastructure/rate_limiter.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/py.typed +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/scoring/__init__.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/scoring/availability.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/scoring/compliance.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/scoring/trust.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/scoring/types.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/simple_guard.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/types.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/__init__.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/_core.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/agent_card.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/certificate.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/protocol.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/semver.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/signature.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/url_security.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/assets/.!58931!favicon.ico +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/assets/favicon.ico +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/assets/logo.png +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/getting-started/concepts.md +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/getting-started/installation.md +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/getting-started/quickstart.md +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/guides/badge-verification.md +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/guides/configuration.md +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/includes/abbreviations.md +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/index.md +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/javascripts/extra.js +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/stylesheets/extra.css +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/stylesheets/unified.css +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/README.md +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/secure_ping_pong/README.md +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/secure_ping_pong/client.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/secure_ping_pong/server.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/simple_agent/README.md +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/simple_agent/agent_executor.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/simple_agent/main.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/simple_agent/requirements.txt +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/simple_agent/test_client.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/__init__.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/e2e/__init__.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/Dockerfile.test +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/README.md +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/__init__.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/docker-compose.yml +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/requirements.txt +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/test_badge_keeper.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/test_dv_badge_flow.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/test_dv_order_api.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/test_dv_sdk.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/test_grpc_scoring.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/test_real_executor.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/test_server_integration.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/test_simple_guard.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/__init__.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_agent_card.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_badge_keeper.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_cache.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_certificate.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_config.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_core_validator.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_errors.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_executor.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_message_validator.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_pop_badge.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_protocol_validator.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_rate_limiter.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_semver_validator.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_signature_validator.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_simple_guard.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_types.py +0 -0
- {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_url_security.py +0 -0
|
@@ -107,6 +107,7 @@ jobs:
|
|
|
107
107
|
--ignore=tests/integration/test_dv_badge_flow.py \
|
|
108
108
|
--ignore=tests/integration/test_dv_order_api.py \
|
|
109
109
|
--ignore=tests/integration/test_dv_sdk.py \
|
|
110
|
+
--ignore=tests/integration/test_mcp_service.py \
|
|
110
111
|
-v --tb=short --junit-xml=/workspace/test-results.xml
|
|
111
112
|
|
|
112
113
|
- name: Upload test results
|
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.4.0] - 2026-01-18
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **RFC-002 Alignment**: TrustLevel enum values now match RFC-002 §5 exactly
|
|
14
|
+
- **BadgeClaims**: Aligned claim field names with RFC-002 specification
|
|
15
|
+
- **to_dict()**: Now preserves `cnf` claim for IAL-1 badges (round-trip serialization)
|
|
16
|
+
- **has_key_binding**: Consistently checks both `ial=='1'` AND `cnf` presence
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **MCP Service Client**: RFC-006/RFC-007 operations via MCP protocol
|
|
20
|
+
- **MCP gRPC Client**: Server identity operations
|
|
21
|
+
|
|
10
22
|
## [2.3.1] - 2025-01-14
|
|
11
23
|
|
|
12
24
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""gRPC client wrapper for capiscio-core."""
|
|
2
2
|
|
|
3
|
-
from typing import Optional
|
|
3
|
+
from typing import Generator, Optional
|
|
4
4
|
|
|
5
5
|
import grpc
|
|
6
6
|
|
|
@@ -9,6 +9,7 @@ from capiscio_sdk._rpc.process import ProcessManager, get_process_manager
|
|
|
9
9
|
# Import generated stubs
|
|
10
10
|
from capiscio_sdk._rpc.gen.capiscio.v1 import badge_pb2, badge_pb2_grpc
|
|
11
11
|
from capiscio_sdk._rpc.gen.capiscio.v1 import did_pb2, did_pb2_grpc
|
|
12
|
+
from capiscio_sdk._rpc.gen.capiscio.v1 import mcp_pb2, mcp_pb2_grpc
|
|
12
13
|
from capiscio_sdk._rpc.gen.capiscio.v1 import trust_pb2, trust_pb2_grpc
|
|
13
14
|
from capiscio_sdk._rpc.gen.capiscio.v1 import revocation_pb2, revocation_pb2_grpc
|
|
14
15
|
from capiscio_sdk._rpc.gen.capiscio.v1 import scoring_pb2, scoring_pb2_grpc
|
|
@@ -59,6 +60,7 @@ class CapiscioRPCClient:
|
|
|
59
60
|
# Service stubs (initialized on connect)
|
|
60
61
|
self._badge_stub: Optional[badge_pb2_grpc.BadgeServiceStub] = None
|
|
61
62
|
self._did_stub: Optional[did_pb2_grpc.DIDServiceStub] = None
|
|
63
|
+
self._mcp_stub: Optional[mcp_pb2_grpc.MCPServiceStub] = None
|
|
62
64
|
self._trust_stub: Optional[trust_pb2_grpc.TrustStoreServiceStub] = None
|
|
63
65
|
self._revocation_stub: Optional[revocation_pb2_grpc.RevocationServiceStub] = None
|
|
64
66
|
self._scoring_stub: Optional[scoring_pb2_grpc.ScoringServiceStub] = None
|
|
@@ -68,6 +70,7 @@ class CapiscioRPCClient:
|
|
|
68
70
|
# Service wrappers
|
|
69
71
|
self._badge: Optional["BadgeClient"] = None
|
|
70
72
|
self._did: Optional["DIDClient"] = None
|
|
73
|
+
self._mcp: Optional["MCPClient"] = None
|
|
71
74
|
self._trust: Optional["TrustStoreClient"] = None
|
|
72
75
|
self._revocation: Optional["RevocationClient"] = None
|
|
73
76
|
self._scoring: Optional["ScoringClient"] = None
|
|
@@ -103,6 +106,7 @@ class CapiscioRPCClient:
|
|
|
103
106
|
# Initialize stubs
|
|
104
107
|
self._badge_stub = badge_pb2_grpc.BadgeServiceStub(self._channel)
|
|
105
108
|
self._did_stub = did_pb2_grpc.DIDServiceStub(self._channel)
|
|
109
|
+
self._mcp_stub = mcp_pb2_grpc.MCPServiceStub(self._channel)
|
|
106
110
|
self._trust_stub = trust_pb2_grpc.TrustStoreServiceStub(self._channel)
|
|
107
111
|
self._revocation_stub = revocation_pb2_grpc.RevocationServiceStub(self._channel)
|
|
108
112
|
self._scoring_stub = scoring_pb2_grpc.ScoringServiceStub(self._channel)
|
|
@@ -112,6 +116,7 @@ class CapiscioRPCClient:
|
|
|
112
116
|
# Initialize service wrappers
|
|
113
117
|
self._badge = BadgeClient(self._badge_stub)
|
|
114
118
|
self._did = DIDClient(self._did_stub)
|
|
119
|
+
self._mcp = MCPClient(self._mcp_stub)
|
|
115
120
|
self._trust = TrustStoreClient(self._trust_stub)
|
|
116
121
|
self._revocation = RevocationClient(self._revocation_stub)
|
|
117
122
|
self._scoring = ScoringClient(self._scoring_stub)
|
|
@@ -129,6 +134,7 @@ class CapiscioRPCClient:
|
|
|
129
134
|
# Clear stubs
|
|
130
135
|
self._badge_stub = None
|
|
131
136
|
self._did_stub = None
|
|
137
|
+
self._mcp_stub = None
|
|
132
138
|
self._trust_stub = None
|
|
133
139
|
self._revocation_stub = None
|
|
134
140
|
self._scoring_stub = None
|
|
@@ -159,6 +165,13 @@ class CapiscioRPCClient:
|
|
|
159
165
|
assert self._did is not None
|
|
160
166
|
return self._did
|
|
161
167
|
|
|
168
|
+
@property
|
|
169
|
+
def mcp(self) -> "MCPClient":
|
|
170
|
+
"""Access the MCPService (RFC-006 / RFC-007)."""
|
|
171
|
+
self._ensure_connected()
|
|
172
|
+
assert self._mcp is not None
|
|
173
|
+
return self._mcp
|
|
174
|
+
|
|
162
175
|
@property
|
|
163
176
|
def trust(self) -> "TrustStoreClient":
|
|
164
177
|
"""Access the TrustStoreService."""
|
|
@@ -501,7 +514,7 @@ class BadgeClient:
|
|
|
501
514
|
renew_before_seconds: int = 60,
|
|
502
515
|
check_interval_seconds: int = 30,
|
|
503
516
|
trust_level: int = 1,
|
|
504
|
-
):
|
|
517
|
+
) -> Generator[dict, None, None]:
|
|
505
518
|
"""Start a badge keeper daemon (RFC-002 §7.3).
|
|
506
519
|
|
|
507
520
|
The keeper automatically renews badges before they expire, ensuring
|
|
@@ -521,8 +534,8 @@ class BadgeClient:
|
|
|
521
534
|
trust_level: Trust level for CA mode (1-4, default: 1)
|
|
522
535
|
|
|
523
536
|
Yields:
|
|
524
|
-
KeeperEvent dicts with: type, badge_jti, subject, trust_level,
|
|
525
|
-
|
|
537
|
+
dict: KeeperEvent dicts with keys: type, badge_jti, subject, trust_level,
|
|
538
|
+
expires_at, error, error_code, timestamp, token
|
|
526
539
|
|
|
527
540
|
Example:
|
|
528
541
|
# CA mode
|
|
@@ -1264,6 +1277,395 @@ class SimpleGuardClient:
|
|
|
1264
1277
|
}, None
|
|
1265
1278
|
|
|
1266
1279
|
|
|
1280
|
+
class MCPClient:
|
|
1281
|
+
"""Client wrapper for MCPService (RFC-006 Tool Authority + RFC-007 Server Identity).
|
|
1282
|
+
|
|
1283
|
+
This client provides access to MCP security operations including:
|
|
1284
|
+
- Tool access evaluation (RFC-006 §6.2-6.4)
|
|
1285
|
+
- Server identity verification (RFC-007 §7.2)
|
|
1286
|
+
- Server identity parsing from HTTP/JSON-RPC transports
|
|
1287
|
+
|
|
1288
|
+
Example:
|
|
1289
|
+
from capiscio_sdk._rpc.client import CapiscioRPCClient
|
|
1290
|
+
|
|
1291
|
+
client = CapiscioRPCClient()
|
|
1292
|
+
client.connect()
|
|
1293
|
+
|
|
1294
|
+
# Evaluate tool access with a badge
|
|
1295
|
+
result = client.mcp.evaluate_tool_access(
|
|
1296
|
+
tool_name="write_file",
|
|
1297
|
+
params_hash="abc123",
|
|
1298
|
+
server_origin="https://files.example.com",
|
|
1299
|
+
badge_jws=badge_token,
|
|
1300
|
+
)
|
|
1301
|
+
|
|
1302
|
+
if result["decision"] == "allow":
|
|
1303
|
+
print(f"Tool access granted for {result['agent_did']}")
|
|
1304
|
+
else:
|
|
1305
|
+
print(f"Access denied: {result['deny_reason']}")
|
|
1306
|
+
|
|
1307
|
+
# Verify server identity
|
|
1308
|
+
server_result = client.mcp.verify_server_identity(
|
|
1309
|
+
server_did="did:web:example.com:mcp:files",
|
|
1310
|
+
server_badge=server_badge,
|
|
1311
|
+
transport_origin="https://files.example.com",
|
|
1312
|
+
)
|
|
1313
|
+
"""
|
|
1314
|
+
|
|
1315
|
+
def __init__(self, stub: mcp_pb2_grpc.MCPServiceStub) -> None:
|
|
1316
|
+
self._stub = stub
|
|
1317
|
+
|
|
1318
|
+
def evaluate_tool_access(
|
|
1319
|
+
self,
|
|
1320
|
+
tool_name: str,
|
|
1321
|
+
params_hash: str = "",
|
|
1322
|
+
server_origin: str = "",
|
|
1323
|
+
*,
|
|
1324
|
+
badge_jws: Optional[str] = None,
|
|
1325
|
+
api_key: Optional[str] = None,
|
|
1326
|
+
policy_version: str = "",
|
|
1327
|
+
trusted_issuers: Optional[list[str]] = None,
|
|
1328
|
+
min_trust_level: int = 0,
|
|
1329
|
+
accept_level_zero: bool = False,
|
|
1330
|
+
allowed_tools: Optional[list[str]] = None,
|
|
1331
|
+
) -> dict:
|
|
1332
|
+
"""Evaluate tool access request (RFC-006 §6.2-6.4).
|
|
1333
|
+
|
|
1334
|
+
Evaluates whether a caller (identified by badge or API key) is
|
|
1335
|
+
authorized to invoke a specific tool. Returns both a decision
|
|
1336
|
+
and evidence record atomically.
|
|
1337
|
+
|
|
1338
|
+
Args:
|
|
1339
|
+
tool_name: Name of the tool being invoked
|
|
1340
|
+
params_hash: Hash of the tool parameters (for audit)
|
|
1341
|
+
server_origin: Origin of the MCP server handling the request
|
|
1342
|
+
badge_jws: Caller's badge JWT (for badged access)
|
|
1343
|
+
api_key: Caller's API key (for API key access)
|
|
1344
|
+
policy_version: Optional policy version to use
|
|
1345
|
+
trusted_issuers: List of trusted badge issuers
|
|
1346
|
+
min_trust_level: Minimum required trust level (0-4)
|
|
1347
|
+
accept_level_zero: Accept self-signed (level 0) badges
|
|
1348
|
+
allowed_tools: Explicit list of allowed tools (if set, tool_name must match)
|
|
1349
|
+
|
|
1350
|
+
Returns:
|
|
1351
|
+
Dict with:
|
|
1352
|
+
decision: "allow" or "deny"
|
|
1353
|
+
deny_reason: Reason if denied (e.g., "badge_missing", "trust_insufficient")
|
|
1354
|
+
deny_detail: Detailed error message if denied
|
|
1355
|
+
agent_did: DID of the authenticated agent (if authenticated)
|
|
1356
|
+
badge_jti: Badge JTI (if badge was used)
|
|
1357
|
+
auth_level: Authentication level ("anonymous", "api_key", "badge")
|
|
1358
|
+
trust_level: Agent's trust level (0-4)
|
|
1359
|
+
evidence_json: RFC-006 §7 evidence record as JSON
|
|
1360
|
+
evidence_id: Unique evidence record ID
|
|
1361
|
+
timestamp: Evaluation timestamp (ISO format)
|
|
1362
|
+
|
|
1363
|
+
Example:
|
|
1364
|
+
# Evaluate with badge
|
|
1365
|
+
result = client.mcp.evaluate_tool_access(
|
|
1366
|
+
tool_name="write_file",
|
|
1367
|
+
params_hash=hashlib.sha256(json.dumps(params).encode()).hexdigest(),
|
|
1368
|
+
server_origin="https://files.example.com",
|
|
1369
|
+
badge_jws=badge_token,
|
|
1370
|
+
min_trust_level=2, # Require OV or higher
|
|
1371
|
+
)
|
|
1372
|
+
|
|
1373
|
+
if result["decision"] == "allow":
|
|
1374
|
+
# Proceed with tool execution
|
|
1375
|
+
pass
|
|
1376
|
+
else:
|
|
1377
|
+
raise PermissionError(result["deny_detail"])
|
|
1378
|
+
"""
|
|
1379
|
+
# Build config
|
|
1380
|
+
config = mcp_pb2.EvaluateConfig(
|
|
1381
|
+
trusted_issuers=trusted_issuers or [],
|
|
1382
|
+
min_trust_level=min_trust_level,
|
|
1383
|
+
accept_level_zero=accept_level_zero,
|
|
1384
|
+
allowed_tools=allowed_tools or [],
|
|
1385
|
+
)
|
|
1386
|
+
|
|
1387
|
+
# Build request with caller credential
|
|
1388
|
+
request = mcp_pb2.EvaluateToolAccessRequest(
|
|
1389
|
+
tool_name=tool_name,
|
|
1390
|
+
params_hash=params_hash,
|
|
1391
|
+
server_origin=server_origin,
|
|
1392
|
+
policy_version=policy_version,
|
|
1393
|
+
config=config,
|
|
1394
|
+
)
|
|
1395
|
+
|
|
1396
|
+
# Set credential (badge or api_key, mutually exclusive)
|
|
1397
|
+
if badge_jws:
|
|
1398
|
+
request.badge_jws = badge_jws
|
|
1399
|
+
elif api_key:
|
|
1400
|
+
request.api_key = api_key
|
|
1401
|
+
|
|
1402
|
+
response = self._stub.EvaluateToolAccess(request)
|
|
1403
|
+
|
|
1404
|
+
# Map enums to strings
|
|
1405
|
+
decision_map = {
|
|
1406
|
+
mcp_pb2.MCP_DECISION_UNSPECIFIED: "unspecified",
|
|
1407
|
+
mcp_pb2.MCP_DECISION_ALLOW: "allow",
|
|
1408
|
+
mcp_pb2.MCP_DECISION_DENY: "deny",
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
deny_reason_map = {
|
|
1412
|
+
mcp_pb2.MCP_DENY_REASON_UNSPECIFIED: "",
|
|
1413
|
+
mcp_pb2.MCP_DENY_REASON_BADGE_MISSING: "badge_missing",
|
|
1414
|
+
mcp_pb2.MCP_DENY_REASON_BADGE_INVALID: "badge_invalid",
|
|
1415
|
+
mcp_pb2.MCP_DENY_REASON_BADGE_EXPIRED: "badge_expired",
|
|
1416
|
+
mcp_pb2.MCP_DENY_REASON_BADGE_REVOKED: "badge_revoked",
|
|
1417
|
+
mcp_pb2.MCP_DENY_REASON_TRUST_INSUFFICIENT: "trust_insufficient",
|
|
1418
|
+
mcp_pb2.MCP_DENY_REASON_TOOL_NOT_ALLOWED: "tool_not_allowed",
|
|
1419
|
+
mcp_pb2.MCP_DENY_REASON_ISSUER_UNTRUSTED: "issuer_untrusted",
|
|
1420
|
+
mcp_pb2.MCP_DENY_REASON_POLICY_DENIED: "policy_denied",
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
auth_level_map = {
|
|
1424
|
+
mcp_pb2.MCP_AUTH_LEVEL_UNSPECIFIED: "unspecified",
|
|
1425
|
+
mcp_pb2.MCP_AUTH_LEVEL_ANONYMOUS: "anonymous",
|
|
1426
|
+
mcp_pb2.MCP_AUTH_LEVEL_API_KEY: "api_key",
|
|
1427
|
+
mcp_pb2.MCP_AUTH_LEVEL_BADGE: "badge",
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
# Format timestamp
|
|
1431
|
+
timestamp_str = ""
|
|
1432
|
+
if response.timestamp:
|
|
1433
|
+
from datetime import datetime, timezone
|
|
1434
|
+
timestamp_str = datetime.fromtimestamp(
|
|
1435
|
+
response.timestamp.seconds + response.timestamp.nanos / 1e9,
|
|
1436
|
+
timezone.utc
|
|
1437
|
+
).isoformat()
|
|
1438
|
+
|
|
1439
|
+
return {
|
|
1440
|
+
"decision": decision_map.get(response.decision, "unspecified"),
|
|
1441
|
+
"deny_reason": deny_reason_map.get(response.deny_reason, ""),
|
|
1442
|
+
"deny_detail": response.deny_detail,
|
|
1443
|
+
"agent_did": response.agent_did,
|
|
1444
|
+
"badge_jti": response.badge_jti,
|
|
1445
|
+
"auth_level": auth_level_map.get(response.auth_level, "unspecified"),
|
|
1446
|
+
"trust_level": response.trust_level,
|
|
1447
|
+
"evidence_json": response.evidence_json,
|
|
1448
|
+
"evidence_id": response.evidence_id,
|
|
1449
|
+
"timestamp": timestamp_str,
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
def verify_server_identity(
|
|
1453
|
+
self,
|
|
1454
|
+
server_did: str,
|
|
1455
|
+
server_badge: str = "",
|
|
1456
|
+
transport_origin: str = "",
|
|
1457
|
+
endpoint_path: str = "",
|
|
1458
|
+
*,
|
|
1459
|
+
trusted_issuers: Optional[list[str]] = None,
|
|
1460
|
+
min_trust_level: int = 0,
|
|
1461
|
+
accept_level_zero: bool = False,
|
|
1462
|
+
offline_mode: bool = False,
|
|
1463
|
+
skip_origin_binding: bool = False,
|
|
1464
|
+
) -> dict:
|
|
1465
|
+
"""Verify MCP server identity (RFC-007 §7.2).
|
|
1466
|
+
|
|
1467
|
+
Verifies a server's disclosed identity (DID + badge) and checks
|
|
1468
|
+
that it matches the transport origin. Returns the verification
|
|
1469
|
+
state and any errors.
|
|
1470
|
+
|
|
1471
|
+
Args:
|
|
1472
|
+
server_did: Server's DID (did:web:... or did:key:...)
|
|
1473
|
+
server_badge: Server's badge JWT (optional for level 0)
|
|
1474
|
+
transport_origin: Origin from the transport (e.g., "https://files.example.com")
|
|
1475
|
+
endpoint_path: Endpoint path being accessed
|
|
1476
|
+
trusted_issuers: List of trusted badge issuers
|
|
1477
|
+
min_trust_level: Minimum required trust level (0-4)
|
|
1478
|
+
accept_level_zero: Accept self-signed (level 0) servers
|
|
1479
|
+
offline_mode: Skip online validation (use cache only)
|
|
1480
|
+
skip_origin_binding: Skip transport origin binding check (RFC-007 §5.3)
|
|
1481
|
+
|
|
1482
|
+
Returns:
|
|
1483
|
+
Dict with:
|
|
1484
|
+
state: Server state ("verified_principal", "declared_principal", "unverified_origin")
|
|
1485
|
+
trust_level: Server's trust level (0-4)
|
|
1486
|
+
server_did: Verified server DID
|
|
1487
|
+
badge_jti: Server badge JTI (if badge was provided)
|
|
1488
|
+
error_code: Error code if verification failed
|
|
1489
|
+
error_detail: Detailed error message
|
|
1490
|
+
|
|
1491
|
+
Example:
|
|
1492
|
+
# Verify server before trusting tool results
|
|
1493
|
+
result = client.mcp.verify_server_identity(
|
|
1494
|
+
server_did="did:web:files.example.com:mcp:files",
|
|
1495
|
+
server_badge=server_badge_token,
|
|
1496
|
+
transport_origin="https://files.example.com",
|
|
1497
|
+
min_trust_level=1, # Require at least DV
|
|
1498
|
+
)
|
|
1499
|
+
|
|
1500
|
+
if result["state"] == "verified_principal":
|
|
1501
|
+
print(f"Server verified at trust level {result['trust_level']}")
|
|
1502
|
+
else:
|
|
1503
|
+
print(f"Server verification failed: {result['error_detail']}")
|
|
1504
|
+
"""
|
|
1505
|
+
# Build config
|
|
1506
|
+
config = mcp_pb2.MCPVerifyConfig(
|
|
1507
|
+
trusted_issuers=trusted_issuers or [],
|
|
1508
|
+
min_trust_level=min_trust_level,
|
|
1509
|
+
accept_level_zero=accept_level_zero,
|
|
1510
|
+
offline_mode=offline_mode,
|
|
1511
|
+
skip_origin_binding=skip_origin_binding,
|
|
1512
|
+
)
|
|
1513
|
+
|
|
1514
|
+
request = mcp_pb2.VerifyServerIdentityRequest(
|
|
1515
|
+
server_did=server_did,
|
|
1516
|
+
server_badge=server_badge,
|
|
1517
|
+
transport_origin=transport_origin,
|
|
1518
|
+
endpoint_path=endpoint_path,
|
|
1519
|
+
config=config,
|
|
1520
|
+
)
|
|
1521
|
+
|
|
1522
|
+
response = self._stub.VerifyServerIdentity(request)
|
|
1523
|
+
|
|
1524
|
+
# Map enums to strings
|
|
1525
|
+
state_map = {
|
|
1526
|
+
mcp_pb2.MCP_SERVER_STATE_UNSPECIFIED: "unspecified",
|
|
1527
|
+
mcp_pb2.MCP_SERVER_STATE_VERIFIED_PRINCIPAL: "verified_principal",
|
|
1528
|
+
mcp_pb2.MCP_SERVER_STATE_DECLARED_PRINCIPAL: "declared_principal",
|
|
1529
|
+
mcp_pb2.MCP_SERVER_STATE_UNVERIFIED_ORIGIN: "unverified_origin",
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
error_code_map = {
|
|
1533
|
+
mcp_pb2.MCP_SERVER_ERROR_NONE: "",
|
|
1534
|
+
mcp_pb2.MCP_SERVER_ERROR_DID_INVALID: "did_invalid",
|
|
1535
|
+
mcp_pb2.MCP_SERVER_ERROR_BADGE_INVALID: "badge_invalid",
|
|
1536
|
+
mcp_pb2.MCP_SERVER_ERROR_BADGE_EXPIRED: "badge_expired",
|
|
1537
|
+
mcp_pb2.MCP_SERVER_ERROR_BADGE_REVOKED: "badge_revoked",
|
|
1538
|
+
mcp_pb2.MCP_SERVER_ERROR_TRUST_INSUFFICIENT: "trust_insufficient",
|
|
1539
|
+
mcp_pb2.MCP_SERVER_ERROR_ORIGIN_MISMATCH: "origin_mismatch",
|
|
1540
|
+
mcp_pb2.MCP_SERVER_ERROR_PATH_MISMATCH: "path_mismatch",
|
|
1541
|
+
mcp_pb2.MCP_SERVER_ERROR_ISSUER_UNTRUSTED: "issuer_untrusted",
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
return {
|
|
1545
|
+
"state": state_map.get(response.state, "unspecified"),
|
|
1546
|
+
"trust_level": response.trust_level,
|
|
1547
|
+
"server_did": response.server_did,
|
|
1548
|
+
"badge_jti": response.badge_jti,
|
|
1549
|
+
"error_code": error_code_map.get(response.error_code, ""),
|
|
1550
|
+
"error_detail": response.error_detail,
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
def parse_server_identity_http(
|
|
1554
|
+
self,
|
|
1555
|
+
capiscio_server_did: str = "",
|
|
1556
|
+
capiscio_server_badge: str = "",
|
|
1557
|
+
) -> dict:
|
|
1558
|
+
"""Parse server identity from HTTP headers (RFC-007 §5.2).
|
|
1559
|
+
|
|
1560
|
+
Extracts server identity from HTTP headers. Use this before
|
|
1561
|
+
verify_server_identity() to extract the DID and badge.
|
|
1562
|
+
|
|
1563
|
+
Args:
|
|
1564
|
+
capiscio_server_did: Value of Capiscio-Server-DID header
|
|
1565
|
+
capiscio_server_badge: Value of Capiscio-Server-Badge header
|
|
1566
|
+
|
|
1567
|
+
Returns:
|
|
1568
|
+
Dict with:
|
|
1569
|
+
server_did: Extracted server DID
|
|
1570
|
+
server_badge: Extracted server badge JWT
|
|
1571
|
+
identity_present: Whether identity was present
|
|
1572
|
+
|
|
1573
|
+
Example:
|
|
1574
|
+
# Extract from HTTP headers
|
|
1575
|
+
headers = response.headers
|
|
1576
|
+
identity = client.mcp.parse_server_identity_http(
|
|
1577
|
+
capiscio_server_did=headers.get("Capiscio-Server-DID", ""),
|
|
1578
|
+
capiscio_server_badge=headers.get("Capiscio-Server-Badge", ""),
|
|
1579
|
+
)
|
|
1580
|
+
|
|
1581
|
+
if identity["identity_present"]:
|
|
1582
|
+
# Verify the extracted identity
|
|
1583
|
+
result = client.mcp.verify_server_identity(
|
|
1584
|
+
server_did=identity["server_did"],
|
|
1585
|
+
server_badge=identity["server_badge"],
|
|
1586
|
+
transport_origin="https://files.example.com",
|
|
1587
|
+
)
|
|
1588
|
+
"""
|
|
1589
|
+
http_headers = mcp_pb2.MCPHttpHeaders(
|
|
1590
|
+
capiscio_server_did=capiscio_server_did,
|
|
1591
|
+
capiscio_server_badge=capiscio_server_badge,
|
|
1592
|
+
)
|
|
1593
|
+
|
|
1594
|
+
request = mcp_pb2.ParseServerIdentityRequest(http_headers=http_headers)
|
|
1595
|
+
response = self._stub.ParseServerIdentity(request)
|
|
1596
|
+
|
|
1597
|
+
return {
|
|
1598
|
+
"server_did": response.server_did,
|
|
1599
|
+
"server_badge": response.server_badge,
|
|
1600
|
+
"identity_present": response.identity_present,
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
def parse_server_identity_jsonrpc(self, meta_json: str) -> dict:
|
|
1604
|
+
"""Parse server identity from JSON-RPC _meta (RFC-007 §5.3).
|
|
1605
|
+
|
|
1606
|
+
Extracts server identity from JSON-RPC _meta field. Use this
|
|
1607
|
+
for stdio transport or any JSON-RPC based MCP connection.
|
|
1608
|
+
|
|
1609
|
+
Args:
|
|
1610
|
+
meta_json: JSON string of the _meta object containing
|
|
1611
|
+
"serverDid" and "serverBadge" fields
|
|
1612
|
+
|
|
1613
|
+
Returns:
|
|
1614
|
+
Dict with:
|
|
1615
|
+
server_did: Extracted server DID
|
|
1616
|
+
server_badge: Extracted server badge JWT
|
|
1617
|
+
identity_present: Whether identity was present
|
|
1618
|
+
|
|
1619
|
+
Example:
|
|
1620
|
+
# Extract from JSON-RPC response
|
|
1621
|
+
meta = response.get("_meta", {})
|
|
1622
|
+
identity = client.mcp.parse_server_identity_jsonrpc(
|
|
1623
|
+
meta_json=json.dumps(meta)
|
|
1624
|
+
)
|
|
1625
|
+
|
|
1626
|
+
if identity["identity_present"]:
|
|
1627
|
+
# Verify the extracted identity
|
|
1628
|
+
result = client.mcp.verify_server_identity(
|
|
1629
|
+
server_did=identity["server_did"],
|
|
1630
|
+
server_badge=identity["server_badge"],
|
|
1631
|
+
transport_origin="", # N/A for stdio
|
|
1632
|
+
)
|
|
1633
|
+
"""
|
|
1634
|
+
jsonrpc_meta = mcp_pb2.MCPJsonRpcMeta(meta_json=meta_json)
|
|
1635
|
+
|
|
1636
|
+
request = mcp_pb2.ParseServerIdentityRequest(jsonrpc_meta=jsonrpc_meta)
|
|
1637
|
+
response = self._stub.ParseServerIdentity(request)
|
|
1638
|
+
|
|
1639
|
+
return {
|
|
1640
|
+
"server_did": response.server_did,
|
|
1641
|
+
"server_badge": response.server_badge,
|
|
1642
|
+
"identity_present": response.identity_present,
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
def health(self, client_version: str = "") -> dict:
|
|
1646
|
+
"""Check MCP service health and version compatibility.
|
|
1647
|
+
|
|
1648
|
+
Args:
|
|
1649
|
+
client_version: Client's version for compatibility check
|
|
1650
|
+
|
|
1651
|
+
Returns:
|
|
1652
|
+
Dict with:
|
|
1653
|
+
healthy: Whether service is healthy
|
|
1654
|
+
core_version: capiscio-core version
|
|
1655
|
+
proto_version: Proto/gRPC version
|
|
1656
|
+
version_compatible: Whether versions are compatible
|
|
1657
|
+
"""
|
|
1658
|
+
request = mcp_pb2.MCPHealthRequest(client_version=client_version)
|
|
1659
|
+
response = self._stub.Health(request)
|
|
1660
|
+
|
|
1661
|
+
return {
|
|
1662
|
+
"healthy": response.healthy,
|
|
1663
|
+
"core_version": response.core_version,
|
|
1664
|
+
"proto_version": response.proto_version,
|
|
1665
|
+
"version_compatible": response.version_compatible,
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
|
|
1267
1669
|
class RegistryClient:
|
|
1268
1670
|
"""Client wrapper for RegistryService."""
|
|
1269
1671
|
|