capiscio-sdk 2.3.0__py3-none-any.whl → 2.4.0__py3-none-any.whl
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/__init__.py +1 -1
- capiscio_sdk/_rpc/client.py +406 -4
- capiscio_sdk/badge.py +85 -15
- capiscio_sdk/integrations/fastapi.py +18 -6
- capiscio_sdk/validators/message.py +4 -3
- {capiscio_sdk-2.3.0.dist-info → capiscio_sdk-2.4.0.dist-info}/METADATA +1 -1
- {capiscio_sdk-2.3.0.dist-info → capiscio_sdk-2.4.0.dist-info}/RECORD +9 -9
- {capiscio_sdk-2.3.0.dist-info → capiscio_sdk-2.4.0.dist-info}/WHEEL +0 -0
- {capiscio_sdk-2.3.0.dist-info → capiscio_sdk-2.4.0.dist-info}/licenses/LICENSE +0 -0
capiscio_sdk/__init__.py
CHANGED
capiscio_sdk/_rpc/client.py
CHANGED
|
@@ -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
|
|
capiscio_sdk/badge.py
CHANGED
|
@@ -30,7 +30,7 @@ Example usage:
|
|
|
30
30
|
from dataclasses import dataclass, field
|
|
31
31
|
from datetime import datetime
|
|
32
32
|
from enum import Enum
|
|
33
|
-
from typing import List, Optional, Union
|
|
33
|
+
from typing import Generator, List, Optional, Union
|
|
34
34
|
|
|
35
35
|
from capiscio_sdk._rpc.client import CapiscioRPCClient
|
|
36
36
|
|
|
@@ -64,16 +64,30 @@ class VerifyMode(Enum):
|
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
class TrustLevel(Enum):
|
|
67
|
-
"""Trust level as defined in RFC-002.
|
|
67
|
+
"""Trust level as defined in RFC-002 §5.
|
|
68
|
+
|
|
69
|
+
Levels:
|
|
70
|
+
LEVEL_0 (SS): Self-Signed - did:key, iss == sub. Development only.
|
|
71
|
+
LEVEL_1 (REG): Registered - Account registration with CA.
|
|
72
|
+
LEVEL_2 (DV): Domain Validated - DNS/HTTP domain ownership proof.
|
|
73
|
+
LEVEL_3 (OV): Organization Validated - Legal entity verification.
|
|
74
|
+
LEVEL_4 (EV): Extended Validated - Manual review + security audit.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
LEVEL_0 = "0"
|
|
78
|
+
"""Self-Signed (SS) - did:key, iss == sub. Development only."""
|
|
68
79
|
|
|
69
80
|
LEVEL_1 = "1"
|
|
70
|
-
"""
|
|
81
|
+
"""Registered (REG) - Account registration with CA."""
|
|
71
82
|
|
|
72
83
|
LEVEL_2 = "2"
|
|
73
|
-
"""
|
|
84
|
+
"""Domain Validated (DV) - DNS/HTTP domain ownership proof."""
|
|
74
85
|
|
|
75
86
|
LEVEL_3 = "3"
|
|
76
|
-
"""
|
|
87
|
+
"""Organization Validated (OV) - Legal entity verification."""
|
|
88
|
+
|
|
89
|
+
LEVEL_4 = "4"
|
|
90
|
+
"""Extended Validated (EV) - Manual review + security audit."""
|
|
77
91
|
|
|
78
92
|
@classmethod
|
|
79
93
|
def from_string(cls, value: str) -> "TrustLevel":
|
|
@@ -81,7 +95,7 @@ class TrustLevel(Enum):
|
|
|
81
95
|
for level in cls:
|
|
82
96
|
if level.value == value:
|
|
83
97
|
return level
|
|
84
|
-
raise ValueError(f"Unknown trust level: {value}")
|
|
98
|
+
raise ValueError(f"Unknown trust level: {value}. Valid levels: 0 (SS), 1 (REG), 2 (DV), 3 (OV), 4 (EV)")
|
|
85
99
|
|
|
86
100
|
|
|
87
101
|
@dataclass
|
|
@@ -90,15 +104,24 @@ class BadgeClaims:
|
|
|
90
104
|
|
|
91
105
|
Attributes:
|
|
92
106
|
jti: Unique badge identifier (UUID).
|
|
93
|
-
issuer: Badge issuer URL (CA).
|
|
107
|
+
issuer: Badge issuer URL (CA) or did:key for self-signed.
|
|
94
108
|
subject: Agent DID (did:web format).
|
|
95
109
|
audience: Optional list of intended audience URLs.
|
|
96
110
|
issued_at: When the badge was issued.
|
|
97
111
|
expires_at: When the badge expires.
|
|
98
|
-
trust_level: Trust level
|
|
112
|
+
trust_level: Trust level per RFC-002 §5:
|
|
113
|
+
- 0 (SS): Self-Signed - Development only
|
|
114
|
+
- 1 (REG): Registered - Account registration
|
|
115
|
+
- 2 (DV): Domain Validated - DNS/HTTP proof
|
|
116
|
+
- 3 (OV): Organization Validated - Legal entity
|
|
117
|
+
- 4 (EV): Extended Validated - Security audit
|
|
99
118
|
domain: Agent's verified domain.
|
|
100
119
|
agent_name: Human-readable agent name.
|
|
101
120
|
agent_id: Extracted agent ID from subject DID.
|
|
121
|
+
ial: Identity Assurance Level (RFC-002 §7.2.1):
|
|
122
|
+
- "0": Account-attested (no key proof)
|
|
123
|
+
- "1": Proof of Possession (key holder verified, has cnf claim)
|
|
124
|
+
raw_claims: Original JWT claims dict for advanced access.
|
|
102
125
|
"""
|
|
103
126
|
|
|
104
127
|
jti: str
|
|
@@ -110,6 +133,8 @@ class BadgeClaims:
|
|
|
110
133
|
domain: str
|
|
111
134
|
agent_name: str = ""
|
|
112
135
|
audience: List[str] = field(default_factory=list)
|
|
136
|
+
ial: str = "0" # RFC-002 §7.2.1: Default IAL-0 (account-attested)
|
|
137
|
+
raw_claims: Optional[dict] = field(default=None, repr=False) # For advanced access
|
|
113
138
|
|
|
114
139
|
@property
|
|
115
140
|
def agent_id(self) -> str:
|
|
@@ -133,6 +158,11 @@ class BadgeClaims:
|
|
|
133
158
|
@classmethod
|
|
134
159
|
def from_dict(cls, data: dict) -> "BadgeClaims":
|
|
135
160
|
"""Create BadgeClaims from a dictionary."""
|
|
161
|
+
# Handle audience - can be string or list
|
|
162
|
+
aud = data.get("aud", [])
|
|
163
|
+
if isinstance(aud, str):
|
|
164
|
+
aud = [aud] if aud else []
|
|
165
|
+
|
|
136
166
|
return cls(
|
|
137
167
|
jti=data.get("jti", ""),
|
|
138
168
|
issuer=data.get("iss", ""),
|
|
@@ -142,12 +172,18 @@ class BadgeClaims:
|
|
|
142
172
|
trust_level=TrustLevel.from_string(data.get("trust_level", "1")),
|
|
143
173
|
domain=data.get("domain", ""),
|
|
144
174
|
agent_name=data.get("agent_name", ""),
|
|
145
|
-
audience=
|
|
175
|
+
audience=aud,
|
|
176
|
+
ial=data.get("ial", "0"), # RFC-002 §7.2.1
|
|
177
|
+
raw_claims=data, # Preserve for advanced access (cnf, key, etc.)
|
|
146
178
|
)
|
|
147
179
|
|
|
148
180
|
def to_dict(self) -> dict:
|
|
149
|
-
"""Convert to dictionary.
|
|
150
|
-
|
|
181
|
+
"""Convert to dictionary.
|
|
182
|
+
|
|
183
|
+
Preserves the cnf (confirmation) claim for IAL-1 badges to support
|
|
184
|
+
round-trip serialization.
|
|
185
|
+
"""
|
|
186
|
+
result = {
|
|
151
187
|
"jti": self.jti,
|
|
152
188
|
"iss": self.issuer,
|
|
153
189
|
"sub": self.subject,
|
|
@@ -157,7 +193,36 @@ class BadgeClaims:
|
|
|
157
193
|
"domain": self.domain,
|
|
158
194
|
"agent_name": self.agent_name,
|
|
159
195
|
"aud": self.audience,
|
|
196
|
+
"ial": self.ial,
|
|
160
197
|
}
|
|
198
|
+
# Preserve cnf claim for IAL-1 / key-bound badges if it was present
|
|
199
|
+
if self.raw_claims is not None and "cnf" in self.raw_claims:
|
|
200
|
+
result["cnf"] = self.raw_claims["cnf"]
|
|
201
|
+
return result
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def has_key_binding(self) -> bool:
|
|
205
|
+
"""Check if this badge has IAL-1 key binding (ial='1' and cnf claim).
|
|
206
|
+
|
|
207
|
+
Per RFC-002 §7.2.1, IAL-1 badges MUST include a 'cnf' (confirmation) claim
|
|
208
|
+
that cryptographically binds the badge to the agent's private key.
|
|
209
|
+
"""
|
|
210
|
+
return (
|
|
211
|
+
self.ial == "1"
|
|
212
|
+
and self.raw_claims is not None
|
|
213
|
+
and "cnf" in self.raw_claims
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def confirmation_key(self) -> Optional[dict]:
|
|
218
|
+
"""Get the confirmation key (cnf claim) if present.
|
|
219
|
+
|
|
220
|
+
Returns the JWK thumbprint or key from the cnf claim for IAL-1 badges.
|
|
221
|
+
Returns None for IAL-0 badges or if cnf is not present.
|
|
222
|
+
"""
|
|
223
|
+
if self.raw_claims is None:
|
|
224
|
+
return None
|
|
225
|
+
return self.raw_claims.get("cnf")
|
|
161
226
|
|
|
162
227
|
|
|
163
228
|
@dataclass
|
|
@@ -454,7 +519,12 @@ async def request_badge(
|
|
|
454
519
|
ca_url: Certificate Authority URL (default: CapiscIO registry).
|
|
455
520
|
api_key: API key for authentication with the CA.
|
|
456
521
|
domain: Agent's domain (required for verification).
|
|
457
|
-
trust_level: Requested trust level
|
|
522
|
+
trust_level: Requested trust level per RFC-002 §5:
|
|
523
|
+
- 1 (REG): Registered - Account registration
|
|
524
|
+
- 2 (DV): Domain Validated - DNS/HTTP proof
|
|
525
|
+
- 3 (OV): Organization Validated - Legal entity
|
|
526
|
+
- 4 (EV): Extended Validated - Security audit
|
|
527
|
+
Note: LEVEL_0 (Self-Signed) is not available via CA request.
|
|
458
528
|
audience: Optional audience restrictions for the badge.
|
|
459
529
|
timeout: Request timeout in seconds (not used with gRPC).
|
|
460
530
|
|
|
@@ -652,7 +722,7 @@ def start_badge_keeper(
|
|
|
652
722
|
renew_before_seconds: int = 60,
|
|
653
723
|
check_interval_seconds: int = 30,
|
|
654
724
|
trust_level: Union[TrustLevel, str, int] = TrustLevel.LEVEL_1,
|
|
655
|
-
):
|
|
725
|
+
) -> Generator[dict, None, None]:
|
|
656
726
|
"""Start a badge keeper daemon (RFC-002 §7.3).
|
|
657
727
|
|
|
658
728
|
The keeper automatically renews badges before they expire, ensuring
|
|
@@ -672,8 +742,8 @@ def start_badge_keeper(
|
|
|
672
742
|
trust_level: Trust level for CA mode (1-4, default: 1)
|
|
673
743
|
|
|
674
744
|
Yields:
|
|
675
|
-
KeeperEvent dicts with: type, badge_jti, subject, trust_level,
|
|
676
|
-
|
|
745
|
+
dict: KeeperEvent dicts with keys: type, badge_jti, subject, trust_level,
|
|
746
|
+
expires_at, error, error_code, timestamp, token
|
|
677
747
|
|
|
678
748
|
Example:
|
|
679
749
|
# CA mode - production
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""FastAPI integration for Capiscio SimpleGuard."""
|
|
2
|
-
from typing import Callable, Awaitable, Any, Dict
|
|
2
|
+
from typing import Callable, Awaitable, Any, Dict, List, Optional
|
|
3
3
|
try:
|
|
4
4
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
5
5
|
from starlette.requests import Request
|
|
@@ -15,22 +15,34 @@ import time
|
|
|
15
15
|
class CapiscioMiddleware(BaseHTTPMiddleware):
|
|
16
16
|
"""
|
|
17
17
|
Middleware to enforce A2A identity verification on incoming requests.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
app: The ASGI application.
|
|
21
|
+
guard: SimpleGuard instance for verification.
|
|
22
|
+
exclude_paths: List of paths to skip verification (e.g., ["/health", "/.well-known/agent-card.json"]).
|
|
18
23
|
"""
|
|
19
|
-
def __init__(
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
app: ASGIApp,
|
|
27
|
+
guard: SimpleGuard,
|
|
28
|
+
exclude_paths: Optional[List[str]] = None
|
|
29
|
+
) -> None:
|
|
20
30
|
super().__init__(app)
|
|
21
31
|
self.guard = guard
|
|
32
|
+
self.exclude_paths = exclude_paths or []
|
|
22
33
|
|
|
23
34
|
async def dispatch(
|
|
24
35
|
self,
|
|
25
36
|
request: Request,
|
|
26
37
|
call_next: Callable[[Request], Awaitable[Response]]
|
|
27
38
|
) -> Response:
|
|
28
|
-
# Allow
|
|
29
|
-
# For now, we assume everything under /agent/ needs protection
|
|
30
|
-
# But let's just check for the header.
|
|
31
|
-
|
|
39
|
+
# Allow CORS preflight
|
|
32
40
|
if request.method == "OPTIONS":
|
|
33
41
|
return await call_next(request)
|
|
42
|
+
|
|
43
|
+
# Skip verification for excluded paths
|
|
44
|
+
if request.url.path in self.exclude_paths:
|
|
45
|
+
return await call_next(request)
|
|
34
46
|
|
|
35
47
|
# RFC-002 §9.1: X-Capiscio-Badge header
|
|
36
48
|
auth_header = request.headers.get("X-Capiscio-Badge")
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Message validation logic."""
|
|
2
2
|
from typing import TYPE_CHECKING, Any, Dict, List
|
|
3
3
|
from ..types import ValidationResult, ValidationIssue, ValidationSeverity
|
|
4
|
-
|
|
4
|
+
# Import legacy scorer directly to avoid deprecation warning (we only use score_not_tested)
|
|
5
|
+
from ..scoring.availability import AvailabilityScorer as _LegacyAvailabilityScorer
|
|
5
6
|
|
|
6
7
|
if TYPE_CHECKING:
|
|
7
8
|
from ..scoring.types import ComplianceScore
|
|
@@ -19,8 +20,8 @@ class MessageValidator:
|
|
|
19
20
|
def __init__(self) -> None:
|
|
20
21
|
"""Initialize message validator."""
|
|
21
22
|
self._url_validator = URLSecurityValidator()
|
|
22
|
-
|
|
23
|
-
self._availability_scorer =
|
|
23
|
+
# Use legacy scorer directly (not the deprecated wrapper) for score_not_tested()
|
|
24
|
+
self._availability_scorer = _LegacyAvailabilityScorer()
|
|
24
25
|
|
|
25
26
|
def validate(self, message: Dict[str, Any], skip_signature_verification: bool = True) -> ValidationResult:
|
|
26
27
|
"""
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
capiscio_sdk/__init__.py,sha256=
|
|
2
|
-
capiscio_sdk/badge.py,sha256=
|
|
1
|
+
capiscio_sdk/__init__.py,sha256=s4wpl1PgPbYiF75ArKjBNSlkmXPqnzm99YzkWMcx3UY,2683
|
|
2
|
+
capiscio_sdk/badge.py,sha256=nUdsJNtUfSD-wWpIpeyzoM7JsBbkoLpxsQRKQAmtpkE,27446
|
|
3
3
|
capiscio_sdk/badge_keeper.py,sha256=JrxksWiGM7zfKi8BxCqVbVfg4RKEQELH0rUx3Jf-nfg,10900
|
|
4
4
|
capiscio_sdk/config.py,sha256=-1KNubnRE06SENRXcnGvrj-y1EfKz2uAMdz7s0n6ark,3972
|
|
5
5
|
capiscio_sdk/dv.py,sha256=wCHjkWTpeKR0INcrusYG-ub3-rnI-e3JiO0nWgaKoF4,8078
|
|
@@ -9,13 +9,13 @@ capiscio_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
9
9
|
capiscio_sdk/simple_guard.py,sha256=ZOmJQCN0MVud07pqAzFJYf0tYBkwMv2t2zXUKC6IDLg,13717
|
|
10
10
|
capiscio_sdk/types.py,sha256=A_0SLhrbX7apkH5yYGTkG-_284xRjCK0N3lEseVRJRs,7978
|
|
11
11
|
capiscio_sdk/_rpc/__init__.py,sha256=nMnCp5CIisoEA2SX3tYgLprAOyMxVxFfKIWj3glkneA,275
|
|
12
|
-
capiscio_sdk/_rpc/client.py,sha256=
|
|
12
|
+
capiscio_sdk/_rpc/client.py,sha256=8XNDx4TeWzkZcv1oSlyhSuBerUkJuVjt23WsADkwWzo,63639
|
|
13
13
|
capiscio_sdk/_rpc/process.py,sha256=5jyPUDT3Ig1x2UXhAPWPuKqQScAeMkzvs8USshqYrCM,7445
|
|
14
14
|
capiscio_sdk/_rpc/gen/__init__.py,sha256=JgqmySPbQjyx56NVnyIe_BvNdjwWy-or_OcZPomydYE,29
|
|
15
15
|
capiscio_sdk/infrastructure/__init__.py,sha256=5yK7kbk1tcHqlnFIN0lR5N_4ZWWquNivLfHkXOAtso0,152
|
|
16
16
|
capiscio_sdk/infrastructure/cache.py,sha256=FjIRRzNbsPxFZrxdeek51IwFSmR682Wpj1bvvmgqwiU,1841
|
|
17
17
|
capiscio_sdk/infrastructure/rate_limiter.py,sha256=7Q_GfcooWKjioDeB46PpPb00laDmkLpUYGM0Ui8nXqI,3642
|
|
18
|
-
capiscio_sdk/integrations/fastapi.py,sha256=
|
|
18
|
+
capiscio_sdk/integrations/fastapi.py,sha256=EQ28RmG6Gu_DTamMxH0jFZmWW4d6rzvSk4CBkmx070s,3308
|
|
19
19
|
capiscio_sdk/scoring/__init__.py,sha256=ldC3WyM7jbcGjsEWQK_anB7VjG8y9mZMbNaBUwPgRrY,3621
|
|
20
20
|
capiscio_sdk/scoring/availability.py,sha256=CzXA1ED48U1Cc06sh0Mtl_kxZP6af-9cceBumTXQhO8,9130
|
|
21
21
|
capiscio_sdk/scoring/compliance.py,sha256=JZyYuT18A_eiDNdOz-doTIYwW6YhVPvfRj_siNAkkTY,9780
|
|
@@ -25,12 +25,12 @@ capiscio_sdk/validators/__init__.py,sha256=q1UjIFUkztKgfC-5N8mGb-zfET3vsEbKbxtN7
|
|
|
25
25
|
capiscio_sdk/validators/_core.py,sha256=S76QX_KTdqDbuQPy9WxV2Z0jP4o1H65rABWBwp_6R74,13301
|
|
26
26
|
capiscio_sdk/validators/agent_card.py,sha256=d8VfGXvtHiuU0DuC1W64Z_JPVqOVh-nRMVShOAznJbk,18506
|
|
27
27
|
capiscio_sdk/validators/certificate.py,sha256=7RguWk9ahtrxe3TuRqiv5t5vBGta_cUp4qjcx7GwAl0,14373
|
|
28
|
-
capiscio_sdk/validators/message.py,sha256=
|
|
28
|
+
capiscio_sdk/validators/message.py,sha256=vQ8LJGmKL_P_V3oicr15j9dLgwLDjNtUkhbpKngt1ho,15646
|
|
29
29
|
capiscio_sdk/validators/protocol.py,sha256=bkaJJXseulTJ4Sdiio8gE8Q_Pyqj0BRsJe6BGHSQSnA,5377
|
|
30
30
|
capiscio_sdk/validators/semver.py,sha256=mlF3GO5ZPA-w6FzSxhjcr56sgCdS0YVVAd1dUr1bxWs,6385
|
|
31
31
|
capiscio_sdk/validators/signature.py,sha256=lI8XzaKfG_dXSOQXZ40Lda0ntga9EqqC4zAId2kOt6g,8072
|
|
32
32
|
capiscio_sdk/validators/url_security.py,sha256=SdpOrB48hrfgAMuLvpWH2P0LLCJtg6QBohGDIye8f1E,9802
|
|
33
|
-
capiscio_sdk-2.
|
|
34
|
-
capiscio_sdk-2.
|
|
35
|
-
capiscio_sdk-2.
|
|
36
|
-
capiscio_sdk-2.
|
|
33
|
+
capiscio_sdk-2.4.0.dist-info/METADATA,sha256=G2WODrHlhYHQqNdGhGbQxGC4M36kIpeDcQ5VFOdwy_g,17302
|
|
34
|
+
capiscio_sdk-2.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
35
|
+
capiscio_sdk-2.4.0.dist-info/licenses/LICENSE,sha256=AMM_E-ILcCpX0JALqX3BL9yfgSx654BtkhX-CBFYp1Q,10758
|
|
36
|
+
capiscio_sdk-2.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|