solidworks-mcp-python 1.5.0__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.
- solidworks_mcp/__init__.py +57 -0
- solidworks_mcp/adapters/__init__.py +40 -0
- solidworks_mcp/adapters/base.py +1882 -0
- solidworks_mcp/adapters/circuit_breaker.py +2052 -0
- solidworks_mcp/adapters/com_executor.py +252 -0
- solidworks_mcp/adapters/complexity_analyzer.py +272 -0
- solidworks_mcp/adapters/connection_pool.py +1884 -0
- solidworks_mcp/adapters/factory.py +366 -0
- solidworks_mcp/adapters/intelligent_router.py +218 -0
- solidworks_mcp/adapters/mock_adapter.py +4016 -0
- solidworks_mcp/adapters/pywin32_adapter.py +2842 -0
- solidworks_mcp/adapters/solidworks/__init__.py +17 -0
- solidworks_mcp/adapters/solidworks/bitmap.py +180 -0
- solidworks_mcp/adapters/solidworks/com_utils.py +56 -0
- solidworks_mcp/adapters/solidworks/dimension_layout.py +753 -0
- solidworks_mcp/adapters/solidworks/documentation.py +1476 -0
- solidworks_mcp/adapters/solidworks/drawing_standard.py +1993 -0
- solidworks_mcp/adapters/solidworks/face_selection.py +392 -0
- solidworks_mcp/adapters/solidworks/features.py +3362 -0
- solidworks_mcp/adapters/solidworks/io.py +3649 -0
- solidworks_mcp/adapters/solidworks/rebuild_diagnostics.py +269 -0
- solidworks_mcp/adapters/solidworks/rebuild_inspector.py +146 -0
- solidworks_mcp/adapters/solidworks/selection.py +308 -0
- solidworks_mcp/adapters/solidworks/sketch.py +2545 -0
- solidworks_mcp/adapters/sw_feature_errors.py +66 -0
- solidworks_mcp/adapters/sw_type_info.py +317 -0
- solidworks_mcp/adapters/vba_adapter.py +318 -0
- solidworks_mcp/adapters/vba_macro_executor.py +215 -0
- solidworks_mcp/agents/__init__.py +68 -0
- solidworks_mcp/agents/design_knowledge/assembly_patterns.md +127 -0
- solidworks_mcp/agents/design_knowledge/hinges.md +118 -0
- solidworks_mcp/agents/design_knowledge/snap_fits.md +93 -0
- solidworks_mcp/agents/design_knowledge/solidworks_basics.md +3 -0
- solidworks_mcp/agents/design_knowledge/tolerancing.md +128 -0
- solidworks_mcp/agents/harness.py +216 -0
- solidworks_mcp/agents/history_db.py +1508 -0
- solidworks_mcp/agents/retrieval_index.py +164 -0
- solidworks_mcp/agents/schemas.py +165 -0
- solidworks_mcp/agents/smoke_test.py +285 -0
- solidworks_mcp/agents/soc_exporter.py +650 -0
- solidworks_mcp/agents/soc_pickup.py +332 -0
- solidworks_mcp/agents/soc_rewind.py +221 -0
- solidworks_mcp/agents/vector_rag.py +665 -0
- solidworks_mcp/cache/__init__.py +5 -0
- solidworks_mcp/cache/response_cache.py +164 -0
- solidworks_mcp/config.py +729 -0
- solidworks_mcp/exceptions.py +37 -0
- solidworks_mcp/security/__init__.py +68 -0
- solidworks_mcp/security/auth.py +128 -0
- solidworks_mcp/security/cors.py +27 -0
- solidworks_mcp/security/rate_limiting.py +90 -0
- solidworks_mcp/security/runtime.py +155 -0
- solidworks_mcp/server.py +777 -0
- solidworks_mcp/server_cli_fixed.py +120 -0
- solidworks_mcp/setup/__init__.py +1 -0
- solidworks_mcp/setup/cli.py +399 -0
- solidworks_mcp/setup/clients.py +157 -0
- solidworks_mcp/setup/config_file.py +230 -0
- solidworks_mcp/setup/handshake.py +202 -0
- solidworks_mcp/setup/launcher.py +102 -0
- solidworks_mcp/setup/legacy.py +100 -0
- solidworks_mcp/setup/registration.py +193 -0
- solidworks_mcp/setup/solidworks.py +49 -0
- solidworks_mcp/tools/__init__.py +64 -0
- solidworks_mcp/tools/analysis.py +723 -0
- solidworks_mcp/tools/automation.py +578 -0
- solidworks_mcp/tools/docs_discovery.py +1476 -0
- solidworks_mcp/tools/drawing.py +1015 -0
- solidworks_mcp/tools/drawing_analysis.py +456 -0
- solidworks_mcp/tools/drawing_automation.py +627 -0
- solidworks_mcp/tools/engineering_data.py +466 -0
- solidworks_mcp/tools/error_classifier.py +232 -0
- solidworks_mcp/tools/export.py +833 -0
- solidworks_mcp/tools/file_facts.py +40 -0
- solidworks_mcp/tools/file_management.py +1473 -0
- solidworks_mcp/tools/input_compat.py +17 -0
- solidworks_mcp/tools/macro_recording.py +464 -0
- solidworks_mcp/tools/modeling.py +2360 -0
- solidworks_mcp/tools/sketching.py +1422 -0
- solidworks_mcp/tools/template_management.py +367 -0
- solidworks_mcp/tools/tool_boundary.py +280 -0
- solidworks_mcp/tools/vba_generation.py +1145 -0
- solidworks_mcp/ui/__init__.py +9 -0
- solidworks_mcp/ui/local_llm.py +725 -0
- solidworks_mcp/ui/routers/__init__.py +10 -0
- solidworks_mcp/ui/routers/checkpoint.py +24 -0
- solidworks_mcp/ui/routers/docs.py +47 -0
- solidworks_mcp/ui/routers/llm.py +83 -0
- solidworks_mcp/ui/routers/local_model.py +69 -0
- solidworks_mcp/ui/routers/model.py +75 -0
- solidworks_mcp/ui/routers/preview.py +38 -0
- solidworks_mcp/ui/routers/session.py +161 -0
- solidworks_mcp/ui/routers/viewer.py +154 -0
- solidworks_mcp/ui/schemas.py +502 -0
- solidworks_mcp/ui/server.py +298 -0
- solidworks_mcp/ui/service.py +481 -0
- solidworks_mcp/ui/services/__init__.py +136 -0
- solidworks_mcp/ui/services/_utils.py +958 -0
- solidworks_mcp/ui/services/checkpoint_service.py +1110 -0
- solidworks_mcp/ui/services/docs_service.py +282 -0
- solidworks_mcp/ui/services/llm_service.py +956 -0
- solidworks_mcp/ui/services/model_service.py +459 -0
- solidworks_mcp/ui/services/parameter_repair_service.py +314 -0
- solidworks_mcp/ui/services/preview_service.py +489 -0
- solidworks_mcp/ui/services/session_service.py +1389 -0
- solidworks_mcp/utils/__init__.py +9 -0
- solidworks_mcp/utils/feature_tree_classifier.py +342 -0
- solidworks_mcp/utils/logging.py +76 -0
- solidworks_mcp/utils/validation.py +77 -0
- solidworks_mcp/version.py +8 -0
- solidworks_mcp_python-1.5.0.dist-info/METADATA +410 -0
- solidworks_mcp_python-1.5.0.dist-info/RECORD +115 -0
- solidworks_mcp_python-1.5.0.dist-info/WHEEL +4 -0
- solidworks_mcp_python-1.5.0.dist-info/entry_points.txt +3 -0
- solidworks_mcp_python-1.5.0.dist-info/licenses/LICENSE +21 -0
solidworks_mcp/server.py
ADDED
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
"""Main SolidWorks MCP Server implementation using FastMCP and PydanticAI.
|
|
2
|
+
|
|
3
|
+
This server provides 154 tools for comprehensive SolidWorks automation with configurable
|
|
4
|
+
deployment (local/remote) and security options.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import inspect
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import platform
|
|
14
|
+
import sys
|
|
15
|
+
import uuid
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
import typer
|
|
20
|
+
from fastmcp import FastMCP
|
|
21
|
+
from loguru import logger
|
|
22
|
+
from pydantic import BaseModel
|
|
23
|
+
from pydantic_ai import Agent
|
|
24
|
+
|
|
25
|
+
from . import adapters, security, tools, utils
|
|
26
|
+
from .adapters.base import AdapterResult
|
|
27
|
+
from .adapters.complexity_analyzer import ComplexityAnalyzer
|
|
28
|
+
from .adapters.intelligent_router import IntelligentRouter
|
|
29
|
+
from .adapters.vba_adapter import VbaGeneratorAdapter
|
|
30
|
+
from .agents.history_db import insert_tool_event
|
|
31
|
+
from .cache.response_cache import CachePolicy, ResponseCache
|
|
32
|
+
from .config import DeploymentMode, SolidWorksMCPConfig, load_config
|
|
33
|
+
from .exceptions import SolidWorksMCPError
|
|
34
|
+
from .version import __version__
|
|
35
|
+
|
|
36
|
+
AGENT_SYSTEM_PROMPT = (
|
|
37
|
+
"You are a SolidWorks automation expert. You have access to comprehensive "
|
|
38
|
+
"SolidWorks tools for CAD automation, modeling, drawing creation, analysis, "
|
|
39
|
+
"and file management. Always prioritize safety, accuracy, and user intent. "
|
|
40
|
+
"For complex operations, break them down into manageable steps."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class MCPServerState(BaseModel):
|
|
45
|
+
"""Server state management - serializable fields only.
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
adapter (Any | None): The adapter value.
|
|
49
|
+
agent (Any | None): The agent value.
|
|
50
|
+
config (SolidWorksMCPConfig): The config value.
|
|
51
|
+
is_connected (bool): The is connected value.
|
|
52
|
+
startup_time (str | None): The startup time value.
|
|
53
|
+
tool_count (int): The tool count value.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
config: SolidWorksMCPConfig
|
|
57
|
+
adapter: Any | None = None
|
|
58
|
+
agent: Any | None = None
|
|
59
|
+
is_connected: bool = False
|
|
60
|
+
startup_time: str | None = None
|
|
61
|
+
tool_count: int = 0
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class SolidWorksMCPServer:
|
|
65
|
+
"""Main SolidWorks MCP Server class.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
config (SolidWorksMCPConfig): Configuration values for the operation.
|
|
69
|
+
|
|
70
|
+
Attributes:
|
|
71
|
+
_db_logging_enabled (Any): The db logging enabled value.
|
|
72
|
+
_db_path (Any): The db path value.
|
|
73
|
+
_db_run_id (Any): The db run id value.
|
|
74
|
+
_setup_complete (Any): The setup complete value.
|
|
75
|
+
config (Any): The config value.
|
|
76
|
+
mcp (Any): The mcp value.
|
|
77
|
+
server (Any): The server value.
|
|
78
|
+
state (Any): The state value.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(self, config: SolidWorksMCPConfig):
|
|
82
|
+
"""Initialize the solid works mcpserver.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
config (SolidWorksMCPConfig): Configuration values for the operation.
|
|
86
|
+
"""
|
|
87
|
+
self.config = config
|
|
88
|
+
self.state = MCPServerState(config=config)
|
|
89
|
+
self.mcp = FastMCP("SolidWorks MCP Server", version=__version__)
|
|
90
|
+
self.server: FastMCP | None = None
|
|
91
|
+
self._setup_complete = False
|
|
92
|
+
self._db_logging_enabled = self._env_truthy(
|
|
93
|
+
os.getenv("SOLIDWORKS_MCP_DB_LOGGING", "0")
|
|
94
|
+
)
|
|
95
|
+
self._db_run_id = os.getenv("SOLIDWORKS_MCP_CONVERSATION_ID") or str(
|
|
96
|
+
uuid.uuid4()
|
|
97
|
+
)
|
|
98
|
+
self._db_path = (
|
|
99
|
+
os.getenv("SOLIDWORKS_MCP_DB_PATH")
|
|
100
|
+
if os.getenv("SOLIDWORKS_MCP_DB_PATH")
|
|
101
|
+
else None
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Runtime objects (not serializable)
|
|
105
|
+
self.adapter: Any | None = None
|
|
106
|
+
self.agent: Any | None = None
|
|
107
|
+
self._router: IntelligentRouter | None = None
|
|
108
|
+
self._vba_adapter: VbaGeneratorAdapter | None = None
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def _env_truthy(value: str | None) -> bool:
|
|
112
|
+
"""Build internal env truthy.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
value (str | None): The value value.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
bool: True if env truthy, otherwise False.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
return str(value or "").strip().lower() in {"1", "true", "yes", "on"}
|
|
122
|
+
|
|
123
|
+
def _log_tool_event(
|
|
124
|
+
self,
|
|
125
|
+
*,
|
|
126
|
+
tool_name: str,
|
|
127
|
+
phase: str,
|
|
128
|
+
payload: dict[str, Any] | None,
|
|
129
|
+
) -> None:
|
|
130
|
+
"""Build internal log tool event.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
tool_name (str): The tool name value.
|
|
134
|
+
phase (str): The phase value.
|
|
135
|
+
payload (dict[str, Any] | None): The payload value.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
None: None.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
if not self._db_logging_enabled:
|
|
142
|
+
return
|
|
143
|
+
try:
|
|
144
|
+
insert_tool_event(
|
|
145
|
+
run_id=self._db_run_id,
|
|
146
|
+
tool_name=tool_name,
|
|
147
|
+
phase=phase,
|
|
148
|
+
payload_json=json.dumps(payload, ensure_ascii=True)
|
|
149
|
+
if payload is not None
|
|
150
|
+
else None,
|
|
151
|
+
db_path=(
|
|
152
|
+
Path(os.path.abspath(self._db_path))
|
|
153
|
+
if isinstance(self._db_path, str)
|
|
154
|
+
else None
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
except Exception as exc:
|
|
158
|
+
logger.debug("Tool event logging skipped due to error: {}", exc)
|
|
159
|
+
|
|
160
|
+
def _configure_runtime_services(self) -> None:
|
|
161
|
+
"""Initialize router and cache services and instrument adapter methods.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
None: None.
|
|
165
|
+
"""
|
|
166
|
+
if self.adapter is None:
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
analyzer = ComplexityAnalyzer(
|
|
170
|
+
parameter_threshold=self.config.complexity_parameter_threshold,
|
|
171
|
+
score_threshold=self.config.complexity_score_threshold,
|
|
172
|
+
)
|
|
173
|
+
cache = ResponseCache(
|
|
174
|
+
CachePolicy(
|
|
175
|
+
enabled=self.config.enable_response_cache,
|
|
176
|
+
default_ttl_seconds=self.config.response_cache_ttl_seconds,
|
|
177
|
+
max_entries=self.config.response_cache_max_entries,
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
self._router = IntelligentRouter(analyzer=analyzer, cache=cache)
|
|
181
|
+
self._vba_adapter = VbaGeneratorAdapter(backing_adapter=self.adapter)
|
|
182
|
+
|
|
183
|
+
if self.config.enable_intelligent_routing:
|
|
184
|
+
self._instrument_adapter_methods()
|
|
185
|
+
|
|
186
|
+
def _instrument_adapter_methods(self) -> None:
|
|
187
|
+
"""Route selected adapter methods through intelligent router.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
None: None.
|
|
191
|
+
"""
|
|
192
|
+
if self.adapter is None or self._router is None:
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
routed_operations = {
|
|
196
|
+
"create_extrusion",
|
|
197
|
+
"create_revolve",
|
|
198
|
+
"create_sweep",
|
|
199
|
+
"create_loft",
|
|
200
|
+
"get_model_info",
|
|
201
|
+
"list_features",
|
|
202
|
+
"list_configurations",
|
|
203
|
+
"get_mass_properties",
|
|
204
|
+
# Additional analysis operations
|
|
205
|
+
"calculate_mass_properties",
|
|
206
|
+
"get_material_properties",
|
|
207
|
+
"analyze_geometry",
|
|
208
|
+
"check_interference",
|
|
209
|
+
# Drawing analysis operations
|
|
210
|
+
"analyze_drawing_comprehensive",
|
|
211
|
+
"analyze_drawing_dimensions",
|
|
212
|
+
"analyze_drawing_views",
|
|
213
|
+
"analyze_drawing_annotations",
|
|
214
|
+
"check_drawing_compliance",
|
|
215
|
+
"check_drawing_standards",
|
|
216
|
+
"compare_drawing_versions",
|
|
217
|
+
# Modeling operations that benefit from routing
|
|
218
|
+
"create_assembly",
|
|
219
|
+
"create_part",
|
|
220
|
+
"create_drawing",
|
|
221
|
+
"insert_component",
|
|
222
|
+
"add_mate",
|
|
223
|
+
# Sketching operations
|
|
224
|
+
"create_sketch",
|
|
225
|
+
"add_circle",
|
|
226
|
+
"add_rectangle",
|
|
227
|
+
"add_line",
|
|
228
|
+
"add_arc",
|
|
229
|
+
"add_spline",
|
|
230
|
+
"add_polygon",
|
|
231
|
+
"add_sketch_constraint",
|
|
232
|
+
"add_sketch_dimension",
|
|
233
|
+
# Drawing operations
|
|
234
|
+
"create_drawing_view",
|
|
235
|
+
"add_drawing_annotation",
|
|
236
|
+
"add_dimension",
|
|
237
|
+
# File and metadata operations
|
|
238
|
+
"get_file_properties",
|
|
239
|
+
"get_dimension",
|
|
240
|
+
"classify_feature_tree",
|
|
241
|
+
"discover_solidworks_docs",
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
for operation_name in routed_operations:
|
|
245
|
+
original_operation = getattr(self.adapter, operation_name, None)
|
|
246
|
+
if original_operation is None or not callable(original_operation):
|
|
247
|
+
continue
|
|
248
|
+
|
|
249
|
+
vba_operation = None
|
|
250
|
+
if self._vba_adapter is not None:
|
|
251
|
+
candidate = getattr(self._vba_adapter, operation_name, None)
|
|
252
|
+
if callable(candidate):
|
|
253
|
+
vba_operation = candidate
|
|
254
|
+
|
|
255
|
+
async def _routed_call(
|
|
256
|
+
*call_args: Any,
|
|
257
|
+
_operation_name: str = operation_name,
|
|
258
|
+
_com_callable: Any = original_operation,
|
|
259
|
+
_vba_callable: Any = vba_operation,
|
|
260
|
+
**call_kwargs: Any,
|
|
261
|
+
) -> AdapterResult[Any]:
|
|
262
|
+
"""Build internal routed call.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
*call_args (Any): Additional positional arguments forwarded to the call.
|
|
266
|
+
_operation_name (str): The operation name value. Defaults to operation_name.
|
|
267
|
+
_com_callable (Any): The com callable value. Defaults to original_operation.
|
|
268
|
+
_vba_callable (Any): The vba callable value. Defaults to vba_operation.
|
|
269
|
+
**call_kwargs (Any): Additional keyword arguments forwarded to the call.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
AdapterResult[Any]: The result produced by the operation.
|
|
273
|
+
"""
|
|
274
|
+
payload: Any
|
|
275
|
+
if call_kwargs:
|
|
276
|
+
payload = {
|
|
277
|
+
"args": call_args,
|
|
278
|
+
"kwargs": call_kwargs,
|
|
279
|
+
}
|
|
280
|
+
elif len(call_args) == 1:
|
|
281
|
+
payload = call_args[0]
|
|
282
|
+
elif call_args:
|
|
283
|
+
payload = {"args": call_args}
|
|
284
|
+
else:
|
|
285
|
+
payload = None
|
|
286
|
+
|
|
287
|
+
if self._router is None:
|
|
288
|
+
return await _com_callable(*call_args, **call_kwargs) # type: ignore[no-any-return]
|
|
289
|
+
|
|
290
|
+
result, _ = await self._router.execute(
|
|
291
|
+
operation=_operation_name,
|
|
292
|
+
payload=payload,
|
|
293
|
+
call_args=call_args,
|
|
294
|
+
call_kwargs=call_kwargs,
|
|
295
|
+
com_operation=_com_callable,
|
|
296
|
+
vba_operation=_vba_callable,
|
|
297
|
+
cache_ttl_seconds=self.config.response_cache_ttl_seconds,
|
|
298
|
+
)
|
|
299
|
+
return result
|
|
300
|
+
|
|
301
|
+
setattr(self.adapter, operation_name, _routed_call)
|
|
302
|
+
|
|
303
|
+
async def setup(self) -> None:
|
|
304
|
+
"""Initialize the server components.
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
None: None.
|
|
308
|
+
"""
|
|
309
|
+
if self._setup_complete:
|
|
310
|
+
return
|
|
311
|
+
|
|
312
|
+
logger.info("Setting up SolidWorks MCP Server...")
|
|
313
|
+
|
|
314
|
+
# Validate environment
|
|
315
|
+
await utils.validate_environment(self.config)
|
|
316
|
+
|
|
317
|
+
# Setup security
|
|
318
|
+
await security.setup_security(self.mcp, self.config)
|
|
319
|
+
|
|
320
|
+
# Create SolidWorks adapter
|
|
321
|
+
self.adapter = await adapters.create_adapter(self.config)
|
|
322
|
+
self._configure_runtime_services()
|
|
323
|
+
self.state.adapter = self.adapter
|
|
324
|
+
|
|
325
|
+
# Register tools and derive canonical count from FastMCP runtime
|
|
326
|
+
await tools.register_tools(self.mcp, self.adapter, self.config)
|
|
327
|
+
self.state.tool_count = len(await self.mcp.list_tools())
|
|
328
|
+
self.server = self.mcp
|
|
329
|
+
|
|
330
|
+
# Setup PydanticAI agent after tools are registered so the agent can bind
|
|
331
|
+
# directly to the in-process FastMCP server without a transport hop.
|
|
332
|
+
await self._setup_agent()
|
|
333
|
+
self.state.agent = self.agent
|
|
334
|
+
|
|
335
|
+
self._setup_complete = True
|
|
336
|
+
logger.info(f"Server setup complete with {self.state.tool_count} tools")
|
|
337
|
+
|
|
338
|
+
async def _setup_agent(self) -> None:
|
|
339
|
+
"""Setup PydanticAI agent for enhanced LLM integration.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
None: None.
|
|
343
|
+
"""
|
|
344
|
+
if self.config.testing or self.config.mock_solidworks:
|
|
345
|
+
self.agent = None
|
|
346
|
+
return
|
|
347
|
+
|
|
348
|
+
if not os.getenv("OPENAI_API_KEY"):
|
|
349
|
+
logger.warning(
|
|
350
|
+
"Skipping PydanticAI agent setup because OPENAI_API_KEY is not configured"
|
|
351
|
+
)
|
|
352
|
+
self.agent = None
|
|
353
|
+
return
|
|
354
|
+
|
|
355
|
+
self.agent = Agent(
|
|
356
|
+
model="openai:gpt-4",
|
|
357
|
+
system_prompt=AGENT_SYSTEM_PROMPT,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
logger.info("PydanticAI agent configured")
|
|
361
|
+
|
|
362
|
+
async def _run_local_stdio(self) -> None:
|
|
363
|
+
"""Start local MCP stdio transport using the available FastMCP API.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
None: None.
|
|
367
|
+
|
|
368
|
+
Raises:
|
|
369
|
+
SolidWorksMCPError: FastMCP server does not expose a stdio runner.
|
|
370
|
+
"""
|
|
371
|
+
if self.server is None:
|
|
372
|
+
self.server = self.mcp
|
|
373
|
+
|
|
374
|
+
stdin_is_readable = False
|
|
375
|
+
try:
|
|
376
|
+
stdin_is_readable = (
|
|
377
|
+
bool(sys.stdin) and not sys.stdin.closed and sys.stdin.readable()
|
|
378
|
+
)
|
|
379
|
+
except Exception:
|
|
380
|
+
stdin_is_readable = False
|
|
381
|
+
|
|
382
|
+
if self.config.mock_solidworks and not stdin_is_readable:
|
|
383
|
+
logger.warning(
|
|
384
|
+
"Skipping FastMCP stdio transport in mock mode because stdin is unavailable"
|
|
385
|
+
)
|
|
386
|
+
return
|
|
387
|
+
|
|
388
|
+
run_stdio = getattr(self.server, "run_stdio", None)
|
|
389
|
+
if callable(run_stdio):
|
|
390
|
+
result = run_stdio()
|
|
391
|
+
if inspect.isawaitable(result):
|
|
392
|
+
await result
|
|
393
|
+
return
|
|
394
|
+
|
|
395
|
+
run_stdio_async = getattr(self.server, "run_stdio_async", None)
|
|
396
|
+
if callable(run_stdio_async):
|
|
397
|
+
await run_stdio_async()
|
|
398
|
+
return
|
|
399
|
+
|
|
400
|
+
raise SolidWorksMCPError("FastMCP server does not expose a stdio runner")
|
|
401
|
+
|
|
402
|
+
async def start(self) -> None:
|
|
403
|
+
"""Start the MCP server.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
None: None.
|
|
407
|
+
"""
|
|
408
|
+
await self.setup()
|
|
409
|
+
|
|
410
|
+
if self.adapter:
|
|
411
|
+
try:
|
|
412
|
+
await self.adapter.connect()
|
|
413
|
+
self.state.is_connected = True
|
|
414
|
+
logger.info("Connected to SolidWorks")
|
|
415
|
+
except Exception as e:
|
|
416
|
+
logger.warning(f"Could not connect to SolidWorks: {e}")
|
|
417
|
+
if not self.config.mock_solidworks:
|
|
418
|
+
logger.warning("Continuing with mock adapter for testing")
|
|
419
|
+
|
|
420
|
+
# Record startup time
|
|
421
|
+
from datetime import datetime
|
|
422
|
+
|
|
423
|
+
self.state.startup_time = datetime.now().isoformat()
|
|
424
|
+
|
|
425
|
+
if self.config.deployment_mode == DeploymentMode.LOCAL:
|
|
426
|
+
# Local stdio mode for MCP
|
|
427
|
+
logger.info("Starting in local MCP mode (stdio)")
|
|
428
|
+
await self._run_local_stdio()
|
|
429
|
+
else:
|
|
430
|
+
# Remote HTTP mode
|
|
431
|
+
logger.info(
|
|
432
|
+
f"Starting in remote mode on {self.config.host}:{self.config.port}"
|
|
433
|
+
)
|
|
434
|
+
await self._start_http_server()
|
|
435
|
+
|
|
436
|
+
async def _start_http_server(self) -> None:
|
|
437
|
+
"""Start HTTP server for remote access.
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
None: None.
|
|
441
|
+
"""
|
|
442
|
+
run_result = self.mcp.run( # type: ignore[func-returns-value]
|
|
443
|
+
transport="http", host=self.config.host, port=self.config.port
|
|
444
|
+
)
|
|
445
|
+
if inspect.isawaitable(run_result):
|
|
446
|
+
await run_result # type: ignore[unreachable]
|
|
447
|
+
|
|
448
|
+
async def stop(self) -> None:
|
|
449
|
+
"""Gracefully stop the server.
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
None: None.
|
|
453
|
+
"""
|
|
454
|
+
logger.info("Stopping SolidWorks MCP Server...")
|
|
455
|
+
|
|
456
|
+
if self.adapter:
|
|
457
|
+
await self.adapter.disconnect()
|
|
458
|
+
self.state.is_connected = False
|
|
459
|
+
|
|
460
|
+
self._setup_complete = False
|
|
461
|
+
self.server = None
|
|
462
|
+
logger.info("Server stopped")
|
|
463
|
+
|
|
464
|
+
async def health_check(self) -> dict[str, Any]:
|
|
465
|
+
"""Get server health status.
|
|
466
|
+
|
|
467
|
+
Returns:
|
|
468
|
+
dict[str, Any]: A dictionary containing the resulting values.
|
|
469
|
+
"""
|
|
470
|
+
adapter_health = None
|
|
471
|
+
if self.adapter:
|
|
472
|
+
adapter_health = await self.adapter.health_check()
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
"status": "healthy" if self.state.is_connected else "warning",
|
|
476
|
+
"config": {
|
|
477
|
+
"deployment_mode": self.config.deployment_mode,
|
|
478
|
+
"adapter_type": self.config.adapter_type,
|
|
479
|
+
"security_level": self.config.security_level,
|
|
480
|
+
"platform": platform.system(),
|
|
481
|
+
},
|
|
482
|
+
"state": {
|
|
483
|
+
"connected": self.state.is_connected,
|
|
484
|
+
"startup_time": self.state.startup_time,
|
|
485
|
+
"tool_count": self.state.tool_count,
|
|
486
|
+
},
|
|
487
|
+
"adapter": adapter_health,
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
async def server_status() -> dict[str, Any]:
|
|
492
|
+
"""Get comprehensive server status information.
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
dict[str, Any]: A dictionary containing the resulting values.
|
|
496
|
+
"""
|
|
497
|
+
# This will be properly implemented when the server instance is available
|
|
498
|
+
return {
|
|
499
|
+
"status": "Server status endpoint - to be implemented with server state",
|
|
500
|
+
"message": "Use the main server health_check method for detailed status",
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
async def list_capabilities() -> dict[str, list[str]]:
|
|
505
|
+
"""List all available SolidWorks capabilities and tool categories.
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
dict[str, list[str]]: A dictionary containing the resulting values.
|
|
509
|
+
"""
|
|
510
|
+
return {
|
|
511
|
+
"modeling": [
|
|
512
|
+
"create_part",
|
|
513
|
+
"create_assembly",
|
|
514
|
+
"create_drawing",
|
|
515
|
+
"create_extrusion",
|
|
516
|
+
"create_revolve",
|
|
517
|
+
"create_sweep",
|
|
518
|
+
"create_loft",
|
|
519
|
+
"create_cut",
|
|
520
|
+
"create_fillet",
|
|
521
|
+
"create_chamfer",
|
|
522
|
+
],
|
|
523
|
+
"sketching": [
|
|
524
|
+
"create_sketch",
|
|
525
|
+
"add_line",
|
|
526
|
+
"add_circle",
|
|
527
|
+
"add_rectangle",
|
|
528
|
+
"add_arc",
|
|
529
|
+
"add_spline",
|
|
530
|
+
"add_dimension",
|
|
531
|
+
"add_relation",
|
|
532
|
+
],
|
|
533
|
+
"drawing": [
|
|
534
|
+
"create_drawing_view",
|
|
535
|
+
"add_section_view",
|
|
536
|
+
"add_detail_view",
|
|
537
|
+
"add_dimension_to_view",
|
|
538
|
+
"add_annotation",
|
|
539
|
+
"create_drawing_template",
|
|
540
|
+
],
|
|
541
|
+
"analysis": [
|
|
542
|
+
"get_mass_properties",
|
|
543
|
+
"perform_fea_analysis",
|
|
544
|
+
"check_interference",
|
|
545
|
+
"analyze_geometry",
|
|
546
|
+
"get_material_properties",
|
|
547
|
+
],
|
|
548
|
+
"export": [
|
|
549
|
+
"export_step",
|
|
550
|
+
"export_iges",
|
|
551
|
+
"export_stl",
|
|
552
|
+
"export_pdf",
|
|
553
|
+
"export_dwg",
|
|
554
|
+
"export_images",
|
|
555
|
+
"batch_export",
|
|
556
|
+
],
|
|
557
|
+
"automation": [
|
|
558
|
+
"generate_vba_macro",
|
|
559
|
+
"record_macro",
|
|
560
|
+
"batch_process",
|
|
561
|
+
"create_design_table",
|
|
562
|
+
"manage_configurations",
|
|
563
|
+
],
|
|
564
|
+
"file_management": [
|
|
565
|
+
"open_file",
|
|
566
|
+
"save_file",
|
|
567
|
+
"save_as",
|
|
568
|
+
"close_file",
|
|
569
|
+
"get_file_properties",
|
|
570
|
+
"manage_references",
|
|
571
|
+
],
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def create_server(config: SolidWorksMCPConfig | None = None) -> SolidWorksMCPServer:
|
|
576
|
+
"""Create a SolidWorks MCP Server instance.
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
config (SolidWorksMCPConfig | None): Configuration values for the operation.
|
|
580
|
+
Defaults to None.
|
|
581
|
+
|
|
582
|
+
Returns:
|
|
583
|
+
SolidWorksMCPServer: The result produced by the operation.
|
|
584
|
+
"""
|
|
585
|
+
if config is None:
|
|
586
|
+
config = load_config()
|
|
587
|
+
|
|
588
|
+
return SolidWorksMCPServer(config)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
async def _run_server(server: SolidWorksMCPServer) -> None:
|
|
592
|
+
"""Run server lifecycle with graceful shutdown.
|
|
593
|
+
|
|
594
|
+
Args:
|
|
595
|
+
server (SolidWorksMCPServer): The server value.
|
|
596
|
+
|
|
597
|
+
Returns:
|
|
598
|
+
None: None.
|
|
599
|
+
"""
|
|
600
|
+
try:
|
|
601
|
+
await server.start()
|
|
602
|
+
except KeyboardInterrupt:
|
|
603
|
+
logger.info("Received interrupt signal")
|
|
604
|
+
except Exception as e:
|
|
605
|
+
logger.error(f"Server error: {e}")
|
|
606
|
+
raise
|
|
607
|
+
finally:
|
|
608
|
+
await server.stop()
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
async def _run_with_config(config: SolidWorksMCPConfig) -> None:
|
|
612
|
+
"""Run the server from a fully prepared config object.
|
|
613
|
+
|
|
614
|
+
Args:
|
|
615
|
+
config (SolidWorksMCPConfig): Configuration values for the operation.
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
None: None.
|
|
619
|
+
"""
|
|
620
|
+
utils.setup_logging(config)
|
|
621
|
+
|
|
622
|
+
logger.info("Starting SolidWorks MCP Server...")
|
|
623
|
+
logger.info(f"Platform: {platform.system()}")
|
|
624
|
+
logger.info(f"Python version: {sys.version}")
|
|
625
|
+
logger.info(f"Deployment mode: {config.deployment_mode}")
|
|
626
|
+
logger.info(f"Security level: {config.security_level}")
|
|
627
|
+
|
|
628
|
+
server = SolidWorksMCPServer(config)
|
|
629
|
+
await _run_server(server)
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def cli(
|
|
633
|
+
config: str | None = typer.Option(
|
|
634
|
+
None,
|
|
635
|
+
"--config",
|
|
636
|
+
help="Configuration file path",
|
|
637
|
+
),
|
|
638
|
+
mode: str | None = typer.Option(
|
|
639
|
+
None,
|
|
640
|
+
"--mode",
|
|
641
|
+
help="Deployment mode (local/remote/hybrid)",
|
|
642
|
+
),
|
|
643
|
+
host: str = typer.Option(
|
|
644
|
+
"localhost",
|
|
645
|
+
"--host",
|
|
646
|
+
help="Server host for remote mode",
|
|
647
|
+
),
|
|
648
|
+
port: int = typer.Option(
|
|
649
|
+
8000,
|
|
650
|
+
"--port",
|
|
651
|
+
help="Server port for remote mode",
|
|
652
|
+
),
|
|
653
|
+
debug: bool = typer.Option(
|
|
654
|
+
False,
|
|
655
|
+
"--debug",
|
|
656
|
+
help="Enable debug mode",
|
|
657
|
+
),
|
|
658
|
+
mock: bool = typer.Option(
|
|
659
|
+
False,
|
|
660
|
+
"--mock",
|
|
661
|
+
help="Use mock SolidWorks for testing",
|
|
662
|
+
),
|
|
663
|
+
) -> None:
|
|
664
|
+
"""Start the SolidWorks MCP server on stdio.
|
|
665
|
+
|
|
666
|
+
Run "solidworks-mcp-python setup" to connect your MCP clients to it.
|
|
667
|
+
|
|
668
|
+
\f
|
|
669
|
+
Everything after the form feed above is left out of ``--help``.
|
|
670
|
+
|
|
671
|
+
Args:
|
|
672
|
+
config: Path to a configuration file.
|
|
673
|
+
mode: Deployment mode: local, remote or hybrid.
|
|
674
|
+
host: Server host for remote mode.
|
|
675
|
+
port: Server port for remote mode.
|
|
676
|
+
debug: Log at DEBUG level.
|
|
677
|
+
mock: Answer from the mock adapter, without SolidWorks.
|
|
678
|
+
"""
|
|
679
|
+
loaded_config = load_config(config)
|
|
680
|
+
|
|
681
|
+
if mode:
|
|
682
|
+
loaded_config.deployment_mode = DeploymentMode(mode)
|
|
683
|
+
if host:
|
|
684
|
+
loaded_config.host = host
|
|
685
|
+
if port:
|
|
686
|
+
loaded_config.port = port
|
|
687
|
+
if debug:
|
|
688
|
+
loaded_config.debug = True
|
|
689
|
+
loaded_config.log_level = "DEBUG"
|
|
690
|
+
if mock:
|
|
691
|
+
loaded_config.mock_solidworks = True
|
|
692
|
+
|
|
693
|
+
asyncio.run(_run_with_config(loaded_config))
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
async def main() -> None:
|
|
697
|
+
"""Legacy async entry point retained for tests and internal callers.
|
|
698
|
+
|
|
699
|
+
Returns:
|
|
700
|
+
None: None.
|
|
701
|
+
"""
|
|
702
|
+
import argparse
|
|
703
|
+
|
|
704
|
+
parser = argparse.ArgumentParser(description="SolidWorks MCP Server")
|
|
705
|
+
parser.add_argument(
|
|
706
|
+
"--config", help="Configuration file path", type=str, default=None
|
|
707
|
+
)
|
|
708
|
+
parser.add_argument(
|
|
709
|
+
"--mode",
|
|
710
|
+
help="Deployment mode (local/remote/hybrid)",
|
|
711
|
+
choices=["local", "remote", "hybrid"],
|
|
712
|
+
default=None,
|
|
713
|
+
)
|
|
714
|
+
parser.add_argument(
|
|
715
|
+
"--host", help="Server host for remote mode", default="localhost"
|
|
716
|
+
)
|
|
717
|
+
parser.add_argument(
|
|
718
|
+
"--port", help="Server port for remote mode", type=int, default=8000
|
|
719
|
+
)
|
|
720
|
+
parser.add_argument("--debug", help="Enable debug mode", action="store_true")
|
|
721
|
+
parser.add_argument(
|
|
722
|
+
"--mock", help="Use mock SolidWorks for testing", action="store_true"
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
args = parser.parse_args()
|
|
726
|
+
|
|
727
|
+
config = load_config(args.config)
|
|
728
|
+
|
|
729
|
+
if args.mode:
|
|
730
|
+
config.deployment_mode = DeploymentMode(args.mode)
|
|
731
|
+
if args.host:
|
|
732
|
+
config.host = args.host
|
|
733
|
+
if args.port:
|
|
734
|
+
config.port = args.port
|
|
735
|
+
if args.debug:
|
|
736
|
+
config.debug = True
|
|
737
|
+
config.log_level = "DEBUG"
|
|
738
|
+
if args.mock:
|
|
739
|
+
config.mock_solidworks = True
|
|
740
|
+
|
|
741
|
+
await _run_with_config(config)
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def cli_main() -> None:
|
|
745
|
+
"""Console script entry point using Typer CLI.
|
|
746
|
+
|
|
747
|
+
``solidworks-mcp setup ...`` runs the guided client setup; anything else
|
|
748
|
+
starts the server.
|
|
749
|
+
|
|
750
|
+
Returns:
|
|
751
|
+
None: None.
|
|
752
|
+
"""
|
|
753
|
+
if sys.argv[1:2] == ["setup"]:
|
|
754
|
+
from .setup.cli import app
|
|
755
|
+
|
|
756
|
+
app(args=sys.argv[2:], prog_name="solidworks-mcp setup")
|
|
757
|
+
return
|
|
758
|
+
typer.run(cli)
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
def run_server() -> None:
|
|
762
|
+
"""Synchronous entry point for the server.
|
|
763
|
+
|
|
764
|
+
Returns:
|
|
765
|
+
None: None.
|
|
766
|
+
"""
|
|
767
|
+
try:
|
|
768
|
+
asyncio.run(main())
|
|
769
|
+
except KeyboardInterrupt:
|
|
770
|
+
logger.info("Server stopped by user")
|
|
771
|
+
except Exception as e:
|
|
772
|
+
logger.error(f"Fatal error: {e}")
|
|
773
|
+
sys.exit(1)
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
if __name__ == "__main__":
|
|
777
|
+
cli_main()
|