stravinsky 0.4.18__py3-none-any.whl → 0.4.66__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.
Potentially problematic release.
This version of stravinsky might be problematic. Click here for more details.
- mcp_bridge/__init__.py +1 -1
- mcp_bridge/auth/__init__.py +16 -6
- mcp_bridge/auth/cli.py +202 -11
- mcp_bridge/auth/oauth.py +1 -2
- mcp_bridge/auth/openai_oauth.py +4 -7
- mcp_bridge/auth/token_store.py +0 -1
- mcp_bridge/cli/__init__.py +1 -1
- mcp_bridge/cli/install_hooks.py +503 -107
- mcp_bridge/cli/session_report.py +0 -3
- mcp_bridge/config/__init__.py +2 -2
- mcp_bridge/config/hook_config.py +3 -5
- mcp_bridge/config/rate_limits.py +108 -13
- mcp_bridge/hooks/HOOKS_SETTINGS.json +17 -4
- mcp_bridge/hooks/__init__.py +14 -4
- mcp_bridge/hooks/agent_reminder.py +4 -4
- mcp_bridge/hooks/auto_slash_command.py +5 -5
- mcp_bridge/hooks/budget_optimizer.py +2 -2
- mcp_bridge/hooks/claude_limits_hook.py +114 -0
- mcp_bridge/hooks/comment_checker.py +3 -4
- mcp_bridge/hooks/compaction.py +2 -2
- mcp_bridge/hooks/context.py +2 -1
- mcp_bridge/hooks/context_monitor.py +2 -2
- mcp_bridge/hooks/delegation_policy.py +85 -0
- mcp_bridge/hooks/directory_context.py +3 -3
- mcp_bridge/hooks/edit_recovery.py +3 -2
- mcp_bridge/hooks/edit_recovery_policy.py +49 -0
- mcp_bridge/hooks/empty_message_sanitizer.py +2 -2
- mcp_bridge/hooks/events.py +160 -0
- mcp_bridge/hooks/git_noninteractive.py +4 -4
- mcp_bridge/hooks/keyword_detector.py +8 -10
- mcp_bridge/hooks/manager.py +35 -22
- mcp_bridge/hooks/notification_hook.py +13 -6
- mcp_bridge/hooks/parallel_enforcement_policy.py +67 -0
- mcp_bridge/hooks/parallel_enforcer.py +5 -5
- mcp_bridge/hooks/parallel_execution.py +22 -10
- mcp_bridge/hooks/post_tool/parallel_validation.py +103 -0
- mcp_bridge/hooks/pre_compact.py +8 -9
- mcp_bridge/hooks/pre_tool/agent_spawn_validator.py +115 -0
- mcp_bridge/hooks/preemptive_compaction.py +2 -3
- mcp_bridge/hooks/routing_notifications.py +80 -0
- mcp_bridge/hooks/rules_injector.py +11 -19
- mcp_bridge/hooks/session_idle.py +4 -4
- mcp_bridge/hooks/session_notifier.py +4 -4
- mcp_bridge/hooks/session_recovery.py +4 -5
- mcp_bridge/hooks/stravinsky_mode.py +1 -1
- mcp_bridge/hooks/subagent_stop.py +1 -3
- mcp_bridge/hooks/task_validator.py +2 -2
- mcp_bridge/hooks/tmux_manager.py +7 -8
- mcp_bridge/hooks/todo_delegation.py +4 -1
- mcp_bridge/hooks/todo_enforcer.py +180 -10
- mcp_bridge/hooks/truncation_policy.py +37 -0
- mcp_bridge/hooks/truncator.py +1 -2
- mcp_bridge/metrics/cost_tracker.py +115 -0
- mcp_bridge/native_search.py +93 -0
- mcp_bridge/native_watcher.py +118 -0
- mcp_bridge/notifications.py +3 -4
- mcp_bridge/orchestrator/enums.py +11 -0
- mcp_bridge/orchestrator/router.py +165 -0
- mcp_bridge/orchestrator/state.py +32 -0
- mcp_bridge/orchestrator/visualization.py +14 -0
- mcp_bridge/orchestrator/wisdom.py +34 -0
- mcp_bridge/prompts/__init__.py +1 -8
- mcp_bridge/prompts/dewey.py +1 -1
- mcp_bridge/prompts/planner.py +2 -4
- mcp_bridge/prompts/stravinsky.py +53 -31
- mcp_bridge/proxy/__init__.py +0 -0
- mcp_bridge/proxy/client.py +70 -0
- mcp_bridge/proxy/model_server.py +157 -0
- mcp_bridge/routing/__init__.py +43 -0
- mcp_bridge/routing/config.py +250 -0
- mcp_bridge/routing/model_tiers.py +135 -0
- mcp_bridge/routing/provider_state.py +261 -0
- mcp_bridge/routing/task_classifier.py +190 -0
- mcp_bridge/server.py +363 -34
- mcp_bridge/server_tools.py +298 -6
- mcp_bridge/tools/__init__.py +19 -8
- mcp_bridge/tools/agent_manager.py +549 -799
- mcp_bridge/tools/background_tasks.py +13 -17
- mcp_bridge/tools/code_search.py +54 -51
- mcp_bridge/tools/continuous_loop.py +0 -1
- mcp_bridge/tools/dashboard.py +19 -0
- mcp_bridge/tools/find_code.py +296 -0
- mcp_bridge/tools/init.py +1 -0
- mcp_bridge/tools/list_directory.py +42 -0
- mcp_bridge/tools/lsp/__init__.py +8 -8
- mcp_bridge/tools/lsp/manager.py +51 -28
- mcp_bridge/tools/lsp/tools.py +98 -65
- mcp_bridge/tools/model_invoke.py +1047 -152
- mcp_bridge/tools/mux_client.py +75 -0
- mcp_bridge/tools/project_context.py +1 -2
- mcp_bridge/tools/query_classifier.py +132 -49
- mcp_bridge/tools/read_file.py +84 -0
- mcp_bridge/tools/replace.py +45 -0
- mcp_bridge/tools/run_shell_command.py +38 -0
- mcp_bridge/tools/search_enhancements.py +347 -0
- mcp_bridge/tools/semantic_search.py +677 -92
- mcp_bridge/tools/session_manager.py +0 -2
- mcp_bridge/tools/skill_loader.py +0 -1
- mcp_bridge/tools/task_runner.py +5 -7
- mcp_bridge/tools/templates.py +3 -3
- mcp_bridge/tools/tool_search.py +331 -0
- mcp_bridge/tools/write_file.py +29 -0
- mcp_bridge/update_manager.py +33 -37
- mcp_bridge/update_manager_pypi.py +6 -8
- mcp_bridge/utils/cache.py +82 -0
- mcp_bridge/utils/process.py +71 -0
- mcp_bridge/utils/session_state.py +51 -0
- mcp_bridge/utils/truncation.py +76 -0
- {stravinsky-0.4.18.dist-info → stravinsky-0.4.66.dist-info}/METADATA +84 -35
- stravinsky-0.4.66.dist-info/RECORD +198 -0
- {stravinsky-0.4.18.dist-info → stravinsky-0.4.66.dist-info}/entry_points.txt +1 -0
- stravinsky_claude_assets/HOOKS_INTEGRATION.md +316 -0
- stravinsky_claude_assets/agents/HOOKS.md +437 -0
- stravinsky_claude_assets/agents/code-reviewer.md +210 -0
- stravinsky_claude_assets/agents/comment_checker.md +580 -0
- stravinsky_claude_assets/agents/debugger.md +254 -0
- stravinsky_claude_assets/agents/delphi.md +495 -0
- stravinsky_claude_assets/agents/dewey.md +248 -0
- stravinsky_claude_assets/agents/explore.md +1198 -0
- stravinsky_claude_assets/agents/frontend.md +472 -0
- stravinsky_claude_assets/agents/implementation-lead.md +164 -0
- stravinsky_claude_assets/agents/momus.md +464 -0
- stravinsky_claude_assets/agents/research-lead.md +141 -0
- stravinsky_claude_assets/agents/stravinsky.md +730 -0
- stravinsky_claude_assets/commands/delphi.md +9 -0
- stravinsky_claude_assets/commands/dewey.md +54 -0
- stravinsky_claude_assets/commands/git-master.md +112 -0
- stravinsky_claude_assets/commands/index.md +49 -0
- stravinsky_claude_assets/commands/publish.md +86 -0
- stravinsky_claude_assets/commands/review.md +73 -0
- stravinsky_claude_assets/commands/str/agent_cancel.md +70 -0
- stravinsky_claude_assets/commands/str/agent_list.md +56 -0
- stravinsky_claude_assets/commands/str/agent_output.md +92 -0
- stravinsky_claude_assets/commands/str/agent_progress.md +74 -0
- stravinsky_claude_assets/commands/str/agent_retry.md +94 -0
- stravinsky_claude_assets/commands/str/cancel.md +51 -0
- stravinsky_claude_assets/commands/str/clean.md +97 -0
- stravinsky_claude_assets/commands/str/continue.md +38 -0
- stravinsky_claude_assets/commands/str/index.md +199 -0
- stravinsky_claude_assets/commands/str/list_watchers.md +96 -0
- stravinsky_claude_assets/commands/str/search.md +205 -0
- stravinsky_claude_assets/commands/str/start_filewatch.md +136 -0
- stravinsky_claude_assets/commands/str/stats.md +71 -0
- stravinsky_claude_assets/commands/str/stop_filewatch.md +89 -0
- stravinsky_claude_assets/commands/str/unwatch.md +42 -0
- stravinsky_claude_assets/commands/str/watch.md +45 -0
- stravinsky_claude_assets/commands/strav.md +53 -0
- stravinsky_claude_assets/commands/stravinsky.md +292 -0
- stravinsky_claude_assets/commands/verify.md +60 -0
- stravinsky_claude_assets/commands/version.md +5 -0
- stravinsky_claude_assets/hooks/README.md +248 -0
- stravinsky_claude_assets/hooks/comment_checker.py +193 -0
- stravinsky_claude_assets/hooks/context.py +38 -0
- stravinsky_claude_assets/hooks/context_monitor.py +153 -0
- stravinsky_claude_assets/hooks/dependency_tracker.py +73 -0
- stravinsky_claude_assets/hooks/edit_recovery.py +46 -0
- stravinsky_claude_assets/hooks/execution_state_tracker.py +68 -0
- stravinsky_claude_assets/hooks/notification_hook.py +103 -0
- stravinsky_claude_assets/hooks/notification_hook_v2.py +96 -0
- stravinsky_claude_assets/hooks/parallel_execution.py +241 -0
- stravinsky_claude_assets/hooks/parallel_reinforcement.py +106 -0
- stravinsky_claude_assets/hooks/parallel_reinforcement_v2.py +112 -0
- stravinsky_claude_assets/hooks/pre_compact.py +123 -0
- stravinsky_claude_assets/hooks/ralph_loop.py +173 -0
- stravinsky_claude_assets/hooks/session_recovery.py +263 -0
- stravinsky_claude_assets/hooks/stop_hook.py +89 -0
- stravinsky_claude_assets/hooks/stravinsky_metrics.py +164 -0
- stravinsky_claude_assets/hooks/stravinsky_mode.py +146 -0
- stravinsky_claude_assets/hooks/subagent_stop.py +98 -0
- stravinsky_claude_assets/hooks/todo_continuation.py +111 -0
- stravinsky_claude_assets/hooks/todo_delegation.py +96 -0
- stravinsky_claude_assets/hooks/tool_messaging.py +281 -0
- stravinsky_claude_assets/hooks/truncator.py +23 -0
- stravinsky_claude_assets/rules/deployment_safety.md +51 -0
- stravinsky_claude_assets/rules/integration_wiring.md +89 -0
- stravinsky_claude_assets/rules/pypi_deployment.md +220 -0
- stravinsky_claude_assets/rules/stravinsky_orchestrator.md +32 -0
- stravinsky_claude_assets/settings.json +152 -0
- stravinsky_claude_assets/skills/chrome-devtools/SKILL.md +81 -0
- stravinsky_claude_assets/skills/sqlite/SKILL.md +77 -0
- stravinsky_claude_assets/skills/supabase/SKILL.md +74 -0
- stravinsky_claude_assets/task_dependencies.json +34 -0
- stravinsky-0.4.18.dist-info/RECORD +0 -88
- {stravinsky-0.4.18.dist-info → stravinsky-0.4.66.dist-info}/WHEEL +0 -0
mcp_bridge/server.py
CHANGED
|
@@ -7,22 +7,21 @@ Optimized for extremely fast startup and protocol compliance:
|
|
|
7
7
|
- Robust crash logging to stderr and /tmp.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
import sys
|
|
11
|
-
import os
|
|
12
10
|
import asyncio
|
|
13
11
|
import logging
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
14
|
import time
|
|
15
15
|
from typing import Any
|
|
16
16
|
|
|
17
17
|
from mcp.server import Server
|
|
18
18
|
from mcp.server.stdio import stdio_server
|
|
19
19
|
from mcp.types import (
|
|
20
|
-
|
|
21
|
-
TextContent,
|
|
22
|
-
Resource,
|
|
20
|
+
GetPromptResult,
|
|
23
21
|
Prompt,
|
|
24
22
|
PromptMessage,
|
|
25
|
-
|
|
23
|
+
TextContent,
|
|
24
|
+
Tool,
|
|
26
25
|
)
|
|
27
26
|
|
|
28
27
|
from . import __version__
|
|
@@ -35,6 +34,26 @@ logging.basicConfig(
|
|
|
35
34
|
)
|
|
36
35
|
logger = logging.getLogger(__name__)
|
|
37
36
|
|
|
37
|
+
# --- LOAD .env FILES (GEMINI_API_KEY, etc.) ---
|
|
38
|
+
# Load from ~/.stravinsky/.env (dedicated config location)
|
|
39
|
+
try:
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
|
|
42
|
+
from dotenv import load_dotenv
|
|
43
|
+
|
|
44
|
+
# Load from ~/.env (user-global, lowest priority)
|
|
45
|
+
home_env = Path.home() / ".env"
|
|
46
|
+
if home_env.exists():
|
|
47
|
+
load_dotenv(home_env, override=False)
|
|
48
|
+
|
|
49
|
+
# Load from ~/.stravinsky/.env (stravinsky config, takes precedence)
|
|
50
|
+
stravinsky_env = Path.home() / ".stravinsky" / ".env"
|
|
51
|
+
if stravinsky_env.exists():
|
|
52
|
+
load_dotenv(stravinsky_env, override=True)
|
|
53
|
+
logger.info(f"[Config] Loaded environment from {stravinsky_env}")
|
|
54
|
+
except ImportError:
|
|
55
|
+
pass # python-dotenv not installed, skip
|
|
56
|
+
|
|
38
57
|
|
|
39
58
|
# Pre-async crash logger
|
|
40
59
|
def install_emergency_logger():
|
|
@@ -120,6 +139,17 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
120
139
|
thinking_budget=arguments.get("thinking_budget", 0),
|
|
121
140
|
)
|
|
122
141
|
|
|
142
|
+
elif name == "invoke_gemini_agentic":
|
|
143
|
+
from .tools.model_invoke import invoke_gemini_agentic
|
|
144
|
+
|
|
145
|
+
result_content = await invoke_gemini_agentic(
|
|
146
|
+
token_store=token_store,
|
|
147
|
+
prompt=arguments["prompt"],
|
|
148
|
+
model=arguments.get("model", "gemini-3-flash"),
|
|
149
|
+
max_turns=arguments.get("max_turns", 10),
|
|
150
|
+
timeout=arguments.get("timeout", 120),
|
|
151
|
+
)
|
|
152
|
+
|
|
123
153
|
elif name == "invoke_openai":
|
|
124
154
|
from .tools.model_invoke import invoke_openai
|
|
125
155
|
|
|
@@ -130,6 +160,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
130
160
|
temperature=arguments.get("temperature", 0.7),
|
|
131
161
|
max_tokens=arguments.get("max_tokens", 4096),
|
|
132
162
|
thinking_budget=arguments.get("thinking_budget", 0),
|
|
163
|
+
reasoning_effort=arguments.get("reasoning_effort", "medium"),
|
|
133
164
|
)
|
|
134
165
|
|
|
135
166
|
# --- CONTEXT DISPATCH ---
|
|
@@ -166,6 +197,13 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
166
197
|
file_pattern=arguments.get("file_pattern", ""),
|
|
167
198
|
)
|
|
168
199
|
|
|
200
|
+
elif name == "list_directory":
|
|
201
|
+
from .tools.list_directory import list_directory
|
|
202
|
+
|
|
203
|
+
result_content = await list_directory(
|
|
204
|
+
path=arguments["path"],
|
|
205
|
+
)
|
|
206
|
+
|
|
169
207
|
elif name == "ast_grep_search":
|
|
170
208
|
from .tools.code_search import ast_grep_search
|
|
171
209
|
|
|
@@ -194,6 +232,56 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
194
232
|
directory=arguments.get("directory", "."),
|
|
195
233
|
)
|
|
196
234
|
|
|
235
|
+
elif name == "read_file":
|
|
236
|
+
from .tools.read_file import read_file
|
|
237
|
+
|
|
238
|
+
result_content = await read_file(
|
|
239
|
+
path=arguments["path"],
|
|
240
|
+
offset=arguments.get("offset", 0),
|
|
241
|
+
limit=arguments.get("limit"),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
elif name == "write_file":
|
|
245
|
+
from .tools.write_file import write_file
|
|
246
|
+
|
|
247
|
+
result_content = await write_file(
|
|
248
|
+
path=arguments["path"],
|
|
249
|
+
content=arguments["content"],
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
elif name == "replace":
|
|
253
|
+
from .tools.replace import replace
|
|
254
|
+
|
|
255
|
+
result_content = await replace(
|
|
256
|
+
path=arguments["path"],
|
|
257
|
+
old_string=arguments["old_string"],
|
|
258
|
+
new_string=arguments["new_string"],
|
|
259
|
+
instruction=arguments["instruction"],
|
|
260
|
+
expected_replacements=arguments.get("expected_replacements", 1),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
elif name == "run_shell_command":
|
|
264
|
+
from .tools.run_shell_command import run_shell_command
|
|
265
|
+
|
|
266
|
+
result_content = await run_shell_command(
|
|
267
|
+
command=arguments["command"],
|
|
268
|
+
description=arguments["description"],
|
|
269
|
+
dir_path=arguments.get("dir_path", "."),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
elif name == "tool_search":
|
|
273
|
+
from .tools.tool_search import search_tools
|
|
274
|
+
from .server_tools import get_tool_definitions
|
|
275
|
+
|
|
276
|
+
# Get all registered tool definitions to search through
|
|
277
|
+
all_tools = get_tool_definitions()
|
|
278
|
+
|
|
279
|
+
result_content = search_tools(
|
|
280
|
+
query=arguments["query"],
|
|
281
|
+
tools=all_tools,
|
|
282
|
+
top_k=arguments.get("top_k", 5),
|
|
283
|
+
)
|
|
284
|
+
|
|
197
285
|
# --- SESSION DISPATCH ---
|
|
198
286
|
elif name == "session_list":
|
|
199
287
|
from .tools.session_manager import list_sessions
|
|
@@ -284,7 +372,15 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
284
372
|
elif name == "agent_list":
|
|
285
373
|
from .tools.agent_manager import agent_list
|
|
286
374
|
|
|
287
|
-
result_content = await agent_list()
|
|
375
|
+
result_content = await agent_list(show_all=arguments.get("show_all", True))
|
|
376
|
+
|
|
377
|
+
elif name == "agent_cleanup":
|
|
378
|
+
from .tools.agent_manager import agent_cleanup
|
|
379
|
+
|
|
380
|
+
result_content = await agent_cleanup(
|
|
381
|
+
max_age_minutes=arguments.get("max_age_minutes", 30),
|
|
382
|
+
statuses=arguments.get("statuses"),
|
|
383
|
+
)
|
|
288
384
|
|
|
289
385
|
elif name == "agent_progress":
|
|
290
386
|
from .tools.agent_manager import agent_progress
|
|
@@ -448,6 +544,63 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
448
544
|
provider=arguments.get("provider", "ollama"),
|
|
449
545
|
)
|
|
450
546
|
|
|
547
|
+
elif name == "find_code":
|
|
548
|
+
from .tools.find_code import find_code
|
|
549
|
+
|
|
550
|
+
result_content = await find_code(
|
|
551
|
+
query=arguments["query"],
|
|
552
|
+
search_type=arguments.get("search_type", "auto"),
|
|
553
|
+
project_path=arguments.get("project_path", "."),
|
|
554
|
+
language=arguments.get("language"),
|
|
555
|
+
n_results=arguments.get("n_results", 10),
|
|
556
|
+
provider=arguments.get("provider", "ollama"),
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
elif name == "multi_query_search":
|
|
560
|
+
from .tools.search_enhancements import multi_query_search
|
|
561
|
+
|
|
562
|
+
result_content = await multi_query_search(
|
|
563
|
+
query=arguments["query"],
|
|
564
|
+
project_path=arguments.get("project_path", "."),
|
|
565
|
+
n_results=arguments.get("n_results", 10),
|
|
566
|
+
num_expansions=arguments.get("num_expansions", 3),
|
|
567
|
+
language=arguments.get("language"),
|
|
568
|
+
node_type=arguments.get("node_type"),
|
|
569
|
+
provider=arguments.get("provider", "ollama"),
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
elif name == "decomposed_search":
|
|
573
|
+
from .tools.search_enhancements import decomposed_search
|
|
574
|
+
|
|
575
|
+
result_content = await decomposed_search(
|
|
576
|
+
query=arguments["query"],
|
|
577
|
+
project_path=arguments.get("project_path", "."),
|
|
578
|
+
n_results=arguments.get("n_results", 10),
|
|
579
|
+
language=arguments.get("language"),
|
|
580
|
+
node_type=arguments.get("node_type"),
|
|
581
|
+
provider=arguments.get("provider", "ollama"),
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
elif name == "enhanced_search":
|
|
585
|
+
from .tools.search_enhancements import enhanced_search
|
|
586
|
+
|
|
587
|
+
result_content = await enhanced_search(
|
|
588
|
+
query=arguments["query"],
|
|
589
|
+
project_path=arguments.get("project_path", "."),
|
|
590
|
+
n_results=arguments.get("n_results", 10),
|
|
591
|
+
mode=arguments.get("mode", "auto"),
|
|
592
|
+
language=arguments.get("language"),
|
|
593
|
+
node_type=arguments.get("node_type"),
|
|
594
|
+
provider=arguments.get("provider", "ollama"),
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
elif name == "get_cost_report":
|
|
598
|
+
from .tools.dashboard import get_cost_report
|
|
599
|
+
|
|
600
|
+
result_content = await get_cost_report(
|
|
601
|
+
session_id=arguments.get("session_id"),
|
|
602
|
+
)
|
|
603
|
+
|
|
451
604
|
elif name == "semantic_index":
|
|
452
605
|
from .tools.semantic_search import index_codebase
|
|
453
606
|
|
|
@@ -466,52 +619,60 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
466
619
|
)
|
|
467
620
|
|
|
468
621
|
elif name == "start_file_watcher":
|
|
469
|
-
from .tools.semantic_search import start_file_watcher
|
|
470
622
|
import json
|
|
471
623
|
|
|
624
|
+
from .tools.semantic_search import start_file_watcher
|
|
625
|
+
|
|
472
626
|
try:
|
|
473
|
-
watcher = start_file_watcher(
|
|
627
|
+
watcher = await start_file_watcher(
|
|
474
628
|
project_path=arguments.get("project_path", "."),
|
|
475
629
|
provider=arguments.get("provider", "ollama"),
|
|
476
630
|
debounce_seconds=arguments.get("debounce_seconds", 2.0),
|
|
477
631
|
)
|
|
478
632
|
|
|
479
|
-
result_content = json.dumps(
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
633
|
+
result_content = json.dumps(
|
|
634
|
+
{
|
|
635
|
+
"status": "started",
|
|
636
|
+
"project_path": str(watcher.project_path),
|
|
637
|
+
"debounce_seconds": watcher.debounce_seconds,
|
|
638
|
+
"provider": watcher.store.provider_name,
|
|
639
|
+
"is_running": watcher.is_running(),
|
|
640
|
+
},
|
|
641
|
+
indent=2,
|
|
642
|
+
)
|
|
486
643
|
except ValueError as e:
|
|
487
644
|
# No index exists
|
|
488
|
-
result_content = json.dumps(
|
|
489
|
-
"error": str(e),
|
|
490
|
-
|
|
491
|
-
|
|
645
|
+
result_content = json.dumps(
|
|
646
|
+
{"error": str(e), "hint": "Run semantic_index() before starting file watcher"},
|
|
647
|
+
indent=2,
|
|
648
|
+
)
|
|
492
649
|
print(f"⚠️ start_file_watcher ValueError: {e}", file=sys.stderr)
|
|
493
650
|
except Exception as e:
|
|
494
651
|
# Unexpected error
|
|
495
652
|
import traceback
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
653
|
+
|
|
654
|
+
result_content = json.dumps(
|
|
655
|
+
{
|
|
656
|
+
"error": f"{type(e).__name__}: {str(e)}",
|
|
657
|
+
"hint": "Check MCP server logs for details",
|
|
658
|
+
},
|
|
659
|
+
indent=2,
|
|
660
|
+
)
|
|
500
661
|
print(f"❌ start_file_watcher error: {e}", file=sys.stderr)
|
|
501
662
|
traceback.print_exc(file=sys.stderr)
|
|
502
663
|
|
|
503
664
|
elif name == "stop_file_watcher":
|
|
504
|
-
from .tools.semantic_search import stop_file_watcher
|
|
505
665
|
import json
|
|
506
666
|
|
|
667
|
+
from .tools.semantic_search import stop_file_watcher
|
|
668
|
+
|
|
507
669
|
stopped = stop_file_watcher(
|
|
508
670
|
project_path=arguments.get("project_path", "."),
|
|
509
671
|
)
|
|
510
672
|
|
|
511
|
-
result_content = json.dumps(
|
|
512
|
-
"stopped": stopped,
|
|
513
|
-
|
|
514
|
-
}, indent=2)
|
|
673
|
+
result_content = json.dumps(
|
|
674
|
+
{"stopped": stopped, "project_path": arguments.get("project_path", ".")}, indent=2
|
|
675
|
+
)
|
|
515
676
|
|
|
516
677
|
elif name == "cancel_indexing":
|
|
517
678
|
from .tools.semantic_search import cancel_indexing
|
|
@@ -531,9 +692,10 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
531
692
|
)
|
|
532
693
|
|
|
533
694
|
elif name == "list_file_watchers":
|
|
534
|
-
from .tools.semantic_search import list_file_watchers
|
|
535
695
|
import json
|
|
536
696
|
|
|
697
|
+
from .tools.semantic_search import list_file_watchers
|
|
698
|
+
|
|
537
699
|
result_content = json.dumps(list_file_watchers(), indent=2)
|
|
538
700
|
|
|
539
701
|
elif name == "multi_query_search":
|
|
@@ -620,7 +782,7 @@ async def list_prompts() -> list[Prompt]:
|
|
|
620
782
|
@server.get_prompt()
|
|
621
783
|
async def get_prompt(name: str, arguments: dict[str, str] | None) -> GetPromptResult:
|
|
622
784
|
"""Get a specific prompt content (lazy loaded)."""
|
|
623
|
-
from .prompts import
|
|
785
|
+
from .prompts import delphi, dewey, document_writer, explore, frontend, multimodal, stravinsky
|
|
624
786
|
|
|
625
787
|
prompts_map = {
|
|
626
788
|
"stravinsky": ("Stravinsky orchestrator system prompt", stravinsky.get_stravinsky_prompt),
|
|
@@ -649,8 +811,105 @@ async def get_prompt(name: str, arguments: dict[str, str] | None) -> GetPromptRe
|
|
|
649
811
|
)
|
|
650
812
|
|
|
651
813
|
|
|
814
|
+
def sync_user_assets():
|
|
815
|
+
"""
|
|
816
|
+
Copy package assets to user scope (~/.claude/) on every MCP load.
|
|
817
|
+
|
|
818
|
+
This ensures all repos get the latest commands, hooks, rules, and agents
|
|
819
|
+
from the installed Stravinsky package.
|
|
820
|
+
|
|
821
|
+
Handles both:
|
|
822
|
+
- Development: .claude/ relative to project root
|
|
823
|
+
- Installed package: stravinsky_claude_assets/ in site-packages
|
|
824
|
+
"""
|
|
825
|
+
from pathlib import Path
|
|
826
|
+
import shutil
|
|
827
|
+
|
|
828
|
+
# Try multiple locations for package assets
|
|
829
|
+
package_dir = Path(__file__).parent.parent # stravinsky/
|
|
830
|
+
|
|
831
|
+
# Location 1: Development - .claude/ at project root
|
|
832
|
+
dev_claude = package_dir / ".claude"
|
|
833
|
+
|
|
834
|
+
# Location 2: Installed package - stravinsky_claude_assets in site-packages
|
|
835
|
+
# When installed via pip/uvx, hatch includes .claude as stravinsky_claude_assets
|
|
836
|
+
installed_claude = package_dir / "stravinsky_claude_assets"
|
|
837
|
+
|
|
838
|
+
# Also check relative to mcp_bridge (alternate install layout)
|
|
839
|
+
mcp_bridge_dir = Path(__file__).parent
|
|
840
|
+
installed_claude_alt = mcp_bridge_dir.parent / "stravinsky_claude_assets"
|
|
841
|
+
|
|
842
|
+
# Find the first existing assets directory
|
|
843
|
+
package_claude = None
|
|
844
|
+
for candidate in [dev_claude, installed_claude, installed_claude_alt]:
|
|
845
|
+
if candidate.exists():
|
|
846
|
+
package_claude = candidate
|
|
847
|
+
break
|
|
848
|
+
|
|
849
|
+
# User scope directory
|
|
850
|
+
user_claude = Path.home() / ".claude"
|
|
851
|
+
|
|
852
|
+
if package_claude is None:
|
|
853
|
+
# Try importlib.resources as last resort (Python 3.9+)
|
|
854
|
+
try:
|
|
855
|
+
import importlib.resources as resources
|
|
856
|
+
|
|
857
|
+
# Check if stravinsky_claude_assets is a package
|
|
858
|
+
with resources.files("stravinsky_claude_assets") as assets_path:
|
|
859
|
+
if assets_path.is_dir():
|
|
860
|
+
package_claude = Path(assets_path)
|
|
861
|
+
except (ImportError, ModuleNotFoundError, TypeError):
|
|
862
|
+
pass
|
|
863
|
+
|
|
864
|
+
if package_claude is None:
|
|
865
|
+
logger.debug(f"Package assets not found (checked: {dev_claude}, {installed_claude})")
|
|
866
|
+
return
|
|
867
|
+
|
|
868
|
+
# Directories to sync
|
|
869
|
+
dirs_to_sync = ["commands", "hooks", "rules", "agents"]
|
|
870
|
+
|
|
871
|
+
for dir_name in dirs_to_sync:
|
|
872
|
+
src_dir = package_claude / dir_name
|
|
873
|
+
dst_dir = user_claude / dir_name
|
|
874
|
+
|
|
875
|
+
if not src_dir.exists():
|
|
876
|
+
continue
|
|
877
|
+
|
|
878
|
+
# Create destination if it doesn't exist
|
|
879
|
+
dst_dir.mkdir(parents=True, exist_ok=True)
|
|
880
|
+
|
|
881
|
+
# Copy all files recursively (overwrite if source is newer)
|
|
882
|
+
for src_file in src_dir.rglob("*"):
|
|
883
|
+
if src_file.is_file():
|
|
884
|
+
# Compute relative path
|
|
885
|
+
rel_path = src_file.relative_to(src_dir)
|
|
886
|
+
dst_file = dst_dir / rel_path
|
|
887
|
+
|
|
888
|
+
# Create parent directories
|
|
889
|
+
dst_file.parent.mkdir(parents=True, exist_ok=True)
|
|
890
|
+
|
|
891
|
+
# Copy if source is newer or dest doesn't exist
|
|
892
|
+
should_copy = not dst_file.exists()
|
|
893
|
+
if dst_file.exists():
|
|
894
|
+
src_mtime = src_file.stat().st_mtime
|
|
895
|
+
dst_mtime = dst_file.stat().st_mtime
|
|
896
|
+
should_copy = src_mtime > dst_mtime
|
|
897
|
+
|
|
898
|
+
if should_copy:
|
|
899
|
+
shutil.copy2(src_file, dst_file)
|
|
900
|
+
logger.debug(f"Synced {dir_name}/{rel_path} to user scope")
|
|
901
|
+
|
|
902
|
+
logger.info("Synced package assets to user scope (~/.claude/)")
|
|
903
|
+
|
|
904
|
+
|
|
652
905
|
async def async_main():
|
|
653
906
|
"""Server execution entry point."""
|
|
907
|
+
# Sync package assets to user scope on every MCP load
|
|
908
|
+
try:
|
|
909
|
+
sync_user_assets()
|
|
910
|
+
except Exception as e:
|
|
911
|
+
logger.warning(f"Failed to sync user assets: {e}")
|
|
912
|
+
|
|
654
913
|
# Initialize hooks at runtime, not import time
|
|
655
914
|
try:
|
|
656
915
|
from .hooks import initialize_hooks
|
|
@@ -685,7 +944,7 @@ async def async_main():
|
|
|
685
944
|
write_stream,
|
|
686
945
|
server.create_initialization_options(),
|
|
687
946
|
)
|
|
688
|
-
except Exception
|
|
947
|
+
except Exception:
|
|
689
948
|
logger.critical("Server process crashed in async_main", exc_info=True)
|
|
690
949
|
sys.exit(1)
|
|
691
950
|
finally:
|
|
@@ -699,9 +958,9 @@ async def async_main():
|
|
|
699
958
|
def main():
|
|
700
959
|
"""Synchronous entry point with CLI arg handling."""
|
|
701
960
|
import argparse
|
|
702
|
-
|
|
703
|
-
from .tools.agent_manager import get_manager
|
|
961
|
+
|
|
704
962
|
from .auth.token_store import TokenStore
|
|
963
|
+
from .tools.agent_manager import get_manager
|
|
705
964
|
|
|
706
965
|
parser = argparse.ArgumentParser(
|
|
707
966
|
description="Stravinsky MCP Bridge - Multi-model AI orchestration for Claude Code. "
|
|
@@ -751,6 +1010,31 @@ def main():
|
|
|
751
1010
|
help="Also clear agent history from .stravinsky/agents.json",
|
|
752
1011
|
)
|
|
753
1012
|
|
|
1013
|
+
# proxy command (model server proxy)
|
|
1014
|
+
proxy_parser = subparsers.add_parser(
|
|
1015
|
+
"proxy",
|
|
1016
|
+
help="Manage the model proxy server",
|
|
1017
|
+
description="Starts or manages the FastAPI model proxy for async execution.",
|
|
1018
|
+
)
|
|
1019
|
+
proxy_subparsers = proxy_parser.add_subparsers(
|
|
1020
|
+
dest="proxy_command", help="Proxy subcommands", metavar="SUBCOMMAND"
|
|
1021
|
+
)
|
|
1022
|
+
proxy_subparsers.add_parser(
|
|
1023
|
+
"start",
|
|
1024
|
+
help="Start the model proxy server",
|
|
1025
|
+
description="Launches the FastAPI server on port 8765.",
|
|
1026
|
+
)
|
|
1027
|
+
proxy_subparsers.add_parser(
|
|
1028
|
+
"stop",
|
|
1029
|
+
help="Stop the model proxy server",
|
|
1030
|
+
description="Terminates any running model proxy process.",
|
|
1031
|
+
)
|
|
1032
|
+
proxy_subparsers.add_parser(
|
|
1033
|
+
"status",
|
|
1034
|
+
help="Check proxy server status",
|
|
1035
|
+
description="Verifies if the proxy server is reachable.",
|
|
1036
|
+
)
|
|
1037
|
+
|
|
754
1038
|
# auth command (authentication)
|
|
755
1039
|
auth_parser = subparsers.add_parser(
|
|
756
1040
|
"auth",
|
|
@@ -862,6 +1146,51 @@ def main():
|
|
|
862
1146
|
print(f"Stopped {count} running agent(s).")
|
|
863
1147
|
return 0
|
|
864
1148
|
|
|
1149
|
+
elif args.command == "proxy":
|
|
1150
|
+
proxy_cmd = getattr(args, "proxy_command", None)
|
|
1151
|
+
import os
|
|
1152
|
+
import sys
|
|
1153
|
+
|
|
1154
|
+
if proxy_cmd == "start":
|
|
1155
|
+
from .proxy.model_server import main as proxy_main
|
|
1156
|
+
|
|
1157
|
+
proxy_main()
|
|
1158
|
+
return 0
|
|
1159
|
+
elif proxy_cmd == "stop":
|
|
1160
|
+
# Simple kill-by-port for now
|
|
1161
|
+
import os
|
|
1162
|
+
import subprocess
|
|
1163
|
+
|
|
1164
|
+
port = int(os.getenv("STRAVINSKY_PROXY_PORT", 8765))
|
|
1165
|
+
try:
|
|
1166
|
+
if sys.platform == "win32":
|
|
1167
|
+
subprocess.run(
|
|
1168
|
+
f"taskkill /F /FI \"PID eq $(netstat -ano | findstr :{port} | awk '{{print $5}}')\"",
|
|
1169
|
+
shell=True,
|
|
1170
|
+
)
|
|
1171
|
+
else:
|
|
1172
|
+
subprocess.run(f"lsof -ti:{port} | xargs kill -9", shell=True)
|
|
1173
|
+
print(f"Stopped proxy on port {port}")
|
|
1174
|
+
except Exception as e:
|
|
1175
|
+
print(f"Failed to stop proxy: {e}")
|
|
1176
|
+
return 0
|
|
1177
|
+
elif proxy_cmd == "status":
|
|
1178
|
+
import socket
|
|
1179
|
+
|
|
1180
|
+
port = int(os.getenv("STRAVINSKY_PROXY_PORT", 8765))
|
|
1181
|
+
host = os.getenv("STRAVINSKY_PROXY_HOST", "127.0.0.1")
|
|
1182
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
1183
|
+
s.settimeout(1)
|
|
1184
|
+
try:
|
|
1185
|
+
s.connect((host, port))
|
|
1186
|
+
print(f"✅ Proxy is RUNNING on {host}:{port}")
|
|
1187
|
+
except Exception:
|
|
1188
|
+
print(f"❌ Proxy is NOT RUNNING on {host}:{port}")
|
|
1189
|
+
return 0
|
|
1190
|
+
else:
|
|
1191
|
+
proxy_parser.print_help()
|
|
1192
|
+
return 0
|
|
1193
|
+
|
|
865
1194
|
elif args.command == "auth":
|
|
866
1195
|
auth_cmd = getattr(args, "auth_command", None)
|
|
867
1196
|
token_store = get_token_store()
|