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
42 changes: 14 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,8 @@ Then, configure your client:
"command": "uvx",
"args": ["jupyter-mcp-server@latest"],
"env": {
"DOCUMENT_URL": "http://localhost:8888",
"DOCUMENT_TOKEN": "MY_TOKEN",
"DOCUMENT_ID": "notebook.ipynb",
"RUNTIME_URL": "http://localhost:8888",
"RUNTIME_TOKEN": "MY_TOKEN",
"JUPYTER_URL": "http://localhost:8888",
"JUPYTER_TOKEN": "MY_TOKEN",
"ALLOW_IMG_OUTPUT": "true"
}
}
Expand All @@ -167,20 +164,14 @@ Then, configure your client:
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "DOCUMENT_URL",
"-e", "DOCUMENT_TOKEN",
"-e", "DOCUMENT_ID",
"-e", "RUNTIME_URL",
"-e", "RUNTIME_TOKEN",
"-e", "JUPYTER_URL",
"-e", "JUPYTER_TOKEN",
"-e", "ALLOW_IMG_OUTPUT",
"datalayer/jupyter-mcp-server:latest"
],
"env": {
"DOCUMENT_URL": "http://host.docker.internal:8888",
"DOCUMENT_TOKEN": "MY_TOKEN",
"DOCUMENT_ID": "notebook.ipynb",
"RUNTIME_URL": "http://host.docker.internal:8888",
"RUNTIME_TOKEN": "MY_TOKEN",
"JUPYTER_URL": "http://host.docker.internal:8888",
"JUPYTER_TOKEN": "MY_TOKEN",
"ALLOW_IMG_OUTPUT": "true"
}
}
Expand All @@ -196,21 +187,15 @@ Then, configure your client:
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "DOCUMENT_URL",
"-e", "DOCUMENT_TOKEN",
"-e", "DOCUMENT_ID",
"-e", "RUNTIME_URL",
"-e", "RUNTIME_TOKEN",
"-e", "JUPYTER_URL",
"-e", "JUPYTER_TOKEN",
"-e", "ALLOW_IMG_OUTPUT",
"--network=host",
"datalayer/jupyter-mcp-server:latest"
],
"env": {
"DOCUMENT_URL": "http://localhost:8888",
"DOCUMENT_TOKEN": "MY_TOKEN",
"DOCUMENT_ID": "notebook.ipynb",
"RUNTIME_URL": "http://localhost:8888",
"RUNTIME_TOKEN": "MY_TOKEN",
"JUPYTER_URL": "http://localhost:8888",
"JUPYTER_TOKEN": "MY_TOKEN",
"ALLOW_IMG_OUTPUT": "true"
}
}
Expand All @@ -221,9 +206,10 @@ Then, configure your client:
</details>

> [!TIP]
> 1. Ensure the `port` of the `DOCUMENT_URL` and `RUNTIME_URL` match those used in the `jupyter lab` command.
> 2. In a basic setup, `DOCUMENT_URL` and `RUNTIME_URL` are the same. `DOCUMENT_TOKEN`, and `RUNTIME_TOKEN` are also the same and is actually the Jupyter Token.
> 3. The `DOCUMENT_ID` parameter specifies the path to the notebook you want to connect to. It should be relative to the directory where JupyterLab was started.
> 1. **Port Configuration**: Ensure the `port` in your Jupyter URLs matches the one used in the `jupyter lab` command. For simplified config, set this in `JUPYTER_URL`.
> 2. **Server Separation**: The different URL and token variables exist because some deployments separate notebook storage (`DOCUMENT_*`) from kernel execution (`RUNTIME_*`). Use `JUPYTER_URL` and `JUPYTER_TOKEN` when both services are on the same server, or set individual variables for advanced deployments.
> 3. **Authentication**: In most cases, document and runtime services use the same authentication token. Use `JUPYTER_TOKEN` for simplified config or set `DOCUMENT_TOKEN` and `RUNTIME_TOKEN` individually for different credentials.
> 4. **Notebook Path**: The `DOCUMENT_ID` parameter specifies the path to the notebook you want to connect to. It should be relative to the directory where JupyterLab was started.
> - **Optional:** If you omit `DOCUMENT_ID`, the MCP client can automatically list all available notebooks on the Jupyter server, allowing you to select one interactively via your prompts.
> - **Flexible:** Even if you set `DOCUMENT_ID`, the MCP client can still browse, list, switch to, or even create new notebooks at any time.

