"""
Dexcom Developer API Tools (OAuth-based)
These tools require OAuth authentication and are conditionally loaded
when ENABLE_DEXCOM=true environment variable is set.
"""
import httpx
from src.cgm.dexcom_official import DexcomOfficialClient, format_egvs_for_display
def register_dexcom_tools(mcp):
"""Register Dexcom OAuth tools to the MCP server."""
@mcp.tool()
def get_dexcom_auth_url(client_id: str, client_secret: str, redirect_uri: str = "http://localhost:8080/callback") -> str:
"""
Generate Dexcom OAuth authorization URL for Sandbox environment.
Use this to get the URL where users can authorize your app.
In Sandbox mode, no password is required - users select from a dropdown.
Args:
client_id: OAuth client ID from Dexcom Developer Portal
client_secret: OAuth client secret (stored for later token exchange)
redirect_uri: Callback URL registered with your Dexcom app
Returns:
Authorization URL to redirect the user to
"""
client = DexcomOfficialClient(
client_id=client_id,
client_secret=client_secret,
redirect_uri=redirect_uri,
sandbox=True
)
auth_url = client.get_authorization_url(state="mcp_sandbox_test")
return f"""
### ๐ Dexcom OAuth ์ธ์ฆ URL (Sandbox)
**์๋ URL๋ก ์ด๋ํ์ฌ ์ธ์ฆํ์ธ์:**
[์ธ์ฆ ํ์ด์ง ์ด๊ธฐ]({auth_url})
> [!NOTE]
> Sandbox ํ๊ฒฝ์์๋ ๋น๋ฐ๋ฒํธ ์
๋ ฅ ์์ด ๋๋กญ๋ค์ด์์ ํ
์คํธ ์ฌ์ฉ์๋ฅผ ์ ํํฉ๋๋ค.
> ์ฌ์ฉ ๊ฐ๋ฅํ ํ
์คํธ ์ฌ์ฉ์: SandboxUser1 ~ SandboxUser7 (SandboxUser7์ G7 ๋ฐ์ดํฐ)
์ธ์ฆ ์๋ฃ ํ redirect_uri๋ก ๋์์ค๋ URL์์ `code` ํ๋ผ๋ฏธํฐ๋ฅผ ํ์ธํ์ธ์.
๊ทธ ์ฝ๋๋ฅผ `get_cgm_sandbox` ๋๊ตฌ์ ์ ๋ฌํ๋ฉด ํ๋น ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ์ ์์ต๋๋ค.
"""
@mcp.tool()
async def get_cgm_sandbox(
client_id: str,
client_secret: str,
authorization_code: str,
redirect_uri: str = "http://localhost:8080/callback"
) -> str:
"""
Get CGM data from Dexcom Developer API Sandbox using authorization code.
This tool uses the official Dexcom API (not Share API) and works with
the Sandbox environment, which provides simulated CGM data for testing.
Args:
client_id: OAuth client ID from Dexcom Developer Portal
client_secret: OAuth client secret from Dexcom Developer Portal
authorization_code: Code received from OAuth callback after user authorization
redirect_uri: Same redirect_uri used in get_dexcom_auth_url
Returns:
Formatted CGM data from the last 24 hours
"""
try:
client = DexcomOfficialClient(
client_id=client_id,
client_secret=client_secret,
redirect_uri=redirect_uri,
sandbox=True
)
# Exchange authorization code for access token
token_response = await client.exchange_code_for_token(authorization_code)
access_token = token_response.get("access_token")
if not access_token:
return "โ ํ ํฐ ๊ตํ ์คํจ: access_token์ ๋ฐ์ง ๋ชปํ์ต๋๋ค."
# Get EGV data
egvs_data = await client.get_egvs(access_token)
# Format for display
result = format_egvs_for_display(egvs_data, limit=10)
result += "\n\n> โ
Dexcom Developer API Sandbox์์ ๋ฐ์ดํฐ๋ฅผ ์ฑ๊ณต์ ์ผ๋ก ์กฐํํ์ต๋๋ค."
return result
except httpx.HTTPStatusError as e:
return f"โ API ์ค๋ฅ: {e.response.status_code} - {e.response.text}"
except Exception as e:
return f"โ ์ค๋ฅ ๋ฐ์: {str(e)}"
@mcp.tool()
async def get_cgm_with_token(
access_token: str,
hours: int = 24,
sandbox: bool = True
) -> str:
"""
Get CGM data using an existing access token.
Use this if you already have a valid access token from a previous OAuth flow.
Args:
access_token: Valid OAuth access token
hours: Number of hours of data to retrieve (default: 24, max: 720 for 30 days)
sandbox: Whether to use sandbox environment (default: True)
Returns:
Formatted CGM data
"""
from datetime import datetime, timedelta
try:
# Create a minimal client just for API calls
base_url = (
"https://sandbox-api.dexcom.com" if sandbox
else "https://api.dexcom.com"
)
end_date = datetime.utcnow()
start_date = end_date - timedelta(hours=min(hours, 720)) # Max 30 days
async with httpx.AsyncClient() as http_client:
response = await http_client.get(
f"{base_url}/v3/users/self/egvs",
params={
"startDate": start_date.strftime("%Y-%m-%dT%H:%M:%S"),
"endDate": end_date.strftime("%Y-%m-%dT%H:%M:%S"),
},
headers={"Authorization": f"Bearer {access_token}"}
)
response.raise_for_status()
egvs_data = response.json()
result = format_egvs_for_display(egvs_data, limit=10)
env_label = "Sandbox" if sandbox else "Production"
result += f"\n\n> โ
Dexcom Developer API ({env_label})์์ ์กฐํ ์๋ฃ"
return result
except httpx.HTTPStatusError as e:
if e.response.status_code == 401:
return "โ ์ธ์ฆ ์คํจ: ํ ํฐ์ด ๋ง๋ฃ๋์๊ฑฐ๋ ์ ํจํ์ง ์์ต๋๋ค. ๋ค์ ์ธ์ฆํด์ฃผ์ธ์."
return f"โ API ์ค๋ฅ: {e.response.status_code} - {e.response.text}"
except Exception as e:
return f"โ ์ค๋ฅ ๋ฐ์: {str(e)}"
@mcp.tool()
async def get_cgm_data(
hours: int = 24,
sandbox: bool = True
) -> str:
"""
Get CGM data using OAuth token from HTTP header (PlayMCP integration).
This tool automatically retrieves the Dexcom access token from the
Authorization header, which is passed by PlayMCP after OAuth authentication.
Use this when:
- User asks "ํ๋น ๋ณด์ฌ์ค" or "what's my glucose?"
- User wants to check recent CGM readings
Args:
hours: Number of hours of data to retrieve (default: 24, max: 720 for 30 days)
sandbox: Whether to use sandbox environment (default: True)
Returns:
Formatted CGM data or auth instructions if token not found
"""
from datetime import datetime, timedelta
from fastmcp.server.dependencies import get_http_headers
# Get access token from HTTP headers (passed by PlayMCP)
headers = get_http_headers()
auth_header = headers.get("authorization", "")
if not auth_header:
return """
### โ ๏ธ Dexcom ์ธ์ฆ ํ์
OAuth ํ ํฐ์ด ์์ต๋๋ค. PlayMCP์์ Dexcom ์ธ์ฆ์ ๋จผ์ ์๋ฃํด์ฃผ์ธ์.
**์ธ์ฆ ๋ฐฉ๋ฒ:**
1. PlayMCP์์ ์ด MCP์ OAuth ์ธ์ฆ ์์
2. Dexcom Sandbox์์ ํ
์คํธ ์ฌ์ฉ์ ์ ํ (์: G7 Mobile App User7)
3. ์ธ์ฆ ์๋ฃ ํ ๋ค์ ์๋
> ์ธ์ฆ์ด ์๋ฃ๋๋ฉด ์๋์ผ๋ก ํ ํฐ์ด ์ ๋ฌ๋ฉ๋๋ค.
"""
# Extract Bearer token
if auth_header.startswith("Bearer "):
access_token = auth_header[7:] # Remove "Bearer " prefix
else:
access_token = auth_header
try:
base_url = (
"https://sandbox-api.dexcom.com" if sandbox
else "https://api.dexcom.com"
)
end_date = datetime.utcnow()
start_date = end_date - timedelta(hours=min(hours, 720))
async with httpx.AsyncClient() as http_client:
response = await http_client.get(
f"{base_url}/v3/users/self/egvs",
params={
"startDate": start_date.strftime("%Y-%m-%dT%H:%M:%S"),
"endDate": end_date.strftime("%Y-%m-%dT%H:%M:%S"),
},
headers={"Authorization": f"Bearer {access_token}"}
)
response.raise_for_status()
egvs_data = response.json()
result = format_egvs_for_display(egvs_data, limit=10)
env_label = "Sandbox" if sandbox else "Production"
result += f"\n\n> โ
Dexcom Developer API ({env_label})์์ ์กฐํ ์๋ฃ"
return result
except httpx.HTTPStatusError as e:
if e.response.status_code == 401:
return "โ ์ธ์ฆ ์คํจ: ํ ํฐ์ด ๋ง๋ฃ๋์๊ฑฐ๋ ์ ํจํ์ง ์์ต๋๋ค. PlayMCP์์ ๋ค์ ์ธ์ฆํด์ฃผ์ธ์."
return f"โ API ์ค๋ฅ: {e.response.status_code} - {e.response.text}"
except Exception as e:
return f"โ ์ค๋ฅ ๋ฐ์: {str(e)}"