Skip to content

Commit 3ab6029

Browse files
authored
Merge pull request #2 from Zsailer/rename
Rename to jupyter-server-mcp
2 parents 35ec316 + 51da508 commit 3ab6029

File tree

13 files changed

+379
-377
lines changed

13 files changed

+379
-377
lines changed

.github/workflows/tests.yml

Lines changed: 7 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -29,50 +29,16 @@ jobs:
2929
3030
- name: Run linting
3131
run: |
32-
# Install linting tools
33-
pip install flake8 black isort
34-
# Check formatting
35-
black --check --diff .
36-
# Check import sorting
37-
isort --check-only --diff .
38-
# Run flake8
39-
flake8 jupyter_server_docs_mcp tests
32+
# Install ruff
33+
pip install ruff
34+
# Check linting and formatting
35+
ruff check jupyter_server_mcp tests
36+
ruff format --check jupyter_server_mcp tests
4037
4138
- name: Run unit tests
4239
run: |
43-
pytest tests/ -v -m "not slow and not integration" --cov=jupyter_server_docs_mcp --cov-report=xml
40+
pytest tests/ -v -m "not slow and not integration"
4441
4542
- name: Run integration tests
4643
run: |
47-
pytest tests/ -v -m "integration"
48-
49-
- name: Upload coverage reports to Codecov
50-
uses: codecov/codecov-action@v4
51-
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
52-
with:
53-
token: ${{ secrets.CODECOV_TOKEN }}
54-
file: ./coverage.xml
55-
fail_ci_if_error: true
56-
57-
test-optional-deps:
58-
runs-on: ubuntu-latest
59-
strategy:
60-
matrix:
61-
extra: [fastmcp, jupyterlab, full]
62-
63-
steps:
64-
- uses: actions/checkout@v4
65-
66-
- name: Set up Python 3.11
67-
uses: actions/setup-python@v5
68-
with:
69-
python-version: '3.11'
70-
71-
- name: Install with ${{ matrix.extra }} dependencies
72-
run: |
73-
python -m pip install --upgrade pip
74-
pip install -e .[${{ matrix.extra }},test]
75-
76-
- name: Run tests with optional dependencies
77-
run: |
78-
pytest tests/ -v --tb=short
44+
pytest tests/ -v -m "integration"

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# Byte-compiled / optimized / DLL files
2+
.claude
3+
.vscode
4+
demo/
25
__pycache__/
36
*.py[codz]
47
*$py.class

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,12 @@ The MCP server will start automatically on `http://localhost:8080/mcp`.
8080

8181
### Core Components
8282

83-
#### MCPServer (`jupyter_server_docs_mcp.mcp_server.MCPServer`)
83+
#### MCPServer (`jupyter_server_mcp.mcp_server.MCPServer`)
8484

8585
A simplified LoggingConfigurable class that manages FastMCP integration:
8686

8787
```python
88-
from jupyter_server_docs_mcp.mcp_server import MCPServer
88+
from jupyter_server_mcp.mcp_server import MCPServer
8989

9090
# Create server
9191
server = MCPServer(name="My Server", port=8080)
@@ -106,7 +106,7 @@ await server.start_server()
106106
- `list_tools()` - Get list of registered tools
107107
- `start_server(host=None)` - Start the HTTP MCP server
108108

109-
#### MCPExtensionApp (`jupyter_server_docs_mcp.extension.MCPExtensionApp`)
109+
#### MCPExtensionApp (`jupyter_server_mcp.extension.MCPExtensionApp`)
110110

111111
Jupyter Server extension that manages the MCP server lifecycle:
112112

@@ -188,14 +188,14 @@ pip install -e ".[dev]"
188188
pytest tests/ -v
189189

190190
# Run with coverage
191-
pytest --cov=jupyter_server_docs_mcp tests/
191+
pytest --cov=jupyter_server_mcp tests/
192192
```
193193

194194
### Project Structure
195195

