agentsid 0.1.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.
@@ -0,0 +1,39 @@
1
+ # Python
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ .venv/
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ .ruff_cache/
10
+ .pytest_cache/
11
+
12
+ # Node
13
+ node_modules/
14
+
15
+ # Environment / Secrets — NEVER commit these
16
+ .env
17
+ .env.local
18
+ .env.production
19
+ !.env.example
20
+ *.pem
21
+ *.key
22
+ *.p12
23
+
24
+ # IDE
25
+ .idea/
26
+ .vscode/
27
+ *.swp
28
+ *.swo
29
+ *~
30
+
31
+ # OS
32
+ .DS_Store
33
+ Thumbs.db
34
+
35
+ # Local config (may contain API keys)
36
+ .agentsid/
37
+
38
+ # Logs
39
+ *.log
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentsid
3
+ Version: 0.1.0
4
+ Summary: Identity and auth for AI agents — drop-in MCP middleware
5
+ License: MIT
6
+ Keywords: agents,ai,auth,identity,mcp,security
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: httpx>=0.25.0
9
+ Description-Content-Type: text/markdown
10
+
11
+ # agentsid
12
+
13
+ Identity and auth for AI agents. Official Python SDK for [agentsid.dev](https://agentsid.dev).
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ pip install agentsid
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ```python
24
+ from agentsid import AgentsID, create_mcp_middleware
25
+
26
+ aid = AgentsID(project_key="aid_proj_...")
27
+
28
+ result = await aid.register_agent(
29
+ name="research-bot",
30
+ on_behalf_of="user_123",
31
+ permissions=["search_*", "save_memory"],
32
+ )
33
+
34
+ middleware = create_mcp_middleware(project_key="aid_proj_...")
35
+ allowed = await middleware.is_allowed(token, "save_memory") # True
36
+ ```
37
+
38
+ ## Links
39
+
40
+ - [Documentation](https://agentsid.dev/docs)
41
+ - [Dashboard](https://agentsid.dev/dashboard)
42
+ - [GitHub](https://github.com/stevenkozeniesky02/agentsid)
@@ -0,0 +1,32 @@
1
+ # agentsid
2
+
3
+ Identity and auth for AI agents. Official Python SDK for [agentsid.dev](https://agentsid.dev).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install agentsid
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ from agentsid import AgentsID, create_mcp_middleware
15
+
16
+ aid = AgentsID(project_key="aid_proj_...")
17
+
18
+ result = await aid.register_agent(
19
+ name="research-bot",
20
+ on_behalf_of="user_123",
21
+ permissions=["search_*", "save_memory"],
22
+ )
23
+
24
+ middleware = create_mcp_middleware(project_key="aid_proj_...")
25
+ allowed = await middleware.is_allowed(token, "save_memory") # True
26
+ ```
27
+
28
+ ## Links
29
+
30
+ - [Documentation](https://agentsid.dev/docs)
31
+ - [Dashboard](https://agentsid.dev/dashboard)
32
+ - [GitHub](https://github.com/stevenkozeniesky02/agentsid)
@@ -0,0 +1,39 @@
1
+ """AgentsID — Identity and auth for AI agents.
2
+
3
+ Quick start:
4
+
5
+ from agentsid import AgentsID
6
+
7
+ aid = AgentsID(project_key="aid_proj_...")
8
+
9
+ # Register an agent
10
+ result = await aid.register_agent(
11
+ name="research-bot",
12
+ on_behalf_of="user_123",
13
+ permissions=["search_memories", "save_memory"],
14
+ )
15
+
16
+ # Validate token + check permission
17
+ check = await aid.validate_token(result["token"], tool="search_memories")
18
+ """
19
+
20
+ from agentsid.client import AgentsID
21
+ from agentsid.errors import (
22
+ AgentsIDError,
23
+ AuthenticationError,
24
+ PermissionDeniedError,
25
+ TokenExpiredError,
26
+ TokenRevokedError,
27
+ )
28
+ from agentsid.middleware import create_mcp_middleware, validate_tool_call
29
+
30
+ __all__ = [
31
+ "AgentsID",
32
+ "AgentsIDError",
33
+ "AuthenticationError",
34
+ "PermissionDeniedError",
35
+ "TokenExpiredError",
36
+ "TokenRevokedError",
37
+ "create_mcp_middleware",
38
+ "validate_tool_call",
39
+ ]
@@ -0,0 +1,164 @@
1
+ """AgentsID Python SDK — main client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import httpx
6
+
7
+ from agentsid.errors import AgentsIDError, AuthenticationError
8
+
9
+ DEFAULT_BASE_URL = "https://agentsid.dev"
10
+ DEFAULT_TIMEOUT = 10.0
11
+
12
+
13
+ class AgentsID:
14
+ """AgentsID client — register agents, validate tokens, manage permissions."""
15
+
16
+ def __init__(
17
+ self,
18
+ project_key: str,
19
+ base_url: str = DEFAULT_BASE_URL,
20
+ timeout: float = DEFAULT_TIMEOUT,
21
+ ) -> None:
22
+ if not project_key:
23
+ raise AgentsIDError("project_key is required", "CONFIG_ERROR")
24
+ self._project_key = project_key
25
+ self._base_url = base_url.rstrip("/")
26
+ self._timeout = timeout
27
+
28
+ # ═══════════════════════════════════════════
29
+ # AGENTS
30
+ # ═══════════════════════════════════════════
31
+
32
+ async def register_agent(
33
+ self,
34
+ name: str,
35
+ on_behalf_of: str,
36
+ permissions: list[str] | None = None,
37
+ ttl_hours: int | None = None,
38
+ metadata: dict | None = None,
39
+ ) -> dict:
40
+ """Register a new agent identity and issue a token."""
41
+ return await self._request("POST", "/agents/", {
42
+ "name": name,
43
+ "on_behalf_of": on_behalf_of,
44
+ "permissions": permissions,
45
+ "ttl_hours": ttl_hours,
46
+ "metadata": metadata,
47
+ })
48
+
49
+ async def get_agent(self, agent_id: str) -> dict:
50
+ return await self._request("GET", f"/agents/{agent_id}")
51
+
52
+ async def list_agents(self, status: str | None = None, limit: int = 50) -> list[dict]:
53
+ params = {}
54
+ if status:
55
+ params["status"] = status
56
+ if limit != 50:
57
+ params["limit"] = str(limit)
58
+ qs = "&".join(f"{k}={v}" for k, v in params.items())
59
+ return await self._request("GET", f"/agents/?{qs}" if qs else "/agents/")
60
+
61
+ async def revoke_agent(self, agent_id: str) -> None:
62
+ await self._request("DELETE", f"/agents/{agent_id}")
63
+
64
+ # ═══════════════════════════════════════════
65
+ # PERMISSIONS
66
+ # ═══════════════════════════════════════════
67
+
68
+ async def set_permissions(self, agent_id: str, rules: list[dict]) -> dict:
69
+ """Set permission rules. Each rule: {"tool_pattern": "...", "action": "allow"|"deny"}"""
70
+ body = [
71
+ {
72
+ "tool_pattern": r.get("tool_pattern", r.get("toolPattern", "")),
73
+ "action": r.get("action", "allow"),
74
+ "conditions": r.get("conditions"),
75
+ "priority": r.get("priority", 0),
76
+ }
77
+ for r in rules
78
+ ]
79
+ return await self._request("PUT", f"/agents/{agent_id}/permissions", body)
80
+
81
+ async def get_permissions(self, agent_id: str) -> list[dict]:
82
+ data = await self._request("GET", f"/agents/{agent_id}/permissions")
83
+ return data.get("rules", [])
84
+
85
+ async def check_permission(
86
+ self, agent_id: str, tool: str, params: dict | None = None
87
+ ) -> dict:
88
+ return await self._request("POST", "/check", {
89
+ "agent_id": agent_id,
90
+ "tool": tool,
91
+ "params": params,
92
+ })
93
+
94
+ # ═══════════════════════════════════════════
95
+ # TOKEN VALIDATION
96
+ # ═══════════════════════════════════════════
97
+
98
+ async def validate_token(
99
+ self, token: str, tool: str | None = None, params: dict | None = None
100
+ ) -> dict:
101
+ return await self._request("POST", "/validate", {
102
+ "token": token,
103
+ "tool": tool,
104
+ "params": params,
105
+ })
106
+
107
+ # ═══════════════════════════════════════════
108
+ # AUDIT
109
+ # ═══════════════════════════════════════════
110
+
111
+ async def get_audit_log(
112
+ self,
113
+ agent_id: str | None = None,
114
+ tool: str | None = None,
115
+ action: str | None = None,
116
+ since: str | None = None,
117
+ limit: int = 100,
118
+ ) -> dict:
119
+ params = {}
120
+ if agent_id:
121
+ params["agent_id"] = agent_id
122
+ if tool:
123
+ params["tool"] = tool
124
+ if action:
125
+ params["action"] = action
126
+ if since:
127
+ params["since"] = since
128
+ params["limit"] = str(limit)
129
+ qs = "&".join(f"{k}={v}" for k, v in params.items())
130
+ return await self._request("GET", f"/audit/?{qs}")
131
+
132
+ # ═══════════════════════════════════════════
133
+ # HTTP CLIENT
134
+ # ═══════════════════════════════════════════
135
+
136
+ async def _request(self, method: str, path: str, body: object | None = None) -> dict:
137
+ url = f"{self._base_url}/api/v1{path}"
138
+ headers = {
139
+ "Authorization": f"Bearer {self._project_key}",
140
+ "Content-Type": "application/json",
141
+ }
142
+
143
+ async with httpx.AsyncClient(timeout=self._timeout) as client:
144
+ response = await client.request(
145
+ method, url, headers=headers,
146
+ json=body if body is not None else None,
147
+ )
148
+
149
+ if response.status_code == 401:
150
+ raise AuthenticationError()
151
+
152
+ if response.status_code == 204:
153
+ return {}
154
+
155
+ data = response.json()
156
+
157
+ if not response.is_success:
158
+ raise AgentsIDError(
159
+ data.get("detail", f"Request failed: {response.status_code}"),
160
+ "API_ERROR",
161
+ response.status_code,
162
+ )
163
+
164
+ return data
@@ -0,0 +1,30 @@
1
+ """AgentsID error classes."""
2
+
3
+
4
+ class AgentsIDError(Exception):
5
+ def __init__(self, message: str, code: str = "UNKNOWN", status_code: int | None = None):
6
+ super().__init__(message)
7
+ self.code = code
8
+ self.status_code = status_code
9
+
10
+
11
+ class AuthenticationError(AgentsIDError):
12
+ def __init__(self, message: str = "Invalid or missing API key"):
13
+ super().__init__(message, "AUTH_ERROR", 401)
14
+
15
+
16
+ class PermissionDeniedError(AgentsIDError):
17
+ def __init__(self, tool: str, reason: str):
18
+ super().__init__(f'Permission denied for tool "{tool}": {reason}', "PERMISSION_DENIED", 403)
19
+ self.tool = tool
20
+ self.reason = reason
21
+
22
+
23
+ class TokenExpiredError(AgentsIDError):
24
+ def __init__(self):
25
+ super().__init__("Agent token has expired", "TOKEN_EXPIRED", 401)
26
+
27
+
28
+ class TokenRevokedError(AgentsIDError):
29
+ def __init__(self):
30
+ super().__init__("Agent token has been revoked", "TOKEN_REVOKED", 401)
@@ -0,0 +1,124 @@
1
+ """AgentsID MCP middleware for Python.
2
+
3
+ Drop-in middleware for Python MCP servers. Validates agent tokens,
4
+ checks per-tool permissions, and blocks unauthorized calls.
5
+
6
+ Usage:
7
+ from agentsid import create_mcp_middleware
8
+
9
+ middleware = create_mcp_middleware(project_key="aid_proj_...")
10
+
11
+ # In your MCP tool handler:
12
+ async def my_tool(params, context):
13
+ auth = await middleware.validate(bearer_token, "my_tool", params)
14
+ # auth.permission.allowed is True/False
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import httpx
20
+
21
+ from agentsid.errors import (
22
+ AgentsIDError,
23
+ PermissionDeniedError,
24
+ TokenExpiredError,
25
+ TokenRevokedError,
26
+ )
27
+
28
+ DEFAULT_BASE_URL = "https://agentsid.dev"
29
+
30
+
31
+ async def validate_tool_call(
32
+ project_key: str,
33
+ token: str,
34
+ tool: str,
35
+ params: dict | None = None,
36
+ base_url: str = DEFAULT_BASE_URL,
37
+ ) -> dict:
38
+ """Validate a tool call against AgentsID. Returns validation result."""
39
+ url = f"{base_url.rstrip('/')}/api/v1/validate"
40
+
41
+ async with httpx.AsyncClient(timeout=10.0) as client:
42
+ response = await client.post(
43
+ url,
44
+ json={"token": token, "tool": tool, "params": params},
45
+ headers={"Content-Type": "application/json"},
46
+ )
47
+
48
+ if not response.is_success:
49
+ return {"valid": False, "reason": f"Validation failed: {response.status_code}"}
50
+
51
+ return response.json()
52
+
53
+
54
+ class MCPMiddleware:
55
+ """MCP middleware instance — validates tool calls against AgentsID."""
56
+
57
+ def __init__(
58
+ self,
59
+ project_key: str,
60
+ base_url: str = DEFAULT_BASE_URL,
61
+ skip_tools: list[str] | None = None,
62
+ on_denied: object | None = None,
63
+ ) -> None:
64
+ self._project_key = project_key
65
+ self._base_url = base_url.rstrip("/")
66
+ self._skip_tools = set(skip_tools or [])
67
+ self._on_denied = on_denied
68
+
69
+ async def validate(
70
+ self, token: str, tool: str, params: dict | None = None
71
+ ) -> dict:
72
+ """Validate a tool call. Raises on denial unless on_denied is set."""
73
+ if tool in self._skip_tools:
74
+ return {"valid": True, "reason": "Tool in skip list"}
75
+
76
+ result = await validate_tool_call(
77
+ self._project_key, token, tool, params, self._base_url
78
+ )
79
+
80
+ if not result.get("valid"):
81
+ reason = result.get("reason", "Unknown")
82
+ if "expired" in reason:
83
+ raise TokenExpiredError()
84
+ if "revoked" in reason:
85
+ raise TokenRevokedError()
86
+
87
+ permission = result.get("permission", {})
88
+ if permission and not permission.get("allowed"):
89
+ reason = permission.get("reason", "Denied")
90
+ if self._on_denied:
91
+ self._on_denied(tool, reason)
92
+ else:
93
+ raise PermissionDeniedError(tool, reason)
94
+
95
+ return result
96
+
97
+ async def is_allowed(self, token: str, tool: str) -> bool:
98
+ """Quick check — returns True/False without raising."""
99
+ try:
100
+ result = await validate_tool_call(
101
+ self._project_key, token, tool, base_url=self._base_url
102
+ )
103
+ return result.get("valid", False) and result.get("permission", {}).get("allowed", False)
104
+ except Exception:
105
+ return False
106
+
107
+
108
+ def create_mcp_middleware(
109
+ project_key: str,
110
+ base_url: str = DEFAULT_BASE_URL,
111
+ skip_tools: list[str] | None = None,
112
+ ) -> MCPMiddleware:
113
+ """Create an MCP middleware instance.
114
+
115
+ Args:
116
+ project_key: Your AgentsID project key (aid_proj_...)
117
+ base_url: AgentsID server URL
118
+ skip_tools: Tool names to skip validation for
119
+ """
120
+ return MCPMiddleware(
121
+ project_key=project_key,
122
+ base_url=base_url,
123
+ skip_tools=skip_tools,
124
+ )
@@ -0,0 +1,16 @@
1
+ [project]
2
+ name = "agentsid"
3
+ version = "0.1.0"
4
+ description = "Identity and auth for AI agents — drop-in MCP middleware"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = ["httpx>=0.25.0"]
8
+ license = {text = "MIT"}
9
+ keywords = ["ai", "agents", "auth", "identity", "mcp", "security"]
10
+
11
+ [build-system]
12
+ requires = ["hatchling"]
13
+ build-backend = "hatchling.build"
14
+
15
+ [tool.hatch.build.targets.wheel]
16
+ packages = ["agentsid"]