Initial commit: Monarch MCP Custom SSE server
This commit is contained in:
11
.env
Normal file
11
.env
Normal file
@@ -0,0 +1,11 @@
|
||||
# Monarch Money Credentials
|
||||
# You can use MONARCH_TOKEN (recommended) OR Email/Password
|
||||
MONARCH_TOKEN=MONARCH_TOKEN=64422c9dec80f6009e89e571a601ccf31488cac0651eae6461be6f78d30fc0db
|
||||
|
||||
# Fallback credentials
|
||||
MONARCH_EMAIL=
|
||||
MONARCH_PASSWORD=
|
||||
|
||||
# Server Configuration
|
||||
PORT=8100
|
||||
LOG_LEVEL=INFO
|
||||
11
.env.example
Normal file
11
.env.example
Normal file
@@ -0,0 +1,11 @@
|
||||
# Monarch Money Credentials
|
||||
# You can use MONARCH_TOKEN (recommended) OR Email/Password
|
||||
MONARCH_TOKEN=
|
||||
|
||||
# Fallback credentials
|
||||
MONARCH_EMAIL=
|
||||
MONARCH_PASSWORD=
|
||||
|
||||
# Server Configuration
|
||||
PORT=8000
|
||||
LOG_LEVEL=INFO
|
||||
30
.gitea/workflows/build.yaml
Normal file
30
.gitea/workflows/build.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Build and Push Monarch MCP Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Login to Gitea Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: gitea.ext.ben.io
|
||||
username: ${{ gitea.actor }}
|
||||
password: ${{ secrets.CR_PAT }}
|
||||
|
||||
- name: Build and Push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: gitea.ext.ben.io/${{ gitea.repository }}:latest
|
||||
41
Dockerfile
Normal file
41
Dockerfile
Normal file
@@ -0,0 +1,41 @@
|
||||
# Stage 1: Builder
|
||||
FROM python:3.12-slim AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install uv for fast dependency management
|
||||
RUN pip install uv
|
||||
|
||||
# Copy only requirements first to leverage Docker cache
|
||||
COPY requirements.txt .
|
||||
|
||||
# Install dependencies into the system python environment of the builder
|
||||
RUN uv pip install --system -r requirements.txt
|
||||
|
||||
# Stage 2: Final Image
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install curl for health checks
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy installed packages from builder
|
||||
COPY --from=builder /usr/local/lib/python3.12/site-packages/ /usr/local/lib/python3.12/site-packages/
|
||||
COPY --from=builder /usr/local/bin/ /usr/local/bin/
|
||||
|
||||
# Copy application code
|
||||
COPY src/ ./src/
|
||||
COPY pyproject.toml .
|
||||
COPY README.md .
|
||||
|
||||
# Set Python path to find the package
|
||||
ENV PYTHONPATH=/app/src
|
||||
|
||||
# Default port
|
||||
EXPOSE 8000
|
||||
|
||||
# Run the server
|
||||
CMD ["python", "src/monarch_mcp_custom/server.py"]
|
||||
41
README.md
Normal file
41
README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Monarch Money Custom MCP Server
|
||||
|
||||
A custom Model Context Protocol (MCP) server for Monarch Money, designed for Docker deployment with SSE (Server-Sent Events) support.
|
||||
|
||||
## 🚀 Setup
|
||||
|
||||
### 1. Obtain Authentication Token
|
||||
Run the setup script locally to authenticate and generate a token:
|
||||
|
||||
```bash
|
||||
python login_setup.py
|
||||
```
|
||||
|
||||
Follow the prompts to log in. Once successful, copy the `MONARCH_TOKEN` printed in the terminal.
|
||||
|
||||
### 2. Configure Environment
|
||||
Create a `.env` file based on `.env.example` and paste your token:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env and set MONARCH_TOKEN=your_token_here
|
||||
```
|
||||
|
||||
### 3. Deploy with Docker
|
||||
Start the server using Docker Compose:
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## 🔌 Connection
|
||||
The server will be available at:
|
||||
- **SSE Endpoint**: `http://localhost:8000/mcp/sse`
|
||||
- **Health Check**: `http://localhost:8000/health`
|
||||
|
||||
## 🛠️ Tools Included
|
||||
- `get_accounts`: View all financial accounts.
|
||||
- `get_transactions`: Fetch recent transactions with filtering.
|
||||
- `get_budgets`: View budget status.
|
||||
- `get_account_holdings`: Detailed investment holdings.
|
||||
- `refresh_accounts`: Trigger a refresh of account data.
|
||||
15
docker-compose.yml
Normal file
15
docker-compose.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
services:
|
||||
monarch-mcp:
|
||||
image: gitea.ext.ben.io/b3nw/monarch-mcp-custom:latest
|
||||
container_name: monarch-mcp-custom
|
||||
ports:
|
||||
- "8000:8000"
|
||||
env_file:
|
||||
- .env
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
restart: unless-stopped
|
||||
64
login_setup.py
Normal file
64
login_setup.py
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Interactive Monarch Money login with MFA support.
|
||||
Saves session securely and provides the token for Docker environment.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import getpass
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add src to sys.path
|
||||
sys.path.insert(0, str(Path(__file__).parent / "src"))
|
||||
|
||||
from monarchmoney import MonarchMoney, RequireMFAException
|
||||
from dotenv import load_dotenv
|
||||
from monarch_mcp_custom.auth import save_token
|
||||
|
||||
|
||||
async def main():
|
||||
load_dotenv()
|
||||
|
||||
print("\n🏦 Monarch Money - Custom MCP Setup")
|
||||
print("=" * 45)
|
||||
print("This script will help you obtain an authentication token")
|
||||
print("for use in your Docker .env file.\n")
|
||||
|
||||
mm = MonarchMoney()
|
||||
|
||||
try:
|
||||
email = input("Email: ")
|
||||
password = getpass.getpass("Password: ")
|
||||
|
||||
try:
|
||||
await mm.login(email, password, use_saved_session=False, save_session=True)
|
||||
print("✅ Login successful!")
|
||||
except RequireMFAException:
|
||||
print("🔐 MFA code required")
|
||||
mfa_code = input("Two Factor Code: ")
|
||||
await mm.multi_factor_authenticate(email, password, mfa_code)
|
||||
print("✅ MFA authentication successful")
|
||||
|
||||
token = mm.token
|
||||
if token:
|
||||
print("\n" + "!" * 50)
|
||||
print("🔑 YOUR MONARCH_TOKEN:")
|
||||
print(f"\n{token}\n")
|
||||
print("!" * 50)
|
||||
print("\nCopy the token above into your .env file as:")
|
||||
print(f"MONARCH_TOKEN={token}")
|
||||
|
||||
# Also save to local keyring for convenience
|
||||
save_token(token)
|
||||
print("\n✅ Token also saved to local system keyring.")
|
||||
else:
|
||||
print("❌ Failed to retrieve token from MonarchMoney instance.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Setup failed: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
33
pyproject.toml
Normal file
33
pyproject.toml
Normal file
@@ -0,0 +1,33 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "monarch-mcp-custom"
|
||||
version = "0.1.0"
|
||||
description = "Custom Monarch Money MCP Server with SSE support"
|
||||
readme = "README.md"
|
||||
license = { text = "MIT" }
|
||||
authors = [
|
||||
{ name = "opencode" }
|
||||
]
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
]
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"mcp[cli]>=1.0.0",
|
||||
"monarchmoney>=0.1.15",
|
||||
"gql>=3.4,<4.0",
|
||||
"python-dotenv>=1.0.0",
|
||||
"pydantic>=2.0.0",
|
||||
"starlette>=0.35.0",
|
||||
"uvicorn>=0.27.0",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
monarch-mcp-custom = "monarch_mcp_custom.server:main"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
7
requirements.txt
Normal file
7
requirements.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
mcp[cli]>=1.0.0
|
||||
monarchmoney>=0.1.15
|
||||
gql>=3.4,<4.0
|
||||
python-dotenv>=1.0.0
|
||||
pydantic>=2.0.0
|
||||
starlette>=0.35.0
|
||||
uvicorn>=0.27.0
|
||||
60
src/monarch_mcp_custom.egg-info/PKG-INFO
Normal file
60
src/monarch_mcp_custom.egg-info/PKG-INFO
Normal file
@@ -0,0 +1,60 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: monarch-mcp-custom
|
||||
Version: 0.1.0
|
||||
Summary: Custom Monarch Money MCP Server with SSE support
|
||||
Author: opencode
|
||||
License: MIT
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Requires-Python: >=3.12
|
||||
Description-Content-Type: text/markdown
|
||||
Requires-Dist: mcp[cli]>=1.0.0
|
||||
Requires-Dist: monarchmoney>=0.1.15
|
||||
Requires-Dist: gql<4.0,>=3.4
|
||||
Requires-Dist: keyring>=24.0.0
|
||||
Requires-Dist: python-dotenv>=1.0.0
|
||||
Requires-Dist: pydantic>=2.0.0
|
||||
Requires-Dist: starlette>=0.35.0
|
||||
Requires-Dist: uvicorn>=0.27.0
|
||||
|
||||
# Monarch Money Custom MCP Server
|
||||
|
||||
A custom Model Context Protocol (MCP) server for Monarch Money, designed for Docker deployment with SSE (Server-Sent Events) support.
|
||||
|
||||
## 🚀 Setup
|
||||
|
||||
### 1. Obtain Authentication Token
|
||||
Run the setup script locally to authenticate and generate a token:
|
||||
|
||||
```bash
|
||||
python login_setup.py
|
||||
```
|
||||
|
||||
Follow the prompts to log in. Once successful, copy the `MONARCH_TOKEN` printed in the terminal.
|
||||
|
||||
### 2. Configure Environment
|
||||
Create a `.env` file based on `.env.example` and paste your token:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env and set MONARCH_TOKEN=your_token_here
|
||||
```
|
||||
|
||||
### 3. Deploy with Docker
|
||||
Start the server using Docker Compose:
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## 🔌 Connection
|
||||
The server will be available at:
|
||||
- **SSE Endpoint**: `http://localhost:8000/mcp/sse`
|
||||
- **Health Check**: `http://localhost:8000/health`
|
||||
|
||||
## 🛠️ Tools Included
|
||||
- `get_accounts`: View all financial accounts.
|
||||
- `get_transactions`: Fetch recent transactions with filtering.
|
||||
- `get_budgets`: View budget status.
|
||||
- `get_account_holdings`: Detailed investment holdings.
|
||||
- `refresh_accounts`: Trigger a refresh of account data.
|
||||
11
src/monarch_mcp_custom.egg-info/SOURCES.txt
Normal file
11
src/monarch_mcp_custom.egg-info/SOURCES.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
README.md
|
||||
pyproject.toml
|
||||
src/monarch_mcp_custom/__init__.py
|
||||
src/monarch_mcp_custom/auth.py
|
||||
src/monarch_mcp_custom/server.py
|
||||
src/monarch_mcp_custom.egg-info/PKG-INFO
|
||||
src/monarch_mcp_custom.egg-info/SOURCES.txt
|
||||
src/monarch_mcp_custom.egg-info/dependency_links.txt
|
||||
src/monarch_mcp_custom.egg-info/entry_points.txt
|
||||
src/monarch_mcp_custom.egg-info/requires.txt
|
||||
src/monarch_mcp_custom.egg-info/top_level.txt
|
||||
1
src/monarch_mcp_custom.egg-info/dependency_links.txt
Normal file
1
src/monarch_mcp_custom.egg-info/dependency_links.txt
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
2
src/monarch_mcp_custom.egg-info/entry_points.txt
Normal file
2
src/monarch_mcp_custom.egg-info/entry_points.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
[console_scripts]
|
||||
monarch-mcp-custom = monarch_mcp_custom.server:main
|
||||
8
src/monarch_mcp_custom.egg-info/requires.txt
Normal file
8
src/monarch_mcp_custom.egg-info/requires.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
mcp[cli]>=1.0.0
|
||||
monarchmoney>=0.1.15
|
||||
gql<4.0,>=3.4
|
||||
keyring>=24.0.0
|
||||
python-dotenv>=1.0.0
|
||||
pydantic>=2.0.0
|
||||
starlette>=0.35.0
|
||||
uvicorn>=0.27.0
|
||||
1
src/monarch_mcp_custom.egg-info/top_level.txt
Normal file
1
src/monarch_mcp_custom.egg-info/top_level.txt
Normal file
@@ -0,0 +1 @@
|
||||
monarch_mcp_custom
|
||||
1
src/monarch_mcp_custom/__init__.py
Normal file
1
src/monarch_mcp_custom/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__version__ = "0.1.0"
|
||||
BIN
src/monarch_mcp_custom/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
src/monarch_mcp_custom/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
src/monarch_mcp_custom/__pycache__/auth.cpython-313.pyc
Normal file
BIN
src/monarch_mcp_custom/__pycache__/auth.cpython-313.pyc
Normal file
Binary file not shown.
70
src/monarch_mcp_custom/auth.py
Normal file
70
src/monarch_mcp_custom/auth.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
Authentication and session management for Monarch Money.
|
||||
Prioritizes environment variables for Docker compatibility.
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
from typing import Optional
|
||||
from monarchmoney import MonarchMoney
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def load_token() -> Optional[str]:
|
||||
"""
|
||||
Loads the authentication token.
|
||||
Checks MONARCH_TOKEN environment variable first.
|
||||
"""
|
||||
# 1. Check environment variable (Best for Docker)
|
||||
token = os.getenv("MONARCH_TOKEN")
|
||||
if token:
|
||||
logger.info("✅ Token loaded from MONARCH_TOKEN environment variable")
|
||||
return token
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def save_token(token: str) -> None:
|
||||
"""Saves the token to the system keyring if available."""
|
||||
try:
|
||||
import keyring
|
||||
|
||||
KEYRING_SERVICE = "com.mcp.monarch-mcp-server"
|
||||
KEYRING_USERNAME = "monarch-token"
|
||||
keyring.set_password(KEYRING_SERVICE, KEYRING_USERNAME, token)
|
||||
logger.info("✅ Token saved securely to keyring")
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Failed to save token to keyring (non-fatal): {e}")
|
||||
|
||||
|
||||
async def get_authenticated_client() -> MonarchMoney:
|
||||
"""
|
||||
Returns an authenticated MonarchMoney client.
|
||||
Raises RuntimeError if no authentication is found.
|
||||
"""
|
||||
token = load_token()
|
||||
if token:
|
||||
try:
|
||||
# The monarchmoney library supports passing the token directly
|
||||
return MonarchMoney(token=token)
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to initialize MonarchMoney with token: {e}")
|
||||
raise
|
||||
|
||||
# Fallback to email/password if token is missing (only if both are present)
|
||||
email = os.getenv("MONARCH_EMAIL")
|
||||
password = os.getenv("MONARCH_PASSWORD")
|
||||
if email and password:
|
||||
try:
|
||||
mm = MonarchMoney()
|
||||
await mm.login(email, password)
|
||||
logger.info("✅ Logged in using email/password credentials")
|
||||
return mm
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Login failed: {e}")
|
||||
raise
|
||||
|
||||
raise RuntimeError(
|
||||
"🔐 Authentication required. Please provide MONARCH_TOKEN or run login_setup.py"
|
||||
)
|
||||
200
src/monarch_mcp_custom/server.py
Normal file
200
src/monarch_mcp_custom/server.py
Normal file
@@ -0,0 +1,200 @@
|
||||
"""
|
||||
Monarch Money MCP Server - Custom SSE Implementation.
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import json
|
||||
import asyncio
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from fastmcp import FastMCP
|
||||
from starlette.applications import Starlette
|
||||
from starlette.responses import JSONResponse
|
||||
from starlette.routing import Route, Mount
|
||||
import uvicorn
|
||||
|
||||
from monarch_mcp_custom.auth import get_authenticated_client
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Initialize FastMCP
|
||||
mcp = FastMCP("Monarch Money Custom")
|
||||
|
||||
# --- Helpers ---
|
||||
|
||||
|
||||
def serialize_json(data: Any) -> str:
|
||||
"""Helper to serialize data to JSON safely."""
|
||||
return json.dumps(data, indent=2, default=str)
|
||||
|
||||
|
||||
# --- MCP Tools ---
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_accounts() -> str:
|
||||
"""Get all financial accounts from Monarch Money."""
|
||||
try:
|
||||
client = await get_authenticated_client()
|
||||
accounts = await client.get_accounts()
|
||||
|
||||
account_list = []
|
||||
for account in accounts.get("accounts", []):
|
||||
account_info = {
|
||||
"id": account.get("id"),
|
||||
"name": account.get("displayName") or account.get("name"),
|
||||
"type": (account.get("type") or {}).get("name"),
|
||||
"balance": account.get("currentBalance"),
|
||||
"institution": (account.get("institution") or {}).get("name"),
|
||||
"is_active": not account.get("deactivatedAt"),
|
||||
}
|
||||
account_list.append(account_info)
|
||||
|
||||
return serialize_json(account_list)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get accounts: {e}")
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_transactions(
|
||||
limit: int = 50,
|
||||
offset: int = 0,
|
||||
start_date: Optional[str] = None,
|
||||
end_date: Optional[str] = None,
|
||||
account_id: Optional[str] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Get transactions from Monarch Money.
|
||||
Dates should be in YYYY-MM-DD format.
|
||||
"""
|
||||
try:
|
||||
client = await get_authenticated_client()
|
||||
filters = {}
|
||||
if start_date:
|
||||
filters["start_date"] = start_date
|
||||
if end_date:
|
||||
filters["end_date"] = end_date
|
||||
if account_id:
|
||||
filters["account_id"] = account_id
|
||||
|
||||
transactions = await client.get_transactions(
|
||||
limit=limit, offset=offset, **filters
|
||||
)
|
||||
|
||||
results = transactions.get("allTransactions", {}).get("results", [])
|
||||
formatted = []
|
||||
for txn in results:
|
||||
formatted.append(
|
||||
{
|
||||
"id": txn.get("id"),
|
||||
"date": txn.get("date"),
|
||||
"amount": txn.get("amount"),
|
||||
"description": txn.get("description"),
|
||||
"category": (txn.get("category") or {}).get("name"),
|
||||
"account": (txn.get("account") or {}).get("displayName"),
|
||||
"merchant": (txn.get("merchant") or {}).get("name"),
|
||||
"is_pending": txn.get("isPending", False),
|
||||
}
|
||||
)
|
||||
|
||||
return serialize_json(formatted)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get transactions: {e}")
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_budgets() -> str:
|
||||
"""Get current budget information."""
|
||||
try:
|
||||
client = await get_authenticated_client()
|
||||
budgets = await client.get_budgets()
|
||||
|
||||
budget_list = []
|
||||
for b in budgets.get("budgets", []):
|
||||
budget_list.append(
|
||||
{
|
||||
"name": b.get("name"),
|
||||
"amount": b.get("amount"),
|
||||
"spent": b.get("spent"),
|
||||
"remaining": b.get("remaining"),
|
||||
"category": (b.get("category") or {}).get("name"),
|
||||
}
|
||||
)
|
||||
|
||||
return serialize_json(budget_list)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get budgets: {e}")
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_account_holdings(account_id: str) -> str:
|
||||
"""Get investment holdings for a specific account."""
|
||||
try:
|
||||
client = await get_authenticated_client()
|
||||
# Ensure account_id is treated correctly (usually string ID in Monarch)
|
||||
holdings = await client.get_account_holdings(account_id)
|
||||
return serialize_json(holdings)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get holdings: {e}")
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def refresh_accounts() -> str:
|
||||
"""Request a refresh of account data from financial institutions."""
|
||||
try:
|
||||
client = await get_authenticated_client()
|
||||
result = await client.request_accounts_refresh()
|
||||
return serialize_json(result)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to refresh accounts: {e}")
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
|
||||
# --- Health Check ---
|
||||
|
||||
|
||||
async def health_check(request):
|
||||
"""Simple health check endpoint."""
|
||||
return JSONResponse({"status": "ok", "timestamp": datetime.now().isoformat()})
|
||||
|
||||
|
||||
# --- ASGI App Setup ---
|
||||
|
||||
|
||||
def create_app():
|
||||
"""Create the Starlette application with MCP mounted at /mcp."""
|
||||
mcp_app = mcp.http_app()
|
||||
|
||||
routes = [
|
||||
Route("/health", health_check, methods=["GET"]),
|
||||
Mount("/mcp", app=mcp_app),
|
||||
]
|
||||
|
||||
return Starlette(routes=routes, lifespan=mcp_app.lifespan)
|
||||
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
def main():
|
||||
"""Entry point for running the server."""
|
||||
port = int(os.getenv("PORT", 8000))
|
||||
uvicorn.run(app, host="0.0.0.0", port=port)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user