|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import os |
|
|
import uvicorn |
|
|
import inspect |
|
|
|
|
|
from mcp.server.fastmcp import FastMCP |
|
|
from starlette.requests import Request |
|
|
from starlette.responses import PlainTextResponse, JSONResponse, RedirectResponse |
|
|
from starlette.middleware.sessions import SessionMiddleware |
|
|
from fastapi import FastAPI |
|
|
|
|
|
from authlib.integrations.starlette_client import OAuth, OAuthError |
|
|
|
|
|
from langchain_community.utilities import SQLDatabase |
|
|
from langchain_community.tools.sql_database.tool import QuerySQLCheckerTool |
|
|
from langchain_openai import ChatOpenAI |
|
|
|
|
|
llm = ChatOpenAI( |
|
|
api_key=os.environ.get('OPENAI_API_KEY', None), |
|
|
base_url=os.environ['OPENAI_BASE_URL'], |
|
|
model='gpt-4o-mini', |
|
|
temperature=0 |
|
|
) |
|
|
|
|
|
credit_card_db = SQLDatabase.from_uri(r"sqlite:///data/ccms.db") |
|
|
query_checker_tool = QuerySQLCheckerTool(db=credit_card_db, llm=llm) |
|
|
|
|
|
|
|
|
GOOGLE_CLIENT_ID = os.environ["GOOGLE_CLIENT_ID"] |
|
|
GOOGLE_CLIENT_SECRET = os.environ["GOOGLE_CLIENT_SECRET"] |
|
|
SECRET_KEY = os.environ.get("SESSION_SECRET", "supersecret") |
|
|
|
|
|
|
|
|
app = FastAPI() |
|
|
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY) |
|
|
|
|
|
|
|
|
oauth = OAuth() |
|
|
CONF_URL = 'https://accounts.google.com/.well-known/openid-configuration' |
|
|
oauth.register( |
|
|
name='google', |
|
|
client_id=GOOGLE_CLIENT_ID, |
|
|
client_secret=GOOGLE_CLIENT_SECRET, |
|
|
server_metadata_url=CONF_URL, |
|
|
client_kwargs={ |
|
|
'scope': 'openid email profile' |
|
|
} |
|
|
) |
|
|
|
|
|
|
|
|
mcp = FastMCP("Credit Card Database Server") |
|
|
tool_registry = [] |
|
|
|
|
|
def register_tool(fn): |
|
|
mcp.tool()(fn) |
|
|
sig = inspect.signature(fn) |
|
|
params = [ |
|
|
{ |
|
|
"name": param.name, |
|
|
"type": str(param.annotation) if param.annotation is not inspect._empty else "Any", |
|
|
"default": param.default if param.default is not inspect._empty else None, |
|
|
} |
|
|
for param in sig.parameters.values() |
|
|
] |
|
|
tool_registry.append({ |
|
|
"name": fn.__name__, |
|
|
"description": fn.__doc__.strip() if fn.__doc__ else "", |
|
|
"parameters": params, |
|
|
}) |
|
|
return fn |
|
|
|
|
|
@register_tool |
|
|
def sql_db_list_tables(): |
|
|
"""Returns a comma-separated list of table names in the database.""" |
|
|
return credit_card_db.get_usable_table_names() |
|
|
|
|
|
@register_tool |
|
|
def sql_db_schema(table_names: list[str]) -> str: |
|
|
"""Input 'table_names_str' is a comma-separated string of table names. Returns the DDL SQL schema for these tables.""" |
|
|
return credit_card_db.get_table_info(table_names) |
|
|
|
|
|
@register_tool |
|
|
def sql_db_query_checker(query: str) -> str: |
|
|
"""Checks if the query is valid. If valid, returns the original query; if not, returns the corrected query.""" |
|
|
return query_checker_tool.run(query) |
|
|
|
|
|
@register_tool |
|
|
def sql_db_query(query: str) -> str: |
|
|
"""Executes the query (SELECT only) and returns the result.""" |
|
|
return credit_card_db.run(query) |
|
|
|
|
|
@app.route("/") |
|
|
async def home(request: Request): |
|
|
user = request.session.get("user") |
|
|
if user: |
|
|
username = user["name"] |
|
|
return PlainTextResponse(f"Hello, {username}! You are logged in with Google.\nAccess /mcp/, /tools/") |
|
|
else: |
|
|
return PlainTextResponse("Hello! Please go to https://pgurazada1-credit-card-database-mcp-server.hf.space/login to sign in with Google.") |
|
|
|
|
|
@app.route("/login") |
|
|
async def login(request: Request): |
|
|
redirect_uri = str(request.url_for('auth')).replace("http://", "https://") |
|
|
print("Redirect URI:", redirect_uri) |
|
|
return await oauth.google.authorize_redirect(request, redirect_uri) |
|
|
|
|
|
@app.route("/auth") |
|
|
async def auth(request: Request): |
|
|
token = await oauth.google.authorize_access_token(request) |
|
|
token_dict = dict(token) |
|
|
print("TOKEN:", token_dict) |
|
|
user_info = token_dict.get("userinfo") |
|
|
if not user_info: |
|
|
|
|
|
user_info = await oauth.google.userinfo(request, token=token_dict) |
|
|
request.session["user"] = dict(user_info) |
|
|
return RedirectResponse(url="/") |
|
|
|
|
|
@app.route("/logout") |
|
|
async def logout(request: Request): |
|
|
request.session.pop("user", None) |
|
|
return RedirectResponse(url="/") |
|
|
|
|
|
|
|
|
def require_google_auth(request: Request): |
|
|
user = request.session.get("user") |
|
|
if not user: |
|
|
return RedirectResponse(url="/login") |
|
|
return user |
|
|
|
|
|
@app.route("/mcp/{path:path}", methods=["GET", "POST"]) |
|
|
async def mcp_proxy(request: Request): |
|
|
user = require_google_auth(request) |
|
|
if isinstance(user, RedirectResponse): |
|
|
return user |
|
|
|
|
|
return await mcp.streamable_http_app(request.scope, request.receive, request.send) |
|
|
|
|
|
@app.route("/tools") |
|
|
async def list_tools(request: Request): |
|
|
user = require_google_auth(request) |
|
|
if isinstance(user, RedirectResponse): |
|
|
return user |
|
|
return JSONResponse(tool_registry) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
uvicorn.run(app, host="0.0.0.0", port=8000) |