Spaces:
Sleeping
Sleeping
Upload 6 files
Browse files- LICENSE +21 -0
- init_db.py +35 -0
- mcp_config.py +27 -0
- middleware.py +63 -0
- models.py +36 -0
- test_api.py +83 -0
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 Gemini MCP Server
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
init_db.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Database initialization script for Gemini MCP Server
|
| 4 |
+
Run this script to create all database tables
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from database import engine, Base
|
| 8 |
+
from models import ChatMessage, User, Tenant
|
| 9 |
+
import os
|
| 10 |
+
|
| 11 |
+
def init_database():
|
| 12 |
+
"""Initialize the database with all tables"""
|
| 13 |
+
print("🚀 Initializing database...")
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
# Create all tables
|
| 17 |
+
Base.metadata.create_all(bind=engine)
|
| 18 |
+
print("✅ Database tables created successfully!")
|
| 19 |
+
|
| 20 |
+
# Show created tables
|
| 21 |
+
print("\n📊 Created tables:")
|
| 22 |
+
for table_name in Base.metadata.tables.keys():
|
| 23 |
+
print(f" - {table_name}")
|
| 24 |
+
|
| 25 |
+
print(f"\n💾 Database location: {os.getenv('DATABASE_URL', 'sqlite:///./mcp_server.db')}")
|
| 26 |
+
print("🎉 Database initialization complete!")
|
| 27 |
+
|
| 28 |
+
except Exception as e:
|
| 29 |
+
print(f"❌ Error initializing database: {str(e)}")
|
| 30 |
+
return False
|
| 31 |
+
|
| 32 |
+
return True
|
| 33 |
+
|
| 34 |
+
if __name__ == "__main__":
|
| 35 |
+
init_database()
|
mcp_config.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from dotenv import load_dotenv
|
| 3 |
+
|
| 4 |
+
load_dotenv()
|
| 5 |
+
|
| 6 |
+
class MCPSettings:
|
| 7 |
+
# Server configuration
|
| 8 |
+
SERVER_NAME = os.getenv("SERVER_NAME", "Gemini MCP Server")
|
| 9 |
+
SERVER_VERSION = os.getenv("SERVER_VERSION", "2.0.0")
|
| 10 |
+
|
| 11 |
+
# Model configuration
|
| 12 |
+
MODEL_NAME = "gemini-1.5-flash"
|
| 13 |
+
MODEL_VERSION = "1.5"
|
| 14 |
+
CONTEXT_PROVIDER = "internal"
|
| 15 |
+
|
| 16 |
+
# Rate limiting
|
| 17 |
+
RATE_LIMIT_REQUESTS = int(os.getenv("RATE_LIMIT_REQUESTS", "100"))
|
| 18 |
+
RATE_LIMIT_PERIOD = int(os.getenv("RATE_LIMIT_PERIOD", "60"))
|
| 19 |
+
|
| 20 |
+
# Context settings
|
| 21 |
+
MAX_CONTEXT_RESULTS = int(os.getenv("MAX_CONTEXT_RESULTS", "10"))
|
| 22 |
+
|
| 23 |
+
# Debug mode
|
| 24 |
+
DEBUG = os.getenv("DEBUG", "False").lower() == "true"
|
| 25 |
+
|
| 26 |
+
# Create global settings instance
|
| 27 |
+
mcp_settings = MCPSettings()
|
middleware.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import time
|
| 2 |
+
from typing import Dict
|
| 3 |
+
from fastapi import Request, Response, HTTPException
|
| 4 |
+
from fastapi.responses import JSONResponse
|
| 5 |
+
from mcp_config import mcp_settings
|
| 6 |
+
|
| 7 |
+
# Simple in-memory rate limiting (use Redis in production)
|
| 8 |
+
request_counts: Dict[str, Dict[str, int]] = {}
|
| 9 |
+
|
| 10 |
+
async def rate_limit_middleware(request: Request, call_next):
|
| 11 |
+
"""Rate limiting middleware"""
|
| 12 |
+
client_ip = request.client.host if request.client else "unknown"
|
| 13 |
+
current_time = int(time.time())
|
| 14 |
+
window_start = current_time // mcp_settings.RATE_LIMIT_PERIOD * mcp_settings.RATE_LIMIT_PERIOD
|
| 15 |
+
|
| 16 |
+
# Initialize client data if not exists
|
| 17 |
+
if client_ip not in request_counts:
|
| 18 |
+
request_counts[client_ip] = {}
|
| 19 |
+
|
| 20 |
+
# Clean old windows
|
| 21 |
+
for window in list(request_counts[client_ip].keys()):
|
| 22 |
+
if window < window_start:
|
| 23 |
+
del request_counts[client_ip][window]
|
| 24 |
+
|
| 25 |
+
# Check current window
|
| 26 |
+
if window_start not in request_counts[client_ip]:
|
| 27 |
+
request_counts[client_ip][window_start] = 0
|
| 28 |
+
|
| 29 |
+
# Check rate limit
|
| 30 |
+
if request_counts[client_ip][window_start] >= mcp_settings.RATE_LIMIT_REQUESTS:
|
| 31 |
+
return JSONResponse(
|
| 32 |
+
status_code=429,
|
| 33 |
+
content={
|
| 34 |
+
"code": "RATE_LIMIT_EXCEEDED",
|
| 35 |
+
"message": f"Rate limit exceeded. Max {mcp_settings.RATE_LIMIT_REQUESTS} requests per {mcp_settings.RATE_LIMIT_PERIOD} seconds.",
|
| 36 |
+
"details": {
|
| 37 |
+
"retry_after": mcp_settings.RATE_LIMIT_PERIOD - (current_time % mcp_settings.RATE_LIMIT_PERIOD)
|
| 38 |
+
}
|
| 39 |
+
}
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
# Increment counter
|
| 43 |
+
request_counts[client_ip][window_start] += 1
|
| 44 |
+
|
| 45 |
+
response = await call_next(request)
|
| 46 |
+
return response
|
| 47 |
+
|
| 48 |
+
async def validate_mcp_request(request: Request, call_next):
|
| 49 |
+
"""Validate MCP request format"""
|
| 50 |
+
try:
|
| 51 |
+
response = await call_next(request)
|
| 52 |
+
return response
|
| 53 |
+
except Exception as e:
|
| 54 |
+
if mcp_settings.DEBUG:
|
| 55 |
+
print(f"Request validation error: {str(e)}")
|
| 56 |
+
return JSONResponse(
|
| 57 |
+
status_code=500,
|
| 58 |
+
content={
|
| 59 |
+
"code": "INTERNAL_ERROR",
|
| 60 |
+
"message": "Internal server error",
|
| 61 |
+
"details": {"timestamp": time.time()}
|
| 62 |
+
}
|
| 63 |
+
)
|
models.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, Text, DateTime, Boolean
|
| 2 |
+
from sqlalchemy.sql import func
|
| 3 |
+
from database import Base
|
| 4 |
+
|
| 5 |
+
class ChatMessage(Base):
|
| 6 |
+
__tablename__ = "chat_messages"
|
| 7 |
+
|
| 8 |
+
id = Column(Integer, primary_key=True, index=True)
|
| 9 |
+
user_id = Column(Integer, nullable=False)
|
| 10 |
+
message = Column(Text, nullable=False)
|
| 11 |
+
response = Column(Text, nullable=False)
|
| 12 |
+
context = Column(Text) # JSON string
|
| 13 |
+
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 14 |
+
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
| 15 |
+
|
| 16 |
+
class User(Base):
|
| 17 |
+
__tablename__ = "users"
|
| 18 |
+
|
| 19 |
+
id = Column(Integer, primary_key=True, index=True)
|
| 20 |
+
email = Column(String(255), unique=True, index=True, nullable=False)
|
| 21 |
+
name = Column(String(255), nullable=False)
|
| 22 |
+
hashed_password = Column(String(255), nullable=False)
|
| 23 |
+
is_active = Column(Boolean, default=True)
|
| 24 |
+
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 25 |
+
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
| 26 |
+
|
| 27 |
+
class Tenant(Base):
|
| 28 |
+
__tablename__ = "tenants"
|
| 29 |
+
|
| 30 |
+
id = Column(Integer, primary_key=True, index=True)
|
| 31 |
+
name = Column(String(255), nullable=False)
|
| 32 |
+
domain = Column(String(255), unique=True, index=True)
|
| 33 |
+
owner_id = Column(Integer, nullable=False) # Reference to User
|
| 34 |
+
is_active = Column(Boolean, default=True)
|
| 35 |
+
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 36 |
+
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
test_api.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Simple test script for Gemini MCP Server API
|
| 4 |
+
Run this after starting the server to verify it's working
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import requests
|
| 8 |
+
import json
|
| 9 |
+
import time
|
| 10 |
+
|
| 11 |
+
# Server configuration
|
| 12 |
+
BASE_URL = "http://localhost:8000"
|
| 13 |
+
AUTH_TOKEN = "test-token" # Replace with actual token
|
| 14 |
+
|
| 15 |
+
def test_endpoint(endpoint, method="GET", data=None):
|
| 16 |
+
"""Test a single endpoint"""
|
| 17 |
+
url = f"{BASE_URL}{endpoint}"
|
| 18 |
+
headers = {
|
| 19 |
+
"Content-Type": "application/json",
|
| 20 |
+
"x-mcp-auth": AUTH_TOKEN
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
try:
|
| 24 |
+
if method == "POST":
|
| 25 |
+
response = requests.post(url, headers=headers, json=data, timeout=30)
|
| 26 |
+
else:
|
| 27 |
+
response = requests.get(url, timeout=10)
|
| 28 |
+
|
| 29 |
+
print(f"✅ {method} {endpoint}: {response.status_code}")
|
| 30 |
+
|
| 31 |
+
if response.status_code == 200:
|
| 32 |
+
result = response.json()
|
| 33 |
+
print(f" Response: {json.dumps(result, indent=2)[:200]}...")
|
| 34 |
+
else:
|
| 35 |
+
print(f" Error: {response.text}")
|
| 36 |
+
|
| 37 |
+
return response.status_code == 200
|
| 38 |
+
|
| 39 |
+
except requests.exceptions.RequestException as e:
|
| 40 |
+
print(f"❌ {method} {endpoint}: Connection error - {str(e)}")
|
| 41 |
+
return False
|
| 42 |
+
|
| 43 |
+
def main():
|
| 44 |
+
"""Run all tests"""
|
| 45 |
+
print("🧪 Testing Gemini MCP Server API...")
|
| 46 |
+
print(f"🌐 Server: {BASE_URL}")
|
| 47 |
+
print("-" * 50)
|
| 48 |
+
|
| 49 |
+
tests = [
|
| 50 |
+
("Root endpoint", "/", "GET"),
|
| 51 |
+
("Health check", "/mcp/health", "GET"),
|
| 52 |
+
("Version info", "/mcp/version", "GET"),
|
| 53 |
+
("Capabilities", "/mcp/capabilities", "GET"),
|
| 54 |
+
]
|
| 55 |
+
|
| 56 |
+
# Test basic endpoints
|
| 57 |
+
for name, endpoint, method in tests:
|
| 58 |
+
print(f"\n🔍 Testing {name}...")
|
| 59 |
+
test_endpoint(endpoint, method)
|
| 60 |
+
|
| 61 |
+
# Test process endpoint
|
| 62 |
+
print(f"\n🔍 Testing MCP Process...")
|
| 63 |
+
test_data = {
|
| 64 |
+
"query": "Hello, can you help me with my account?",
|
| 65 |
+
"user_id": "test_user_123",
|
| 66 |
+
"priority": "normal",
|
| 67 |
+
"mcp_version": "1.0"
|
| 68 |
+
}
|
| 69 |
+
test_endpoint("/mcp/process", "POST", test_data)
|
| 70 |
+
|
| 71 |
+
# Test batch endpoint
|
| 72 |
+
print(f"\n🔍 Testing MCP Batch...")
|
| 73 |
+
batch_data = {
|
| 74 |
+
"queries": ["How do I reset my password?", "What are your business hours?"],
|
| 75 |
+
"user_id": "test_user_123",
|
| 76 |
+
"mcp_version": "1.0"
|
| 77 |
+
}
|
| 78 |
+
test_endpoint("/mcp/batch", "POST", batch_data)
|
| 79 |
+
|
| 80 |
+
print("\n🎉 API testing complete!")
|
| 81 |
+
|
| 82 |
+
if __name__ == "__main__":
|
| 83 |
+
main()
|