Skip to content

Commit 38a424a

Browse files
committed
Add OAuth 2.1 authorization server with DPoP support and MCP HTTP integration
- Implement zero-dependency-core OAuth 2.1 server: JWT (HMAC-SHA256), PKCE S256, authorization code flow, Bearer token validation, client registration, and token introspection - Add optional DPoP proof-of-possession via the cryptography library (ES256/RS256 asymmetric signatures) - Extend MCP HTTP server with OAuth endpoints (/authorize, /token, /.well-known/oauth-authorization-server) and DPoP-bound resource access - Add CLI flags: --oauth-issuer, --oauth-signing-key, --oauth-client, --oauth-token-ttl - Add pyproject.toml [oauth] extra with cryptography dependency
1 parent d2696ae commit 38a424a

7 files changed

Lines changed: 2046 additions & 17 deletions

File tree

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@ graphqlite = [
1414
"graphqlite>=0.4.4",
1515
"pysqlite3>=0.6.0",
1616
]
17+
oauth = [
18+
"cryptography>=3.4",
19+
]
1720
dev = [
1821
"graphqlite>=0.4.4",
1922
"pysqlite3>=0.6.0",
23+
"cryptography>=3.4",
2024
"pytest>=7",
2125
"pytest-cov>=4",
2226
"ruff>=0.4",

teaagent/__init__.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,25 @@
4545
from teaagent.mcp_server import handle_mcp_request, serve_mcp_stdio
4646
from teaagent.memory import MemoryCatalog, MemoryEntry
4747
from teaagent.model_routing import ModelRoute, classify_task, route_model
48+
from teaagent.oauth21 import (
49+
HAS_CRYPTOGRAPHY,
50+
DPoPValidationResult,
51+
InvalidClientError,
52+
InvalidDPoPError,
53+
InvalidGrantError,
54+
JWTError,
55+
OAuth21AuthorizationServer,
56+
OAuth21Client,
57+
OAuth21ResourceServer,
58+
OAuth21TokenClaims,
59+
OAuth21TokenResponse,
60+
compute_jwk_thumbprint,
61+
compute_s256_challenge,
62+
create_jwt,
63+
decode_jwt_unsafe,
64+
generate_code_verifier,
65+
verify_jwt,
66+
)
4867
from teaagent.policy import ApprovalPolicy, PermissionMode, parse_permission_mode
4968
from teaagent.portability import (
5069
PortabilityResult,
@@ -140,6 +159,23 @@
140159
'build_aibom',
141160
'build_mcp_http_server',
142161
'build_task_spec',
162+
'HAS_CRYPTOGRAPHY',
163+
'DPoPValidationResult',
164+
'InvalidClientError',
165+
'InvalidDPoPError',
166+
'InvalidGrantError',
167+
'JWTError',
168+
'OAuth21AuthorizationServer',
169+
'OAuth21Client',
170+
'OAuth21ResourceServer',
171+
'OAuth21TokenClaims',
172+
'OAuth21TokenResponse',
173+
'compute_jwk_thumbprint',
174+
'compute_s256_challenge',
175+
'create_jwt',
176+
'decode_jwt_unsafe',
177+
'generate_code_verifier',
178+
'verify_jwt',
143179
'build_workspace_tool_registry',
144180
'check_graphqlite_runtime',
145181
'check_llm_configuration',

teaagent/cli.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,30 @@ def build_parser() -> argparse.ArgumentParser:
499499
default=[],
500500
help='Permit this Origin header. Can be repeated. Default: allow all origins.',
501501
)
502+
mcp_serve.add_argument(
503+
'--oauth-issuer',
504+
default=None,
505+
help='Enable OAuth 2.1 / DPoP. Issuer URL (e.g. https://mcp.example.com).',
506+
)
507+
mcp_serve.add_argument(
508+
'--oauth-signing-key',
509+
default=None,
510+
help='HMAC signing key for JWT access tokens (min 16 chars).',
511+
)
512+
mcp_serve.add_argument(
513+
'--oauth-client',
514+
action='append',
515+
default=[],
516+
metavar='ID:SECRET:REDIRECT_URI',
517+
help='Pre-register an OAuth client. Format: client_id:client_secret:redirect_uri. '
518+
'Can be repeated.',
519+
)
520+
mcp_serve.add_argument(
521+
'--oauth-token-ttl',
522+
type=int,
523+
default=3600,
524+
help='Access token TTL in seconds. Default 3600.',
525+
)
502526
mcp_serve.set_defaults(func=mcp_serve_command)
503527

504528
workspace = subparsers.add_parser('workspace', help='Inspect workspace tool pack.')
@@ -831,14 +855,39 @@ def ultrawork_stop_command(args: argparse.Namespace) -> int:
831855

832856

833857
def mcp_serve_command(args: argparse.Namespace) -> int:
858+
from teaagent.oauth21 import OAuth21AuthorizationServer
859+
834860
registry = build_workspace_tool_registry(args.root)
835861
if args.http:
862+
oauth_server = None
863+
if args.oauth_issuer and args.oauth_signing_key:
864+
oauth_server = OAuth21AuthorizationServer(
865+
signing_key=args.oauth_signing_key,
866+
issuer=args.oauth_issuer,
867+
token_ttl=args.oauth_token_ttl,
868+
)
869+
for spec in args.oauth_client or []:
870+
parts = spec.split(':', 2)
871+
if len(parts) != 3:
872+
print(
873+
f'Invalid --oauth-client format: {spec} (expected ID:SECRET:REDIRECT_URI)',
874+
file=sys.stderr,
875+
)
876+
return 1
877+
oauth_server.register_client(*parts)
878+
elif args.oauth_issuer or args.oauth_signing_key:
879+
print(
880+
'Both --oauth-issuer and --oauth-signing-key must be provided to enable OAuth.',
881+
file=sys.stderr,
882+
)
883+
return 1
836884
return serve_mcp_http(
837885
registry,
838886
host=args.host,
839887
port=args.port,
840888
auth_token=args.auth_token,
841889
allowed_origins=args.allowed_origin or None,
890+
oauth_server=oauth_server,
842891
)
843892
return serve_mcp_stdio(registry)
844893

0 commit comments

Comments
 (0)