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.
- common/__init__.py +21 -0
- common/types.py +182 -0
- lightfast_mcp/__init__.py +50 -0
- lightfast_mcp/core/__init__.py +14 -0
- lightfast_mcp/core/base_server.py +205 -0
- lightfast_mcp/exceptions.py +55 -0
- lightfast_mcp/servers/__init__.py +1 -0
- lightfast_mcp/servers/blender/__init__.py +5 -0
- lightfast_mcp/servers/blender/server.py +358 -0
- lightfast_mcp/servers/blender_mcp_server.py +82 -0
- lightfast_mcp/servers/mock/__init__.py +5 -0
- lightfast_mcp/servers/mock/server.py +101 -0
- lightfast_mcp/servers/mock/tools.py +161 -0
- lightfast_mcp/servers/mock_server.py +78 -0
- lightfast_mcp/utils/__init__.py +1 -0
- lightfast_mcp/utils/logging_utils.py +69 -0
- mseep_lightfast_mcp-0.0.1.dist-info/METADATA +36 -0
- mseep_lightfast_mcp-0.0.1.dist-info/RECORD +43 -0
- mseep_lightfast_mcp-0.0.1.dist-info/WHEEL +5 -0
- mseep_lightfast_mcp-0.0.1.dist-info/entry_points.txt +7 -0
- mseep_lightfast_mcp-0.0.1.dist-info/licenses/LICENSE +21 -0
- mseep_lightfast_mcp-0.0.1.dist-info/top_level.txt +3 -0
- tools/__init__.py +46 -0
- tools/ai/__init__.py +8 -0
- tools/ai/conversation_cli.py +345 -0
- tools/ai/conversation_client.py +399 -0
- tools/ai/conversation_session.py +342 -0
- tools/ai/providers/__init__.py +11 -0
- tools/ai/providers/base_provider.py +64 -0
- tools/ai/providers/claude_provider.py +200 -0
- tools/ai/providers/openai_provider.py +204 -0
- tools/ai/tool_executor.py +257 -0
- tools/common/__init__.py +99 -0
- tools/common/async_utils.py +419 -0
- tools/common/errors.py +222 -0
- tools/common/logging.py +252 -0
- tools/common/types.py +130 -0
- tools/orchestration/__init__.py +15 -0
- tools/orchestration/cli.py +320 -0
- tools/orchestration/config_loader.py +348 -0
- tools/orchestration/server_orchestrator.py +466 -0
- tools/orchestration/server_registry.py +187 -0
- tools/orchestration/server_selector.py +242 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"""CLI interface for the new ConversationClient."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
import signal
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.markdown import Markdown
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
14
|
+
|
|
15
|
+
from tools.common import get_logger
|
|
16
|
+
from tools.orchestration.config_loader import load_server_configs
|
|
17
|
+
|
|
18
|
+
from .conversation_client import ConversationClient, create_conversation_client
|
|
19
|
+
|
|
20
|
+
app = typer.Typer(help="Conversation client CLI using the new architecture")
|
|
21
|
+
console = Console()
|
|
22
|
+
logger = get_logger("ConversationCLI")
|
|
23
|
+
|
|
24
|
+
# Global client for signal handling
|
|
25
|
+
current_client: Optional[ConversationClient] = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def handle_interrupt(signum, frame):
|
|
29
|
+
"""Handle Ctrl+C gracefully."""
|
|
30
|
+
if current_client:
|
|
31
|
+
console.print("\n[yellow]Shutting down gracefully...[/yellow]")
|
|
32
|
+
asyncio.create_task(current_client.disconnect_from_servers())
|
|
33
|
+
sys.exit(0)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
signal.signal(signal.SIGINT, handle_interrupt)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def print_step_info(step) -> None:
|
|
40
|
+
"""Print information about a completed conversation step."""
|
|
41
|
+
step_title = f"Step {step.step_number + 1}"
|
|
42
|
+
|
|
43
|
+
if step.text and step.tool_calls:
|
|
44
|
+
step_title += " (Text + Tool Calls)"
|
|
45
|
+
elif step.tool_calls:
|
|
46
|
+
step_title += " (Tool Calls Only)"
|
|
47
|
+
elif step.text:
|
|
48
|
+
step_title += " (Text Only)"
|
|
49
|
+
|
|
50
|
+
# Print step header
|
|
51
|
+
console.print(f"\n[bold blue]{step_title}[/bold blue]")
|
|
52
|
+
|
|
53
|
+
# Print text if present
|
|
54
|
+
if step.text:
|
|
55
|
+
console.print(Panel(Markdown(step.text), title="Response", border_style="blue"))
|
|
56
|
+
|
|
57
|
+
# Print tool calls and results
|
|
58
|
+
if step.tool_calls:
|
|
59
|
+
for i, tool_call in enumerate(step.tool_calls):
|
|
60
|
+
tool_title = f"Tool Call {i + 1}: {tool_call.tool_name}"
|
|
61
|
+
|
|
62
|
+
# Find corresponding result
|
|
63
|
+
result = None
|
|
64
|
+
for tool_result in step.tool_results:
|
|
65
|
+
if tool_result.id == tool_call.id:
|
|
66
|
+
result = tool_result
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
# Format tool call info
|
|
70
|
+
info_parts = [f"**Tool:** {tool_call.tool_name}"]
|
|
71
|
+
if tool_call.server_name:
|
|
72
|
+
info_parts.append(f"**Server:** {tool_call.server_name}")
|
|
73
|
+
if tool_call.arguments:
|
|
74
|
+
info_parts.append(f"**Arguments:** `{tool_call.arguments}`")
|
|
75
|
+
|
|
76
|
+
if result:
|
|
77
|
+
if result.is_success:
|
|
78
|
+
info_parts.append(f"**Result:** {result.result}")
|
|
79
|
+
border_style = "green"
|
|
80
|
+
elif result.is_error:
|
|
81
|
+
info_parts.append(f"**Error:** {result.error}")
|
|
82
|
+
border_style = "red"
|
|
83
|
+
else:
|
|
84
|
+
info_parts.append("**Status:** Pending")
|
|
85
|
+
border_style = "yellow"
|
|
86
|
+
else:
|
|
87
|
+
info_parts.append("**Status:** No result")
|
|
88
|
+
border_style = "yellow"
|
|
89
|
+
|
|
90
|
+
console.print(
|
|
91
|
+
Panel(
|
|
92
|
+
"\n".join(info_parts), title=tool_title, border_style=border_style
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@app.command()
|
|
98
|
+
def chat(
|
|
99
|
+
config_path: str = typer.Option(
|
|
100
|
+
"config/servers.yaml",
|
|
101
|
+
"--config",
|
|
102
|
+
"-c",
|
|
103
|
+
help="Path to server configuration file",
|
|
104
|
+
),
|
|
105
|
+
ai_provider: str = typer.Option(
|
|
106
|
+
"claude", "--provider", "-p", help="AI provider (claude or openai)"
|
|
107
|
+
),
|
|
108
|
+
max_steps: int = typer.Option(
|
|
109
|
+
None,
|
|
110
|
+
"--max-steps",
|
|
111
|
+
"-s",
|
|
112
|
+
help="Maximum number of steps (overrides environment variable)",
|
|
113
|
+
),
|
|
114
|
+
api_key: Optional[str] = typer.Option(
|
|
115
|
+
None, "--api-key", "-k", help="API key (optional, uses environment variables)"
|
|
116
|
+
),
|
|
117
|
+
):
|
|
118
|
+
"""Start an interactive chat session with AI and connected MCP servers."""
|
|
119
|
+
asyncio.run(async_chat(config_path, ai_provider, max_steps, api_key))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
async def async_chat(
|
|
123
|
+
config_path: str, ai_provider: str, max_steps: Optional[int], api_key: Optional[str]
|
|
124
|
+
):
|
|
125
|
+
"""Async chat implementation."""
|
|
126
|
+
global current_client
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
# Load server configuration
|
|
130
|
+
servers = load_server_configs(config_path)
|
|
131
|
+
if not servers:
|
|
132
|
+
console.print(
|
|
133
|
+
"[red]No servers configured. Please check your configuration file.[/red]"
|
|
134
|
+
)
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
# Get max_steps from environment if not provided
|
|
138
|
+
if max_steps is None:
|
|
139
|
+
max_steps = int(os.getenv("LIGHTFAST_MAX_STEPS", "5"))
|
|
140
|
+
|
|
141
|
+
console.print("[green]Loading servers and connecting...[/green]")
|
|
142
|
+
|
|
143
|
+
# Create and connect client
|
|
144
|
+
with Progress(
|
|
145
|
+
SpinnerColumn(),
|
|
146
|
+
TextColumn("[progress.description]{task.description}"),
|
|
147
|
+
console=console,
|
|
148
|
+
) as progress:
|
|
149
|
+
task = progress.add_task("Connecting to servers...", total=None)
|
|
150
|
+
|
|
151
|
+
client_result = await create_conversation_client(
|
|
152
|
+
servers=servers,
|
|
153
|
+
ai_provider=ai_provider,
|
|
154
|
+
api_key=api_key,
|
|
155
|
+
max_steps=max_steps,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if not client_result.is_success:
|
|
159
|
+
console.print(
|
|
160
|
+
f"[red]Failed to create client: {client_result.error}[/red]"
|
|
161
|
+
)
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
current_client = client_result.data
|
|
165
|
+
progress.update(task, description="Connected!")
|
|
166
|
+
|
|
167
|
+
# Display connected servers and tools
|
|
168
|
+
if current_client is not None:
|
|
169
|
+
connected_servers = current_client.get_connected_servers()
|
|
170
|
+
if connected_servers:
|
|
171
|
+
console.print(
|
|
172
|
+
f"\n[green][OK] Connected to {len(connected_servers)} servers:[/green]"
|
|
173
|
+
)
|
|
174
|
+
tools_by_server = current_client.get_available_tools()
|
|
175
|
+
for server in connected_servers:
|
|
176
|
+
tools = tools_by_server.get(server, [])
|
|
177
|
+
console.print(f" • {server}: {len(tools)} tools")
|
|
178
|
+
else:
|
|
179
|
+
console.print(
|
|
180
|
+
"[yellow]Warning: No servers connected successfully[/yellow]"
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
console.print("[red]Error: Client is not available[/red]")
|
|
184
|
+
|
|
185
|
+
console.print(f"\n[blue]AI Provider:[/blue] {ai_provider}")
|
|
186
|
+
console.print(f"[blue]Max Steps:[/blue] {max_steps}")
|
|
187
|
+
console.print(
|
|
188
|
+
"\n[green]Chat started! Type 'quit' or 'exit' to end the session.[/green]"
|
|
189
|
+
)
|
|
190
|
+
console.print("[dim]Use Ctrl+C to exit gracefully.[/dim]\n")
|
|
191
|
+
|
|
192
|
+
# Interactive chat loop
|
|
193
|
+
while True:
|
|
194
|
+
try:
|
|
195
|
+
# Get user input
|
|
196
|
+
user_input = console.input("[bold cyan]You:[/bold cyan] ").strip()
|
|
197
|
+
|
|
198
|
+
if user_input.lower() in ["quit", "exit", "q"]:
|
|
199
|
+
break
|
|
200
|
+
|
|
201
|
+
if not user_input:
|
|
202
|
+
continue
|
|
203
|
+
|
|
204
|
+
console.print("\n[yellow]🤖 Processing...[/yellow]")
|
|
205
|
+
|
|
206
|
+
# Send message and get result
|
|
207
|
+
if current_client is not None:
|
|
208
|
+
chat_result = await current_client.chat(user_input)
|
|
209
|
+
|
|
210
|
+
if not chat_result.is_success:
|
|
211
|
+
console.print(f"[red]Error: {chat_result.error}[/red]")
|
|
212
|
+
continue
|
|
213
|
+
|
|
214
|
+
conversation_result = chat_result.data
|
|
215
|
+
|
|
216
|
+
# Display the steps
|
|
217
|
+
for step in conversation_result.steps:
|
|
218
|
+
print_step_info(step)
|
|
219
|
+
|
|
220
|
+
console.print("\n" + "=" * 50 + "\n")
|
|
221
|
+
else:
|
|
222
|
+
console.print("[red]Error: Client is not available[/red]")
|
|
223
|
+
|
|
224
|
+
except KeyboardInterrupt:
|
|
225
|
+
break
|
|
226
|
+
except Exception as e:
|
|
227
|
+
console.print(f"[red]Error during chat: {e}[/red]")
|
|
228
|
+
logger.error(f"Chat error: {e}")
|
|
229
|
+
|
|
230
|
+
except Exception as e:
|
|
231
|
+
console.print(f"[red]Failed to start chat: {e}[/red]")
|
|
232
|
+
logger.error(f"Setup error: {e}")
|
|
233
|
+
|
|
234
|
+
finally:
|
|
235
|
+
if current_client:
|
|
236
|
+
console.print("[yellow]Disconnecting from servers...[/yellow]")
|
|
237
|
+
await current_client.disconnect_from_servers()
|
|
238
|
+
console.print("[green]Disconnected. Goodbye![/green]")
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@app.command()
|
|
242
|
+
def test(
|
|
243
|
+
config_path: str = typer.Option(
|
|
244
|
+
"config/servers.yaml",
|
|
245
|
+
"--config",
|
|
246
|
+
"-c",
|
|
247
|
+
help="Path to server configuration file",
|
|
248
|
+
),
|
|
249
|
+
ai_provider: str = typer.Option(
|
|
250
|
+
"claude", "--provider", "-p", help="AI provider (claude or openai)"
|
|
251
|
+
),
|
|
252
|
+
max_steps: int = typer.Option(
|
|
253
|
+
3, "--max-steps", "-s", help="Maximum number of steps for test"
|
|
254
|
+
),
|
|
255
|
+
message: str = typer.Option(
|
|
256
|
+
"Hello! What tools do you have available?",
|
|
257
|
+
"--message",
|
|
258
|
+
"-m",
|
|
259
|
+
help="Test message to send",
|
|
260
|
+
),
|
|
261
|
+
):
|
|
262
|
+
"""Test the conversation client with a single message."""
|
|
263
|
+
asyncio.run(async_test(config_path, ai_provider, max_steps, message))
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
async def async_test(config_path: str, ai_provider: str, max_steps: int, message: str):
|
|
267
|
+
"""Async test implementation."""
|
|
268
|
+
global current_client
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
# Load server configuration
|
|
272
|
+
servers = load_server_configs(config_path)
|
|
273
|
+
if not servers:
|
|
274
|
+
console.print("[red]No servers configured.[/red]")
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
console.print("[green]Testing conversation client...[/green]")
|
|
278
|
+
|
|
279
|
+
# Create and connect client
|
|
280
|
+
client_result = await create_conversation_client(
|
|
281
|
+
servers=servers,
|
|
282
|
+
ai_provider=ai_provider,
|
|
283
|
+
max_steps=max_steps,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
if not client_result.is_success:
|
|
287
|
+
console.print(f"[red]Failed to create client: {client_result.error}[/red]")
|
|
288
|
+
return
|
|
289
|
+
|
|
290
|
+
current_client = client_result.data
|
|
291
|
+
|
|
292
|
+
# Display status
|
|
293
|
+
if current_client is not None:
|
|
294
|
+
connected_servers = current_client.get_connected_servers()
|
|
295
|
+
console.print(f"Connected servers: {connected_servers}")
|
|
296
|
+
|
|
297
|
+
tools_by_server = current_client.get_available_tools()
|
|
298
|
+
console.print(f"Available tools: {tools_by_server}")
|
|
299
|
+
else:
|
|
300
|
+
console.print("[red]Error: Client is not available[/red]")
|
|
301
|
+
return
|
|
302
|
+
|
|
303
|
+
# Send test message
|
|
304
|
+
console.print(f"\n[cyan]Sending message:[/cyan] {message}")
|
|
305
|
+
|
|
306
|
+
if current_client is not None:
|
|
307
|
+
chat_result = await current_client.chat(message)
|
|
308
|
+
|
|
309
|
+
if not chat_result.is_success:
|
|
310
|
+
console.print(f"[red]Chat failed: {chat_result.error}[/red]")
|
|
311
|
+
return
|
|
312
|
+
|
|
313
|
+
conversation_result = chat_result.data
|
|
314
|
+
|
|
315
|
+
# Display results
|
|
316
|
+
console.print(
|
|
317
|
+
f"\n[green]Completed {len(conversation_result.steps)} steps:[/green]"
|
|
318
|
+
)
|
|
319
|
+
for step in conversation_result.steps:
|
|
320
|
+
print_step_info(step)
|
|
321
|
+
|
|
322
|
+
# Display summary
|
|
323
|
+
console.print("\n[blue]Conversation Summary:[/blue]")
|
|
324
|
+
console.print(
|
|
325
|
+
f" • Total tool calls: {conversation_result.total_tool_calls}"
|
|
326
|
+
)
|
|
327
|
+
console.print(
|
|
328
|
+
f" • Successful: {conversation_result.successful_tool_calls}"
|
|
329
|
+
)
|
|
330
|
+
console.print(f" • Failed: {conversation_result.failed_tool_calls}")
|
|
331
|
+
console.print(f" • Success rate: {conversation_result.success_rate:.1%}")
|
|
332
|
+
else:
|
|
333
|
+
console.print("[red]Error: Client is not available[/red]")
|
|
334
|
+
|
|
335
|
+
except Exception as e:
|
|
336
|
+
console.print(f"[red]Test failed: {e}[/red]")
|
|
337
|
+
logger.error(f"Test error: {e}")
|
|
338
|
+
|
|
339
|
+
finally:
|
|
340
|
+
if current_client:
|
|
341
|
+
await current_client.disconnect_from_servers()
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
if __name__ == "__main__":
|
|
345
|
+
app()
|