belgie-mcp 0.3.0__py3-none-any.whl → 0.4.0__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.
belgie_mcp/__init__.py CHANGED
@@ -1,11 +1,13 @@
1
1
  from belgie_mcp.metadata import create_protected_resource_metadata_router
2
2
  from belgie_mcp.plugin import McpPlugin
3
+ from belgie_mcp.user import get_user_from_access_token
3
4
  from belgie_mcp.verifier import BelgieOAuthTokenVerifier, mcp_auth, mcp_token_verifier
4
5
 
5
6
  __all__ = [
6
7
  "BelgieOAuthTokenVerifier",
7
8
  "McpPlugin",
8
9
  "create_protected_resource_metadata_router",
10
+ "get_user_from_access_token",
9
11
  "mcp_auth",
10
12
  "mcp_token_verifier",
11
13
  ]
belgie_mcp/user.py ADDED
@@ -0,0 +1,112 @@
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ import binascii
5
+ import json
6
+ from dataclasses import dataclass
7
+ from typing import TYPE_CHECKING, Any, ClassVar, Final
8
+ from uuid import UUID
9
+
10
+ from mcp.server.auth.middleware.auth_context import get_access_token
11
+
12
+ if TYPE_CHECKING:
13
+ from belgie_core.core.belgie import Belgie
14
+ from belgie_proto import DBConnection, UserProtocol
15
+ from mcp.server.auth.provider import AccessToken
16
+
17
+
18
+ @dataclass(frozen=True, slots=True, kw_only=True)
19
+ class UserLookup:
20
+ claim: str = "sub"
21
+ MIN_PARTS: ClassVar[Final[int]] = 2
22
+
23
+ async def get_user_from_access_token(self, belgie: Belgie) -> UserProtocol | None:
24
+ if belgie.db is None:
25
+ msg = "Belgie.db must be configured before calling get_user_from_access_token"
26
+ raise RuntimeError(msg)
27
+
28
+ access_token = get_access_token()
29
+ if access_token is None:
30
+ return None
31
+
32
+ token_value = self._extract_token_value(access_token)
33
+ if token_value is None:
34
+ return None
35
+
36
+ payload = self._decode_jwt_payload(token_value)
37
+ if payload is None:
38
+ return None
39
+
40
+ claim_value = payload.get(self.claim)
41
+ if not isinstance(claim_value, str):
42
+ return None
43
+
44
+ try:
45
+ user_id = UUID(claim_value)
46
+ except ValueError:
47
+ return None
48
+
49
+ return await self._load_user_from_belgie(belgie, user_id)
50
+
51
+ @staticmethod
52
+ def _extract_token_value(access_token: AccessToken) -> str | None:
53
+ token_value = getattr(access_token, "token", None)
54
+ if isinstance(token_value, str) and token_value:
55
+ return token_value
56
+ return None
57
+
58
+ @classmethod
59
+ def _decode_jwt_payload(cls, token: str) -> dict[str, Any] | None:
60
+ parts = token.split(".")
61
+ if len(parts) < cls.MIN_PARTS:
62
+ return None
63
+
64
+ payload_segment = parts[1]
65
+ payload_bytes = cls._base64url_decode(payload_segment)
66
+ if payload_bytes is None:
67
+ return None
68
+
69
+ try:
70
+ payload = json.loads(payload_bytes.decode("utf-8"))
71
+ except (json.JSONDecodeError, UnicodeDecodeError):
72
+ return None
73
+
74
+ if not isinstance(payload, dict):
75
+ return None
76
+
77
+ return payload
78
+
79
+ @staticmethod
80
+ def _base64url_decode(value: str) -> bytes | None:
81
+ if not value:
82
+ return None
83
+
84
+ padding = "=" * (-len(value) % 4)
85
+ try:
86
+ return base64.urlsafe_b64decode(f"{value}{padding}")
87
+ except (ValueError, binascii.Error):
88
+ return None
89
+
90
+ async def _load_user_from_belgie(self, belgie: Belgie, user_id: UUID) -> UserProtocol | None:
91
+ dependency = belgie.db.dependency
92
+ db_or_generator = dependency()
93
+
94
+ if self._is_async_generator(db_or_generator):
95
+ async for db in db_or_generator:
96
+ return await self._load_user_from_db(belgie, db, user_id)
97
+ return None
98
+
99
+ return await self._load_user_from_db(belgie, db_or_generator, user_id)
100
+
101
+ @staticmethod
102
+ def _is_async_generator(value: object) -> bool:
103
+ return hasattr(value, "__aiter__")
104
+
105
+ @staticmethod
106
+ async def _load_user_from_db(belgie: Belgie, db: DBConnection, user_id: UUID) -> UserProtocol | None:
107
+ client = belgie(db)
108
+ return await client.adapter.get_user_by_id(client.db, user_id)
109
+
110
+
111
+ async def get_user_from_access_token(belgie: Belgie) -> UserProtocol | None:
112
+ return await UserLookup().get_user_from_access_token(belgie)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: belgie-mcp
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Add your description here
5
5
  Author: Matt LeMay
6
6
  Author-email: Matt LeMay <mplemay@users.noreply.github.com>
@@ -0,0 +1,9 @@
1
+ belgie_mcp/__init__.py,sha256=7kREKGNDhhvwJs8Sm3d0FziJUTV6jciN_w3uUNGVHCE,445
2
+ belgie_mcp/metadata.py,sha256=OWnxWCrKDzPcTL5M4JWU0NcnK6KHxGR5F10pjNECkQ4,1651
3
+ belgie_mcp/plugin.py,sha256=RqUQq9yfWEfw_J8qJXZjaVCWrNjVD-Y39Ls1PnJHSAA,2607
4
+ belgie_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ belgie_mcp/user.py,sha256=b9Ba22HIapffexEa1xo4zQkNDO8O5iKlbIddzOWM7I4,3529
6
+ belgie_mcp/verifier.py,sha256=CWuBZjp6dNvZ-qm2cjn_cqNzC26Acv3x7e_6jHazIO8,4686
7
+ belgie_mcp-0.4.0.dist-info/WHEEL,sha256=iHtWm8nRfs0VRdCYVXocAWFW8ppjHL-uTJkAdZJKOBM,80
8
+ belgie_mcp-0.4.0.dist-info/METADATA,sha256=QgOR5cZUiDLT3_pe6H1ghvkvZ3N9cNvi3f5FbnOMek0,322
9
+ belgie_mcp-0.4.0.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- belgie_mcp/__init__.py,sha256=Ci70TriZn02SP8Ak0YEJ37qDQX7Uy_1M6sUlqcziXIY,356
2
- belgie_mcp/metadata.py,sha256=OWnxWCrKDzPcTL5M4JWU0NcnK6KHxGR5F10pjNECkQ4,1651
3
- belgie_mcp/plugin.py,sha256=RqUQq9yfWEfw_J8qJXZjaVCWrNjVD-Y39Ls1PnJHSAA,2607
4
- belgie_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- belgie_mcp/verifier.py,sha256=CWuBZjp6dNvZ-qm2cjn_cqNzC26Acv3x7e_6jHazIO8,4686
6
- belgie_mcp-0.3.0.dist-info/WHEEL,sha256=iHtWm8nRfs0VRdCYVXocAWFW8ppjHL-uTJkAdZJKOBM,80
7
- belgie_mcp-0.3.0.dist-info/METADATA,sha256=CgTQtwQuuKfQKVnS5MZarsLiGt6OFrlU-0kUlWdcU94,322
8
- belgie_mcp-0.3.0.dist-info/RECORD,,