This commit is contained in:
lda
2026-08-30 05:53:43 +07:00 Verified
parent 1cdf822825
commit db1c344e0f
6 changed files with 1100 additions and 3 deletions
+3
View File
@@ -9,6 +9,7 @@ authors = [
requires-python = ">=3.14"
dependencies = [
"fastapi>=0.141.1",
"fastmcp>=3.4.7",
"ipython>=9.16.1",
"matplotlib>=3.11.1",
"matplotlib-inline>=0.1.7",
@@ -18,6 +19,8 @@ dependencies = [
[project.scripts]
ipython-demo = "ipython_demo.app:main"
ipython-webapp = "ipython_webapp.app:main"
ipython-mcp = "ipython_mcp.app:main"
[build-system]
requires = ["uv_build>=0.12.6,<0.13.0"]
View File
+65
View File
@@ -0,0 +1,65 @@
# FastMCP is not FastAPI: this module exposes MCP tools, not HTTP routes.
from typing import Literal
from fastmcp import FastMCP
import ipython_demo
from ipython_demo.models import CallOptions, CallResponse, ShellInfo
app = FastMCP(name="IPython Demo")
shell_app = ipython_demo.App()
@app.tool
def run_code(
code: str,
shell_name: Literal["last", "new"] | str = "last",
options: CallOptions | None = None,
) -> CallResponse:
"""Run code in a named shell and return a frontend-facing call response.
use shell_name="new" to create a new shell, or shell_name="last" to use the most recently selected shell. Otherwise, use an existing shell name to run code in that shell.
"""
return shell_app.run_code(code, shell_name, options=options)
@app.tool
def list_shells() -> list[ShellInfo]:
"""Return frontend-safe metadata for all known shells."""
return shell_app.list_shells()
@app.tool
def get_shell(shell_name: Literal["last"] | str) -> ShellInfo:
"""Return frontend-safe metadata for a single shell."""
return (
shell_app.get_last_shell()
if shell_name == "last"
else shell_app.get_shell(shell_name)
)
@app.tool
def create_shell() -> ShellInfo:
"""Create a fresh named shell and return its metadata."""
return shell_app.create_shell()
@app.tool
def get_last_shell() -> ShellInfo:
"""
Return metadata for the most recently selected shell.
get_shell('last') is equivalent, but this is a more explicit name.
"""
return shell_app.get_last_shell()
def main() -> None:
"""Run the MCP server over FastMCP's default transport."""
app.run()
if __name__ == "__main__":
main()
+13 -3
View File
@@ -39,7 +39,9 @@ def get_shell(shell_name: str):
@app.post("/shells/{shell_name}/run")
def run_code(
shell_name: Annotated[str, Path(..., description="The shell to run code in")],
code: Annotated[str, Body(..., description="The code to execute", media_type="text/plain")],
code: Annotated[
str, Body(..., description="The code to execute", media_type="text/plain")
],
options: Annotated[CallOptions, Depends()],
):
return ShellApp.run_code(code, shell_name, options=options)
@@ -47,13 +49,21 @@ def run_code(
@app.post("/run")
def run_code_2(
code: Annotated[str, Body(..., description="The code to execute", media_type="text/plain")],
code: Annotated[
str, Body(..., description="The code to execute", media_type="text/plain")
],
options: Annotated[CallOptions, Depends()],
shell_name: Annotated[str, Query(description="The shell to run code in")] = "last",
):
return ShellApp.run_code(code, shell_name, options=options)
if __name__ == "__main__":
def main() -> None:
"""Run the HTTP API with Uvicorn."""
import uvicorn
uvicorn.run(app, host="::", port=8000, log_level="info")
if __name__ == "__main__":
main()
+27
View File
@@ -0,0 +1,27 @@
import unittest
from fastmcp import FastMCP
from ipython_mcp.app import app
class MCPAppTests(unittest.IsolatedAsyncioTestCase):
async def test_exports_typed_ipython_tools_from_plain_fastmcp(self):
self.assertIsInstance(app, FastMCP)
tools = await app.list_tools()
self.assertEqual(
{tool.name for tool in tools},
{
"run_code",
"list_shells",
"get_shell",
"create_shell",
"get_last_shell",
},
)
if __name__ == "__main__":
unittest.main()
Generated
+992
View File
File diff suppressed because it is too large Load Diff