stravinsky 0.2.52__py3-none-any.whl → 0.4.18__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/token_store.py +113 -11
- mcp_bridge/cli/__init__.py +6 -0
- mcp_bridge/cli/install_hooks.py +1265 -0
- mcp_bridge/cli/session_report.py +585 -0
- mcp_bridge/config/MANIFEST_SCHEMA.md +305 -0
- mcp_bridge/config/README.md +276 -0
- mcp_bridge/config/hook_config.py +249 -0
- mcp_bridge/config/hooks_manifest.json +138 -0
- mcp_bridge/config/rate_limits.py +222 -0
- mcp_bridge/config/skills_manifest.json +128 -0
- mcp_bridge/hooks/HOOKS_SETTINGS.json +175 -0
- mcp_bridge/hooks/README.md +215 -0
- mcp_bridge/hooks/__init__.py +119 -60
- mcp_bridge/hooks/edit_recovery.py +42 -37
- mcp_bridge/hooks/git_noninteractive.py +89 -0
- mcp_bridge/hooks/keyword_detector.py +30 -0
- mcp_bridge/hooks/manager.py +8 -0
- mcp_bridge/hooks/notification_hook.py +103 -0
- mcp_bridge/hooks/parallel_execution.py +111 -0
- mcp_bridge/hooks/pre_compact.py +82 -183
- mcp_bridge/hooks/rules_injector.py +507 -0
- mcp_bridge/hooks/session_notifier.py +125 -0
- mcp_bridge/{native_hooks → hooks}/stravinsky_mode.py +51 -16
- mcp_bridge/hooks/subagent_stop.py +98 -0
- mcp_bridge/hooks/task_validator.py +73 -0
- mcp_bridge/hooks/tmux_manager.py +141 -0
- mcp_bridge/hooks/todo_continuation.py +90 -0
- mcp_bridge/hooks/todo_delegation.py +88 -0
- mcp_bridge/hooks/tool_messaging.py +267 -0
- mcp_bridge/hooks/truncator.py +21 -17
- mcp_bridge/notifications.py +151 -0
- mcp_bridge/prompts/multimodal.py +24 -3
- mcp_bridge/server.py +214 -49
- mcp_bridge/server_tools.py +445 -0
- mcp_bridge/tools/__init__.py +22 -18
- mcp_bridge/tools/agent_manager.py +220 -32
- mcp_bridge/tools/code_search.py +97 -11
- mcp_bridge/tools/lsp/__init__.py +7 -0
- mcp_bridge/tools/lsp/manager.py +448 -0
- mcp_bridge/tools/lsp/tools.py +637 -150
- mcp_bridge/tools/model_invoke.py +208 -106
- mcp_bridge/tools/query_classifier.py +323 -0
- mcp_bridge/tools/semantic_search.py +3042 -0
- mcp_bridge/tools/templates.py +32 -18
- mcp_bridge/update_manager.py +589 -0
- mcp_bridge/update_manager_pypi.py +299 -0
- stravinsky-0.4.18.dist-info/METADATA +468 -0
- stravinsky-0.4.18.dist-info/RECORD +88 -0
- stravinsky-0.4.18.dist-info/entry_points.txt +5 -0
- mcp_bridge/native_hooks/edit_recovery.py +0 -46
- mcp_bridge/native_hooks/todo_delegation.py +0 -54
- mcp_bridge/native_hooks/truncator.py +0 -23
- stravinsky-0.2.52.dist-info/METADATA +0 -204
- stravinsky-0.2.52.dist-info/RECORD +0 -63
- stravinsky-0.2.52.dist-info/entry_points.txt +0 -3
- /mcp_bridge/{native_hooks → hooks}/context.py +0 -0
- {stravinsky-0.2.52.dist-info → stravinsky-0.4.18.dist-info}/WHEEL +0 -0
mcp_bridge/server.py
CHANGED
|
@@ -95,56 +95,9 @@ async def list_tools() -> list[Tool]:
|
|
|
95
95
|
return get_tool_definitions()
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
def _format_tool_log(name: str, arguments: dict[str, Any]) -> str:
|
|
99
|
-
"""Format a concise log message for tool calls."""
|
|
100
|
-
# LSP tools - show file:line
|
|
101
|
-
if name.startswith("lsp_"):
|
|
102
|
-
file_path = arguments.get("file_path", "")
|
|
103
|
-
if file_path:
|
|
104
|
-
# Shorten path to last 2 components
|
|
105
|
-
parts = file_path.split("/")
|
|
106
|
-
short_path = "/".join(parts[-2:]) if len(parts) > 2 else file_path
|
|
107
|
-
line = arguments.get("line", "")
|
|
108
|
-
if line:
|
|
109
|
-
return f"→ {name}: {short_path}:{line}"
|
|
110
|
-
return f"→ {name}: {short_path}"
|
|
111
|
-
query = arguments.get("query", "")
|
|
112
|
-
if query:
|
|
113
|
-
return f"→ {name}: query='{query[:40]}'"
|
|
114
|
-
return f"→ {name}"
|
|
115
|
-
|
|
116
|
-
# Model invocation - show agent context if present
|
|
117
|
-
if name in ("invoke_gemini", "invoke_openai"):
|
|
118
|
-
agent_ctx = arguments.get("agent_context", {})
|
|
119
|
-
agent_type = agent_ctx.get("agent_type", "direct") if agent_ctx else "direct"
|
|
120
|
-
model = arguments.get("model", "default")
|
|
121
|
-
prompt = arguments.get("prompt", "")
|
|
122
|
-
# Summarize prompt
|
|
123
|
-
summary = " ".join(prompt.split())[:80] + "..." if len(prompt) > 80 else prompt
|
|
124
|
-
return f"[{agent_type}] → {model}: {summary}"
|
|
125
|
-
|
|
126
|
-
# Search tools - show pattern
|
|
127
|
-
if name in ("grep_search", "ast_grep_search", "ast_grep_replace"):
|
|
128
|
-
pattern = arguments.get("pattern", "")[:50]
|
|
129
|
-
return f"→ {name}: pattern='{pattern}'"
|
|
130
|
-
|
|
131
|
-
# Agent tools - show agent type/task_id
|
|
132
|
-
if name == "agent_spawn":
|
|
133
|
-
agent_type = arguments.get("agent_type", "explore")
|
|
134
|
-
desc = arguments.get("description", "")[:40]
|
|
135
|
-
return f"→ {name}: [{agent_type}] {desc}"
|
|
136
|
-
if name in ("agent_output", "agent_cancel", "agent_progress"):
|
|
137
|
-
task_id = arguments.get("task_id", "")
|
|
138
|
-
return f"→ {name}: {task_id}"
|
|
139
|
-
|
|
140
|
-
# Default - just tool name
|
|
141
|
-
return f"→ {name}"
|
|
142
|
-
|
|
143
|
-
|
|
144
98
|
@server.call_tool()
|
|
145
99
|
async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
146
100
|
"""Handle tool calls with deep lazy loading of implementations."""
|
|
147
|
-
logger.info(_format_tool_log(name, arguments))
|
|
148
101
|
hook_manager = get_hook_manager_lazy()
|
|
149
102
|
token_store = get_token_store()
|
|
150
103
|
|
|
@@ -190,6 +143,19 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
190
143
|
|
|
191
144
|
result_content = await get_system_health()
|
|
192
145
|
|
|
146
|
+
elif name == "semantic_health":
|
|
147
|
+
from .tools.semantic_search import semantic_health
|
|
148
|
+
|
|
149
|
+
result_content = await semantic_health(
|
|
150
|
+
project_path=arguments.get("project_path", "."),
|
|
151
|
+
provider=arguments.get("provider", "ollama"),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
elif name == "lsp_health":
|
|
155
|
+
from .tools.lsp.tools import lsp_health
|
|
156
|
+
|
|
157
|
+
result_content = await lsp_health()
|
|
158
|
+
|
|
193
159
|
# --- SEARCH DISPATCH ---
|
|
194
160
|
elif name == "grep_search":
|
|
195
161
|
from .tools.code_search import grep_search
|
|
@@ -423,11 +389,191 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
423
389
|
character=arguments["character"],
|
|
424
390
|
)
|
|
425
391
|
|
|
392
|
+
elif name == "lsp_code_action_resolve":
|
|
393
|
+
from .tools.lsp import lsp_code_action_resolve
|
|
394
|
+
|
|
395
|
+
result_content = await lsp_code_action_resolve(
|
|
396
|
+
file_path=arguments["file_path"],
|
|
397
|
+
action_code=arguments["action_code"],
|
|
398
|
+
line=arguments.get("line"),
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
elif name == "lsp_extract_refactor":
|
|
402
|
+
from .tools.lsp import lsp_extract_refactor
|
|
403
|
+
|
|
404
|
+
result_content = await lsp_extract_refactor(
|
|
405
|
+
file_path=arguments["file_path"],
|
|
406
|
+
start_line=arguments["start_line"],
|
|
407
|
+
start_char=arguments["start_char"],
|
|
408
|
+
end_line=arguments["end_line"],
|
|
409
|
+
end_char=arguments["end_char"],
|
|
410
|
+
new_name=arguments["new_name"],
|
|
411
|
+
kind=arguments.get("kind", "function"),
|
|
412
|
+
)
|
|
413
|
+
|
|
426
414
|
elif name == "lsp_servers":
|
|
427
415
|
from .tools.lsp import lsp_servers
|
|
428
416
|
|
|
429
417
|
result_content = await lsp_servers()
|
|
430
418
|
|
|
419
|
+
elif name == "lsp_diagnostics":
|
|
420
|
+
from .tools.code_search import lsp_diagnostics
|
|
421
|
+
|
|
422
|
+
result_content = await lsp_diagnostics(
|
|
423
|
+
file_path=arguments["file_path"],
|
|
424
|
+
severity=arguments.get("severity", "all"),
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
elif name == "semantic_search":
|
|
428
|
+
from .tools.semantic_search import semantic_search
|
|
429
|
+
|
|
430
|
+
result_content = await semantic_search(
|
|
431
|
+
query=arguments["query"],
|
|
432
|
+
project_path=arguments.get("project_path", "."),
|
|
433
|
+
n_results=arguments.get("n_results", 10),
|
|
434
|
+
language=arguments.get("language"),
|
|
435
|
+
node_type=arguments.get("node_type"),
|
|
436
|
+
provider=arguments.get("provider", "ollama"),
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
elif name == "hybrid_search":
|
|
440
|
+
from .tools.semantic_search import hybrid_search
|
|
441
|
+
|
|
442
|
+
result_content = await hybrid_search(
|
|
443
|
+
query=arguments["query"],
|
|
444
|
+
pattern=arguments.get("pattern"),
|
|
445
|
+
project_path=arguments.get("project_path", "."),
|
|
446
|
+
n_results=arguments.get("n_results", 10),
|
|
447
|
+
language=arguments.get("language"),
|
|
448
|
+
provider=arguments.get("provider", "ollama"),
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
elif name == "semantic_index":
|
|
452
|
+
from .tools.semantic_search import index_codebase
|
|
453
|
+
|
|
454
|
+
result_content = await index_codebase(
|
|
455
|
+
project_path=arguments.get("project_path", "."),
|
|
456
|
+
force=arguments.get("force", False),
|
|
457
|
+
provider=arguments.get("provider", "ollama"),
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
elif name == "semantic_stats":
|
|
461
|
+
from .tools.semantic_search import semantic_stats
|
|
462
|
+
|
|
463
|
+
result_content = await semantic_stats(
|
|
464
|
+
project_path=arguments.get("project_path", "."),
|
|
465
|
+
provider=arguments.get("provider", "ollama"),
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
elif name == "start_file_watcher":
|
|
469
|
+
from .tools.semantic_search import start_file_watcher
|
|
470
|
+
import json
|
|
471
|
+
|
|
472
|
+
try:
|
|
473
|
+
watcher = start_file_watcher(
|
|
474
|
+
project_path=arguments.get("project_path", "."),
|
|
475
|
+
provider=arguments.get("provider", "ollama"),
|
|
476
|
+
debounce_seconds=arguments.get("debounce_seconds", 2.0),
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
result_content = json.dumps({
|
|
480
|
+
"status": "started",
|
|
481
|
+
"project_path": str(watcher.project_path),
|
|
482
|
+
"debounce_seconds": watcher.debounce_seconds,
|
|
483
|
+
"provider": watcher.store.provider_name,
|
|
484
|
+
"is_running": watcher.is_running()
|
|
485
|
+
}, indent=2)
|
|
486
|
+
except ValueError as e:
|
|
487
|
+
# No index exists
|
|
488
|
+
result_content = json.dumps({
|
|
489
|
+
"error": str(e),
|
|
490
|
+
"hint": "Run semantic_index() before starting file watcher"
|
|
491
|
+
}, indent=2)
|
|
492
|
+
print(f"⚠️ start_file_watcher ValueError: {e}", file=sys.stderr)
|
|
493
|
+
except Exception as e:
|
|
494
|
+
# Unexpected error
|
|
495
|
+
import traceback
|
|
496
|
+
result_content = json.dumps({
|
|
497
|
+
"error": f"{type(e).__name__}: {str(e)}",
|
|
498
|
+
"hint": "Check MCP server logs for details"
|
|
499
|
+
}, indent=2)
|
|
500
|
+
print(f"❌ start_file_watcher error: {e}", file=sys.stderr)
|
|
501
|
+
traceback.print_exc(file=sys.stderr)
|
|
502
|
+
|
|
503
|
+
elif name == "stop_file_watcher":
|
|
504
|
+
from .tools.semantic_search import stop_file_watcher
|
|
505
|
+
import json
|
|
506
|
+
|
|
507
|
+
stopped = stop_file_watcher(
|
|
508
|
+
project_path=arguments.get("project_path", "."),
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
result_content = json.dumps({
|
|
512
|
+
"stopped": stopped,
|
|
513
|
+
"project_path": arguments.get("project_path", ".")
|
|
514
|
+
}, indent=2)
|
|
515
|
+
|
|
516
|
+
elif name == "cancel_indexing":
|
|
517
|
+
from .tools.semantic_search import cancel_indexing
|
|
518
|
+
|
|
519
|
+
result_content = cancel_indexing(
|
|
520
|
+
project_path=arguments.get("project_path", "."),
|
|
521
|
+
provider=arguments.get("provider", "ollama"),
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
elif name == "delete_index":
|
|
525
|
+
from .tools.semantic_search import delete_index
|
|
526
|
+
|
|
527
|
+
result_content = delete_index(
|
|
528
|
+
project_path=arguments.get("project_path", "."),
|
|
529
|
+
provider=arguments.get("provider"), # None if not specified
|
|
530
|
+
delete_all=arguments.get("delete_all", False),
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
elif name == "list_file_watchers":
|
|
534
|
+
from .tools.semantic_search import list_file_watchers
|
|
535
|
+
import json
|
|
536
|
+
|
|
537
|
+
result_content = json.dumps(list_file_watchers(), indent=2)
|
|
538
|
+
|
|
539
|
+
elif name == "multi_query_search":
|
|
540
|
+
from .tools.semantic_search import multi_query_search
|
|
541
|
+
|
|
542
|
+
result_content = await multi_query_search(
|
|
543
|
+
query=arguments["query"],
|
|
544
|
+
project_path=arguments.get("project_path", "."),
|
|
545
|
+
n_results=arguments.get("n_results", 10),
|
|
546
|
+
num_expansions=arguments.get("num_expansions", 3),
|
|
547
|
+
language=arguments.get("language"),
|
|
548
|
+
node_type=arguments.get("node_type"),
|
|
549
|
+
provider=arguments.get("provider", "ollama"),
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
elif name == "decomposed_search":
|
|
553
|
+
from .tools.semantic_search import decomposed_search
|
|
554
|
+
|
|
555
|
+
result_content = await decomposed_search(
|
|
556
|
+
query=arguments["query"],
|
|
557
|
+
project_path=arguments.get("project_path", "."),
|
|
558
|
+
n_results=arguments.get("n_results", 10),
|
|
559
|
+
language=arguments.get("language"),
|
|
560
|
+
node_type=arguments.get("node_type"),
|
|
561
|
+
provider=arguments.get("provider", "ollama"),
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
elif name == "enhanced_search":
|
|
565
|
+
from .tools.semantic_search import enhanced_search
|
|
566
|
+
|
|
567
|
+
result_content = await enhanced_search(
|
|
568
|
+
query=arguments["query"],
|
|
569
|
+
project_path=arguments.get("project_path", "."),
|
|
570
|
+
n_results=arguments.get("n_results", 10),
|
|
571
|
+
mode=arguments.get("mode", "auto"),
|
|
572
|
+
language=arguments.get("language"),
|
|
573
|
+
node_type=arguments.get("node_type"),
|
|
574
|
+
provider=arguments.get("provider", "ollama"),
|
|
575
|
+
)
|
|
576
|
+
|
|
431
577
|
else:
|
|
432
578
|
result_content = f"Unknown tool: {name}"
|
|
433
579
|
|
|
@@ -441,7 +587,10 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
441
587
|
processed_text = await hook_manager.execute_post_tool_call(
|
|
442
588
|
name, arguments, result_content[0].text
|
|
443
589
|
)
|
|
444
|
-
|
|
590
|
+
# Only update if processed_text is non-empty to avoid empty text blocks
|
|
591
|
+
# (API error: cache_control cannot be set for empty text blocks)
|
|
592
|
+
if processed_text:
|
|
593
|
+
result_content[0].text = processed_text
|
|
445
594
|
elif isinstance(result_content, str):
|
|
446
595
|
result_content = await hook_manager.execute_post_tool_call(
|
|
447
596
|
name, arguments, result_content
|
|
@@ -510,6 +659,16 @@ async def async_main():
|
|
|
510
659
|
except Exception as e:
|
|
511
660
|
logger.error(f"Failed to initialize hooks: {e}")
|
|
512
661
|
|
|
662
|
+
# Clean up stale ChromaDB locks on startup
|
|
663
|
+
try:
|
|
664
|
+
from .tools.semantic_search import cleanup_stale_chromadb_locks
|
|
665
|
+
|
|
666
|
+
removed_count = cleanup_stale_chromadb_locks()
|
|
667
|
+
if removed_count > 0:
|
|
668
|
+
logger.info(f"Cleaned up {removed_count} stale ChromaDB lock(s)")
|
|
669
|
+
except Exception as e:
|
|
670
|
+
logger.warning(f"Failed to cleanup ChromaDB locks: {e}")
|
|
671
|
+
|
|
513
672
|
# Start background token refresh scheduler
|
|
514
673
|
try:
|
|
515
674
|
from .auth.token_refresh import background_token_refresh
|
|
@@ -529,6 +688,12 @@ async def async_main():
|
|
|
529
688
|
except Exception as e:
|
|
530
689
|
logger.critical("Server process crashed in async_main", exc_info=True)
|
|
531
690
|
sys.exit(1)
|
|
691
|
+
finally:
|
|
692
|
+
logger.info("Initiating shutdown sequence...")
|
|
693
|
+
from .tools.lsp.manager import get_lsp_manager
|
|
694
|
+
|
|
695
|
+
lsp_manager = get_lsp_manager()
|
|
696
|
+
await lsp_manager.shutdown()
|
|
532
697
|
|
|
533
698
|
|
|
534
699
|
def main():
|
|
@@ -741,4 +906,4 @@ def main():
|
|
|
741
906
|
|
|
742
907
|
|
|
743
908
|
if __name__ == "__main__":
|
|
744
|
-
main()
|
|
909
|
+
sys.exit(main())
|