agentmesh-platform 1.0.0a1__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.
@@ -0,0 +1,386 @@
1
+ """
2
+ Trust Bridge
3
+
4
+ Unified trust layer across all protocols (A2A, MCP, IATP, ACP).
5
+ Ensures consistent trust model regardless of underlying protocol.
6
+
7
+ Integrates with agent-os IATP module for trust verification.
8
+ """
9
+
10
+ from datetime import datetime
11
+ from typing import Optional, Literal, Any
12
+ from pydantic import BaseModel, Field
13
+ import asyncio
14
+
15
+ from .handshake import TrustHandshake, HandshakeResult
16
+ from .capability import CapabilityScope
17
+
18
+ # Import IATP from agent-os (the source of truth for trust protocol)
19
+ try:
20
+ from modules.iatp import IATPClient, IATPMessage, TrustLevel
21
+ from modules.nexus import NexusClient, ReputationEngine
22
+ AGENT_OS_AVAILABLE = True
23
+ except ImportError:
24
+ # Fallback if agent-os not installed yet (for development)
25
+ AGENT_OS_AVAILABLE = False
26
+ IATPClient = None
27
+ NexusClient = None
28
+
29
+
30
+ class PeerInfo(BaseModel):
31
+ """Information about a peer agent."""
32
+
33
+ peer_did: str
34
+ peer_name: Optional[str] = None
35
+ protocol: str # "a2a", "mcp", "iatp", "acp"
36
+
37
+ # Trust info
38
+ trust_score: int = Field(default=0, ge=0, le=1000)
39
+ trust_verified: bool = False
40
+ last_verified: Optional[datetime] = None
41
+
42
+ # Capabilities
43
+ capabilities: list[str] = Field(default_factory=list)
44
+
45
+ # Connection info
46
+ endpoint: Optional[str] = None
47
+ connected_at: Optional[datetime] = None
48
+
49
+
50
+ class TrustBridge(BaseModel):
51
+ """
52
+ Unified trust bridge for multi-protocol agent communication.
53
+
54
+ The TrustBridge ensures that regardless of which protocol is used
55
+ (A2A, MCP, IATP, ACP), trust verification happens consistently.
56
+ """
57
+
58
+ agent_did: str = Field(..., description="This agent's DID")
59
+
60
+ # Trust thresholds
61
+ default_trust_threshold: int = Field(default=700, ge=0, le=1000)
62
+
63
+ # Known peers
64
+ peers: dict[str, PeerInfo] = Field(default_factory=dict)
65
+
66
+ # Handshake handler
67
+ _handshake: Optional[TrustHandshake] = None
68
+
69
+ model_config = {"arbitrary_types_allowed": True}
70
+
71
+ def __init__(self, **data):
72
+ super().__init__(**data)
73
+ self._handshake = TrustHandshake(agent_did=self.agent_did)
74
+
75
+ async def verify_peer(
76
+ self,
77
+ peer_did: str,
78
+ protocol: str = "iatp",
79
+ required_trust_score: Optional[int] = None,
80
+ required_capabilities: Optional[list[str]] = None,
81
+ ) -> HandshakeResult:
82
+ """
83
+ Verify a peer before communication.
84
+
85
+ This is the core trust gate - all inter-agent communication
86
+ must pass through verification.
87
+
88
+ Args:
89
+ peer_did: The peer's DID
90
+ protocol: Protocol to use for verification
91
+ required_trust_score: Minimum trust score required
92
+ required_capabilities: Capabilities the peer must have
93
+
94
+ Returns:
95
+ HandshakeResult with verification status
96
+ """
97
+ threshold = required_trust_score or self.default_trust_threshold
98
+
99
+ # Perform handshake
100
+ result = await self._handshake.initiate(
101
+ peer_did=peer_did,
102
+ protocol=protocol,
103
+ required_trust_score=threshold,
104
+ required_capabilities=required_capabilities,
105
+ )
106
+
107
+ # Update peer info
108
+ if result.verified:
109
+ self.peers[peer_did] = PeerInfo(
110
+ peer_did=peer_did,
111
+ peer_name=result.peer_name,
112
+ protocol=protocol,
113
+ trust_score=result.trust_score,
114
+ trust_verified=True,
115
+ last_verified=datetime.utcnow(),
116
+ capabilities=result.capabilities,
117
+ )
118
+
119
+ return result
120
+
121
+ async def is_peer_trusted(
122
+ self,
123
+ peer_did: str,
124
+ required_score: Optional[int] = None,
125
+ ) -> bool:
126
+ """Quick check if a peer is trusted."""
127
+ peer = self.peers.get(peer_did)
128
+ if not peer or not peer.trust_verified:
129
+ return False
130
+
131
+ threshold = required_score or self.default_trust_threshold
132
+ return peer.trust_score >= threshold
133
+
134
+ def get_peer(self, peer_did: str) -> Optional[PeerInfo]:
135
+ """Get information about a known peer."""
136
+ return self.peers.get(peer_did)
137
+
138
+ def get_trusted_peers(self, min_score: Optional[int] = None) -> list[PeerInfo]:
139
+ """Get all peers meeting trust threshold."""
140
+ threshold = min_score or self.default_trust_threshold
141
+ return [
142
+ peer for peer in self.peers.values()
143
+ if peer.trust_verified and peer.trust_score >= threshold
144
+ ]
145
+
146
+ async def revoke_peer_trust(self, peer_did: str, reason: str) -> bool:
147
+ """Revoke trust for a peer."""
148
+ if peer_did in self.peers:
149
+ self.peers[peer_did].trust_verified = False
150
+ self.peers[peer_did].trust_score = 0
151
+ return True
152
+ return False
153
+
154
+
155
+ class ProtocolBridge(BaseModel):
156
+ """
157
+ Protocol translation layer.
158
+
159
+ Translates between A2A, MCP, IATP, and ACP transparently,
160
+ maintaining trust guarantees across protocol boundaries.
161
+ """
162
+
163
+ agent_did: str
164
+ trust_bridge: Optional[TrustBridge] = None
165
+
166
+ # Protocol handlers
167
+ supported_protocols: list[str] = Field(
168
+ default=["a2a", "mcp", "iatp", "acp"]
169
+ )
170
+
171
+ model_config = {"arbitrary_types_allowed": True}
172
+
173
+ def __init__(self, **data):
174
+ super().__init__(**data)
175
+ if not self.trust_bridge:
176
+ self.trust_bridge = TrustBridge(agent_did=self.agent_did)
177
+
178
+ async def send_message(
179
+ self,
180
+ peer_did: str,
181
+ message: Any,
182
+ source_protocol: str,
183
+ target_protocol: Optional[str] = None,
184
+ ) -> Any:
185
+ """
186
+ Send a message to a peer, translating protocols if needed.
187
+
188
+ Args:
189
+ peer_did: Target peer's DID
190
+ message: Message to send
191
+ source_protocol: Protocol the message is in
192
+ target_protocol: Protocol to send as (auto-detect if None)
193
+ """
194
+ # Verify trust first
195
+ if not await self.trust_bridge.is_peer_trusted(peer_did):
196
+ result = await self.trust_bridge.verify_peer(peer_did, source_protocol)
197
+ if not result.verified:
198
+ raise PermissionError(f"Peer not trusted: {peer_did}")
199
+
200
+ peer = self.trust_bridge.get_peer(peer_did)
201
+ dest_protocol = target_protocol or peer.protocol
202
+
203
+ # Translate if needed
204
+ if source_protocol != dest_protocol:
205
+ message = await self._translate(message, source_protocol, dest_protocol)
206
+
207
+ # Send via appropriate handler
208
+ return await self._send(peer_did, message, dest_protocol)
209
+
210
+ async def _translate(
211
+ self,
212
+ message: Any,
213
+ from_protocol: str,
214
+ to_protocol: str,
215
+ ) -> Any:
216
+ """Translate message between protocols."""
217
+ # Protocol translation mappings
218
+ if from_protocol == "a2a" and to_protocol == "mcp":
219
+ return self._a2a_to_mcp(message)
220
+ elif from_protocol == "mcp" and to_protocol == "a2a":
221
+ return self._mcp_to_a2a(message)
222
+ elif from_protocol == "iatp":
223
+ # IATP can wrap any protocol
224
+ return message
225
+ else:
226
+ # Default: pass through
227
+ return message
228
+
229
+ def _a2a_to_mcp(self, message: dict) -> dict:
230
+ """Convert A2A message to MCP format."""
231
+ # A2A task -> MCP tool call
232
+ return {
233
+ "method": "tools/call",
234
+ "params": {
235
+ "name": message.get("task_type", "execute"),
236
+ "arguments": message.get("parameters", {}),
237
+ },
238
+ }
239
+
240
+ def _mcp_to_a2a(self, message: dict) -> dict:
241
+ """Convert MCP message to A2A format."""
242
+ # MCP tool call -> A2A task
243
+ params = message.get("params", {})
244
+ return {
245
+ "task_type": params.get("name", "execute"),
246
+ "parameters": params.get("arguments", {}),
247
+ }
248
+
249
+ async def _send(self, peer_did: str, message: Any, protocol: str) -> Any:
250
+ """Send message via protocol handler."""
251
+ # In production, would dispatch to actual protocol handlers
252
+ # For now, return a placeholder
253
+ return {
254
+ "status": "sent",
255
+ "peer": peer_did,
256
+ "protocol": protocol,
257
+ }
258
+
259
+ def get_protocol_for_peer(self, peer_did: str) -> Optional[str]:
260
+ """Get the preferred protocol for a peer."""
261
+ peer = self.trust_bridge.get_peer(peer_did)
262
+ return peer.protocol if peer else None
263
+
264
+
265
+ class A2AAdapter:
266
+ """
267
+ Adapter for Google A2A (Agent-to-Agent) protocol.
268
+
269
+ Supports:
270
+ - Agent Card discovery
271
+ - Task lifecycle management
272
+ - Collaboration messaging
273
+ """
274
+
275
+ def __init__(self, agent_did: str, trust_bridge: TrustBridge):
276
+ self.agent_did = agent_did
277
+ self.trust_bridge = trust_bridge
278
+
279
+ async def discover_agent(self, endpoint: str) -> Optional[dict]:
280
+ """
281
+ Discover an agent via A2A Agent Card.
282
+
283
+ GET /.well-known/agent.json
284
+ """
285
+ # Would make HTTP request in production
286
+ return {
287
+ "name": "discovered-agent",
288
+ "description": "An A2A-compatible agent",
289
+ "capabilities": ["task/execute"],
290
+ }
291
+
292
+ async def create_task(
293
+ self,
294
+ peer_did: str,
295
+ task_type: str,
296
+ parameters: dict,
297
+ ) -> dict:
298
+ """Create a task on a peer agent."""
299
+ # Verify trust
300
+ if not await self.trust_bridge.is_peer_trusted(peer_did):
301
+ raise PermissionError("Peer not trusted")
302
+
303
+ return {
304
+ "task_id": f"task_{peer_did}_{datetime.utcnow().timestamp()}",
305
+ "status": "created",
306
+ "type": task_type,
307
+ }
308
+
309
+ async def get_task_status(self, peer_did: str, task_id: str) -> dict:
310
+ """Get status of a task."""
311
+ return {
312
+ "task_id": task_id,
313
+ "status": "running",
314
+ }
315
+
316
+
317
+ class MCPAdapter:
318
+ """
319
+ Adapter for Anthropic MCP (Model Context Protocol).
320
+
321
+ Supports:
322
+ - Tool registration
323
+ - Resource binding
324
+ - Governed tool invocation
325
+
326
+ All MCP tool calls route through AgentMesh policy engine.
327
+ """
328
+
329
+ def __init__(self, agent_did: str, trust_bridge: TrustBridge):
330
+ self.agent_did = agent_did
331
+ self.trust_bridge = trust_bridge
332
+ self._registered_tools: dict[str, dict] = {}
333
+
334
+ def register_tool(
335
+ self,
336
+ name: str,
337
+ description: str,
338
+ input_schema: dict,
339
+ required_capability: Optional[str] = None,
340
+ ) -> None:
341
+ """Register a tool with the MCP adapter."""
342
+ self._registered_tools[name] = {
343
+ "name": name,
344
+ "description": description,
345
+ "inputSchema": input_schema,
346
+ "required_capability": required_capability,
347
+ }
348
+
349
+ async def call_tool(
350
+ self,
351
+ peer_did: str,
352
+ tool_name: str,
353
+ arguments: dict,
354
+ ) -> dict:
355
+ """
356
+ Call a tool on a peer, with governance.
357
+
358
+ Unlike raw MCP, this:
359
+ 1. Verifies peer trust
360
+ 2. Checks capability scope
361
+ 3. Logs for audit
362
+ """
363
+ # Verify trust
364
+ if not await self.trust_bridge.is_peer_trusted(peer_did):
365
+ raise PermissionError("Peer not trusted for MCP tool call")
366
+
367
+ peer = self.trust_bridge.get_peer(peer_did)
368
+
369
+ # Check capability if tool requires one
370
+ tool = self._registered_tools.get(tool_name)
371
+ if tool and tool.get("required_capability"):
372
+ if tool["required_capability"] not in peer.capabilities:
373
+ raise PermissionError(
374
+ f"Peer lacks capability: {tool['required_capability']}"
375
+ )
376
+
377
+ # Execute (would actually call MCP in production)
378
+ return {
379
+ "tool": tool_name,
380
+ "result": "success",
381
+ "governed": True,
382
+ }
383
+
384
+ def list_tools(self) -> list[dict]:
385
+ """List all registered tools."""
386
+ return list(self._registered_tools.values())
@@ -0,0 +1,293 @@
1
+ """
2
+ Capability Scoping
3
+
4
+ Capability-scoped credential issuance per tool/resource per agent.
5
+ Agents cannot access any resource not explicitly in their credential scope.
6
+ """
7
+
8
+ from datetime import datetime
9
+ from typing import Optional, Literal
10
+ from pydantic import BaseModel, Field
11
+ import hashlib
12
+ import uuid
13
+
14
+
15
+ class CapabilityGrant(BaseModel):
16
+ """
17
+ A specific capability grant to an agent.
18
+
19
+ Capabilities follow the format: action:resource[:qualifier]
20
+ Examples:
21
+ - read:data
22
+ - write:reports
23
+ - execute:tools:calculator
24
+ - admin:*
25
+ """
26
+
27
+ grant_id: str = Field(default_factory=lambda: f"grant_{uuid.uuid4().hex[:12]}")
28
+
29
+ # Capability specification
30
+ capability: str = Field(..., description="Capability string (e.g., 'read:data')")
31
+ action: str = Field(..., description="Action part (e.g., 'read')")
32
+ resource: str = Field(..., description="Resource part (e.g., 'data')")
33
+ qualifier: Optional[str] = Field(None, description="Optional qualifier")
34
+
35
+ # Grant metadata
36
+ granted_to: str = Field(..., description="DID of grantee")
37
+ granted_by: str = Field(..., description="DID of grantor")
38
+
39
+ # Scope restrictions
40
+ resource_ids: list[str] = Field(
41
+ default_factory=list,
42
+ description="Specific resource IDs this grant applies to"
43
+ )
44
+ conditions: dict = Field(
45
+ default_factory=dict,
46
+ description="Additional conditions for this grant"
47
+ )
48
+
49
+ # Timing
50
+ granted_at: datetime = Field(default_factory=datetime.utcnow)
51
+ expires_at: Optional[datetime] = Field(None)
52
+
53
+ # Status
54
+ active: bool = Field(default=True)
55
+ revoked_at: Optional[datetime] = Field(None)
56
+
57
+ @classmethod
58
+ def parse_capability(cls, capability: str) -> tuple[str, str, Optional[str]]:
59
+ """Parse a capability string into components."""
60
+ parts = capability.split(":")
61
+ if len(parts) < 2:
62
+ raise ValueError(f"Invalid capability format: {capability}")
63
+
64
+ action = parts[0]
65
+ resource = parts[1]
66
+ qualifier = parts[2] if len(parts) > 2 else None
67
+
68
+ return action, resource, qualifier
69
+
70
+ @classmethod
71
+ def create(
72
+ cls,
73
+ capability: str,
74
+ granted_to: str,
75
+ granted_by: str,
76
+ resource_ids: Optional[list[str]] = None,
77
+ expires_at: Optional[datetime] = None,
78
+ ) -> "CapabilityGrant":
79
+ """Create a new capability grant."""
80
+ action, resource, qualifier = cls.parse_capability(capability)
81
+
82
+ return cls(
83
+ capability=capability,
84
+ action=action,
85
+ resource=resource,
86
+ qualifier=qualifier,
87
+ granted_to=granted_to,
88
+ granted_by=granted_by,
89
+ resource_ids=resource_ids or [],
90
+ expires_at=expires_at,
91
+ )
92
+
93
+ def is_valid(self) -> bool:
94
+ """Check if grant is currently valid."""
95
+ if not self.active:
96
+ return False
97
+ if self.expires_at and datetime.utcnow() > self.expires_at:
98
+ return False
99
+ return True
100
+
101
+ def matches(self, requested: str, resource_id: Optional[str] = None) -> bool:
102
+ """
103
+ Check if this grant satisfies a requested capability.
104
+
105
+ Supports:
106
+ - Exact match: read:data matches read:data
107
+ - Wildcard: read:* matches read:data
108
+ - Resource scoping: if resource_ids set, must match
109
+ """
110
+ if not self.is_valid():
111
+ return False
112
+
113
+ req_action, req_resource, req_qualifier = self.parse_capability(requested)
114
+
115
+ # Check action
116
+ if self.action != "*" and self.action != req_action:
117
+ return False
118
+
119
+ # Check resource
120
+ if self.resource != "*" and self.resource != req_resource:
121
+ return False
122
+
123
+ # Check qualifier if present
124
+ if req_qualifier and self.qualifier:
125
+ if self.qualifier != "*" and self.qualifier != req_qualifier:
126
+ return False
127
+
128
+ # Check resource ID if scoped
129
+ if self.resource_ids and resource_id:
130
+ if resource_id not in self.resource_ids:
131
+ return False
132
+
133
+ return True
134
+
135
+ def revoke(self) -> None:
136
+ """Revoke this grant."""
137
+ self.active = False
138
+ self.revoked_at = datetime.utcnow()
139
+
140
+
141
+ class CapabilityScope(BaseModel):
142
+ """
143
+ Complete capability scope for an agent.
144
+
145
+ Aggregates all grants and provides capability checking.
146
+ """
147
+
148
+ agent_did: str
149
+ grants: list[CapabilityGrant] = Field(default_factory=list)
150
+
151
+ # Denied capabilities (blocklist)
152
+ denied: list[str] = Field(default_factory=list)
153
+
154
+ def add_grant(self, grant: CapabilityGrant) -> None:
155
+ """Add a capability grant."""
156
+ if grant.granted_to != self.agent_did:
157
+ raise ValueError("Grant is for different agent")
158
+ self.grants.append(grant)
159
+
160
+ def has_capability(
161
+ self,
162
+ capability: str,
163
+ resource_id: Optional[str] = None,
164
+ ) -> bool:
165
+ """
166
+ Check if agent has a capability.
167
+
168
+ Checks:
169
+ 1. Not in denied list
170
+ 2. Has matching grant
171
+ 3. Grant is valid (not expired, not revoked)
172
+ """
173
+ # Check denied first
174
+ if capability in self.denied:
175
+ return False
176
+
177
+ # Check for matching grant
178
+ for grant in self.grants:
179
+ if grant.matches(capability, resource_id):
180
+ return True
181
+
182
+ return False
183
+
184
+ def get_capabilities(self) -> list[str]:
185
+ """Get list of all active capabilities."""
186
+ capabilities = set()
187
+ for grant in self.grants:
188
+ if grant.is_valid():
189
+ capabilities.add(grant.capability)
190
+ return list(capabilities)
191
+
192
+ def filter_capabilities(self, requested: list[str]) -> list[str]:
193
+ """Filter a list of requested capabilities to only those allowed."""
194
+ return [cap for cap in requested if self.has_capability(cap)]
195
+
196
+ def deny(self, capability: str) -> None:
197
+ """Add a capability to the deny list."""
198
+ if capability not in self.denied:
199
+ self.denied.append(capability)
200
+
201
+ def revoke_all(self) -> int:
202
+ """Revoke all grants. Returns count of revoked grants."""
203
+ count = 0
204
+ for grant in self.grants:
205
+ if grant.active:
206
+ grant.revoke()
207
+ count += 1
208
+ return count
209
+
210
+ def revoke_from(self, grantor_did: str) -> int:
211
+ """Revoke all grants from a specific grantor."""
212
+ count = 0
213
+ for grant in self.grants:
214
+ if grant.active and grant.granted_by == grantor_did:
215
+ grant.revoke()
216
+ count += 1
217
+ return count
218
+
219
+ def cleanup_expired(self) -> int:
220
+ """Remove expired and revoked grants. Returns count removed."""
221
+ before = len(self.grants)
222
+ self.grants = [g for g in self.grants if g.is_valid()]
223
+ return before - len(self.grants)
224
+
225
+
226
+ class CapabilityRegistry:
227
+ """
228
+ Central registry for capability grants.
229
+
230
+ Tracks who has what capabilities across the mesh.
231
+ """
232
+
233
+ def __init__(self):
234
+ self._scopes: dict[str, CapabilityScope] = {}
235
+ self._grants_by_grantor: dict[str, list[str]] = {} # grantor -> [grant_ids]
236
+
237
+ def get_scope(self, agent_did: str) -> CapabilityScope:
238
+ """Get or create capability scope for an agent."""
239
+ if agent_did not in self._scopes:
240
+ self._scopes[agent_did] = CapabilityScope(agent_did=agent_did)
241
+ return self._scopes[agent_did]
242
+
243
+ def grant(
244
+ self,
245
+ capability: str,
246
+ to_agent: str,
247
+ from_agent: str,
248
+ resource_ids: Optional[list[str]] = None,
249
+ ) -> CapabilityGrant:
250
+ """Grant a capability to an agent."""
251
+ grant = CapabilityGrant.create(
252
+ capability=capability,
253
+ granted_to=to_agent,
254
+ granted_by=from_agent,
255
+ resource_ids=resource_ids,
256
+ )
257
+
258
+ scope = self.get_scope(to_agent)
259
+ scope.add_grant(grant)
260
+
261
+ # Track by grantor
262
+ if from_agent not in self._grants_by_grantor:
263
+ self._grants_by_grantor[from_agent] = []
264
+ self._grants_by_grantor[from_agent].append(grant.grant_id)
265
+
266
+ return grant
267
+
268
+ def check(
269
+ self,
270
+ agent_did: str,
271
+ capability: str,
272
+ resource_id: Optional[str] = None,
273
+ ) -> bool:
274
+ """Check if an agent has a capability."""
275
+ scope = self._scopes.get(agent_did)
276
+ if not scope:
277
+ return False
278
+ return scope.has_capability(capability, resource_id)
279
+
280
+ def revoke_all_from(self, grantor_did: str) -> int:
281
+ """Revoke all grants made by a grantor (e.g., when grantor is compromised)."""
282
+ count = 0
283
+ for scope in self._scopes.values():
284
+ count += scope.revoke_from(grantor_did)
285
+ return count
286
+
287
+ def get_agents_with_capability(self, capability: str) -> list[str]:
288
+ """Get all agents that have a specific capability."""
289
+ result = []
290
+ for agent_did, scope in self._scopes.items():
291
+ if scope.has_capability(capability):
292
+ result.append(agent_did)
293
+ return result