-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmain.py
More file actions
110 lines (89 loc) · 3.27 KB
/
Copy pathmain.py
File metadata and controls
110 lines (89 loc) · 3.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
from __future__ import annotations
import logging
from contextlib import asynccontextmanager
from typing import AsyncIterator
import uvicorn
from mcp.server.fastmcp import FastMCP
from starlette.applications import Starlette
from server.config import settings
from server.embeddings.bm25 import BM25SparseProvider, close_sparse_embedding_provider
from server.embeddings.jina import close_embedding_provider
from server.state import (
get_commit_store,
get_store,
set_commit_store,
set_sparse_provider,
set_store,
)
from server.store.commit_store import CommitStore
from server.store.qdrant import QdrantStore
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(_: FastMCP) -> AsyncIterator[None]:
logger.info("Starting semcode MCP server...")
store = QdrantStore()
await store.ensure_collection()
set_store(store)
commit_store = CommitStore()
await commit_store.ensure_collection()
set_commit_store(commit_store)
sparse_provider = BM25SparseProvider()
set_sparse_provider(sparse_provider)
logger.info(
"Qdrant collections ready. Use `reindex` / `index_history` MCP tools to index services."
)
yield
try:
await get_store().close()
await get_commit_store().close()
except RuntimeError:
pass
await close_embedding_provider()
await close_sparse_embedding_provider()
logger.info("semcode MCP server stopped.")
# For streamable-http we drive the Starlette app's lifespan ourselves (see main),
# so the per-MCP-session lifespan would re-init the store on every client connect.
mcp = FastMCP(
"semcode",
instructions="Semantic code search across microservices codebases. Supports Go, Java, Python, and TypeScript/React.",
lifespan=lifespan if settings.mcp_transport != "streamable-http" else None,
host=settings.mcp_host,
port=settings.mcp_port,
)
def _wrap_http_lifespan(app: Starlette) -> None:
original = app.router.lifespan_context
@asynccontextmanager
async def combined(scope_app: Starlette) -> AsyncIterator[None]:
async with lifespan(mcp):
async with original(scope_app):
yield
app.router.lifespan_context = combined
def main() -> None:
from server.tools.search import register_search_tools
from server.tools.index import register_index_tools
from server.tools.admin import register_admin_tools
from server.tools.history import register_history_tools
from server.prompts.service import register_service_prompts
from server.prompts.system import register_system_prompts
from server.routes.reindex import register_http_routes
register_search_tools(mcp)
register_index_tools(mcp)
register_admin_tools(mcp)
register_history_tools(mcp)
register_service_prompts(mcp)
register_system_prompts(mcp)
register_http_routes(mcp)
if settings.mcp_transport == "streamable-http":
app = mcp.streamable_http_app()
_wrap_http_lifespan(app)
uvicorn.run(
app,
host=settings.mcp_host,
port=settings.mcp_port,
log_level=mcp.settings.log_level.lower(),
)
else:
mcp.run(transport=settings.mcp_transport)
if __name__ == "__main__":
main()