mseep-lightfast-mcp 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. common/__init__.py +21 -0
  2. common/types.py +182 -0
  3. lightfast_mcp/__init__.py +50 -0
  4. lightfast_mcp/core/__init__.py +14 -0
  5. lightfast_mcp/core/base_server.py +205 -0
  6. lightfast_mcp/exceptions.py +55 -0
  7. lightfast_mcp/servers/__init__.py +1 -0
  8. lightfast_mcp/servers/blender/__init__.py +5 -0
  9. lightfast_mcp/servers/blender/server.py +358 -0
  10. lightfast_mcp/servers/blender_mcp_server.py +82 -0
  11. lightfast_mcp/servers/mock/__init__.py +5 -0
  12. lightfast_mcp/servers/mock/server.py +101 -0
  13. lightfast_mcp/servers/mock/tools.py +161 -0
  14. lightfast_mcp/servers/mock_server.py +78 -0
  15. lightfast_mcp/utils/__init__.py +1 -0
  16. lightfast_mcp/utils/logging_utils.py +69 -0
  17. mseep_lightfast_mcp-0.0.1.dist-info/METADATA +36 -0
  18. mseep_lightfast_mcp-0.0.1.dist-info/RECORD +43 -0
  19. mseep_lightfast_mcp-0.0.1.dist-info/WHEEL +5 -0
  20. mseep_lightfast_mcp-0.0.1.dist-info/entry_points.txt +7 -0
  21. mseep_lightfast_mcp-0.0.1.dist-info/licenses/LICENSE +21 -0
  22. mseep_lightfast_mcp-0.0.1.dist-info/top_level.txt +3 -0
  23. tools/__init__.py +46 -0
  24. tools/ai/__init__.py +8 -0
  25. tools/ai/conversation_cli.py +345 -0
  26. tools/ai/conversation_client.py +399 -0
  27. tools/ai/conversation_session.py +342 -0
  28. tools/ai/providers/__init__.py +11 -0
  29. tools/ai/providers/base_provider.py +64 -0
  30. tools/ai/providers/claude_provider.py +200 -0
  31. tools/ai/providers/openai_provider.py +204 -0
  32. tools/ai/tool_executor.py +257 -0
  33. tools/common/__init__.py +99 -0
  34. tools/common/async_utils.py +419 -0
  35. tools/common/errors.py +222 -0
  36. tools/common/logging.py +252 -0
  37. tools/common/types.py +130 -0
  38. tools/orchestration/__init__.py +15 -0
  39. tools/orchestration/cli.py +320 -0
  40. tools/orchestration/config_loader.py +348 -0
  41. tools/orchestration/server_orchestrator.py +466 -0
  42. tools/orchestration/server_registry.py +187 -0
  43. tools/orchestration/server_selector.py +242 -0
