codrspot-processor-mcp 0.1.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.
- codepreproc_client/__init__.py +14 -0
- codepreproc_client/__main__.py +9 -0
- codepreproc_client/layer1_business/__init__.py +0 -0
- codepreproc_client/layer1_business/allowed_scope_cache.py +62 -0
- codepreproc_client/layer1_business/api_client/__init__.py +0 -0
- codepreproc_client/layer1_business/api_client/lease_client.py +34 -0
- codepreproc_client/layer1_business/api_client/net_guard.py +98 -0
- codepreproc_client/layer1_business/api_client/promote_client.py +59 -0
- codepreproc_client/layer1_business/api_client/reasoning_client.py +163 -0
- codepreproc_client/layer1_business/api_client/registry_client.py +119 -0
- codepreproc_client/layer1_business/api_client/superkg_client.py +327 -0
- codepreproc_client/layer1_business/cli/__init__.py +0 -0
- codepreproc_client/layer1_business/cli/init.py +578 -0
- codepreproc_client/layer1_business/cli/main.py +78 -0
- codepreproc_client/layer1_business/config.py +104 -0
- codepreproc_client/layer1_business/git_utils.py +67 -0
- codepreproc_client/layer1_business/license/__init__.py +0 -0
- codepreproc_client/layer1_business/license/fingerprint.py +61 -0
- codepreproc_client/layer1_business/license/jwt_client.py +60 -0
- codepreproc_client/layer1_business/license/project_lock.py +44 -0
- codepreproc_client/layer1_business/local_registry.py +40 -0
- codepreproc_client/layer1_business/manifest/__init__.py +0 -0
- codepreproc_client/layer1_business/manifest/credentials.py +35 -0
- codepreproc_client/layer1_business/mcp/__init__.py +0 -0
- codepreproc_client/layer1_business/mcp/action_monitor.py +246 -0
- codepreproc_client/layer1_business/mcp/lifecycle.py +649 -0
- codepreproc_client/layer1_business/mcp/server.py +656 -0
- codepreproc_client/layer1_business/mcp/tools.py +1665 -0
- codepreproc_client/layer1_business/project_registry.py +99 -0
- codepreproc_client/layer2_tooling/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/ast_graph.py +426 -0
- codepreproc_client/layer2_tooling/condensation/bm25_store.py +132 -0
- codepreproc_client/layer2_tooling/condensation/chunking/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/chunking/ast_chunker.py +313 -0
- codepreproc_client/layer2_tooling/condensation/chunking/languages.py +238 -0
- codepreproc_client/layer2_tooling/condensation/embed_worker.py +128 -0
- codepreproc_client/layer2_tooling/condensation/embeddings.py +452 -0
- codepreproc_client/layer2_tooling/condensation/extract/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/extract/compactor.py +103 -0
- codepreproc_client/layer2_tooling/condensation/extract/providers/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/extract/providers/base.py +38 -0
- codepreproc_client/layer2_tooling/condensation/indexer.py +945 -0
- codepreproc_client/layer2_tooling/condensation/ppl/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/ppl/emitter.py +162 -0
- codepreproc_client/layer2_tooling/condensation/ppl/parser.py +352 -0
- codepreproc_client/layer2_tooling/condensation/qdrant_store.py +387 -0
- codepreproc_client/layer2_tooling/condensation/scanning/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/scanning/manifest_scanner.py +72 -0
- codepreproc_client/layer2_tooling/condensation/scanning/md_scanner.py +97 -0
- codepreproc_client/layer2_tooling/condensation/scanning/repo_mapper.py +495 -0
- codepreproc_client/layer2_tooling/condensation/session_vector_store.py +69 -0
- codepreproc_client/layer2_tooling/coupling/__init__.py +0 -0
- codepreproc_client/layer2_tooling/coupling/domain.py +263 -0
- codepreproc_client/layer2_tooling/coupling/graph.py +255 -0
- codepreproc_client/layer2_tooling/coupling/layer2_gate.py +189 -0
- codepreproc_client/layer2_tooling/coupling/projector.py +139 -0
- codepreproc_client/layer2_tooling/coupling/resolver.py +150 -0
- codepreproc_client/layer2_tooling/materialization/__init__.py +0 -0
- codepreproc_client/layer2_tooling/materialization/pipeline/__init__.py +0 -0
- codepreproc_client/layer2_tooling/materialization/pipeline/execute_semantic.py +1249 -0
- codepreproc_client/layer2_tooling/materialization/pipeline/filesystem_reorg.py +575 -0
- codepreproc_client/layer2_tooling/materialization/pipeline/patch_generator.py +127 -0
- codepreproc_client/layer2_tooling/materialization/pipeline/patch_validator.py +101 -0
- codepreproc_client/layer2_tooling/materialization/semantic/__init__.py +19 -0
- codepreproc_client/layer2_tooling/materialization/semantic/anchors.py +61 -0
- codepreproc_client/layer2_tooling/materialization/semantic/applicability.py +75 -0
- codepreproc_client/layer2_tooling/materialization/semantic/edit_planner.py +264 -0
- codepreproc_client/layer2_tooling/materialization/semantic/materializer.py +121 -0
- codepreproc_client/layer2_tooling/materialization/semantic/merger.py +73 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/__init__.py +0 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/add_import.py +36 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/append_argument.py +37 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/insert_literal.py +24 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/remove_import.py +33 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/remove_statement_unique.py +14 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/rename_symbol_local.py +16 -0
- codepreproc_client/layer2_tooling/materialization/semantic/region_resolver.py +229 -0
- codepreproc_client/layer2_tooling/materialization/semantic/synthesizer.py +186 -0
- codepreproc_client/layer2_tooling/materialization/semantic/target_locator.py +94 -0
- codepreproc_client/layer2_tooling/materialization/semantic/validator.py +322 -0
- codepreproc_client/layer2_tooling/materialization/snippet/__init__.py +0 -0
- codepreproc_client/layer2_tooling/materialization/snippet/assembler.py +503 -0
- codepreproc_client/layer2_tooling/materialization/snippet/loader.py +187 -0
- codepreproc_client/layer2_tooling/retrieval/__init__.py +0 -0
- codepreproc_client/layer2_tooling/retrieval/graph_walk.py +47 -0
- codepreproc_client/layer2_tooling/retrieval/hybrid.py +95 -0
- codepreproc_client/layer2_tooling/retrieval/rerank_worker.py +86 -0
- codepreproc_client/layer2_tooling/retrieval/reranker.py +213 -0
- codepreproc_client/layer2_tooling/watcher/__init__.py +0 -0
- codepreproc_client/layer2_tooling/watcher/fs_watcher.py +3 -0
- codrspot_processor_mcp-0.1.0.dist-info/METADATA +396 -0
- codrspot_processor_mcp-0.1.0.dist-info/RECORD +100 -0
- codrspot_processor_mcp-0.1.0.dist-info/WHEEL +5 -0
- codrspot_processor_mcp-0.1.0.dist-info/entry_points.txt +3 -0
- codrspot_processor_mcp-0.1.0.dist-info/licenses/LICENSE +23 -0
- codrspot_processor_mcp-0.1.0.dist-info/top_level.txt +2 -0
- contracts/__init__.py +0 -0
- contracts/dtos.py +1239 -0
- contracts/ir.py +102 -0
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
"""stdio MCP server implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import importlib.metadata
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import time
|
|
11
|
+
from logging.handlers import RotatingFileHandler
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from urllib.parse import urlparse
|
|
14
|
+
from urllib.request import url2pathname
|
|
15
|
+
|
|
16
|
+
# GitPython raises ImportError at import time if no `git` executable is on
|
|
17
|
+
# PATH; downgrade to a warning so a missing/unusual PATH doesn't take down
|
|
18
|
+
# the whole MCP server (git-dependent tools would just fail when used).
|
|
19
|
+
os.environ.setdefault("GIT_PYTHON_REFRESH", "warn")
|
|
20
|
+
|
|
21
|
+
from dotenv import load_dotenv
|
|
22
|
+
|
|
23
|
+
def _default_codepreproc_home() -> str:
|
|
24
|
+
try:
|
|
25
|
+
return str(Path.home() / ".codepreproc")
|
|
26
|
+
except RuntimeError:
|
|
27
|
+
return str(Path(".codepreproc").resolve())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Try the source-tree path first (editable install), then the canonical home location.
|
|
31
|
+
load_dotenv(Path(__file__).parents[4] / ".env", override=False)
|
|
32
|
+
load_dotenv(Path(os.environ.get("CODEPREPROC_HOME") or _default_codepreproc_home()) / ".env", override=False)
|
|
33
|
+
|
|
34
|
+
import anyio
|
|
35
|
+
import mcp.types as types
|
|
36
|
+
from mcp.server.models import InitializationOptions
|
|
37
|
+
from mcp.server.stdio import stdio_server
|
|
38
|
+
from mcp.types import ServerCapabilities, ToolsCapability
|
|
39
|
+
|
|
40
|
+
# Stdio MCP transport treats every byte on stdout as JSON-RPC. Some transitive
|
|
41
|
+
# dependencies (e.g. bm25s's benchmark helper) `print()` a Windows fallback
|
|
42
|
+
# notice at import time, which corrupts the stream — redirect stdout to
|
|
43
|
+
# stderr for the duration of these imports so that noise lands somewhere safe.
|
|
44
|
+
import sys as _sys
|
|
45
|
+
from contextlib import redirect_stdout as _redirect_stdout
|
|
46
|
+
|
|
47
|
+
with _redirect_stdout(_sys.stderr):
|
|
48
|
+
from codepreproc_client.layer1_business.config import (
|
|
49
|
+
apply_env_overrides,
|
|
50
|
+
get_credentials_path,
|
|
51
|
+
get_home_dir,
|
|
52
|
+
get_server_url,
|
|
53
|
+
get_settings,
|
|
54
|
+
get_superkg_settings,
|
|
55
|
+
)
|
|
56
|
+
from codepreproc_client.layer1_business.local_registry import load_local_projects
|
|
57
|
+
from codepreproc_client.layer2_tooling.condensation.session_vector_store import SessionVectorStore
|
|
58
|
+
from codepreproc_client.layer1_business.api_client.reasoning_client import LLMRuntime
|
|
59
|
+
from codepreproc_client.layer1_business.mcp.action_monitor import ActionMonitor, ToolExecutionError
|
|
60
|
+
from codepreproc_client.layer1_business.mcp.lifecycle import (
|
|
61
|
+
InstrumentedServer,
|
|
62
|
+
MCPConnectionLifecycleLogger,
|
|
63
|
+
resolve_session_roots,
|
|
64
|
+
)
|
|
65
|
+
from codepreproc_client.layer1_business.mcp.tools import CodepreprocTools, SessionState
|
|
66
|
+
from codepreproc_client.layer1_business.project_registry import ProjectRegistry
|
|
67
|
+
from codepreproc_client.layer1_business.api_client.superkg_client import SuperKGClient
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def configure_logging(log_dir: Path, debug: bool) -> logging.Logger:
|
|
71
|
+
"""Configure application logging."""
|
|
72
|
+
|
|
73
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
logger = logging.getLogger("codepreproc")
|
|
75
|
+
logger.setLevel(logging.DEBUG if debug else logging.INFO)
|
|
76
|
+
logger.handlers.clear()
|
|
77
|
+
handler = RotatingFileHandler(log_dir / "mcp_server.log", maxBytes=10 * 1024 * 1024, backupCount=5, encoding="utf-8")
|
|
78
|
+
handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s"))
|
|
79
|
+
logger.addHandler(handler)
|
|
80
|
+
return logger
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _tool_schema(properties: dict, required: list[str] | None = None) -> dict:
|
|
84
|
+
return {"type": "object", "properties": properties, "required": required or []}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _path_from_file_uri(uri: str) -> Path:
|
|
88
|
+
parsed = urlparse(uri)
|
|
89
|
+
if parsed.scheme != "file":
|
|
90
|
+
raise ValueError(f"Unsupported root URI scheme: {parsed.scheme!r}")
|
|
91
|
+
raw_path = f"//{parsed.netloc}{parsed.path}" if parsed.netloc else parsed.path
|
|
92
|
+
return Path(url2pathname(raw_path)).resolve()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def _mint_bootstrap_jwt(local_projects: dict) -> tuple[str | None, "JWTClient | None"]:
|
|
96
|
+
"""Mint a JWT using any one locally registered project's fingerprint, for
|
|
97
|
+
calls that aren't project-scoped (shared defaults, the snippet library,
|
|
98
|
+
LLM reasoning). Returns (None, None) if no project is registered yet or
|
|
99
|
+
the server is unreachable - the MCP server must still start offline,
|
|
100
|
+
degrading to library defaults. The returned JWTClient is reused (rather
|
|
101
|
+
than re-minting) so later calls only re-mint once the lease nears expiry."""
|
|
102
|
+
from codepreproc_client.layer1_business.license.fingerprint import derive_fingerprint
|
|
103
|
+
from codepreproc_client.layer1_business.license.jwt_client import JWTClient
|
|
104
|
+
from codepreproc_client.layer1_business.manifest.credentials import load_manifest
|
|
105
|
+
|
|
106
|
+
if not local_projects:
|
|
107
|
+
return None, None
|
|
108
|
+
try:
|
|
109
|
+
any_project = next(iter(local_projects.values()))
|
|
110
|
+
manifest = load_manifest(get_credentials_path())
|
|
111
|
+
attestation = derive_fingerprint(any_project.project_id, any_project.root)
|
|
112
|
+
jwt_client = JWTClient(base_url=get_server_url(), manifest=manifest, fingerprint_attestation=attestation)
|
|
113
|
+
jwt = await jwt_client.get_bearer_token()
|
|
114
|
+
return jwt, jwt_client
|
|
115
|
+
except Exception:
|
|
116
|
+
return None, None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
async def _load_defaults(jwt: str | None):
|
|
120
|
+
from contracts.dtos import DefaultsConfig
|
|
121
|
+
|
|
122
|
+
if jwt is None:
|
|
123
|
+
return DefaultsConfig()
|
|
124
|
+
try:
|
|
125
|
+
from codepreproc_client.layer1_business.api_client.registry_client import get_defaults
|
|
126
|
+
|
|
127
|
+
return await get_defaults(base_url=get_server_url(), jwt=jwt)
|
|
128
|
+
except Exception:
|
|
129
|
+
return DefaultsConfig()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async def main() -> None:
|
|
133
|
+
settings = get_settings()
|
|
134
|
+
|
|
135
|
+
from contracts.dtos import Registry as _Registry
|
|
136
|
+
|
|
137
|
+
local_projects = load_local_projects(get_home_dir())
|
|
138
|
+
bootstrap_jwt, bootstrap_jwt_client = await _mint_bootstrap_jwt(local_projects)
|
|
139
|
+
defaults = await _load_defaults(bootstrap_jwt)
|
|
140
|
+
registry_data = apply_env_overrides(_Registry(defaults=defaults, projects=local_projects))
|
|
141
|
+
|
|
142
|
+
settings.defaults = registry_data.defaults
|
|
143
|
+
logger = configure_logging(settings.logs_dir, settings.debug)
|
|
144
|
+
lifecycle_logger = MCPConnectionLifecycleLogger(
|
|
145
|
+
logging.getLogger("codepreproc.mcp.lifecycle"),
|
|
146
|
+
settings.mcp_connection_verbosity,
|
|
147
|
+
)
|
|
148
|
+
await lifecycle_logger.emit(
|
|
149
|
+
"server_start",
|
|
150
|
+
fields={
|
|
151
|
+
"pid": os.getpid(),
|
|
152
|
+
"version_source": "package_metadata",
|
|
153
|
+
},
|
|
154
|
+
verbose_fields={
|
|
155
|
+
"log_path": str(settings.logs_dir / "mcp_server.log"),
|
|
156
|
+
"connection_verbosity": settings.mcp_connection_verbosity,
|
|
157
|
+
"debug_enabled": settings.debug,
|
|
158
|
+
},
|
|
159
|
+
mirror_to_client=False,
|
|
160
|
+
)
|
|
161
|
+
# Source-chunk vectors never leave the client machine (architecture doc S2/S3):
|
|
162
|
+
# this is a local embedded store, never a network endpoint, regardless of
|
|
163
|
+
# registry_data.defaults.qdrant_url/qdrant_grpc (those configure the
|
|
164
|
+
# server-side canonical gist store instead, see gist_embedder.py).
|
|
165
|
+
qdrant_store = SessionVectorStore(path=settings.indexes_dir / "session_vectors")
|
|
166
|
+
logger.info("session_vector_store_init path=%s", settings.indexes_dir / "session_vectors")
|
|
167
|
+
|
|
168
|
+
async def _reasoning_jwt_provider() -> str:
|
|
169
|
+
if bootstrap_jwt_client is None:
|
|
170
|
+
raise RuntimeError("no project registered yet - cannot mint a JWT for LLM reasoning calls")
|
|
171
|
+
return await bootstrap_jwt_client.get_bearer_token()
|
|
172
|
+
|
|
173
|
+
llm_runtime = LLMRuntime(get_server_url(), _reasoning_jwt_provider)
|
|
174
|
+
project_registry = ProjectRegistry(registry_data)
|
|
175
|
+
|
|
176
|
+
# Any /v1/superkg/* (or other project-scoped) call must present a JWT
|
|
177
|
+
# naming that specific project_id in claims["project_ids"] (see
|
|
178
|
+
# layer2_infra/api/authz.py on the server) - the single shared
|
|
179
|
+
# bootstrap_jwt above only names whichever project was first found, so
|
|
180
|
+
# it can't be reused here. Mint (and cache) one JWT per project instead.
|
|
181
|
+
_project_jwt_clients: dict[str, "JWTClient"] = {}
|
|
182
|
+
|
|
183
|
+
async def _project_jwt_provider(project_id: str) -> str:
|
|
184
|
+
from codepreproc_client.layer1_business.license.fingerprint import derive_fingerprint
|
|
185
|
+
from codepreproc_client.layer1_business.license.jwt_client import JWTClient
|
|
186
|
+
from codepreproc_client.layer1_business.manifest.credentials import load_manifest
|
|
187
|
+
|
|
188
|
+
client = _project_jwt_clients.get(project_id)
|
|
189
|
+
if client is None:
|
|
190
|
+
project = project_registry.registry.projects.get(project_id)
|
|
191
|
+
if project is None:
|
|
192
|
+
raise RuntimeError(f"unknown project_id={project_id!r} - cannot mint a JWT for it")
|
|
193
|
+
manifest = load_manifest(get_credentials_path())
|
|
194
|
+
attestation = derive_fingerprint(project_id, project.root)
|
|
195
|
+
client = JWTClient(base_url=get_server_url(), manifest=manifest, fingerprint_attestation=attestation)
|
|
196
|
+
_project_jwt_clients[project_id] = client
|
|
197
|
+
return await client.get_bearer_token()
|
|
198
|
+
|
|
199
|
+
superkg_enabled, superkg_enrich_timeout = get_superkg_settings()
|
|
200
|
+
superkg = SuperKGClient(
|
|
201
|
+
base_url=get_server_url(),
|
|
202
|
+
jwt_provider=_project_jwt_provider,
|
|
203
|
+
enabled=superkg_enabled,
|
|
204
|
+
enrich_timeout=superkg_enrich_timeout,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
from codepreproc_client.layer2_tooling.materialization.snippet.loader import SnippetLoader
|
|
208
|
+
|
|
209
|
+
snippet_loader = await SnippetLoader.from_api(
|
|
210
|
+
base_url=get_server_url(), jwt=bootstrap_jwt, base_dir=settings.home_dir
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
tools = CodepreprocTools(
|
|
214
|
+
settings,
|
|
215
|
+
project_registry,
|
|
216
|
+
qdrant_store,
|
|
217
|
+
llm_runtime,
|
|
218
|
+
logger,
|
|
219
|
+
superkg,
|
|
220
|
+
snippet_loader=snippet_loader,
|
|
221
|
+
jwt_provider=_project_jwt_provider,
|
|
222
|
+
)
|
|
223
|
+
sessions: dict[int, SessionState] = {}
|
|
224
|
+
# NOTE (Phase 4/5 follow-up): once lease_client.lease_world_model() is called
|
|
225
|
+
# against a real deployed server, each SessionState should own a
|
|
226
|
+
# WorldModelLeaseGuard (mcp/lifecycle.py) populated from that response, and
|
|
227
|
+
# tool handlers should read from guard.lease instead of hitting the API
|
|
228
|
+
# per-op. The guard's discard() must run when the session's last call
|
|
229
|
+
# completes - it's already wired for atexit/SIGINT/SIGTERM/SIGBREAK.
|
|
230
|
+
|
|
231
|
+
server = InstrumentedServer("codepreproc", lifecycle_logger=lifecycle_logger)
|
|
232
|
+
|
|
233
|
+
@server.list_tools()
|
|
234
|
+
async def list_tools() -> list[types.Tool]:
|
|
235
|
+
return [
|
|
236
|
+
types.Tool(name="list_projects", description="List configured projects", inputSchema=_tool_schema({})),
|
|
237
|
+
types.Tool(name="switch_project", description="Set the active project for this session", inputSchema=_tool_schema({"project": {"type": "string"}}, ["project"])),
|
|
238
|
+
types.Tool(
|
|
239
|
+
name="analyze_request",
|
|
240
|
+
description="Analyze a coding request and return a semantic execution pack",
|
|
241
|
+
inputSchema=_tool_schema({"prompt": {"type": "string"}, "project": {"type": ["string", "null"]}}, ["prompt"]),
|
|
242
|
+
),
|
|
243
|
+
types.Tool(
|
|
244
|
+
name="analyze_filesystem_reorg",
|
|
245
|
+
description="Analyze a filesystem move or rename request and cache a validated reorganization plan",
|
|
246
|
+
inputSchema=_tool_schema({"prompt": {"type": "string"}, "project": {"type": ["string", "null"]}}, ["prompt"]),
|
|
247
|
+
),
|
|
248
|
+
types.Tool(name="preview_semantic_plan", description="Fetch the semantic plan for a task", inputSchema=_tool_schema({"task_id": {"type": "string"}}, ["task_id"])),
|
|
249
|
+
types.Tool(name="preview_filesystem_plan", description="Fetch the cached filesystem reorganization plan for a task", inputSchema=_tool_schema({"task_id": {"type": "string"}}, ["task_id"])),
|
|
250
|
+
types.Tool(name="preview_patch", description="Fetch materialized patches for a task", inputSchema=_tool_schema({"task_id": {"type": "string"}}, ["task_id"])),
|
|
251
|
+
types.Tool(name="validate_patch", description="Fetch the structural validation outcome for a task", inputSchema=_tool_schema({"task_id": {"type": "string"}}, ["task_id"])),
|
|
252
|
+
types.Tool(
|
|
253
|
+
name="disambiguate_region",
|
|
254
|
+
description="Continue an ambiguous task from a chosen region",
|
|
255
|
+
inputSchema=_tool_schema({"task_id": {"type": "string"}, "chosen_region_id": {"type": "string"}}, ["task_id", "chosen_region_id"]),
|
|
256
|
+
),
|
|
257
|
+
types.Tool(
|
|
258
|
+
name="apply_patch",
|
|
259
|
+
description="Apply one or all materialized patches for a task",
|
|
260
|
+
inputSchema=_tool_schema({"task_id": {"type": "string"}, "patch_index": {"type": ["integer", "null"]}}, ["task_id"]),
|
|
261
|
+
),
|
|
262
|
+
types.Tool(
|
|
263
|
+
name="apply_filesystem_plan",
|
|
264
|
+
description="Apply a cached filesystem move or rename plan",
|
|
265
|
+
inputSchema=_tool_schema({"task_id": {"type": "string"}}, ["task_id"]),
|
|
266
|
+
),
|
|
267
|
+
types.Tool(
|
|
268
|
+
name="usage_report",
|
|
269
|
+
description="Return accumulated LLM usage and cost",
|
|
270
|
+
inputSchema=_tool_schema({"project": {"type": ["string", "null"]}, "since_days": {"type": "integer"}}),
|
|
271
|
+
),
|
|
272
|
+
types.Tool(
|
|
273
|
+
name="set_llm_policy",
|
|
274
|
+
description="Override a project's LLM policy for the current session",
|
|
275
|
+
inputSchema=_tool_schema({"project": {"type": "string"}, "policy": {"type": "string"}}, ["project", "policy"]),
|
|
276
|
+
),
|
|
277
|
+
types.Tool(name="reindex", description="Run full or incremental indexing for a project", inputSchema=_tool_schema({"project": {"type": ["string", "null"]}, "full": {"type": "boolean"}})),
|
|
278
|
+
types.Tool(name="project_status", description="Return git and index status for a project", inputSchema=_tool_schema({"project": {"type": ["string", "null"]}})),
|
|
279
|
+
types.Tool(
|
|
280
|
+
name="search_context",
|
|
281
|
+
description=(
|
|
282
|
+
"Semantic search over an indexed project. Returns the most relevant code chunks "
|
|
283
|
+
"(file, symbol, signature, body) ranked by embedding + BM25 + cross-encoder reranker. "
|
|
284
|
+
"Use this instead of reading files directly to get compacted, relevant context without wasting tokens."
|
|
285
|
+
),
|
|
286
|
+
inputSchema=_tool_schema(
|
|
287
|
+
{
|
|
288
|
+
"query": {"type": "string"},
|
|
289
|
+
"project": {"type": ["string", "null"]},
|
|
290
|
+
"top_k": {"type": "integer", "default": 10},
|
|
291
|
+
},
|
|
292
|
+
["query"],
|
|
293
|
+
),
|
|
294
|
+
),
|
|
295
|
+
types.Tool(
|
|
296
|
+
name="map_context",
|
|
297
|
+
description=(
|
|
298
|
+
"Semantic map of codebase components relevant to a query. "
|
|
299
|
+
"Returns ranked code chunks with their caller/callee relationships from the AST graph, "
|
|
300
|
+
"a per-file symbol index, a dependency graph, and an optional LLM-synthesized narrative "
|
|
301
|
+
"(local model only, zero remote cost) explaining how the components work together. "
|
|
302
|
+
"Use this BEFORE analyze_request or before reading files directly — it gives Claude the "
|
|
303
|
+
"architectural context needed to plan edits without re-reading the entire codebase."
|
|
304
|
+
),
|
|
305
|
+
inputSchema=_tool_schema(
|
|
306
|
+
{
|
|
307
|
+
"query": {"type": "string"},
|
|
308
|
+
"project": {"type": ["string", "null"]},
|
|
309
|
+
"top_k": {"type": "integer", "default": 10},
|
|
310
|
+
"include_bodies": {"type": "boolean", "default": True},
|
|
311
|
+
"synthesize": {"type": "boolean", "default": True},
|
|
312
|
+
},
|
|
313
|
+
["query"],
|
|
314
|
+
),
|
|
315
|
+
),
|
|
316
|
+
types.Tool(
|
|
317
|
+
name="generate_document",
|
|
318
|
+
description=(
|
|
319
|
+
"Generate a file (e.g. Markdown, text) by retrieving relevant code context via semantic search "
|
|
320
|
+
"and synthesizing it with an LLM. Use for tasks like 'create CORE_PIPELINE.md documenting the "
|
|
321
|
+
"architecture' where analyze_request would fail because there is no existing code region to target. "
|
|
322
|
+
"Writes the generated content to output_path (relative to the project root)."
|
|
323
|
+
),
|
|
324
|
+
inputSchema=_tool_schema(
|
|
325
|
+
{
|
|
326
|
+
"prompt": {"type": "string"},
|
|
327
|
+
"output_path": {"type": "string"},
|
|
328
|
+
"project": {"type": ["string", "null"]},
|
|
329
|
+
"top_k": {"type": "integer", "default": 15},
|
|
330
|
+
},
|
|
331
|
+
["prompt", "output_path"],
|
|
332
|
+
),
|
|
333
|
+
),
|
|
334
|
+
types.Tool(
|
|
335
|
+
name="generate_business_logic",
|
|
336
|
+
description=(
|
|
337
|
+
"Extract and document the current business logic of the active project from its repo state. "
|
|
338
|
+
"Runs multiple semantic searches across domain models, rules, workflows, and entry points, "
|
|
339
|
+
"then synthesizes a BUSINESS_LOGIC.md (or custom output_path) describing what the system does "
|
|
340
|
+
"from a business perspective — entities, invariants, workflows, and external integrations. "
|
|
341
|
+
"Overwrites the file if it already exists. Use this to get an up-to-date picture of the "
|
|
342
|
+
"business domain whenever the codebase changes significantly."
|
|
343
|
+
),
|
|
344
|
+
inputSchema=_tool_schema(
|
|
345
|
+
{
|
|
346
|
+
"project": {"type": ["string", "null"]},
|
|
347
|
+
"output_path": {"type": "string", "default": "BUSINESS_LOGIC.md"},
|
|
348
|
+
"top_k": {"type": "integer", "default": 20},
|
|
349
|
+
},
|
|
350
|
+
),
|
|
351
|
+
),
|
|
352
|
+
types.Tool(
|
|
353
|
+
name="list_snippets",
|
|
354
|
+
description=(
|
|
355
|
+
"List available snippets in the snippet library. "
|
|
356
|
+
"Filter by framework (nest, fastapi, flutter, cmake, laravel, axum, aspnetcore, spring), "
|
|
357
|
+
"language (typescript, python, dart, c, php, rust, csharp, java) "
|
|
358
|
+
"or layer (domain, application, infrastructure, interface)."
|
|
359
|
+
),
|
|
360
|
+
inputSchema=_tool_schema(
|
|
361
|
+
{
|
|
362
|
+
"framework": {"type": ["string", "null"]},
|
|
363
|
+
"language": {"type": ["string", "null"]},
|
|
364
|
+
"layer": {"type": ["string", "null"]},
|
|
365
|
+
}
|
|
366
|
+
),
|
|
367
|
+
),
|
|
368
|
+
types.Tool(
|
|
369
|
+
name="init_project",
|
|
370
|
+
description=(
|
|
371
|
+
"Scan a directory and register it as a new project in the codepreproc registry. "
|
|
372
|
+
"Uses the session root (i.e. the directory Claude Code is open in) when no path is given. "
|
|
373
|
+
"Does nothing if the project is already registered. "
|
|
374
|
+
"After success, call reindex to build the index."
|
|
375
|
+
),
|
|
376
|
+
inputSchema=_tool_schema({"path": {"type": ["string", "null"]}}),
|
|
377
|
+
),
|
|
378
|
+
types.Tool(
|
|
379
|
+
name="assemble_from_snippets",
|
|
380
|
+
description=(
|
|
381
|
+
"Two-phase DDD code generation from the snippet library. "
|
|
382
|
+
"Phase 1 (low-cost model): resolves intent, selects snippets, binds variables, defines scope. "
|
|
383
|
+
"Phase 2 (high-capability model): integrates instantiated snippets into target files. "
|
|
384
|
+
"Produces files_created and files_modified respecting DDD layer boundaries. "
|
|
385
|
+
"Use for creating new CRUD modules from scratch, not for modifying existing code regions."
|
|
386
|
+
),
|
|
387
|
+
inputSchema=_tool_schema(
|
|
388
|
+
{
|
|
389
|
+
"prompt": {"type": "string"},
|
|
390
|
+
"project": {"type": ["string", "null"]},
|
|
391
|
+
},
|
|
392
|
+
["prompt"],
|
|
393
|
+
),
|
|
394
|
+
),
|
|
395
|
+
types.Tool(
|
|
396
|
+
name="generate_repo_map",
|
|
397
|
+
description=(
|
|
398
|
+
"Generate a full hierarchical map of the repository. "
|
|
399
|
+
"Builds a structured JSON with artifacts, packages, modules, components, "
|
|
400
|
+
"interfaces, implementations, methods, properties, and relationships. "
|
|
401
|
+
"For monorepos, auto-detects apps/ (artifacts) and packages/ (reusable modules); "
|
|
402
|
+
"each direct subfolder of those is treated as a unit, and its src/ directory "
|
|
403
|
+
"is the module root. If the layout is not detected, returns needs_clarification=true "
|
|
404
|
+
"with questions to answer via artifacts_folder and modules_folder. "
|
|
405
|
+
"Excludes .git from the scan. "
|
|
406
|
+
"Stores the result in the knowledge graph and writes repo_map.json to the project root."
|
|
407
|
+
),
|
|
408
|
+
inputSchema=_tool_schema({
|
|
409
|
+
"project": {"type": ["string", "null"]},
|
|
410
|
+
"artifacts_folder": {"type": ["string", "null"], "description": "Top-level folder that contains deployable apps/artifacts (e.g. 'apps')."},
|
|
411
|
+
"modules_folder": {"type": ["string", "null"], "description": "Top-level folder that contains reusable shared modules (e.g. 'packages')."},
|
|
412
|
+
}),
|
|
413
|
+
),
|
|
414
|
+
types.Tool(
|
|
415
|
+
name="kg_context",
|
|
416
|
+
description=(
|
|
417
|
+
"Retrieve SuperKG context for a project in a specific reasoning mode. "
|
|
418
|
+
"Modes: narrative (story/explanation), reflexive (self-analysis), "
|
|
419
|
+
"comparative (cross-system), consolidation (aggregation), simulation (scenario projection). "
|
|
420
|
+
"Use this to get persistent knowledge and insights about the project from SuperKG."
|
|
421
|
+
),
|
|
422
|
+
inputSchema=_tool_schema(
|
|
423
|
+
{
|
|
424
|
+
"project": {"type": ["string", "null"]},
|
|
425
|
+
"mode": {
|
|
426
|
+
"type": "string",
|
|
427
|
+
"enum": ["narrative", "reflexive", "comparative", "consolidation", "simulation"],
|
|
428
|
+
"default": "narrative",
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
),
|
|
432
|
+
),
|
|
433
|
+
types.Tool(
|
|
434
|
+
name="kg_insights",
|
|
435
|
+
description="List insights stored in SuperKG for a project.",
|
|
436
|
+
inputSchema=_tool_schema({"project": {"type": ["string", "null"]}}),
|
|
437
|
+
),
|
|
438
|
+
types.Tool(
|
|
439
|
+
name="kg_patterns",
|
|
440
|
+
description="List observed patterns across all projects in SuperKG.",
|
|
441
|
+
inputSchema=_tool_schema({}),
|
|
442
|
+
),
|
|
443
|
+
types.Tool(
|
|
444
|
+
name="project_pseudocode",
|
|
445
|
+
description=(
|
|
446
|
+
"Return the entire indexed project as typed pseudo-code: every file with its symbols "
|
|
447
|
+
"(classes, functions, methods) and their type signatures. "
|
|
448
|
+
"Signature-only mode (default) reads only the SQLite AST graph — zero LLM cost, sub-second. "
|
|
449
|
+
"Set include_bodies=true to also fetch full source bodies from Qdrant, ordered by line. "
|
|
450
|
+
"Use file_pattern (e.g. 'src/**/*.py') to scope to a subtree. "
|
|
451
|
+
"Gives Claude a project-wide typed interface view without reading individual files."
|
|
452
|
+
),
|
|
453
|
+
inputSchema=_tool_schema(
|
|
454
|
+
{
|
|
455
|
+
"project": {"type": ["string", "null"]},
|
|
456
|
+
"file_pattern": {"type": ["string", "null"], "description": "Glob to filter files, e.g. 'src/**/*.py'"},
|
|
457
|
+
"include_bodies": {"type": "boolean", "default": False},
|
|
458
|
+
"exclude_tests": {"type": "boolean", "default": True},
|
|
459
|
+
"max_files": {"type": "integer", "default": 200},
|
|
460
|
+
}
|
|
461
|
+
),
|
|
462
|
+
),
|
|
463
|
+
types.Tool(
|
|
464
|
+
name="correct_graph",
|
|
465
|
+
description=(
|
|
466
|
+
"Submit a human-authored Business Logic Diff to correct the SuperKG knowledge graph. "
|
|
467
|
+
"Use when the graph state diverges from truth — e.g. after a manual change outside "
|
|
468
|
+
"codepreproc. Express the correction as before/after rule lists, not direct node/edge "
|
|
469
|
+
"edits, so it is auditable through the same writer as the automatic patch path."
|
|
470
|
+
),
|
|
471
|
+
inputSchema=_tool_schema(
|
|
472
|
+
{
|
|
473
|
+
"change": {"type": "string", "description": "Short identifier for this correction (e.g. 'remove-email-uniqueness-check')"},
|
|
474
|
+
"before": {"type": "array", "items": {"type": "string"}, "description": "Current rules/capabilities as they exist in the graph"},
|
|
475
|
+
"after": {"type": "array", "items": {"type": "string"}, "description": "Target rules/capabilities after this correction"},
|
|
476
|
+
"rules_added": {"type": "array", "items": {"type": "string"}},
|
|
477
|
+
"rules_removed": {"type": "array", "items": {"type": "string"}},
|
|
478
|
+
"rules_modified": {"type": "array", "items": {"type": "string"}},
|
|
479
|
+
"project": {"type": ["string", "null"]},
|
|
480
|
+
},
|
|
481
|
+
["change"],
|
|
482
|
+
),
|
|
483
|
+
),
|
|
484
|
+
types.Tool(
|
|
485
|
+
name="coupling_project",
|
|
486
|
+
description=(
|
|
487
|
+
"Project the cross-repo coupling structure for a set of registered repositories in ONE call. "
|
|
488
|
+
"Returns a compact summary with: symbol table (project ids), edge list (src→dst with counts), "
|
|
489
|
+
"Ca/Ce/instability metrics per pair, and non-trivial SCCs (dependency cycles between repos). "
|
|
490
|
+
"The full edge set is stored in-memory behind a handle; use coupling_expand to retrieve detail. "
|
|
491
|
+
"Prefer anchors (concrete project_id list) over intent (fuzzy text → resolved to project set). "
|
|
492
|
+
"Operators: anchors=[id,...] for structural query; intent='...' for fuzzy; "
|
|
493
|
+
"coupling_expand(handle_id, edge_id) for per-edge detail."
|
|
494
|
+
),
|
|
495
|
+
inputSchema=_tool_schema(
|
|
496
|
+
{
|
|
497
|
+
"anchors": {
|
|
498
|
+
"type": "array",
|
|
499
|
+
"items": {"type": "string"},
|
|
500
|
+
"description": "List of registered project_ids to include in the coupling projection.",
|
|
501
|
+
},
|
|
502
|
+
"intent": {
|
|
503
|
+
"type": ["string", "null"],
|
|
504
|
+
"description": "Fuzzy description resolved to a project set when anchors is empty.",
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
),
|
|
508
|
+
),
|
|
509
|
+
types.Tool(
|
|
510
|
+
name="coupling_expand",
|
|
511
|
+
description=(
|
|
512
|
+
"Expand a coupling handle returned by coupling_project to retrieve edge detail on demand. "
|
|
513
|
+
"With edge_id: returns full ResolutionEdge detail (src/dst symbols, file paths, epoch). "
|
|
514
|
+
"Without edge_id: returns a paginated list of edges for the handle."
|
|
515
|
+
),
|
|
516
|
+
inputSchema=_tool_schema(
|
|
517
|
+
{
|
|
518
|
+
"handle_id": {"type": "string", "description": "Handle returned by coupling_project."},
|
|
519
|
+
"edge_id": {"type": ["string", "null"], "description": "UUID of a specific edge to expand."},
|
|
520
|
+
"offset": {"type": "integer", "default": 0},
|
|
521
|
+
"limit": {"type": "integer", "default": 50},
|
|
522
|
+
},
|
|
523
|
+
["handle_id"],
|
|
524
|
+
),
|
|
525
|
+
),
|
|
526
|
+
]
|
|
527
|
+
|
|
528
|
+
@server.call_tool()
|
|
529
|
+
async def call_tool(name: str, arguments: dict | None) -> types.CallToolResult:
|
|
530
|
+
session = server.request_context.session
|
|
531
|
+
request_context = server.request_context
|
|
532
|
+
state = sessions.setdefault(id(session), SessionState())
|
|
533
|
+
root_uris = await resolve_session_roots(
|
|
534
|
+
session=session,
|
|
535
|
+
lifecycle_logger=lifecycle_logger,
|
|
536
|
+
request_id=request_context.request_id,
|
|
537
|
+
)
|
|
538
|
+
if root_uris:
|
|
539
|
+
state.roots = [_path_from_file_uri(uri) for uri in root_uris]
|
|
540
|
+
else:
|
|
541
|
+
state.roots = state.roots or []
|
|
542
|
+
|
|
543
|
+
dispatch = {
|
|
544
|
+
"list_projects": lambda monitor: tools.list_projects(state),
|
|
545
|
+
"switch_project": lambda monitor: tools.switch_project(state, arguments or {}),
|
|
546
|
+
"analyze_request": lambda monitor: tools.analyze_request(state, arguments or {}, monitor=monitor),
|
|
547
|
+
"analyze_filesystem_reorg": lambda monitor: tools.analyze_filesystem_reorg(state, arguments or {}, monitor=monitor),
|
|
548
|
+
"preview_semantic_plan": lambda monitor: tools.preview_semantic_plan(state, arguments or {}),
|
|
549
|
+
"preview_filesystem_plan": lambda monitor: tools.preview_filesystem_plan(state, arguments or {}),
|
|
550
|
+
"preview_patch": lambda monitor: tools.preview_patch(state, arguments or {}),
|
|
551
|
+
"validate_patch": lambda monitor: tools.validate_patch(state, arguments or {}),
|
|
552
|
+
"disambiguate_region": lambda monitor: tools.disambiguate_region(state, arguments or {}),
|
|
553
|
+
"apply_patch": lambda monitor: tools.apply_patch(state, arguments or {}),
|
|
554
|
+
"apply_filesystem_plan": lambda monitor: tools.apply_filesystem_plan(state, arguments or {}, monitor=monitor),
|
|
555
|
+
"usage_report": lambda monitor: tools.usage_report(state, arguments or {}),
|
|
556
|
+
"set_llm_policy": lambda monitor: tools.set_llm_policy(state, arguments or {}),
|
|
557
|
+
"reindex": lambda monitor: tools.reindex(state, arguments or {}, monitor=monitor),
|
|
558
|
+
"project_status": lambda monitor: tools.project_status(state, arguments or {}),
|
|
559
|
+
"search_context": lambda monitor: tools.search_context(state, arguments or {}, monitor=monitor),
|
|
560
|
+
"map_context": lambda monitor: tools.map_context(state, arguments or {}, monitor=monitor),
|
|
561
|
+
"generate_document": lambda monitor: tools.generate_document(state, arguments or {}, monitor=monitor),
|
|
562
|
+
"generate_business_logic": lambda monitor: tools.generate_business_logic(state, arguments or {}, monitor=monitor),
|
|
563
|
+
"list_snippets": lambda monitor: tools.list_snippets(state, arguments or {}),
|
|
564
|
+
"assemble_from_snippets": lambda monitor: tools.assemble_from_snippets(state, arguments or {}, monitor=monitor),
|
|
565
|
+
"init_project": lambda monitor: tools.init_project(state, arguments or {}),
|
|
566
|
+
"generate_repo_map": lambda monitor: tools.generate_repo_map(state, arguments or {}, monitor=monitor),
|
|
567
|
+
"kg_context": lambda monitor: tools.kg_context(state, arguments or {}),
|
|
568
|
+
"kg_insights": lambda monitor: tools.kg_insights(state, arguments or {}),
|
|
569
|
+
"kg_patterns": lambda monitor: tools.kg_patterns(state, arguments or {}),
|
|
570
|
+
"correct_graph": lambda monitor: tools.correct_graph(state, arguments or {}),
|
|
571
|
+
"project_pseudocode": lambda monitor: tools.project_pseudocode(state, arguments or {}),
|
|
572
|
+
"coupling_project": lambda monitor: tools.coupling_project(state, arguments or {}, monitor=monitor),
|
|
573
|
+
"coupling_expand": lambda monitor: tools.coupling_expand(state, arguments or {}),
|
|
574
|
+
}
|
|
575
|
+
if name not in dispatch:
|
|
576
|
+
raise ValueError(f"unknown_tool: {name}")
|
|
577
|
+
monitor = ActionMonitor(
|
|
578
|
+
session=session,
|
|
579
|
+
logger=logger,
|
|
580
|
+
tool=name,
|
|
581
|
+
request_id=request_context.request_id,
|
|
582
|
+
progress_token=request_context.meta.progressToken if request_context.meta else None,
|
|
583
|
+
timeout_seconds=settings.defaults.mcp_action_timeout_seconds,
|
|
584
|
+
heartbeat_seconds=settings.defaults.mcp_action_heartbeat_seconds,
|
|
585
|
+
project=(arguments or {}).get("project"),
|
|
586
|
+
)
|
|
587
|
+
try:
|
|
588
|
+
await monitor.start(f"{name}: iniciando")
|
|
589
|
+
result = await dispatch[name](monitor)
|
|
590
|
+
await monitor.complete(f"{name}: completado")
|
|
591
|
+
return types.CallToolResult(
|
|
592
|
+
content=[types.TextContent(type="text", text=json.dumps(result, ensure_ascii=False))],
|
|
593
|
+
structuredContent=result,
|
|
594
|
+
isError=False,
|
|
595
|
+
)
|
|
596
|
+
except asyncio.CancelledError as exc:
|
|
597
|
+
timeout_error = monitor.timeout_error()
|
|
598
|
+
if timeout_error is None:
|
|
599
|
+
raise
|
|
600
|
+
payload = timeout_error.to_payload(tool=name, stage=timeout_error.details.get("stage"))
|
|
601
|
+
await monitor.fail(payload["message"], stage=payload["details"].get("stage"), details=payload["details"])
|
|
602
|
+
return types.CallToolResult(
|
|
603
|
+
content=[types.TextContent(type="text", text=json.dumps(payload, ensure_ascii=False))],
|
|
604
|
+
structuredContent=payload,
|
|
605
|
+
isError=True,
|
|
606
|
+
)
|
|
607
|
+
except ToolExecutionError as exc:
|
|
608
|
+
payload = exc.to_payload(tool=name, stage=exc.details.get("stage"))
|
|
609
|
+
await monitor.fail(payload["message"], stage=payload["details"].get("stage"), details=payload["details"])
|
|
610
|
+
return types.CallToolResult(
|
|
611
|
+
content=[types.TextContent(type="text", text=json.dumps(payload, ensure_ascii=False))],
|
|
612
|
+
structuredContent=payload,
|
|
613
|
+
isError=True,
|
|
614
|
+
)
|
|
615
|
+
except Exception as exc:
|
|
616
|
+
logger.exception("Tool error in %s", name)
|
|
617
|
+
error_payload = {
|
|
618
|
+
"error": "tool_failed",
|
|
619
|
+
"message": str(exc)[:500],
|
|
620
|
+
"details": {
|
|
621
|
+
"tool": name,
|
|
622
|
+
"stage": monitor.stage_name,
|
|
623
|
+
"idle_seconds": int(max(0, time.monotonic() - monitor.last_relevant_change_at)),
|
|
624
|
+
"last_progress": monitor.last_progress,
|
|
625
|
+
},
|
|
626
|
+
}
|
|
627
|
+
await monitor.fail(error_payload["message"], stage=monitor.stage_name, details=error_payload["details"])
|
|
628
|
+
return types.CallToolResult(
|
|
629
|
+
content=[types.TextContent(type="text", text=json.dumps(error_payload, ensure_ascii=False))],
|
|
630
|
+
structuredContent=error_payload,
|
|
631
|
+
isError=True,
|
|
632
|
+
)
|
|
633
|
+
finally:
|
|
634
|
+
await monitor.close()
|
|
635
|
+
|
|
636
|
+
version = importlib.metadata.version("codepreproc")
|
|
637
|
+
try:
|
|
638
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
639
|
+
await server.run(
|
|
640
|
+
read_stream,
|
|
641
|
+
write_stream,
|
|
642
|
+
InitializationOptions(
|
|
643
|
+
server_name="codepreproc",
|
|
644
|
+
server_version=version,
|
|
645
|
+
capabilities=ServerCapabilities(tools=ToolsCapability(listChanged=False)),
|
|
646
|
+
),
|
|
647
|
+
)
|
|
648
|
+
finally:
|
|
649
|
+
await llm_runtime.close()
|
|
650
|
+
await _db.close_pool()
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def run() -> None:
|
|
654
|
+
"""Console script entrypoint."""
|
|
655
|
+
|
|
656
|
+
anyio.run(main)
|