ndurner commited on
Commit
02ec6d5
·
1 Parent(s): b19cc5f

add MCP server

Browse files
demo/health.py CHANGED
@@ -3,8 +3,13 @@ from __future__ import annotations
3
  import html
4
  import importlib.util
5
  import re
 
6
  import shutil
7
  import subprocess
 
 
 
 
8
  from dataclasses import dataclass
9
  from itertools import zip_longest
10
  from typing import Iterable
@@ -96,8 +101,56 @@ def _check_yt_dlp_python() -> ToolStatus:
96
  return ToolStatus(label, False, "yt_dlp_ejs missing (JS sites will fail)")
97
 
98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  def run_health_report() -> HealthReport:
100
- tool_statuses = [_check_deno(), _check_yt_dlp_python()]
101
  ok = all(status.ok for status in tool_statuses)
102
  if ok:
103
  summary = "All systems look good—ready for notebook-style experimentation."
 
3
  import html
4
  import importlib.util
5
  import re
6
+ import os
7
  import shutil
8
  import subprocess
9
+ import asyncio
10
+ import sys
11
+ from pathlib import Path
12
+ import logging
13
  from dataclasses import dataclass
14
  from itertools import zip_longest
15
  from typing import Iterable
 
101
  return ToolStatus(label, False, "yt_dlp_ejs missing (JS sites will fail)")
102
 
103
 
104
+ def _check_mcp_health() -> ToolStatus:
105
+ label = "MCP server"
106
+ try:
107
+ from fastmcp import Client # type: ignore
108
+ from fastmcp.client.transports import StdioTransport # type: ignore
109
+ except Exception as exc: # pragma: no cover - defensive
110
+ return ToolStatus(label, False, f"fastmcp missing: {exc}")
111
+
112
+ repo_root = Path(__file__).resolve().parents[1]
113
+ mcp_src = repo_root / "mcp" / "src"
114
+ existing_py_path = os.environ.get("PYTHONPATH", "")
115
+ py_path = (
116
+ f"{mcp_src}{os.pathsep}{existing_py_path}"
117
+ if existing_py_path
118
+ else str(mcp_src)
119
+ )
120
+
121
+ transport = StdioTransport(
122
+ command=sys.executable,
123
+ args=["-m", "aileen3_mcp.server"],
124
+ env={"PYTHONPATH": py_path},
125
+ )
126
+
127
+ async def probe() -> ToolStatus:
128
+ try:
129
+ async with Client(transport) as client:
130
+ result = await client.call_tool("health", {})
131
+ except Exception as exc:
132
+ logging.warning("MCP health probe failed: %s", exc)
133
+ return ToolStatus(label, False, f"Probe failed: {exc}")
134
+
135
+ # FastMCP returns a CallToolResult; unwrap to the underlying payload if present.
136
+ payload = getattr(result, "data", None) or getattr(result, "structured_content", None) or result
137
+
138
+ if isinstance(payload, dict):
139
+ ok = bool(payload.get("ok", False))
140
+ detail = str(payload.get("detail", "no detail"))
141
+ else:
142
+ ok = False
143
+ detail = str(payload)
144
+
145
+ if not ok:
146
+ logging.warning("MCP health probe reported not-ok: %s", detail)
147
+ return ToolStatus(label, ok, detail)
148
+
149
+ return asyncio.run(probe())
150
+
151
+
152
  def run_health_report() -> HealthReport:
153
+ tool_statuses = [_check_deno(), _check_yt_dlp_python(), _check_mcp_health()]
154
  ok = all(status.ok for status in tool_statuses)
155
  if ok:
156
  summary = "All systems look good—ready for notebook-style experimentation."
demo/requirements.txt CHANGED
@@ -1,2 +1,3 @@
1
  gradio>=6.0.0.dev0
2
  yt-dlp[default]>=2025.11.12
 
 
1
  gradio>=6.0.0.dev0
2
  yt-dlp[default]>=2025.11.12
3
+ fastmcp>=0.1.11
mcp/README.md ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Aileen3 MCP Server
2
+
3
+ Lightweight MCP server exposing a health check tool for local use by the demo app.
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ python -m pip install -e ./mcp
9
+ aileen3-mcp # starts the stdio MCP server
10
+ ```
11
+
12
+ The server provides a single `health` tool that reports an `"ok": true` payload and a short message.
13
+
14
+ ## ToDo
15
+ * write proper project description: add to README.md and pyproject.toml
mcp/pyproject.toml ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "aileen3-mcp"
3
+ version = "0.1.0"
4
+ description = "Aileen 3 Core MCP server."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ authors = [
8
+ { name = "Nils Durner" },
9
+ ]
10
+ dependencies = ["fastmcp>=0.1.11"]
11
+
12
+ [project.scripts]
13
+ aileen3-mcp = "aileen3_mcp.server:main"
14
+
15
+ [build-system]
16
+ requires = ["setuptools>=64", "wheel"]
17
+ build-backend = "setuptools.build_meta"
mcp/src/aileen3_mcp/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ """Aileen3 MCP server package."""
2
+
3
+ __all__ = ["server"]
mcp/src/aileen3_mcp/server.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from dataclasses import asdict, dataclass
5
+
6
+ from fastmcp import FastMCP
7
+
8
+ log = logging.getLogger(__name__)
9
+
10
+
11
+ @dataclass
12
+ class HealthResult:
13
+ ok: bool
14
+ detail: str
15
+
16
+
17
+ def make_app() -> FastMCP:
18
+ """Create the MCP application with a health tool."""
19
+ app = FastMCP("aileen3-mcp")
20
+
21
+ @app.tool()
22
+ def health() -> dict:
23
+ """Return a basic health payload."""
24
+ result = HealthResult(ok=True, detail="aileen3-mcp ready")
25
+ log.debug("Health probe returning %s", result)
26
+ return asdict(result)
27
+
28
+ return app
29
+
30
+
31
+ def main() -> None:
32
+ logging.basicConfig(level=logging.INFO)
33
+ app = make_app()
34
+ app.run() # stdio transport by default
35
+
36
+
37
+ if __name__ == "__main__":
38
+ main()