From 1d01446423c3e01e61b2b3c03abf2481839b2160 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 24 Jul 2026 16:41:32 +0000 Subject: [PATCH] Add HTTP/SSE transport for Smithery; fix NWB Container parse error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mcp_server.py: MCP_TRANSPORT=sse env var switches to HTTP+SSE (FastMCP SSE mode on 0.0.0.0:$PORT), enabling deployment to Railway/Render for Smithery and other hosted MCP platforms. Default remains stdio for Claude Desktop/Cursor local use. ingest_linkml.py: Handle schemas where is_a references a class defined outside the submitted file (e.g. NWB's NWBContainer → Container, which is a PyNWB runtime base class not in nwb.yml). class_induced_slots() now falls back to directly-declared slots on ValueError/KeyError, and is_a is silently cleared when the parent cannot be resolved within the schema view — build_registry_entities already handles None is_a. Procfile + railway.toml: Railway deployment config so the MCP server can be hosted with an HTTPS URL for Smithery registration. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01EXeTeJsXisxTRXay6EoBSQ --- Procfile | 1 + neuro_ghost/ingest_linkml.py | 28 +++++++++++++++++++++++++--- neuro_ghost/mcp_server.py | 14 ++++++++++++-- railway.toml | 6 ++++++ 4 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 Procfile create mode 100644 railway.toml diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..7e0adfd --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: MCP_TRANSPORT=sse PORT=$PORT python -m neuro_ghost.mcp_server diff --git a/neuro_ghost/ingest_linkml.py b/neuro_ghost/ingest_linkml.py index a306643..fab86d7 100644 --- a/neuro_ghost/ingest_linkml.py +++ b/neuro_ghost/ingest_linkml.py @@ -248,16 +248,38 @@ def parse_linkml(path: Path) -> dict[str, Any]: slots: dict[str, dict] = {} for cls_name in sv.all_classes(): - cls_def = sv.get_class(cls_name) - induced_slots = sv.class_induced_slots(cls_name) + cls_def = sv.get_class(cls_name) + + try: + induced_slots = sv.class_induced_slots(cls_name) + except (ValueError, KeyError): + # is_a points to a class outside this schema (e.g. NWB's "Container") + # Fall back to directly-declared slots only. + induced_slots = list(cls_def.attributes.values()) + for sname in (cls_def.slots or []): + try: + s = sv.get_slot(sname) + if s: + induced_slots.append(s) + except Exception: + pass class_uri = cls_def.class_uri or "" resolved_iri = resolve_prefix(class_uri, prefixes) if class_uri else "" + # Strip is_a if the parent class isn't in this schema — build_registry_entities + # already handles None gracefully; leaving a dangling name would be misleading. + is_a = cls_def.is_a + if is_a: + try: + sv.get_class(is_a) + except (ValueError, KeyError): + is_a = None + classes[cls_name] = { "iri": resolved_iri, "definition": cls_def.description or "", - "is_a": cls_def.is_a, + "is_a": is_a, "is_abstract": bool(cls_def.abstract), "slots": [slot.name for slot in induced_slots], } diff --git a/neuro_ghost/mcp_server.py b/neuro_ghost/mcp_server.py index ba598b4..6a8e16e 100644 --- a/neuro_ghost/mcp_server.py +++ b/neuro_ghost/mcp_server.py @@ -428,8 +428,18 @@ def ingest_schema( # --------------------------------------------------------------------------- def main() -> None: - """Entry point for the `neuroghost-mcp` console script.""" - mcp.run() + """Entry point for the `neuroghost-mcp` console script. + + Transport is selected by the MCP_TRANSPORT env var: + stdio (default) — for Claude Desktop / Cursor / local use + sse — HTTP+SSE for Smithery and other hosted platforms + """ + transport = os.environ.get("MCP_TRANSPORT", "stdio") + if transport == "sse": + port = int(os.environ.get("PORT", "8000")) + mcp.run(transport="sse", host="0.0.0.0", port=port) + else: + mcp.run() if __name__ == "__main__": diff --git a/railway.toml b/railway.toml new file mode 100644 index 0000000..b2d9e49 --- /dev/null +++ b/railway.toml @@ -0,0 +1,6 @@ +[build] +builder = "nixpacks" + +[deploy] +startCommand = "MCP_TRANSPORT=sse python -m neuro_ghost.mcp_server" +restartPolicyType = "on_failure"