agentmesh-platform 1.0.0a1__py3-none-any.whl → 1.0.0a2__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.
agentmesh/__init__.py CHANGED
@@ -15,29 +15,25 @@ __version__ = "1.0.0-alpha"
15
15
  from .identity import (
16
16
  AgentIdentity,
17
17
  AgentDID,
18
- IdentityRegistry,
19
18
  Credential,
20
19
  CredentialManager,
21
20
  DelegationChain,
22
21
  DelegationLink,
23
22
  HumanSponsor,
24
- SponsorRegistry,
25
23
  RiskScorer,
26
24
  RiskScore,
27
25
  SPIFFEIdentity,
26
+ SVID,
28
27
  )
29
28
 
30
29
  # Layer 2: Trust & Protocol Bridge
31
30
  from .trust import (
32
31
  TrustBridge,
33
32
  ProtocolBridge,
34
- A2AAdapter,
35
- MCPAdapter,
36
33
  TrustHandshake,
37
34
  HandshakeResult,
38
35
  CapabilityScope,
39
36
  CapabilityGrant,
40
- CapabilityRegistry,
41
37
  )
42
38
 
43
39
  # Layer 3: Governance & Compliance Plane
@@ -45,10 +41,10 @@ from .governance import (
45
41
  PolicyEngine,
46
42
  Policy,
47
43
  PolicyRule,
48
- PolicyResult,
44
+ PolicyDecision,
49
45
  ComplianceEngine,
50
46
  ComplianceFramework,
51
- ComplianceControl,
47
+ ComplianceReport,
52
48
  AuditLog,
53
49
  AuditEntry,
54
50
  MerkleAuditChain,
@@ -73,22 +69,19 @@ __all__ = [
73
69
  # Layer 1: Identity
74
70
  "AgentIdentity",
75
71
  "AgentDID",
76
- "IdentityRegistry",
77
72
  "Credential",
78
73
  "CredentialManager",
79
74
  "DelegationChain",
80
75
  "DelegationLink",
81
76
  "HumanSponsor",
82
- "SponsorRegistry",
83
77
  "RiskScorer",
84
78
  "RiskScore",
85
79
  "SPIFFEIdentity",
80
+ "SVID",
86
81
 
87
82
  # Layer 2: Trust
88
83
  "TrustBridge",
89
84
  "ProtocolBridge",
90
- "A2AAdapter",
91
- "MCPAdapter",
92
85
  "TrustHandshake",
93
86
  "HandshakeResult",
94
87
  "CapabilityScope",
@@ -99,10 +92,10 @@ __all__ = [
99
92
  "PolicyEngine",
100
93
  "Policy",
101
94
  "PolicyRule",
102
- "PolicyResult",
95
+ "PolicyDecision",
103
96
  "ComplianceEngine",
104
97
  "ComplianceFramework",
105
- "ComplianceControl",
98
+ "ComplianceReport",
106
99
  "AuditLog",
107
100
  "AuditEntry",
108
101
  "MerkleAuditChain",
agentmesh/cli/main.py CHANGED
@@ -3,6 +3,7 @@ AgentMesh CLI - Main Entry Point
3
3
 
4
4
  Commands:
5
5
  - init: Scaffold a governed agent in 30 seconds
6
+ - proxy: Start an MCP proxy with governance
6
7
  - register: Register an agent with AgentMesh
7
8
  - run: Run a governed agent
8
9
  - status: Check agent status and trust score
@@ -16,6 +17,7 @@ from rich.table import Table
16
17
  from rich.panel import Panel
17
18
  from rich import box
18
19
  from pathlib import Path
20
+ from typing import Optional
19
21
  import json
20
22
  import yaml
21
23
  import os
@@ -396,6 +398,135 @@ def audit(agent: str, limit: int, fmt: str):
396
398
  console.print(table)
397
399
 
398
400
 
401
+ # Import proxy command from proxy module
402
+ from .proxy import proxy
403
+ app.add_command(proxy)
404
+
405
+
406
+ @app.command()
407
+ @click.option("--claude", is_flag=True, help="Generate Claude Desktop config")
408
+ @click.option("--config-path", type=click.Path(), help="Path to claude_desktop_config.json")
409
+ @click.option("--backup/--no-backup", default=True, help="Backup existing config")
410
+ def init_integration(claude: bool, config_path: str, backup: bool):
411
+ """
412
+ Initialize AgentMesh integration with existing tools.
413
+
414
+ Examples:
415
+
416
+ # Setup Claude Desktop to use AgentMesh proxy
417
+ agentmesh init-integration --claude
418
+
419
+ # Specify custom config path
420
+ agentmesh init-integration --claude --config-path ~/custom/config.json
421
+ """
422
+ if claude:
423
+ _init_claude_integration(config_path, backup)
424
+ else:
425
+ console.print("[yellow]Please specify an integration type (e.g., --claude)[/yellow]")
426
+
427
+
428
+ def _init_claude_integration(config_path: Optional[str], backup: bool):
429
+ """Initialize Claude Desktop integration."""
430
+ console.print("\n[bold blue]🔧 Setting up Claude Desktop Integration[/bold blue]\n")
431
+
432
+ # Determine config path
433
+ if not config_path:
434
+ # Default Claude Desktop config locations
435
+ import platform
436
+ system = platform.system()
437
+
438
+ if system == "Darwin": # macOS
439
+ default_path = Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json"
440
+ elif system == "Windows":
441
+ default_path = Path.home() / "AppData" / "Roaming" / "Claude" / "claude_desktop_config.json"
442
+ else: # Linux
443
+ default_path = Path.home() / ".config" / "claude" / "claude_desktop_config.json"
444
+
445
+ config_path = default_path
446
+ else:
447
+ config_path = Path(config_path)
448
+
449
+ console.print(f"Config path: {config_path}")
450
+
451
+ # Check if config exists
452
+ if not config_path.exists():
453
+ console.print(f"[yellow]Config file not found at {config_path}[/yellow]")
454
+ console.print("Creating new config file...")
455
+ config_path.parent.mkdir(parents=True, exist_ok=True)
456
+ config = {"mcpServers": {}}
457
+ else:
458
+ # Backup existing config
459
+ if backup:
460
+ backup_path = config_path.with_suffix(".json.backup")
461
+ import shutil
462
+ shutil.copy(config_path, backup_path)
463
+ console.print(f"[dim]✓ Backed up existing config to {backup_path}[/dim]")
464
+
465
+ # Load existing config
466
+ with open(config_path) as f:
467
+ config = json.load(f)
468
+
469
+ # Ensure mcpServers exists
470
+ if "mcpServers" not in config:
471
+ config["mcpServers"] = {}
472
+
473
+ # Add example AgentMesh proxy configuration
474
+ example_server = {
475
+ "filesystem-protected": {
476
+ "command": "agentmesh",
477
+ "args": [
478
+ "proxy",
479
+ "--target", "npx",
480
+ "--target", "-y",
481
+ "--target", "@modelcontextprotocol/server-filesystem",
482
+ "--target", str(Path.home())
483
+ ],
484
+ "env": {},
485
+ }
486
+ }
487
+
488
+ # Check if already configured
489
+ has_agentmesh = any(
490
+ "agentmesh" in str(server.get("command", ""))
491
+ for server in config["mcpServers"].values()
492
+ )
493
+
494
+ if not has_agentmesh:
495
+ config["mcpServers"].update(example_server)
496
+ console.print("\n[green]✓ Added AgentMesh-protected filesystem server example[/green]")
497
+ else:
498
+ console.print("\n[yellow]⚠️ AgentMesh proxy already configured[/yellow]")
499
+
500
+ # Save updated config
501
+ with open(config_path, "w") as f:
502
+ json.dump(config, f, indent=2)
503
+
504
+ console.print(f"\n[green]✓ Updated {config_path}[/green]")
505
+
506
+ # Show instructions
507
+ console.print()
508
+ console.print(Panel(
509
+ """[bold]Next Steps:[/bold]
510
+
511
+ 1. Restart Claude Desktop
512
+ 2. AgentMesh will now intercept all tool calls to the protected server
513
+ 3. View logs in the terminal where Claude Desktop was launched
514
+
515
+ [bold]Customization:[/bold]
516
+ Edit {path} to:
517
+ - Add more protected servers
518
+ - Change policy level (--policy strict|moderate|permissive)
519
+ - Disable verification footers (--no-footer)
520
+
521
+ [bold]Example Usage:[/bold]
522
+ In Claude Desktop, try: "Read the contents of my home directory"
523
+ AgentMesh will enforce policies and add trust verification to outputs.
524
+ """.format(path=config_path),
525
+ title="🎉 Claude Desktop Integration Ready",
526
+ border_style="green",
527
+ ))
528
+
529
+
399
530
  def main():
400
531
  """Main entry point."""
401
532
  app()
agentmesh/cli/proxy.py ADDED
@@ -0,0 +1,448 @@
1
+ """
2
+ AgentMesh MCP Proxy
3
+
4
+ A transparent proxy for MCP (Model Context Protocol) servers that adds:
5
+ - Policy enforcement on tool calls
6
+ - Trust score tracking
7
+ - Audit logging
8
+ - Verification footers
9
+
10
+ Usage:
11
+ agentmesh proxy --target npx -y @modelcontextprotocol/server-filesystem /path
12
+ """
13
+
14
+ import asyncio
15
+ import json
16
+ import sys
17
+ from datetime import datetime
18
+ from pathlib import Path
19
+ from typing import Any, Dict, Optional, List
20
+ import subprocess
21
+
22
+ import click
23
+ from rich.console import Console
24
+
25
+ from agentmesh import PolicyEngine, AuditLog, RewardEngine
26
+ from agentmesh.identity import AgentIdentity
27
+
28
+ console = Console(stderr=True) # Use stderr for logs to keep stdout clean for MCP
29
+
30
+
31
+ class MCPProxy:
32
+ """
33
+ MCP Proxy that intercepts tool calls and enforces governance.
34
+
35
+ Sits between an MCP client (like Claude Desktop) and an MCP server,
36
+ intercepting JSON-RPC messages and enforcing policies.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ target_command: List[str],
42
+ policy: str = "strict",
43
+ identity_name: str = "mcp-proxy",
44
+ enable_footer: bool = True,
45
+ ):
46
+ """
47
+ Initialize the MCP proxy.
48
+
49
+ Args:
50
+ target_command: Command to spawn the target MCP server
51
+ policy: Policy level (strict, moderate, permissive)
52
+ identity_name: Name for the proxy agent identity
53
+ enable_footer: Whether to add verification footers to outputs
54
+ """
55
+ self.target_command = target_command
56
+ self.policy_level = policy
57
+ self.enable_footer = enable_footer
58
+
59
+ # Create proxy identity
60
+ console.print(f"[dim]🔐 Initializing AgentMesh proxy identity...[/dim]")
61
+ self.identity = AgentIdentity.create(
62
+ name=identity_name,
63
+ sponsor="proxy@agentmesh.ai",
64
+ capabilities=["tool:*"]
65
+ )
66
+
67
+ # Initialize governance components
68
+ self.policy_engine = PolicyEngine()
69
+ self._load_default_policies()
70
+
71
+ self.audit_log = AuditLog()
72
+
73
+ self.reward_engine = RewardEngine()
74
+ self.trust_score = 800 # Starting score
75
+
76
+ # Process handle
77
+ self.target_process: Optional[subprocess.Popen] = None
78
+
79
+ console.print(f"[dim]✓ Proxy initialized with trust score: {self.trust_score}/1000[/dim]")
80
+
81
+ def _load_default_policies(self):
82
+ """Load default policies based on policy level."""
83
+ if self.policy_level == "strict":
84
+ policy_yaml = """
85
+ version: "1.0"
86
+ name: "strict-mcp-policy"
87
+ description: "Strict policy for MCP tool calls"
88
+ agents: ["*"]
89
+ default_action: "deny"
90
+ rules:
91
+ - name: "block-etc-access"
92
+ description: "Block access to /etc"
93
+ condition: "action.path == '/etc/passwd' or action.path == '/etc/shadow'"
94
+ action: "deny"
95
+ priority: 100
96
+ enabled: true
97
+
98
+ - name: "block-root-access"
99
+ description: "Block access to /root"
100
+ condition: "action.path == '/root/.ssh'"
101
+ action: "deny"
102
+ priority: 100
103
+ enabled: true
104
+
105
+ - name: "block-dangerous-filesystem-ops"
106
+ description: "Block dangerous filesystem operations"
107
+ condition: "action.tool == 'filesystem_write' or action.tool == 'filesystem_delete'"
108
+ action: "deny"
109
+ priority: 90
110
+ enabled: true
111
+
112
+ - name: "allow-read-operations"
113
+ description: "Allow filesystem read operations"
114
+ condition: "action.tool == 'filesystem_read'"
115
+ action: "allow"
116
+ priority: 50
117
+ enabled: true
118
+ """
119
+ elif self.policy_level == "moderate":
120
+ policy_yaml = """
121
+ version: "1.0"
122
+ name: "moderate-mcp-policy"
123
+ description: "Moderate policy for MCP tool calls"
124
+ agents: ["*"]
125
+ default_action: "allow"
126
+ rules:
127
+ - name: "warn-on-write"
128
+ description: "Warn on write operations"
129
+ condition: "action.tool == 'filesystem_write'"
130
+ action: "warn"
131
+ priority: 50
132
+ enabled: true
133
+ """
134
+ else: # permissive
135
+ policy_yaml = """
136
+ version: "1.0"
137
+ name: "permissive-mcp-policy"
138
+ description: "Permissive policy for MCP tool calls"
139
+ agents: ["*"]
140
+ default_action: "allow"
141
+ rules: []
142
+ """
143
+
144
+ try:
145
+ self.policy_engine.load_yaml(policy_yaml)
146
+ except Exception as e:
147
+ console.print(f"[yellow]⚠️ Warning: Could not load policy: {e}[/yellow]")
148
+
149
+ async def start(self):
150
+ """Start the proxy server."""
151
+ console.print(f"[dim]🚀 Starting MCP proxy...[/dim]")
152
+ console.print(f"[dim] Target: {' '.join(self.target_command)}[/dim]")
153
+ console.print(f"[dim] Policy: {self.policy_level}[/dim]")
154
+ console.print(f"[dim] Agent DID: {self.identity.did}[/dim]")
155
+
156
+ # Start target MCP server as subprocess
157
+ self.target_process = subprocess.Popen(
158
+ self.target_command,
159
+ stdin=subprocess.PIPE,
160
+ stdout=subprocess.PIPE,
161
+ stderr=subprocess.PIPE,
162
+ bufsize=0,
163
+ )
164
+
165
+ console.print(f"[dim]✓ Target server started (PID: {self.target_process.pid})[/dim]")
166
+ console.print(f"[dim]🔒 AgentMesh governance active[/dim]")
167
+
168
+ # Start message handling loops
169
+ read_task = asyncio.create_task(self._read_from_target())
170
+ write_task = asyncio.create_task(self._read_from_client())
171
+
172
+ try:
173
+ await asyncio.gather(read_task, write_task)
174
+ except KeyboardInterrupt:
175
+ console.print("\n[dim]🛑 Shutting down proxy...[/dim]")
176
+ finally:
177
+ if self.target_process:
178
+ self.target_process.terminate()
179
+ self.target_process.wait()
180
+
181
+ async def _read_from_client(self):
182
+ """Read JSON-RPC messages from stdin (MCP client)."""
183
+ loop = asyncio.get_event_loop()
184
+
185
+ while True:
186
+ try:
187
+ # Read a line from stdin
188
+ line = await loop.run_in_executor(None, sys.stdin.readline)
189
+ if not line:
190
+ break
191
+
192
+ # Parse JSON-RPC message
193
+ try:
194
+ message = json.loads(line.strip())
195
+ except json.JSONDecodeError:
196
+ # Not a JSON message, pass through
197
+ self._write_to_target(line)
198
+ continue
199
+
200
+ # Intercept tool calls
201
+ if message.get("method") == "tools/call":
202
+ message = await self._handle_tool_call(message)
203
+
204
+ # Forward to target
205
+ self._write_to_target(json.dumps(message) + "\n")
206
+
207
+ except Exception as e:
208
+ console.print(f"[red]Error reading from client: {e}[/red]")
209
+ break
210
+
211
+ async def _read_from_target(self):
212
+ """Read responses from target MCP server."""
213
+ loop = asyncio.get_event_loop()
214
+
215
+ while True:
216
+ try:
217
+ # Read from target stdout
218
+ line = await loop.run_in_executor(
219
+ None,
220
+ self.target_process.stdout.readline
221
+ )
222
+
223
+ if not line:
224
+ break
225
+
226
+ # Parse JSON-RPC response
227
+ try:
228
+ message = json.loads(line.decode().strip())
229
+
230
+ # Add verification footer if enabled
231
+ if self.enable_footer and "result" in message:
232
+ message = self._add_verification_footer(message)
233
+
234
+ sys.stdout.write(json.dumps(message) + "\n")
235
+ sys.stdout.flush()
236
+ except json.JSONDecodeError:
237
+ # Not JSON, pass through
238
+ sys.stdout.write(line.decode())
239
+ sys.stdout.flush()
240
+
241
+ except Exception as e:
242
+ console.print(f"[red]Error reading from target: {e}[/red]")
243
+ break
244
+
245
+ def _write_to_target(self, data: str):
246
+ """Write data to target server stdin."""
247
+ try:
248
+ self.target_process.stdin.write(data.encode())
249
+ self.target_process.stdin.flush()
250
+ except Exception as e:
251
+ console.print(f"[red]Error writing to target: {e}[/red]")
252
+
253
+ async def _handle_tool_call(self, message: Dict[str, Any]) -> Dict[str, Any]:
254
+ """
255
+ Handle a tools/call request - enforce policy.
256
+
257
+ Args:
258
+ message: The JSON-RPC message
259
+
260
+ Returns:
261
+ Modified message (allowed) or error response (blocked)
262
+ """
263
+ params = message.get("params", {})
264
+ tool_name = params.get("name", "unknown")
265
+ arguments = params.get("arguments", {})
266
+
267
+ # Build policy context
268
+ context = {
269
+ "action": {
270
+ "tool": tool_name,
271
+ "path": arguments.get("path", ""),
272
+ }
273
+ }
274
+
275
+ # Check policy
276
+ decision = self.policy_engine.evaluate(self.identity.did, context)
277
+
278
+ # Log the decision
279
+ self._audit_log_tool_call(tool_name, arguments, decision)
280
+
281
+ if not decision.allowed:
282
+ console.print(
283
+ f"[yellow]🔒 BLOCKED: {tool_name} - {decision.reason}[/yellow]"
284
+ )
285
+
286
+ # Return error response
287
+ error_response = {
288
+ "jsonrpc": "2.0",
289
+ "id": message.get("id"),
290
+ "error": {
291
+ "code": -32001,
292
+ "message": f"Policy violation: {decision.reason}",
293
+ "data": {
294
+ "agentmesh": {
295
+ "blocked": True,
296
+ "policy": decision.policy_name,
297
+ "rule": decision.matched_rule,
298
+ "trust_score": self.trust_score,
299
+ }
300
+ }
301
+ }
302
+ }
303
+
304
+ # Don't forward to target, return error directly
305
+ sys.stdout.write(json.dumps(error_response) + "\n")
306
+ sys.stdout.flush()
307
+
308
+ # Return a marker to skip forwarding
309
+ return {"_agentmesh_blocked": True}
310
+
311
+ if decision.action == "warn":
312
+ console.print(
313
+ f"[yellow]⚠️ WARNING: {tool_name} - {decision.reason}[/yellow]"
314
+ )
315
+ else:
316
+ console.print(f"[dim]✓ Allowed: {tool_name}[/dim]")
317
+
318
+ # Update trust score
319
+ self._update_trust_score(tool_name, allowed=True)
320
+
321
+ return message
322
+
323
+ def _add_verification_footer(self, message: Dict[str, Any]) -> Dict[str, Any]:
324
+ """Add AgentMesh verification footer to tool output."""
325
+ result = message.get("result", {})
326
+
327
+ # Check if result has content we can append to
328
+ if isinstance(result, dict) and "content" in result:
329
+ content_list = result.get("content", [])
330
+
331
+ # Get DID as string
332
+ did_str = str(self.identity.did) if hasattr(self.identity.did, '__str__') else self.identity.did
333
+
334
+ # Add footer as a new content item
335
+ footer = {
336
+ "type": "text",
337
+ "text": (
338
+ f"\n\n> 🔒 Verified by AgentMesh (Trust Score: {self.trust_score}/1000)\n"
339
+ f"> Agent: {did_str[:40]}...\n"
340
+ f"> Policy: {self.policy_level} | Audit: Enabled"
341
+ )
342
+ }
343
+
344
+ if isinstance(content_list, list):
345
+ content_list.append(footer)
346
+
347
+ result["content"] = content_list
348
+ message["result"] = result
349
+
350
+ return message
351
+
352
+ def _audit_log_tool_call(
353
+ self,
354
+ tool_name: str,
355
+ arguments: Dict[str, Any],
356
+ decision: Any
357
+ ):
358
+ """Log tool call to audit trail."""
359
+ entry = {
360
+ "timestamp": datetime.utcnow().isoformat() + "Z",
361
+ "agent": self.identity.did,
362
+ "action": "mcp_tool_call",
363
+ "tool": tool_name,
364
+ "arguments": arguments,
365
+ "decision": decision.action,
366
+ "allowed": decision.allowed,
367
+ "policy": decision.policy_name,
368
+ "rule": decision.matched_rule,
369
+ "trust_score": self.trust_score,
370
+ }
371
+
372
+ # In production, would write to persistent audit log
373
+ # For now, just track in memory
374
+ console.print(f"[dim]📋 Audit: {tool_name} - {decision.action}[/dim]")
375
+
376
+ def _update_trust_score(self, tool_name: str, allowed: bool):
377
+ """Update trust score based on tool usage."""
378
+ if allowed:
379
+ # Small increase for allowed actions
380
+ self.trust_score = min(1000, self.trust_score + 1)
381
+ else:
382
+ # Larger decrease for blocked actions
383
+ self.trust_score = max(0, self.trust_score - 10)
384
+
385
+
386
+ @click.command()
387
+ @click.option(
388
+ "--target",
389
+ "-t",
390
+ multiple=True,
391
+ required=True,
392
+ help="Target MCP server command (can specify multiple times for args)",
393
+ )
394
+ @click.option(
395
+ "--policy",
396
+ "-p",
397
+ type=click.Choice(["strict", "moderate", "permissive"]),
398
+ default="strict",
399
+ help="Policy enforcement level",
400
+ )
401
+ @click.option(
402
+ "--no-footer",
403
+ is_flag=True,
404
+ help="Disable verification footers in output",
405
+ )
406
+ @click.option(
407
+ "--identity",
408
+ "-i",
409
+ default="mcp-proxy",
410
+ help="Agent identity name",
411
+ )
412
+ def proxy(target: tuple, policy: str, no_footer: bool, identity: str):
413
+ """
414
+ Start an AgentMesh MCP proxy.
415
+
416
+ Wraps an existing MCP server with governance, policy enforcement,
417
+ and trust scoring.
418
+
419
+ Examples:
420
+
421
+ # Proxy a filesystem server
422
+ agentmesh proxy --target npx --target -y \\
423
+ --target @modelcontextprotocol/server-filesystem --target /Users/me
424
+
425
+ # Moderate policy with no footers
426
+ agentmesh proxy --policy moderate --no-footer \\
427
+ --target python --target my_mcp_server.py
428
+ """
429
+ # Convert tuple to list
430
+ target_cmd = list(target)
431
+
432
+ if not target_cmd:
433
+ console.print("[red]Error: --target is required[/red]")
434
+ sys.exit(1)
435
+
436
+ # Create and start proxy
437
+ proxy_server = MCPProxy(
438
+ target_command=target_cmd,
439
+ policy=policy,
440
+ identity_name=identity,
441
+ enable_footer=not no_footer,
442
+ )
443
+
444
+ try:
445
+ asyncio.run(proxy_server.start())
446
+ except KeyboardInterrupt:
447
+ console.print("\n[dim]Proxy stopped[/dim]")
448
+ sys.exit(0)
@@ -0,0 +1,7 @@
1
+ """
2
+ Core Module
3
+
4
+ Low-level services for AgentMesh.
5
+ """
6
+
7
+ __all__ = []
@@ -0,0 +1,17 @@
1
+ """
2
+ Core Identity Module
3
+
4
+ Certificate Authority for issuing SPIFFE/SVID certificates.
5
+ """
6
+
7
+ from .ca import (
8
+ CertificateAuthority,
9
+ RegistrationRequest,
10
+ RegistrationResponse,
11
+ )
12
+
13
+ __all__ = [
14
+ "CertificateAuthority",
15
+ "RegistrationRequest",
16
+ "RegistrationResponse",
17
+ ]