Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Tests

on:
push:
branches: [ main, dev ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .[test]

- name: Run linting
run: |
# Install linting tools
pip install flake8 black isort
# Check formatting
black --check --diff .
# Check import sorting
isort --check-only --diff .
# Run flake8
flake8 jupyter_server_docs_mcp tests

- name: Run unit tests
run: |
pytest tests/ -v -m "not slow and not integration" --cov=jupyter_server_docs_mcp --cov-report=xml

- name: Run integration tests
run: |
pytest tests/ -v -m "integration"

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
fail_ci_if_error: true

test-optional-deps:
runs-on: ubuntu-latest
strategy:
matrix:
extra: [fastmcp, jupyterlab, full]

steps:
- uses: actions/checkout@v4

- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install with ${{ matrix.extra }} dependencies
run: |
python -m pip install --upgrade pip
pip install -e .[${{ matrix.extra }},test]

- name: Run tests with optional dependencies
run: |
pytest tests/ -v --tb=short
219 changes: 217 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,217 @@
# jupyter-server-docs-mcp
An MCP server for Jupyter Server Documents
# Jupyter Server MCP Extension

A configurable MCP (Model Context Protocol) server extension for Jupyter Server that allows dynamic registration of Python functions as tools accessible to MCP clients from a running Jupyter Server.

## Overview

This extension provides a simplified, trait-based approach to exposing Jupyter functionality through the MCP protocol. It can dynamically load and register tools from various Python packages, making them available to AI assistants and other MCP clients.

## Key Features

- **Simplified Architecture**: Direct function registration without complex abstractions
- **Configurable Tool Loading**: Register tools via string specifications (`module:function`)
- **Jupyter Integration**: Seamless integration with Jupyter Server extension system
- **HTTP Transport**: FastMCP-based HTTP server with proper MCP protocol support
- **Traitlets Configuration**: Full configuration support through Jupyter's traitlets system

## Installation

```bash
pip install -e .
```

## Quick Start

### 1. Basic Configuration

Create a `jupyter_config.py` file:

```python
c = get_config()

# Basic MCP server settings
c.MCPExtensionApp.mcp_name = "My Jupyter MCP Server"
c.MCPExtensionApp.mcp_port = 8080

# Register tools from existing packages
c.MCPExtensionApp.mcp_tools = [
# Standard library tools
"os:getcwd",
"json:dumps",
"time:time",

# Jupyter AI Tools - Notebook operations
"jupyter_ai_tools.toolkits.notebook:read_notebook",
"jupyter_ai_tools.toolkits.notebook:edit_cell",

# JupyterLab Commands Toolkit
"jupyterlab_commands_toolkit.tools:clear_all_outputs_in_notebook",
"jupyterlab_commands_toolkit.tools:open_document",
]
```

### 2. Start Jupyter Server

```bash
jupyter lab --config=jupyter_config.py
```

The MCP server will start automatically on `http://localhost:8080/mcp`.

### 3. Connect MCP Clients

**Claude Code Configuration:**
```json
{
"mcpServers": {
"jupyter-mcp": {
"command": "python",
"args": ["-c", "pass"],
"transport": {
"type": "http",
"url": "http://localhost:8080/mcp"
}
}
}
}
```

## Architecture

### Core Components

#### MCPServer (`jupyter_server_docs_mcp.mcp_server.MCPServer`)

A simplified LoggingConfigurable class that manages FastMCP integration:

```python
from jupyter_server_docs_mcp.mcp_server import MCPServer

# Create server
server = MCPServer(name="My Server", port=8080)

# Register functions
def my_tool(message: str) -> str:
return f"Hello, {message}!"

server.register_tool(my_tool)

# Start server
await server.start_server()
```

**Key Methods:**
- `register_tool(func, name=None, description=None)` - Register a Python function
- `register_tools(tools)` - Register multiple functions (list or dict)
- `list_tools()` - Get list of registered tools
- `start_server(host=None)` - Start the HTTP MCP server

#### MCPExtensionApp (`jupyter_server_docs_mcp.extension.MCPExtensionApp`)

Jupyter Server extension that manages the MCP server lifecycle:

**Configuration Traits:**
- `mcp_name` - Server name (default: "Jupyter MCP Server")
- `mcp_port` - Server port (default: 3001)
- `mcp_tools` - List of tools to register (format: "module:function")

### Tool Loading System

Tools are loaded using string specifications in the format `module_path:function_name`:

```python
# Examples
"os:getcwd" # Standard library
"jupyter_ai_tools.toolkits.notebook:read_notebook" # External package
"math:sqrt" # Built-in modules
```

The extension dynamically imports the module and registers the function with FastMCP.

## Configuration Examples

### Minimal Setup
```python
c = get_config()
c.MCPExtensionApp.mcp_port = 8080
```

### Full Configuration
```python
c = get_config()

# MCP Server Configuration
c.MCPExtensionApp.mcp_name = "Advanced Jupyter MCP Server"
c.MCPExtensionApp.mcp_port = 8080
c.MCPExtensionApp.mcp_tools = [
# File system operations (jupyter-ai-tools)
"jupyter_ai_tools.toolkits.file_system:read",
"jupyter_ai_tools.toolkits.file_system:write",
"jupyter_ai_tools.toolkits.file_system:edit",
"jupyter_ai_tools.toolkits.file_system:ls",
"jupyter_ai_tools.toolkits.file_system:glob",

# Notebook operations (jupyter-ai-tools)
"jupyter_ai_tools.toolkits.notebook:read_notebook",
"jupyter_ai_tools.toolkits.notebook:edit_cell",
"jupyter_ai_tools.toolkits.notebook:add_cell",
"jupyter_ai_tools.toolkits.notebook:delete_cell",
"jupyter_ai_tools.toolkits.notebook:create_notebook",

# Git operations (jupyter-ai-tools)
"jupyter_ai_tools.toolkits.git:git_status",
"jupyter_ai_tools.toolkits.git:git_add",
"jupyter_ai_tools.toolkits.git:git_commit",
"jupyter_ai_tools.toolkits.git:git_push",

# JupyterLab operations (jupyterlab-commands-toolkit)
"jupyterlab_commands_toolkit.tools:clear_all_outputs_in_notebook",
"jupyterlab_commands_toolkit.tools:open_document",
"jupyterlab_commands_toolkit.tools:open_markdown_file_in_preview_mode",
"jupyterlab_commands_toolkit.tools:show_diff_of_current_notebook",

# Utility functions
"os:getcwd",
"json:dumps",
"time:time",
"platform:system",
]
```

### Running Tests

```bash
# Install development dependencies
pip install -e ".[dev]"

# Run tests
pytest tests/ -v

# Run with coverage
pytest --cov=jupyter_server_docs_mcp tests/
```

### Project Structure

```
jupyter_server_docs_mcp/
├── jupyter_server_docs_mcp/
│ ├── __init__.py
│ ├── mcp_server.py # Core MCP server implementation
│ └── extension.py # Jupyter Server extension
├── tests/
│ ├── test_mcp_server.py # MCPServer tests
│ └── test_extension.py # Extension tests
├── demo/
│ ├── jupyter_config.py # Example configuration
│ └── *.py # Debug/diagnostic scripts
└── pyproject.toml # Package configuration
```

## Contributing

1. Fork the repository
2. Create a feature branch
3. Add tests for new functionality
4. Ensure all tests pass: `pytest tests/`
5. Submit a pull request
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"ServerApp": {
"jpserver_extensions": {
"jupyter_server_docs_mcp": true
}
}
}
17 changes: 17 additions & 0 deletions jupyter_server_docs_mcp/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Jupyter Server MCP Extension with configurable tools."""

__version__ = "0.1.0"

from .extension import MCPExtensionApp
from typing import Any, Dict, List


def _jupyter_server_extension_points() -> List[Dict[str, Any]]: # pragma: no cover
return [
{
"module": "jupyter_server_docs_mcp.extension",
"app": MCPExtensionApp,
},
]

__all__ = ["MCPExtensionApp"]
Loading