Expand Down
108 changes: 98 additions & 10 deletions jupyter_mcp_server/CLI.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def _common_options(f):
"--runtime-url",
envvar="RUNTIME_URL",
type=click.STRING,
default="http://localhost:8888",
default=None,
help="The runtime URL to use. For the jupyter provider, this is the Jupyter server URL. For the datalayer provider, this is the Datalayer runtime URL.",
),
click.option(
Expand All @@ -59,7 +59,7 @@ def _common_options(f):
"--document-url",
envvar="DOCUMENT_URL",
type=click.STRING,
default="http://localhost:8888",
default=None,
help="The document URL to use. For the jupyter provider, this is the Jupyter server URL. For the datalayer provider, this is the Datalayer document URL.",
),
click.option(
Expand All @@ -75,13 +75,77 @@ def _common_options(f):
type=click.STRING,
default=None,
help="The document token to use for authentication with the provider. If not provided, the provider should accept anonymous requests.",
),
click.option(
"--jupyter-url",
envvar="JUPYTER_URL",
type=click.STRING,
default=None,
help="The Jupyter URL to use as default for both document and runtime URLs. If not provided, individual URL settings take precedence.",
),
click.option(
"--jupyter-token",
envvar="JUPYTER_TOKEN",
type=click.STRING,
default=None,
help="The Jupyter token to use as default for both document and runtime tokens. If not provided, individual token settings take precedence.",
)
]
# Apply decorators in reverse order
for option in reversed(options):
f = option(f)
return f


def _resolve_url_and_token_variables(
jupyter_url, jupyter_token,
document_url, document_token,
runtime_url, runtime_token,
) -> tuple[str, str | None, str, str | None]:
"""Resolve URL and token variables based on priority logic.

Priority order:
1. Individual URL/token variables take precedence if set
2. JUPYTER_URL/JUPYTER_TOKEN used as fallback if individual variables are None
3. Keep original default values if neither individual nor merged variables are set

Args:
jupyter_url: The merged Jupyter URL variable
jupyter_token: The merged Jupyter token variable
document_url: The individual document URL (takes precedence if set)
document_token: The individual document token (takes precedence if set)
runtime_url: The individual runtime URL (takes precedence if set)
runtime_token: The individual runtime token (takes precedence if set)

Returns:
Tuple of (resolved_document_url, resolved_document_token, resolved_runtime_url, resolved_runtime_token)
"""

# Resolve document_url
if document_url is not None:
resolved_document_url = document_url
elif jupyter_url is not None:
resolved_document_url = jupyter_url
else:
resolved_document_url = "http://localhost:8888"

# Resolve runtime_url
if runtime_url is not None:
resolved_runtime_url = runtime_url
elif jupyter_url is not None:
resolved_runtime_url = jupyter_url
else:
resolved_runtime_url = "http://localhost:8888"

# Resolve document_token
resolved_document_token = document_token or jupyter_token

# Resolve runtime_token
resolved_runtime_token = runtime_token or jupyter_token

return resolved_document_url, resolved_document_token, resolved_runtime_url, resolved_runtime_token


def _do_start(
transport: str,
start_new_runtime: bool,
Expand Down Expand Up @@ -191,6 +255,8 @@ def server(
document_url: str,
document_id: str,
document_token: str,
jupyter_url: str,
jupyter_token: str,
port: int,
provider: str,
):
Expand All @@ -206,15 +272,25 @@ def server(
return

# No subcommand provided - execute the default start behavior
# Resolve URL and token variables based on priority logic
resolved_document_url, resolved_document_token, resolved_runtime_url, resolved_runtime_token = _resolve_url_and_token_variables(
jupyter_url=jupyter_url,
jupyter_token=jupyter_token,
document_url=document_url,
document_token=document_token,
runtime_url=runtime_url,
runtime_token=runtime_token,
)

_do_start(
transport=transport,
start_new_runtime=start_new_runtime,
runtime_url=runtime_url,
runtime_url=resolved_runtime_url,
runtime_id=runtime_id,
runtime_token=runtime_token,
document_url=document_url,
runtime_token=resolved_runtime_token,
document_url=resolved_document_url,
document_id=document_id,
document_token=document_token,
document_token=resolved_document_token,
port=port,
provider=provider,
)
Expand Down Expand Up @@ -322,19 +398,31 @@ def start_command(
document_url: str,
document_id: str,
document_token: str,
jupyter_url: str,
jupyter_token: str,
port: int,
provider: str,
):
"""Start the Jupyter MCP server with a transport."""
# Resolve URL and token variables based on priority logic
resolved_document_url, resolved_document_token, resolved_runtime_url, resolved_runtime_token = _resolve_url_and_token_variables(
jupyter_url=jupyter_url,
jupyter_token=jupyter_token,
document_url=document_url,
document_token=document_token,
runtime_url=runtime_url,
runtime_token=runtime_token,
)

_do_start(
transport=transport,
start_new_runtime=start_new_runtime,
runtime_url=runtime_url,
runtime_url=resolved_runtime_url,
runtime_id=runtime_id,
runtime_token=runtime_token,
document_url=document_url,
runtime_token=resolved_runtime_token,
document_url=resolved_document_url,
document_id=document_id,
document_token=document_token,
document_token=resolved_document_token,
port=port,
provider=provider,
)
Expand Down
2 changes: 1 addition & 1 deletion jupyter_mcp_server/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

"""Jupyter MCP Server."""

__version__ = "0.16.2"
__version__ = "0.16.3"
Loading