196196
```
197-
jupyter_server_docs_mcp/
198-
├── jupyter_server_docs_mcp/
197+
jupyter_server_mcp/
198+
├── jupyter_server_mcp/
199199
│ ├── __init__.py
200200
│ ├── mcp_server.py # Core MCP server implementation
201201
│ └── extension.py # Jupyter Server extension
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"ServerApp": {
33
"jpserver_extensions": {
4-
"jupyter_server_docs_mcp": true
4+
"jupyter_server_mcp": true
55
}
66
}
77
}

jupyter_server_docs_mcp/__init__.py

Lines changed: 0 additions & 17 deletions
This file was deleted.

jupyter_server_mcp/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Jupyter Server MCP Extension with configurable tools."""
2+
3+
from typing import Any
4+
5+
from .extension import MCPExtensionApp
6+
7+
__version__ = "0.1.0"
8+
9+
10+
def _jupyter_server_extension_points() -> list[dict[str, Any]]:
11+
# pragma: no cover
12+
return [
13+
{
14+
"module": "jupyter_server_mcp.extension",
15+
"app": MCPExtensionApp,
16+
},
17+
]
18+
19+
20+
__all__ = ["MCPExtensionApp"]
Lines changed: 65 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
"""Jupyter Server extension for managing MCP server."""
22

33
import asyncio
4+
import contextlib
45
import importlib
56
import logging
6-
from typing import Optional
77

88
from jupyter_server.extension.application import ExtensionApp
9-
from traitlets import Int, Unicode, List
9+
from traitlets import Int, List, Unicode
1010

1111
from .mcp_server import MCPServer
1212

@@ -15,66 +15,73 @@
1515

1616
class MCPExtensionApp(ExtensionApp):
1717
"""The Jupyter Server MCP extension app."""
18-
19-
name = "jupyter_server_docs_mcp"
18+
19+
name = "jupyter_server_mcp"
2020
description = "Jupyter Server extension providing MCP server for tool registration"
21-
21+
2222
# Configurable traits
23-
mcp_port = Int(
24-
default_value=3001,
25-
help="Port for the MCP server to listen on"
26-
).tag(config=True)
27-
23+
mcp_port = Int(default_value=3001, help="Port for the MCP server to listen on").tag(
24+
config=True
25+
)
26+
2827
mcp_name = Unicode(
29-
default_value="Jupyter MCP Server",
30-
help="Name for the MCP server"
28+
default_value="Jupyter MCP Server", help="Name for the MCP server"
3129
).tag(config=True)
32-
30+
3331
mcp_tools = List(
3432
trait=Unicode(),
3533
default_value=[],
36-
help="List of tools to register with the MCP server. "
37-
"Format: 'module_path:function_name' (e.g., 'os:getcwd', 'math:sqrt')"
34+
help=(
35+
"List of tools to register with the MCP server. "
36+
"Format: 'module_path:function_name' "
37+
"(e.g., 'os:getcwd', 'math:sqrt')"
38+
),
3839
).tag(config=True)
39-
40-
mcp_server_instance: Optional[object] = None
41-
mcp_server_task: Optional[asyncio.Task] = None
42-
40+
41+
mcp_server_instance: object | None = None
42+
mcp_server_task: asyncio.Task | None = None
43+
4344
def _load_function_from_string(self, tool_spec: str):
4445
"""Load a function from a string specification.
45-
46+
4647
Args:
47-
tool_spec: Function specification in format 'module_path:function_name'
48-
48+
tool_spec: Function specification in format
49+
'module_path:function_name'
50+
4951
Returns:
5052
The loaded function object
51-
53+
5254
Raises:
5355
ValueError: If tool_spec format is invalid
5456
ImportError: If module cannot be imported
5557
AttributeError: If function not found in module
5658
"""
57-
if ':' not in tool_spec:
58-
raise ValueError(f"Invalid tool specification '{tool_spec}'. Expected format: 'module_path:function_name'")
59-
60-
module_path, function_name = tool_spec.rsplit(':', 1)
61-
59+
if ":" not in tool_spec:
60+
msg = (
61+
f"Invalid tool specification '{tool_spec}'. "
62+
f"Expected format: 'module_path:function_name'"
63+
)
64+
raise ValueError(msg)
65+
66+
module_path, function_name = tool_spec.rsplit(":", 1)
67+
6268
try:
6369
module = importlib.import_module(module_path)
64-
function = getattr(module, function_name)
65-
return function
70+
return getattr(module, function_name)
6671
except ImportError as e:
67-
raise ImportError(f"Could not import module '{module_path}': {e}")
72+
msg = f"Could not import module '{module_path}': {e}"
73+
raise ImportError(msg) from e
6874
except AttributeError as e:
69-
raise AttributeError(f"Function '{function_name}' not found in module '{module_path}': {e}")
70-
75+
msg = f"Function '{function_name}' not found in module '{module_path}': {e}"
76+
raise AttributeError(msg) from e
77+
7178
def _register_configured_tools(self):
7279
"""Register tools specified in the mcp_tools configuration."""
7380
if not self.mcp_tools:
7481
return
75-
82+
7683
logger.info(f"Registering {len(self.mcp_tools)} configured tools")
77-
84+
7885
for tool_spec in self.mcp_tools:
7986
try:
8087
function = self._load_function_from_string(tool_spec)
@@ -83,69 +90,66 @@ def _register_configured_tools(self):
8390
except Exception as e:
8491
logger.error(f"❌ Failed to register tool '{tool_spec}': {e}")
8592
continue
86-
93+
8794
def initialize(self):
8895
"""Initialize the extension."""
8996
super().initialize()
9097
# serverapp will be available as self.serverapp after parent initialization
91-
98+
9299
def initialize_handlers(self):
93100
"""Initialize the handlers for the extension."""
94101
# No HTTP handlers needed - MCP server runs on separate port
95-
pass
96-
102+
97103
def initialize_settings(self):
98-
"""Initialize settings for the extension."""
104+
"""Initialize settings for the extension."""
99105
# Configuration is handled by traitlets
100-
pass
101-
106+
102107
async def start_extension(self):
103108
"""Start the extension - called after Jupyter Server starts."""
104109
try:
105-
self.log.info(f"Starting MCP server '{self.mcp_name}' on port {self.mcp_port}")
106-
110+
self.log.info(
111+
f"Starting MCP server '{self.mcp_name}' on port {self.mcp_port}"
112+
)
113+
107114
self.mcp_server_instance = MCPServer(
108-
parent=self,
109-
name=self.mcp_name,
110-
port=self.mcp_port
115+
parent=self, name=self.mcp_name, port=self.mcp_port
111116
)
112-
117+
113118
# Register configured tools
114119
self._register_configured_tools()
115-
120+
116121
# Start the MCP server in a background task
117122
self.mcp_server_task = asyncio.create_task(
118123
self.mcp_server_instance.start_server()
119124
)
120-
125+
121126
# Give the server a moment to start
122127
await asyncio.sleep(0.5)
123-
128+
124129
self.log.info(f"✅ MCP server started on port {self.mcp_port}")
125130
if self.mcp_tools:
126-
self.log.info(f"Registered {len(self.mcp_server_instance._registered_tools)} tools from configuration")
131+
registered_count = len(self.mcp_server_instance._registered_tools)
132+
self.log.info(f"Registered {registered_count} tools from configuration")
127133
else:
128134
self.log.info("Use mcp_server_instance.register_tool() to add tools")
129-
135+
130136
except Exception as e:
131137
self.log.error(f"Failed to start MCP server: {e}")
132138
raise
133-
134-
async def _start_jupyter_server_extension(self, serverapp):
139+
140+
async def _start_jupyter_server_extension(self, serverapp): # noqa: ARG002
135141
"""Start the extension - called after Jupyter Server starts."""
136142
await self.start_extension()
137-
143+
138144
async def stop_extension(self):
139145
"""Stop the extension - called when Jupyter Server shuts down."""
140146
if self.mcp_server_task and not self.mcp_server_task.done():
141147
self.log.info("Stopping MCP server")
142148
self.mcp_server_task.cancel()
143-
try:
149+
with contextlib.suppress(asyncio.CancelledError):
144150
await self.mcp_server_task
145-
except asyncio.CancelledError:
146-
pass
147-
151+
148152
# Always clean up
149153
self.mcp_server_task = None
150154
self.mcp_server_instance = None
151-
self.log.info("MCP server stopped")
155+
self.log.info("MCP server stopped")

0 commit comments

Comments
 (0)