authgent 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,13 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .eggs/
7
+ .venv/
8
+ venv/
9
+ .mypy_cache/
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ .coverage
13
+ htmlcov/
@@ -0,0 +1,308 @@
1
+ Metadata-Version: 2.4
2
+ Name: authgent
3
+ Version: 0.1.0
4
+ Summary: Python SDK for authgent — token validation, delegation chains, DPoP, middleware
5
+ Project-URL: Homepage, https://github.com/authgent/authgent
6
+ Project-URL: Documentation, https://github.com/authgent/authgent/tree/main/sdks/python
7
+ Project-URL: Repository, https://github.com/authgent/authgent
8
+ Project-URL: Changelog, https://github.com/authgent/authgent/blob/main/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/authgent/authgent/issues
10
+ Author: Dhruv Agnihotri
11
+ License-Expression: Apache-2.0
12
+ Keywords: a2a,agent,ai-agent,auth,delegation,dpop,identity,mcp,oauth
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Security
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: cryptography>=43.0.0
22
+ Requires-Dist: httpx>=0.27.0
23
+ Requires-Dist: pydantic>=2.9.0
24
+ Requires-Dist: pyjwt>=2.9.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
27
+ Requires-Dist: pytest>=8.3.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.6.0; extra == 'dev'
29
+ Provides-Extra: fastapi
30
+ Requires-Dist: fastapi>=0.115.0; extra == 'fastapi'
31
+ Provides-Extra: flask
32
+ Requires-Dist: flask>=3.0.0; extra == 'flask'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # authgent — Python SDK
36
+
37
+ The open-source identity SDK for AI agents. Token verification, delegation chain validation, DPoP sender-constrained tokens, and middleware for FastAPI and Flask.
38
+
39
+ [![PyPI](https://img.shields.io/pypi/v/authgent.svg)](https://pypi.org/project/authgent/)
40
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-3776AB.svg)](https://python.org)
41
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](../../LICENSE)
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ pip install authgent
47
+
48
+ # With FastAPI middleware
49
+ pip install authgent[fastapi]
50
+
51
+ # With Flask middleware
52
+ pip install authgent[flask]
53
+ ```
54
+
55
+ ## Quick Start
56
+
57
+ ### Verify a Token
58
+
59
+ ```python
60
+ from authgent import verify_token
61
+
62
+ identity = await verify_token(
63
+ token="eyJ...",
64
+ issuer="http://localhost:8000",
65
+ )
66
+
67
+ print(identity.subject) # "client:agnt_xxx"
68
+ print(identity.scopes) # ["search:execute"]
69
+ print(identity.delegation_chain) # DelegationChain(depth=0)
70
+ ```
71
+
72
+ ### Validate Delegation Chains
73
+
74
+ ```python
75
+ from authgent import verify_token, verify_delegation_chain
76
+
77
+ identity = await verify_token(token=token, issuer="http://localhost:8000")
78
+
79
+ # Enforce: max 3 hops, must originate from a human
80
+ verify_delegation_chain(
81
+ identity.delegation_chain,
82
+ max_depth=3,
83
+ require_human_root=True,
84
+ allowed_actors=["client:agnt_trusted_orchestrator"],
85
+ )
86
+ ```
87
+
88
+ ### Server API Client
89
+
90
+ ```python
91
+ from authgent import AgentAuthClient
92
+
93
+ client = AgentAuthClient("http://localhost:8000")
94
+
95
+ # Register an agent
96
+ agent = await client.register_agent(
97
+ name="search-bot",
98
+ scopes=["search:execute"],
99
+ )
100
+
101
+ # Get a token
102
+ token = await client.get_token(
103
+ client_id=agent.client_id,
104
+ client_secret=agent.client_secret,
105
+ scope="search:execute",
106
+ )
107
+
108
+ # Delegate to another agent (token exchange)
109
+ delegated = await client.exchange_token(
110
+ subject_token=token.access_token,
111
+ audience="https://downstream-api.example.com",
112
+ scopes=["read"],
113
+ client_id=downstream_agent.client_id,
114
+ client_secret=downstream_agent.client_secret,
115
+ )
116
+
117
+ # Introspect a token
118
+ info = await client.introspect_token(delegated.access_token)
119
+ print(info["active"]) # True
120
+ print(info["act"]) # {"sub": "client:agnt_search", "act": {"sub": "user:123"}}
121
+
122
+ # Revoke a token
123
+ await client.revoke_token(delegated.access_token)
124
+ ```
125
+
126
+ ### DPoP (Sender-Constrained Tokens)
127
+
128
+ ```python
129
+ from authgent import DPoPClient
130
+
131
+ # Create an ephemeral key pair
132
+ dpop = DPoPClient.create()
133
+ print(dpop.jkt) # JWK thumbprint for cnf binding
134
+
135
+ # Generate proof for a request
136
+ proof = dpop.create_proof(
137
+ method="POST",
138
+ url="https://api.example.com/data",
139
+ access_token="eyJ...",
140
+ )
141
+ # Send as DPoP header alongside the access token
142
+
143
+ # Verify an incoming DPoP proof
144
+ from authgent import verify_dpop_proof
145
+
146
+ result = verify_dpop_proof(
147
+ proof_jwt=request.headers["DPoP"],
148
+ access_token=token,
149
+ http_method="POST",
150
+ http_uri="https://api.example.com/data",
151
+ )
152
+ print(result["jkt"]) # JWK thumbprint — must match token's cnf.jkt
153
+ ```
154
+
155
+ ## Middleware
156
+
157
+ ### FastAPI
158
+
159
+ ```python
160
+ from fastapi import FastAPI, Depends, Request
161
+ from authgent.middleware.fastapi import AgentAuthMiddleware, get_agent_identity
162
+
163
+ app = FastAPI()
164
+ app.add_middleware(AgentAuthMiddleware, issuer="http://localhost:8000")
165
+
166
+ @app.post("/tools/search")
167
+ async def search(request: Request):
168
+ identity = get_agent_identity(request)
169
+ print(f"Agent: {identity.subject}")
170
+ print(f"Scopes: {identity.scopes}")
171
+ print(f"Delegation depth: {identity.delegation_chain.depth}")
172
+ return {"results": [...]}
173
+ ```
174
+
175
+ ### Flask
176
+
177
+ ```python
178
+ from flask import Flask, g
179
+ from authgent.middleware.flask import agent_auth_required
180
+
181
+ app = Flask(__name__)
182
+
183
+ @app.route("/tools/search", methods=["POST"])
184
+ @agent_auth_required(issuer="http://localhost:8000", scopes=["search:execute"])
185
+ def search():
186
+ identity = g.agent_identity
187
+ return {"agent": identity.subject}
188
+ ```
189
+
190
+ ### MCP Scope Challenge
191
+
192
+ ```python
193
+ from authgent.middleware.scope_challenge import ScopeChallengeHandler
194
+
195
+ handler = ScopeChallengeHandler(
196
+ server_url="http://localhost:8000",
197
+ client_id="agnt_xxx",
198
+ client_secret="sec_xxx",
199
+ )
200
+
201
+ # Automatically detect 403 scope challenges and trigger step-up
202
+ result = await handler.handle_scope_challenge(
203
+ response=http_response,
204
+ action="access_pii",
205
+ scope="data:pii:read",
206
+ )
207
+ ```
208
+
209
+ ## Adapters
210
+
211
+ ### MCP Server
212
+
213
+ ```python
214
+ from authgent.adapters.mcp import MCPAuthProvider
215
+
216
+ auth = MCPAuthProvider(server_url="http://localhost:8000")
217
+ identity = await auth.verify(token)
218
+
219
+ # Discovery URLs for MCP clients
220
+ auth.metadata_url # http://localhost:8000/.well-known/oauth-authorization-server
221
+ auth.jwks_url # http://localhost:8000/.well-known/jwks.json
222
+ ```
223
+
224
+ ### Protected Resource Metadata (RFC 9728)
225
+
226
+ ```python
227
+ from authgent.adapters.protected_resource import ProtectedResourceMetadata
228
+
229
+ metadata = ProtectedResourceMetadata(
230
+ resource="https://mcp-server.example.com",
231
+ authorization_servers=["http://localhost:8000"],
232
+ scopes_supported=["tools:execute", "db:read"],
233
+ )
234
+
235
+ # Serve at /.well-known/oauth-protected-resource
236
+ @app.get("/.well-known/oauth-protected-resource")
237
+ async def resource_metadata():
238
+ return metadata.to_dict()
239
+ ```
240
+
241
+ ## Error Handling
242
+
243
+ All SDK errors extend `AuthgentError`:
244
+
245
+ ```python
246
+ from authgent import (
247
+ verify_token,
248
+ AuthgentError,
249
+ InvalidTokenError,
250
+ DelegationError,
251
+ DPoPError,
252
+ )
253
+
254
+ try:
255
+ identity = await verify_token(token=token, issuer=issuer)
256
+ except InvalidTokenError as e:
257
+ # Token expired, wrong issuer, bad signature
258
+ print(f"Invalid token: {e}")
259
+ except DelegationError as e:
260
+ # Chain too deep, unauthorized actor, scope escalation
261
+ print(f"Delegation violation: {e}")
262
+ except DPoPError as e:
263
+ # Proof mismatch, expired, wrong binding
264
+ print(f"DPoP error: {e}")
265
+ except AuthgentError as e:
266
+ # Any other SDK error
267
+ print(f"Auth error: {e}")
268
+ ```
269
+
270
+ ## API Reference
271
+
272
+ ### Core Functions
273
+
274
+ | Function | Description |
275
+ |:---------|:------------|
276
+ | `verify_token(token, issuer)` | Verify JWT against issuer's JWKS, return `AgentIdentity` |
277
+ | `verify_delegation_chain(chain, ...)` | Enforce depth, actors, human root policies |
278
+ | `verify_dpop_proof(proof_jwt, ...)` | Verify DPoP proof-of-possession |
279
+ | `DPoPClient.create()` | Create ephemeral DPoP proof generator |
280
+ | `AgentAuthClient(url)` | Full server API client |
281
+
282
+ ### Models
283
+
284
+ | Class | Fields |
285
+ |:------|:-------|
286
+ | `AgentIdentity` | `subject`, `scopes`, `claims`, `delegation_chain` |
287
+ | `DelegationChain` | `depth`, `actors`, `human_root` |
288
+ | `TokenClaims` | `sub`, `scope`, `iss`, `exp`, `iat`, `jti`, `act`, `cnf` |
289
+
290
+ ### Middleware
291
+
292
+ | Import | Framework |
293
+ |:-------|:----------|
294
+ | `authgent.middleware.fastapi` | FastAPI (ASGI) |
295
+ | `authgent.middleware.flask` | Flask (WSGI) |
296
+ | `authgent.middleware.scope_challenge` | MCP scope challenge handler |
297
+
298
+ ### Adapters
299
+
300
+ | Import | Purpose |
301
+ |:-------|:--------|
302
+ | `authgent.adapters.mcp` | MCP server auth provider |
303
+ | `authgent.adapters.protected_resource` | RFC 9728 metadata |
304
+ | `authgent.adapters.langchain` | LangChain tool auth |
305
+
306
+ ## License
307
+
308
+ [Apache 2.0](../../LICENSE)
@@ -0,0 +1,274 @@
1
+ # authgent — Python SDK
2
+
3
+ The open-source identity SDK for AI agents. Token verification, delegation chain validation, DPoP sender-constrained tokens, and middleware for FastAPI and Flask.
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/authgent.svg)](https://pypi.org/project/authgent/)
6
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-3776AB.svg)](https://python.org)
7
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](../../LICENSE)
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install authgent
13
+
14
+ # With FastAPI middleware
15
+ pip install authgent[fastapi]
16
+
17
+ # With Flask middleware
18
+ pip install authgent[flask]
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### Verify a Token
24
+
25
+ ```python
26
+ from authgent import verify_token
27
+
28
+ identity = await verify_token(
29
+ token="eyJ...",
30
+ issuer="http://localhost:8000",
31
+ )
32
+
33
+ print(identity.subject) # "client:agnt_xxx"
34
+ print(identity.scopes) # ["search:execute"]
35
+ print(identity.delegation_chain) # DelegationChain(depth=0)
36
+ ```
37
+
38
+ ### Validate Delegation Chains
39
+
40
+ ```python
41
+ from authgent import verify_token, verify_delegation_chain
42
+
43
+ identity = await verify_token(token=token, issuer="http://localhost:8000")
44
+
45
+ # Enforce: max 3 hops, must originate from a human
46
+ verify_delegation_chain(
47
+ identity.delegation_chain,
48
+ max_depth=3,
49
+ require_human_root=True,
50
+ allowed_actors=["client:agnt_trusted_orchestrator"],
51
+ )
52
+ ```
53
+
54
+ ### Server API Client
55
+
56
+ ```python
57
+ from authgent import AgentAuthClient
58
+
59
+ client = AgentAuthClient("http://localhost:8000")
60
+
61
+ # Register an agent
62
+ agent = await client.register_agent(
63
+ name="search-bot",
64
+ scopes=["search:execute"],
65
+ )
66
+
67
+ # Get a token
68
+ token = await client.get_token(
69
+ client_id=agent.client_id,
70
+ client_secret=agent.client_secret,
71
+ scope="search:execute",
72
+ )
73
+
74
+ # Delegate to another agent (token exchange)
75
+ delegated = await client.exchange_token(
76
+ subject_token=token.access_token,
77
+ audience="https://downstream-api.example.com",
78
+ scopes=["read"],
79
+ client_id=downstream_agent.client_id,
80
+ client_secret=downstream_agent.client_secret,
81
+ )
82
+
83
+ # Introspect a token
84
+ info = await client.introspect_token(delegated.access_token)
85
+ print(info["active"]) # True
86
+ print(info["act"]) # {"sub": "client:agnt_search", "act": {"sub": "user:123"}}
87
+
88
+ # Revoke a token
89
+ await client.revoke_token(delegated.access_token)
90
+ ```
91
+
92
+ ### DPoP (Sender-Constrained Tokens)
93
+
94
+ ```python
95
+ from authgent import DPoPClient
96
+
97
+ # Create an ephemeral key pair
98
+ dpop = DPoPClient.create()
99
+ print(dpop.jkt) # JWK thumbprint for cnf binding
100
+
101
+ # Generate proof for a request
102
+ proof = dpop.create_proof(
103
+ method="POST",
104
+ url="https://api.example.com/data",
105
+ access_token="eyJ...",
106
+ )
107
+ # Send as DPoP header alongside the access token
108
+
109
+ # Verify an incoming DPoP proof
110
+ from authgent import verify_dpop_proof
111
+
112
+ result = verify_dpop_proof(
113
+ proof_jwt=request.headers["DPoP"],
114
+ access_token=token,
115
+ http_method="POST",
116
+ http_uri="https://api.example.com/data",
117
+ )
118
+ print(result["jkt"]) # JWK thumbprint — must match token's cnf.jkt
119
+ ```
120
+
121
+ ## Middleware
122
+
123
+ ### FastAPI
124
+
125
+ ```python
126
+ from fastapi import FastAPI, Depends, Request
127
+ from authgent.middleware.fastapi import AgentAuthMiddleware, get_agent_identity
128
+
129
+ app = FastAPI()
130
+ app.add_middleware(AgentAuthMiddleware, issuer="http://localhost:8000")
131
+
132
+ @app.post("/tools/search")
133
+ async def search(request: Request):
134
+ identity = get_agent_identity(request)
135
+ print(f"Agent: {identity.subject}")
136
+ print(f"Scopes: {identity.scopes}")
137
+ print(f"Delegation depth: {identity.delegation_chain.depth}")
138
+ return {"results": [...]}
139
+ ```
140
+
141
+ ### Flask
142
+
143
+ ```python
144
+ from flask import Flask, g
145
+ from authgent.middleware.flask import agent_auth_required
146
+
147
+ app = Flask(__name__)
148
+
149
+ @app.route("/tools/search", methods=["POST"])
150
+ @agent_auth_required(issuer="http://localhost:8000", scopes=["search:execute"])
151
+ def search():
152
+ identity = g.agent_identity
153
+ return {"agent": identity.subject}
154
+ ```
155
+
156
+ ### MCP Scope Challenge
157
+
158
+ ```python
159
+ from authgent.middleware.scope_challenge import ScopeChallengeHandler
160
+
161
+ handler = ScopeChallengeHandler(
162
+ server_url="http://localhost:8000",
163
+ client_id="agnt_xxx",
164
+ client_secret="sec_xxx",
165
+ )
166
+
167
+ # Automatically detect 403 scope challenges and trigger step-up
168
+ result = await handler.handle_scope_challenge(
169
+ response=http_response,
170
+ action="access_pii",
171
+ scope="data:pii:read",
172
+ )
173
+ ```
174
+
175
+ ## Adapters
176
+
177
+ ### MCP Server
178
+
179
+ ```python
180
+ from authgent.adapters.mcp import MCPAuthProvider
181
+
182
+ auth = MCPAuthProvider(server_url="http://localhost:8000")
183
+ identity = await auth.verify(token)
184
+
185
+ # Discovery URLs for MCP clients
186
+ auth.metadata_url # http://localhost:8000/.well-known/oauth-authorization-server
187
+ auth.jwks_url # http://localhost:8000/.well-known/jwks.json
188
+ ```
189
+
190
+ ### Protected Resource Metadata (RFC 9728)
191
+
192
+ ```python
193
+ from authgent.adapters.protected_resource import ProtectedResourceMetadata
194
+
195
+ metadata = ProtectedResourceMetadata(
196
+ resource="https://mcp-server.example.com",
197
+ authorization_servers=["http://localhost:8000"],
198
+ scopes_supported=["tools:execute", "db:read"],
199
+ )
200
+
201
+ # Serve at /.well-known/oauth-protected-resource
202
+ @app.get("/.well-known/oauth-protected-resource")
203
+ async def resource_metadata():
204
+ return metadata.to_dict()
205
+ ```
206
+
207
+ ## Error Handling
208
+
209
+ All SDK errors extend `AuthgentError`:
210
+
211
+ ```python
212
+ from authgent import (
213
+ verify_token,
214
+ AuthgentError,
215
+ InvalidTokenError,
216
+ DelegationError,
217
+ DPoPError,
218
+ )
219
+
220
+ try:
221
+ identity = await verify_token(token=token, issuer=issuer)
222
+ except InvalidTokenError as e:
223
+ # Token expired, wrong issuer, bad signature
224
+ print(f"Invalid token: {e}")
225
+ except DelegationError as e:
226
+ # Chain too deep, unauthorized actor, scope escalation
227
+ print(f"Delegation violation: {e}")
228
+ except DPoPError as e:
229
+ # Proof mismatch, expired, wrong binding
230
+ print(f"DPoP error: {e}")
231
+ except AuthgentError as e:
232
+ # Any other SDK error
233
+ print(f"Auth error: {e}")
234
+ ```
235
+
236
+ ## API Reference
237
+
238
+ ### Core Functions
239
+
240
+ | Function | Description |
241
+ |:---------|:------------|
242
+ | `verify_token(token, issuer)` | Verify JWT against issuer's JWKS, return `AgentIdentity` |
243
+ | `verify_delegation_chain(chain, ...)` | Enforce depth, actors, human root policies |
244
+ | `verify_dpop_proof(proof_jwt, ...)` | Verify DPoP proof-of-possession |
245
+ | `DPoPClient.create()` | Create ephemeral DPoP proof generator |
246
+ | `AgentAuthClient(url)` | Full server API client |
247
+
248
+ ### Models
249
+
250
+ | Class | Fields |
251
+ |:------|:-------|
252
+ | `AgentIdentity` | `subject`, `scopes`, `claims`, `delegation_chain` |
253
+ | `DelegationChain` | `depth`, `actors`, `human_root` |
254
+ | `TokenClaims` | `sub`, `scope`, `iss`, `exp`, `iat`, `jti`, `act`, `cnf` |
255
+
256
+ ### Middleware
257
+
258
+ | Import | Framework |
259
+ |:-------|:----------|
260
+ | `authgent.middleware.fastapi` | FastAPI (ASGI) |
261
+ | `authgent.middleware.flask` | Flask (WSGI) |
262
+ | `authgent.middleware.scope_challenge` | MCP scope challenge handler |
263
+
264
+ ### Adapters
265
+
266
+ | Import | Purpose |
267
+ |:-------|:--------|
268
+ | `authgent.adapters.mcp` | MCP server auth provider |
269
+ | `authgent.adapters.protected_resource` | RFC 9728 metadata |
270
+ | `authgent.adapters.langchain` | LangChain tool auth |
271
+
272
+ ## License
273
+
274
+ [Apache 2.0](../../LICENSE)
@@ -0,0 +1,32 @@
1
+ """authgent SDK — token validation, delegation chains, DPoP for AI agents."""
2
+
3
+ from authgent.verify import verify_token
4
+ from authgent.delegation import verify_delegation_chain
5
+ from authgent.dpop import verify_dpop_proof, DPoPClient
6
+ from authgent.client import AgentAuthClient
7
+ from authgent.models import AgentIdentity, DelegationChain, TokenClaims
8
+ from authgent.errors import (
9
+ AuthgentError,
10
+ InvalidTokenError,
11
+ DelegationError,
12
+ DPoPError,
13
+ ServerError,
14
+ )
15
+
16
+ __version__ = "0.1.0"
17
+
18
+ __all__ = [
19
+ "verify_token",
20
+ "verify_delegation_chain",
21
+ "verify_dpop_proof",
22
+ "DPoPClient",
23
+ "AgentAuthClient",
24
+ "AgentIdentity",
25
+ "DelegationChain",
26
+ "TokenClaims",
27
+ "AuthgentError",
28
+ "InvalidTokenError",
29
+ "DelegationError",
30
+ "DPoPError",
31
+ "ServerError",
32
+ ]
@@ -0,0 +1 @@
1
+ """SDK adapters for MCP and other protocols."""