common/__init__.py ADDED
@@ -0,0 +1,21 @@
1
+ """Common types and utilities shared across lightfast-mcp core and tools."""
2
+
3
+ from .types import (
4
+ HealthStatus,
5
+ OperationStatus,
6
+ ServerInfo,
7
+ ServerState,
8
+ ToolCall,
9
+ ToolCallState,
10
+ ToolResult,
11
+ )
12
+
13
+ __all__ = [
14
+ "HealthStatus",
15
+ "OperationStatus",
16
+ "ServerInfo",
17
+ "ServerState",
18
+ "ToolCall",
19
+ "ToolCallState",
20
+ "ToolResult",
21
+ ]
common/types.py ADDED
@@ -0,0 +1,182 @@
1
+ """Shared types for lightfast-mcp core and tools."""
2
+
3
+ import uuid
4
+ from dataclasses import dataclass, field
5
+ from datetime import datetime
6
+ from enum import Enum
7
+ from typing import Any, Dict, List, Optional
8
+
9
+
10
+ class OperationStatus(Enum):
11
+ """Standard operation status codes."""
12
+
13
+ SUCCESS = "success"
14
+ FAILED = "failed"
15
+ PENDING = "pending"
16
+ CANCELLED = "cancelled"
17
+ TIMEOUT = "timeout"
18
+
19
+
20
+ class ServerState(Enum):
21
+ """Server lifecycle states."""
22
+
23
+ STOPPED = "stopped"
24
+ STARTING = "starting"
25
+ RUNNING = "running"
26
+ STOPPING = "stopping"
27
+ ERROR = "error"
28
+
29
+
30
+ class ToolCallState(Enum):
31
+ """States of tool call execution."""
32
+
33
+ CALL = "call"
34
+ RESULT = "result"
35
+ ERROR = "error"
36
+
37
+
38
+ class HealthStatus(Enum):
39
+ """Health check status."""
40
+
41
+ HEALTHY = "healthy"
42
+ UNHEALTHY = "unhealthy"
43
+ DEGRADED = "degraded"
44
+ UNKNOWN = "unknown"
45
+
46
+
47
+ @dataclass
48
+ class ServerInfo:
49
+ """Unified server information for both core and tools usage.
50
+
51
+ This class supports both lightweight usage (core) and comprehensive usage (tools).
52
+ Core servers can use minimal fields, while orchestration tools can use all fields.
53
+ """
54
+
55
+ # Core fields (always required)
56
+ name: str = ""
57
+ server_type: str = "unknown"
58
+ state: ServerState = ServerState.STOPPED
59
+
60
+ # Network configuration
61
+ host: str = "localhost"
62
+ port: int = 8000
63
+ transport: str = "stdio"
64
+ url: Optional[str] = None
65
+
66
+ # Runtime information (optional for core, used by tools)
67
+ pid: Optional[int] = None
68
+ start_time: Optional[datetime] = None
69
+ last_health_check: Optional[datetime] = None
70
+ health_status: HealthStatus = HealthStatus.UNKNOWN
71
+ error_message: str = ""
72
+ error_count: int = 0
73
+
74
+ # Tool information
75
+ tools: List[str] = field(default_factory=list)
76
+ tool_count: int = 0
77
+
78
+ # Legacy support for old ServerInfo interface
79
+ config: Optional[Any] = None
80
+
81
+ def __post_init__(self):
82
+ """Update computed fields."""
83
+ self.tool_count = len(self.tools)
84
+
85
+ @property
86
+ def is_running(self) -> bool:
87
+ """Check if server is in running state."""
88
+ return self.state == ServerState.RUNNING
89
+
90
+ @is_running.setter
91
+ def is_running(self, value: bool):
92
+ """Set running state (legacy support)."""
93
+ self.state = ServerState.RUNNING if value else ServerState.STOPPED
94
+
95
+ @property
96
+ def is_healthy(self) -> bool:
97
+ """Check if server is healthy."""
98
+ return self.health_status == HealthStatus.HEALTHY
99
+
100
+ @is_healthy.setter
101
+ def is_healthy(self, value: bool):
102
+ """Set healthy status (legacy support)."""
103
+ self.health_status = HealthStatus.HEALTHY if value else HealthStatus.UNHEALTHY
104
+
105
+ @property
106
+ def uptime_seconds(self) -> Optional[float]:
107
+ """Calculate uptime in seconds."""
108
+ if self.start_time:
109
+ return (datetime.utcnow() - self.start_time).total_seconds()
110
+ return None
111
+
112
+ @classmethod
113
+ def from_core_config(
114
+ cls, config, state: ServerState = ServerState.STOPPED
115
+ ) -> "ServerInfo":
116
+ """Create ServerInfo from core ServerConfig for lightweight usage."""
117
+ return cls(
118
+ name=config.name,
119
+ server_type=config.config.get("type", "unknown"),
120
+ state=state,
121
+ host=config.host,
122
+ port=config.port,
123
+ transport=config.transport,
124
+ url=f"http://{config.host}:{config.port}{config.path}"
125
+ if config.transport in ["http", "streamable-http"]
126
+ else None,
127
+ config=config, # Store the original config for legacy support
128
+ )
129
+
130
+
131
+ @dataclass
132
+ class ToolCall:
133
+ """Represents a tool call at the application level."""
134
+
135
+ id: str
136
+ tool_name: str
137
+ arguments: Dict[str, Any]
138
+ server_name: Optional[str] = None
139
+ timestamp: Optional[datetime] = None
140
+
141
+ def __post_init__(self):
142
+ if self.timestamp is None:
143
+ self.timestamp = datetime.utcnow()
144
+ if not self.id:
145
+ self.id = str(uuid.uuid4())
146
+
147
+
148
+ @dataclass
149
+ class ToolResult:
150
+ """Represents a tool call result at the application level."""
151
+
152
+ id: str
153
+ tool_name: str
154
+ arguments: Dict[str, Any]
155
+ result: Any = None
156
+ error: Optional[str] = None
157
+ error_code: Optional[str] = None
158
+ server_name: Optional[str] = None
159
+ timestamp: Optional[datetime] = None
160
+ duration_ms: Optional[float] = None
161
+
162
+ def __post_init__(self):
163
+ if self.timestamp is None:
164
+ self.timestamp = datetime.utcnow()
165
+
166
+ @property
167
+ def state(self) -> ToolCallState:
168
+ """Get the current state of this tool result."""
169
+ if self.error:
170
+ return ToolCallState.ERROR
171
+ elif self.result is not None:
172
+ return ToolCallState.RESULT
173
+ else:
174
+ return ToolCallState.CALL
175
+
176
+ @property
177
+ def is_success(self) -> bool:
178
+ return self.state == ToolCallState.RESULT
179
+
180
+ @property
181
+ def is_error(self) -> bool:
182
+ return self.state == ToolCallState.ERROR
@@ -0,0 +1,50 @@
1
+ """
2
+ Lightfast MCP - Production-ready MCP server implementations for creative applications.
3
+
4
+ This package provides core MCP (Model Context Protocol) server implementations
5
+ for creative applications like Blender. It focuses purely on the server
6
+ implementations themselves.
7
+
8
+ 🎯 Core MCP Servers:
9
+ - Blender MCP Server: Control Blender through MCP protocol
10
+ - Mock MCP Server: Testing and development server
11
+
12
+ 🔧 Development Tools:
13
+ - Available separately in the `tools` package
14
+ - Use `from tools.orchestration import get_orchestrator` for development tools
15
+
16
+ Example Usage:
17
+ ```python
18
+ # Core server usage
19
+ from lightfast_mcp.servers.blender import BlenderMCPServer
20
+ from lightfast_mcp.core import ServerConfig
21
+
22
+ config = ServerConfig(name="my-blender", config={"type": "blender"})
23
+ server = BlenderMCPServer(config)
24
+ await server.run()
25
+
26
+ # Development tools (optional)
27
+ from tools.orchestration import get_orchestrator
28
+ orchestrator = get_orchestrator()
29
+ # orchestrator.start_server(config) # Example usage
30
+ ```
31
+ """
32
+
33
+ # Core MCP server infrastructure (always available)
34
+ from .core import BaseServer, ServerConfig, ServerInfo
35
+
36
+ # Core server implementations (always available)
37
+ from .servers.blender import BlenderMCPServer
38
+ from .servers.mock import MockMCPServer
39
+
40
+ __all__ = [
41
+ # Core infrastructure
42
+ "BaseServer",
43
+ "ServerConfig",
44
+ "ServerInfo",
45
+ # Server implementations
46
+ "BlenderMCPServer",
47
+ "MockMCPServer",
48
+ ]
49
+
50
+ __version__ = "0.0.1"
@@ -0,0 +1,14 @@
1
+ """Core infrastructure for MCP server implementations."""
2
+
3
+ # Import shared types from common module
4
+ from common import HealthStatus, ServerInfo, ServerState
5
+
6
+ from .base_server import BaseServer, ServerConfig
7
+
8
+ __all__ = [
9
+ "BaseServer",
10
+ "ServerConfig",
11
+ "ServerInfo",
12
+ "ServerState",
13
+ "HealthStatus",
14
+ ]
@@ -0,0 +1,205 @@
1
+ """Base server interface for all MCP servers in the lightfast-mcp ecosystem."""
2
+
3
+ # Import shared types from common module
4
+ from abc import ABC, abstractmethod
5
+ from collections.abc import AsyncIterator
6
+ from contextlib import asynccontextmanager
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime
9
+ from typing import Any, ClassVar
10
+
11
+ from fastmcp import FastMCP
12
+
13
+ # Import shared types from common module
14
+ from common import HealthStatus, ServerInfo, ServerState
15
+
16
+ from ..utils.logging_utils import get_logger
17
+
18
+
19
+ @dataclass
20
+ class ServerConfig:
21
+ """Configuration for an MCP server."""
22
+
23
+ # Basic server info
24
+ name: str
25
+ description: str
26
+ version: str = "1.0.0"
27
+
28
+ # Network configuration
29
+ host: str = "localhost"
30
+ port: int = 8000
31
+ transport: str = "stdio" # stdio, http, or streamable-http
32
+ path: str = "/mcp"
33
+
34
+ # Server-specific configuration
35
+ config: dict[str, Any] = field(default_factory=dict)
36
+
37
+ # Dependencies and requirements
38
+ dependencies: list[str] = field(default_factory=list)
39
+ required_apps: list[str] = field(
40
+ default_factory=list
41
+ ) # e.g., ["Blender", "TouchDesigner"]
42
+
43
+
44
+ class BaseServer(ABC):
45
+ """Base class for all MCP servers in the lightfast-mcp ecosystem."""
46
+
47
+ # Server metadata - subclasses should override these
48
+ SERVER_TYPE: ClassVar[str] = "base"
49
+ SERVER_VERSION: ClassVar[str] = "1.0.0"
50
+ REQUIRED_DEPENDENCIES: ClassVar[list[str]] = []
51
+ REQUIRED_APPS: ClassVar[list[str]] = []
52
+
53
+ def __init__(self, config: ServerConfig):
54
+ """Initialize the base server with configuration."""
55
+ self.config = config
56
+ self.logger = get_logger(f"{self.__class__.__name__}")
57
+ self.mcp: FastMCP | None = None
58
+ self.info = ServerInfo.from_core_config(config)
59
+
60
+ # Initialize the FastMCP instance
61
+ self._init_mcp()
62
+
63
+ def _init_mcp(self):
64
+ """Initialize the FastMCP instance with lifespan management."""
65
+ self.mcp = FastMCP(
66
+ self.config.name,
67
+ description=self.config.description,
68
+ lifespan=self._server_lifespan,
69
+ )
70
+
71
+ # Register tools
72
+ self._register_tools()
73
+
74
+ @abstractmethod
75
+ def _register_tools(self):
76
+ """Register server-specific tools. Must be implemented by subclasses."""
77
+ pass
78
+
79
+ @asynccontextmanager
80
+ async def _server_lifespan(self, server: FastMCP) -> AsyncIterator[dict[str, Any]]:
81
+ """Manage server startup and shutdown lifecycle."""
82
+ try:
83
+ self.logger.info(f"{self.config.name} starting up...")
84
+
85
+ # Perform startup checks
86
+ await self._startup_checks()
87
+
88
+ # Custom startup logic
89
+ await self._on_startup()
90
+
91
+ self.info.state = ServerState.RUNNING
92
+ self.info.health_status = HealthStatus.HEALTHY
93
+
94
+ yield {}
95
+
96
+ except Exception as e:
97
+ self.logger.error(f"Error during {self.config.name} startup: {e}")
98
+ self.info.error_message = str(e)
99
+ self.info.health_status = HealthStatus.UNHEALTHY
100
+ raise
101
+ finally:
102
+ # Custom shutdown logic
103
+ await self._on_shutdown()
104
+
105
+ self.info.state = ServerState.STOPPED
106
+ self.logger.info(f"{self.config.name} shutting down.")
107
+
108
+ async def _startup_checks(self):
109
+ """Perform basic startup checks."""
110
+ # Check dependencies
111
+ for dep in self.REQUIRED_DEPENDENCIES:
112
+ if not await self._check_dependency(dep):
113
+ raise RuntimeError(f"Required dependency not available: {dep}")
114
+
115
+ # Check required applications
116
+ for app in self.REQUIRED_APPS:
117
+ if not await self._check_application(app):
118
+ self.logger.warning(f"Required application may not be available: {app}")
119
+
120
+ async def _check_dependency(self, dependency: str) -> bool:
121
+ """Check if a required dependency is available."""
122
+ try:
123
+ __import__(dependency)
124
+ return True
125
+ except ImportError:
126
+ return False
127
+
128
+ async def _check_application(self, app: str) -> bool:
129
+ """Check if a required application is available. Override in subclasses."""
130
+ return True
131
+
132
+ async def _on_startup(self):
133
+ """Custom startup logic. Override in subclasses if needed."""
134
+ # Default implementation does nothing
135
+ return
136
+
137
+ async def _on_shutdown(self):
138
+ """Custom shutdown logic. Override in subclasses if needed."""
139
+ # Default implementation does nothing
140
+ return
141
+
142
+ async def health_check(self) -> bool:
143
+ """Perform a health check on the server."""
144
+ try:
145
+ # Basic health check - can be overridden by subclasses
146
+ is_healthy = await self._perform_health_check()
147
+ self.info.health_status = (
148
+ HealthStatus.HEALTHY if is_healthy else HealthStatus.UNHEALTHY
149
+ )
150
+ self.info.last_health_check = datetime.utcnow()
151
+ return is_healthy
152
+ except Exception as e:
153
+ self.logger.error(f"Health check failed: {e}")
154
+ self.info.health_status = HealthStatus.UNHEALTHY
155
+ self.info.error_message = str(e)
156
+ return False
157
+
158
+ async def _perform_health_check(self) -> bool:
159
+ """Perform server-specific health check. Override in subclasses."""
160
+ return self.info.is_running
161
+
162
+ def get_tools(self) -> list[str]:
163
+ """Get list of available tools."""
164
+ if self.mcp:
165
+ # This would need to be implemented based on FastMCP's API
166
+ # For now, we'll store tools in info during registration
167
+ pass
168
+ return self.info.tools
169
+
170
+ def run(self, **kwargs):
171
+ """Run the server with the configured transport."""
172
+ if not self.mcp:
173
+ raise RuntimeError("MCP server not initialized")
174
+
175
+ # Build URL for HTTP transports
176
+ if self.config.transport in ["http", "streamable-http"]:
177
+ self.info.url = (
178
+ f"http://{self.config.host}:{self.config.port}{self.config.path}"
179
+ )
180
+ self.logger.info(f"Server will be available at: {self.info.url}")
181
+
182
+ # Run with appropriate transport
183
+ if self.config.transport == "stdio":
184
+ self.mcp.run()
185
+ elif self.config.transport in ["http", "streamable-http"]:
186
+ self.mcp.run(
187
+ transport=self.config.transport,
188
+ host=self.config.host,
189
+ port=self.config.port,
190
+ path=self.config.path,
191
+ **kwargs,
192
+ )
193
+ else:
194
+ raise ValueError(f"Unsupported transport: {self.config.transport}")
195
+
196
+ @classmethod
197
+ def create_from_config(cls, config: ServerConfig) -> "BaseServer":
198
+ """Create a server instance from configuration."""
199
+ return cls(config)
200
+
201
+ def __str__(self) -> str:
202
+ return f"{self.__class__.__name__}({self.config.name})"
203
+
204
+ def __repr__(self) -> str:
205
+ return f"{self.__class__.__name__}(name='{self.config.name}', type='{self.SERVER_TYPE}')"
@@ -0,0 +1,55 @@
1
+ """Custom exceptions for the lightfast-mcp project."""
2
+
3
+
4
+ class LightfastMCPError(Exception):
5
+ """Base exception for all lightfast-mcp related errors."""
6
+
7
+ pass
8
+
9
+
10
+ class ServerStartupError(LightfastMCPError):
11
+ """Raised when a server fails to start up properly."""
12
+
13
+ pass
14
+
15
+
16
+ class ServerConfigurationError(LightfastMCPError):
17
+ """Raised when there are issues with server configuration."""
18
+
19
+ pass
20
+
21
+
22
+ class BlenderMCPError(LightfastMCPError):
23
+ """Base exception for all Blender MCP Server related errors."""
24
+
25
+ pass
26
+
27
+
28
+ class BlenderConnectionError(BlenderMCPError):
29
+ """Raised when there are issues connecting to or maintaining a connection with Blender."""
30
+
31
+ pass
32
+
33
+
34
+ class BlenderCommandError(BlenderMCPError):
35
+ """Raised when a command sent to Blender fails during its execution within Blender."""
36
+
37
+ pass
38
+
39
+
40
+ class BlenderResponseError(BlenderMCPError):
41
+ """Raised when the response from Blender is unexpected, malformed, or indicates an error."""
42
+
43
+ pass
44
+
45
+
46
+ class BlenderTimeoutError(BlenderConnectionError):
47
+ """Raised specifically when a timeout occurs while waiting for a response from Blender."""
48
+
49
+ pass
50
+
51
+
52
+ class InvalidCommandTypeError(BlenderMCPError):
53
+ """Raised if an unsupported command type is sent to Blender."""
54
+
55
+ pass
@@ -0,0 +1 @@
1
+ # This file makes the 'servers' directory a Python package.
@@ -0,0 +1,5 @@
1
+ """Blender MCP server module."""
2
+
3
+ from .server import BlenderMCPServer
4
+
5
+ __all__ = ["BlenderMCPServer"]