belgie-mcp 0.3.1__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)
|
|
@@ -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.1.dist-info/WHEEL,sha256=iHtWm8nRfs0VRdCYVXocAWFW8ppjHL-uTJkAdZJKOBM,80
|
|
7
|
-
belgie_mcp-0.3.1.dist-info/METADATA,sha256=ROO2vpgh0HOgv-6CCmebed-MuV6jzSkwMcXguS2U0J0,322
|
|
8
|
-
belgie_mcp-0.3.1.dist-info/RECORD,,
|
|
File without changes
|