initial mcp server setup
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user