mcp
This commit is contained in:
@@ -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"]
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user