initial mcp server setup

This commit is contained in:
OwusuBlessing
2025-09-11 23:13:58 +01:00
commit 20f96c0f30
141 changed files with 14444 additions and 0 deletions
@@ -0,0 +1,6 @@
# Transport layer implementations
from .transport_manager import TransportManager
from .sse_transport import SSETransport
from .stdio_transport import STDIOTransport
__all__ = ['TransportManager', 'SSETransport', 'STDIOTransport']
@@ -0,0 +1,101 @@
"""
SSE (Server-Sent Events) Transport Implementation
"""
import asyncio
import json
from typing import Dict, Any, Optional
from contextlib import asynccontextmanager
try:
import aiohttp
AIOHTTP_AVAILABLE = True
except ImportError:
AIOHTTP_AVAILABLE = False
from ..core.interfaces import IMCPTransport
class SSETransport(IMCPTransport):
"""SSE transport for MCP communication"""
def __init__(
self,
host: str = "localhost",
port: int = 8050,
endpoint: str = "/sse",
**kwargs
):
if not AIOHTTP_AVAILABLE:
raise ImportError("aiohttp package not installed. Install with: pip install aiohttp")
self.host = host
self.port = port
self.endpoint = endpoint
self._session: Optional[aiohttp.ClientSession] = None
self._response: Optional[aiohttp.ClientResponse] = None
self._connected = False
@asynccontextmanager
async def connect(self):
"""Establish SSE connection"""
try:
self._session = aiohttp.ClientSession()
url = f"http://{self.host}:{self.port}{self.endpoint}"
print(f"Connecting to SSE endpoint: {url}")
self._response = await self._session.get(url)
if self._response.status != 200:
raise ConnectionError(f"SSE connection failed with status {self._response.status}")
self._connected = True
yield self
except Exception as e:
print(f"SSE connection error: {e}")
raise
finally:
await self.close()
async def send_message(self, message: Dict[str, Any]) -> None:
"""Send a message through SSE transport"""
if not self._connected:
raise ConnectionError("SSE transport not connected")
# SSE is typically unidirectional from server to client
# For bidirectional communication, you might need to use a different approach
# or combine SSE with HTTP POST requests
print(f"SSE Transport: Sending message: {message}")
# TODO: Implement actual message sending logic
# This might require HTTP POST to a separate endpoint
# or WebSocket upgrade, depending on server implementation
async def receive_message(self) -> Dict[str, Any]:
"""Receive a message through SSE transport"""
if not self._connected or not self._response:
raise ConnectionError("SSE transport not connected")
# Read SSE data
async for line in self._response.content:
line_str = line.decode('utf-8').strip()
if line_str.startswith('data: '):
data = line_str[6:] # Remove 'data: ' prefix
if data:
try:
return json.loads(data)
except json.JSONDecodeError:
continue
return {}
async def close(self) -> None:
"""Close the SSE connection"""
if self._response:
self._response.close()
if self._session:
await self._session.close()
self._connected = False
print("SSE transport closed")
@@ -0,0 +1,81 @@
"""
STDIO Transport Implementation
"""
import asyncio
import json
import sys
from typing import Dict, Any, Optional
from contextlib import asynccontextmanager
from ..core.interfaces import IMCPTransport
class STDIOTransport(IMCPTransport):
"""STDIO transport for MCP communication"""
def __init__(self, **kwargs):
self._connected = False
self._reader: Optional[asyncio.StreamReader] = None
self._writer: Optional[asyncio.StreamWriter] = None
@asynccontextmanager
async def connect(self):
"""Establish STDIO connection"""
try:
# Use stdin/stdout for communication
self._reader = asyncio.StreamReader()
reader_protocol = asyncio.StreamReaderProtocol(self._reader)
await asyncio.get_event_loop().connect_read_pipe(
lambda: reader_protocol, sys.stdin
)
# For writing, we'll use stdout
self._writer = None # We'll write directly to stdout
self._connected = True
print("STDIO transport connected", file=sys.stderr)
yield self
except Exception as e:
print(f"STDIO connection error: {e}", file=sys.stderr)
raise
finally:
await self.close()
async def send_message(self, message: Dict[str, Any]) -> None:
"""Send a message through STDIO"""
if not self._connected:
raise ConnectionError("STDIO transport not connected")
# Convert message to JSON and send to stdout
message_json = json.dumps(message, separators=(',', ':'))
print(message_json, flush=True)
async def receive_message(self) -> Dict[str, Any]:
"""Receive a message through STDIO"""
if not self._connected or not self._reader:
raise ConnectionError("STDIO transport not connected")
try:
# Read line from stdin
line = await self._reader.readline()
if not line:
return {} # EOF
line_str = line.decode('utf-8').strip()
if line_str:
return json.loads(line_str)
except json.JSONDecodeError as e:
print(f"Invalid JSON received: {e}", file=sys.stderr)
except Exception as e:
print(f"Error receiving message: {e}", file=sys.stderr)
return {}
async def close(self) -> None:
"""Close the STDIO connection"""
self._connected = False
if self._writer:
self._writer.close()
print("STDIO transport closed", file=sys.stderr)
@@ -0,0 +1,81 @@
"""
Transport Manager for easy switching between transport methods
"""
from typing import Optional, Union
from contextlib import asynccontextmanager
from ..core.types import TransportType
from ..core.interfaces import IMCPTransport
from .sse_transport import SSETransport
from .stdio_transport import STDIOTransport
class TransportManager:
"""Manager class for handling different MCP transport methods"""
def __init__(self):
self._current_transport: Optional[IMCPTransport] = None
@asynccontextmanager
async def get_transport(
self,
transport_type: Union[str, TransportType],
**kwargs
):
"""Get a transport instance for the specified type"""
# Convert string to enum if needed
if isinstance(transport_type, str):
transport_type = TransportType(transport_type.lower())
# Create the appropriate transport
if transport_type == TransportType.SSE:
transport = SSETransport(**kwargs)
elif transport_type == TransportType.STDIO:
transport = STDIOTransport(**kwargs)
else:
raise ValueError(f"Unsupported transport type: {transport_type}")
self._current_transport = transport
try:
async with transport.connect():
yield transport
finally:
self._current_transport = None
async def switch_transport(
self,
new_transport_type: Union[str, TransportType],
**kwargs
) -> IMCPTransport:
"""Switch to a different transport type"""
# Close current transport if exists
if self._current_transport:
await self._current_transport.close()
# Convert string to enum if needed
if isinstance(new_transport_type, str):
new_transport_type = TransportType(new_transport_type.lower())
# Create new transport
if new_transport_type == TransportType.SSE:
self._current_transport = SSETransport(**kwargs)
elif new_transport_type == TransportType.STDIO:
self._current_transport = STDIOTransport(**kwargs)
else:
raise ValueError(f"Unsupported transport type: {new_transport_type}")
return self._current_transport
@property
def current_transport(self) -> Optional[IMCPTransport]:
"""Get the currently active transport"""
return self._current_transport
async def close_current_transport(self) -> None:
"""Close the currently active transport"""
if self._current_transport:
await self._current_transport.close()
self._current_transport = None