Semblance / mcp_server /server.py
yueyvettehao's picture
Upload full app (app.py + core/agents/mcp_server/ui/assets/examples/...)
fb2ae51 verified
Raw
History Blame Contribute Delete
2.75 kB
"""FastMCP server exposing the deterministic engine over the MCP protocol.
The agent layer (M5+) talks to these tools as a *client* even though it could import the
engine directly — the client/server boundary is the showcase (golden rule #3). The engine is
built once at import (warm); BioLORD is not loaded unless an unknown pathway appears.
Run standalone (streamable-http on 127.0.0.1):
python -m mcp_server.server
"""
from __future__ import annotations
from fastmcp import FastMCP
import config
from mcp_server.engine import Engine
from mcp_server.search import PathwaySearch
_engine = Engine() # warm: loads assets once, reused across calls
_search = PathwaySearch(embed_query=_engine.embed_query) # optional; disabled without PINECONE_API_KEY
def build_server() -> FastMCP:
"""Construct the FastMCP server. Used by app.py, the demo, and tests."""
mcp = FastMCP(
name="semblance-engine",
instructions="Deterministic GSEA signature comparison. Tools never call an LLM.",
)
@mcp.tool()
def compare_signatures(results: list[dict], params: dict | None = None) -> dict:
"""Compare N canonical GSEA results under cutoff `params`.
Returns a ComparisonResult: 2N signatures, cosine similarity matrix,
dendrogram order + linkage + flat clusters, the NES-correlation baseline,
per-pair drill-downs, and warnings.
"""
return _engine.compare(results, params)
@mcp.tool()
def describe_pathways(names: list[str]) -> dict:
"""Map pathway names to short descriptions (for the interpreter agent)."""
return _engine.describe(names)
@mcp.tool()
def search_pathways(query: str, k: int = 10) -> dict:
"""Semantic free-text search over the MSigDB corpus (optional Pinecone module).
Returns nearest pathways across collections, or `enabled: False` if the search module
is not configured. Never used by the core comparison.
"""
return _search.search(query, k)
@mcp.tool()
def match_pathway(name: str, k: int = 10) -> dict:
"""Map a pathway/term name to its nearest known pathways across collections (cross-ontology).
Embeds the name's concept and returns the closest corpus entries (self excluded), or
`enabled: False` if the search module is not configured.
"""
return _search.match(name, k)
@mcp.tool()
def health() -> dict:
"""Liveness + asset status (used to poll readiness after subprocess launch)."""
return {**_engine.health(), "search_enabled": _search.enabled}
return mcp
mcp = build_server()
if __name__ == "__main__":
mcp.run(transport="streamable-http", host=config.MCP_HOST, port=config.MCP_PORT)