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.
Files changed (113) hide show
  1. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/.github/workflows/integration-tests.yml +1 -0
  2. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/CHANGELOG.md +12 -0
  3. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/PKG-INFO +1 -1
  4. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/_rpc/client.py +406 -4
  5. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/badge.py +85 -15
  6. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/integrations/fastapi.py +18 -6
  7. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/message.py +4 -3
  8. capiscio_sdk-2.4.0/docs/api-reference.md +208 -0
  9. capiscio_sdk-2.4.0/docs/guides/mcp.md +283 -0
  10. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/guides/scoring.md +1 -1
  11. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/mkdocs.yml +5 -0
  12. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/pyproject.toml +1 -1
  13. capiscio_sdk-2.4.0/tests/integration/test_mcp_service.py +377 -0
  14. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_badge.py +79 -5
  15. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_fastapi_integration.py +41 -0
  16. capiscio_sdk-2.3.1/docs/api-reference.md +0 -79
  17. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/.github/copilot-instructions.md +0 -0
  18. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/.github/markdown-link-check-config.json +0 -0
  19. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/.github/workflows/docs.yml +0 -0
  20. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/.github/workflows/pr-checks.yml +0 -0
  21. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/.github/workflows/publish.yml +0 -0
  22. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/.gitignore +0 -0
  23. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/.python-version +0 -0
  24. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/CONTRIBUTING.md +0 -0
  25. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/Dockerfile.test +0 -0
  26. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/LICENSE +0 -0
  27. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/QUICK_REFERENCE.md +0 -0
  28. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/README.md +0 -0
  29. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/RELEASE_GUIDE.md +0 -0
  30. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/SECURITY.md +0 -0
  31. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/__init__.py +0 -0
  32. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/_rpc/__init__.py +0 -0
  33. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/_rpc/gen/__init__.py +0 -0
  34. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/_rpc/process.py +0 -0
  35. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/badge_keeper.py +0 -0
  36. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/config.py +0 -0
  37. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/dv.py +0 -0
  38. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/errors.py +0 -0
  39. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/executor.py +0 -0
  40. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/infrastructure/__init__.py +0 -0
  41. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/infrastructure/cache.py +0 -0
  42. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/infrastructure/rate_limiter.py +0 -0
  43. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/py.typed +0 -0
  44. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/scoring/__init__.py +0 -0
  45. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/scoring/availability.py +0 -0
  46. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/scoring/compliance.py +0 -0
  47. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/scoring/trust.py +0 -0
  48. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/scoring/types.py +0 -0
  49. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/simple_guard.py +0 -0
  50. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/types.py +0 -0
  51. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/__init__.py +0 -0
  52. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/_core.py +0 -0
  53. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/agent_card.py +0 -0
  54. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/certificate.py +0 -0
  55. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/protocol.py +0 -0
  56. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/semver.py +0 -0
  57. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/signature.py +0 -0
  58. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/capiscio_sdk/validators/url_security.py +0 -0
  59. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/assets/.!58931!favicon.ico +0 -0
  60. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/assets/favicon.ico +0 -0
  61. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/assets/logo.png +0 -0
  62. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/getting-started/concepts.md +0 -0
  63. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/getting-started/installation.md +0 -0
  64. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/getting-started/quickstart.md +0 -0
  65. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/guides/badge-verification.md +0 -0
  66. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/guides/configuration.md +0 -0
  67. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/includes/abbreviations.md +0 -0
  68. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/index.md +0 -0
  69. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/javascripts/extra.js +0 -0
  70. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/stylesheets/extra.css +0 -0
  71. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/docs/stylesheets/unified.css +0 -0
  72. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/README.md +0 -0
  73. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/secure_ping_pong/README.md +0 -0
  74. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/secure_ping_pong/client.py +0 -0
  75. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/secure_ping_pong/server.py +0 -0
  76. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/simple_agent/README.md +0 -0
  77. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/simple_agent/agent_executor.py +0 -0
  78. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/simple_agent/main.py +0 -0
  79. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/simple_agent/requirements.txt +0 -0
  80. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/examples/simple_agent/test_client.py +0 -0
  81. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/__init__.py +0 -0
  82. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/e2e/__init__.py +0 -0
  83. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/Dockerfile.test +0 -0
  84. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/README.md +0 -0
  85. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/__init__.py +0 -0
  86. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/docker-compose.yml +0 -0
  87. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/requirements.txt +0 -0
  88. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/test_badge_keeper.py +0 -0
  89. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/test_dv_badge_flow.py +0 -0
  90. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/test_dv_order_api.py +0 -0
  91. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/test_dv_sdk.py +0 -0
  92. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/test_grpc_scoring.py +0 -0
  93. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/test_real_executor.py +0 -0
  94. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/test_server_integration.py +0 -0
  95. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/integration/test_simple_guard.py +0 -0
  96. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/__init__.py +0 -0
  97. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_agent_card.py +0 -0
  98. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_badge_keeper.py +0 -0
  99. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_cache.py +0 -0
  100. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_certificate.py +0 -0
  101. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_config.py +0 -0
  102. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_core_validator.py +0 -0
  103. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_errors.py +0 -0
  104. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_executor.py +0 -0
  105. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_message_validator.py +0 -0
  106. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_pop_badge.py +0 -0
  107. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_protocol_validator.py +0 -0
  108. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_rate_limiter.py +0 -0
  109. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_semver_validator.py +0 -0
  110. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_signature_validator.py +0 -0
  111. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_simple_guard.py +0 -0
  112. {capiscio_sdk-2.3.1 → capiscio_sdk-2.4.0}/tests/unit/test_types.py +0 -0
  113. {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
  Metadata-Version: 2.4
2
2
  Name: capiscio-sdk
3
- Version: 2.3.1
3
+ Version: 2.4.0
4
4
  Summary: Runtime security middleware for A2A agents
5
5
  Project-URL: Homepage, https://capisc.io
6
6
  Project-URL: Documentation, https://docs.capisc.io/sdk-python
@@ -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
- expires_at, error, error_code, timestamp, token
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