Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ The MCP install widget on the front page, {doc}`/quickstart`, and {ref}`clients`

A new *Configure cooldowns* control on the install widget lets you tack a cooldown onto the snippet without leaving the page. Pick a delay in days (uv's `--exclude-newer`, pip's `--uploaded-prior-to`, pipx's `--pip-args`) to wait out community vetting before pulling a fresh release, or pick *Bypass any global cooldown* to skip a `~/.config/uv/uv.toml` cutoff and grab the latest libtmux-mcp anyway. The setting persists across pages, and the embedded *What are cooldowns?* expander links to the [Datadog Security Labs writeup](https://securitylabs.datadoghq.com/articles/dependency-cooldowns/) and [cooldowns.dev](https://cooldowns.dev/) if you want the supply-chain context. (#31)

### Documentation

**MCP install widget ships working cooldown snippets**

The install widget on the front page, {doc}`/quickstart`, and {ref}`clients` now emits a runnable snippet in every method × cooldown cell. uvx days panels apply the cooldown to transitive dependencies while exempting libtmux-mcp itself, so a fresh release stays installable. pipx and pip days panels fall back to the bare install command — neither tool exposes a per-package cooldown override today — and their cooldown note redirects readers to the uvx tab when they want true cooldown enforcement. (#58)

## libtmux-mcp 0.1.0a7 (2026-05-16)

### Breaking changes
Expand Down
117 changes: 66 additions & 51 deletions docs/_ext/widgets/mcp_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,13 +239,10 @@ class Panel:
# re-evaluates ``now - N days`` at every resolver call, so the saved
# ``.mcp.json`` arg ``"P7D"`` stays fresh forever. pip 26.1+ does the
# same at flag-parse time on every invocation.
# * ``<COOLDOWN_DATE>`` is used by pipx days bodies because pipx 1.8.0
# bundles a pip older than 26.1, which rejects the duration form with
# ``Invalid isoformat``. The absolute date is computed in JS from
# ``today - savedDays``; the build-time default drifts daily but
# ``widget.js`` refreshes the slot on every page load.
# pipx and pip days panels don't embed a sentinel — they fall back to
# the bare command because pip has no per-package cooldown override
# (see ``_tool_command`` / ``_pip_prereq_for`` / ``_cooldown_note``).
_DURATION_SENTINEL = "<COOLDOWN_DURATION>"
_DATE_SENTINEL = "<COOLDOWN_DATE>"


def default_cooldown_date(days: int) -> str:
Expand All @@ -263,21 +260,18 @@ def default_cooldown_date(days: int) -> str:


PIP_PREREQ_OFF: str = "pip install --user --upgrade libtmux libtmux-mcp"
PIP_PREREQ_DAYS: str = (
f"pip install --user --upgrade --uploaded-prior-to {_DURATION_SENTINEL}"
" libtmux libtmux-mcp"
)


def _pip_prereq_for(cooldown: Cooldown) -> str:
"""Return the prereq ``pip install`` line for the given cooldown mode.

``bypass`` falls through to the ``off`` form: pip has no global cooldown
config to bypass, so the prereq is identical and the cooldown note on
the panel explains the situation.
All three modes (off / days / bypass) emit the same bare install
line. pip's ``--uploaded-prior-to`` has no per-package override,
so a days-mode cutoff filters fresh libtmux-mcp releases out of
the resolver — the snippet would fail to install. The per-panel
``_cooldown_note`` redirects users to the uvx snippet, which
carries ``--exclude-newer-package`` for true cooldown enforcement.
"""
if cooldown.id == "days":
return PIP_PREREQ_DAYS
return PIP_PREREQ_OFF


Expand All @@ -291,22 +285,36 @@ def _tool_command(method: Method, cooldown: Cooldown) -> str:
"""
if method.id == "uvx":
if cooldown.id == "days":
return f"uvx --exclude-newer {_DURATION_SENTINEL} libtmux-mcp"
# ``--exclude-newer-package libtmux-mcp=2099-01-01`` exempts
# libtmux-mcp itself from the global cutoff so a recently-
# released libtmux-mcp stays inside the resolver. Without
# this, the snippet emits ``no versions of libtmux-mcp``
# whenever the latest libtmux-mcp release is newer than the
# cooldown window. pipx + pip have no per-package override,
# so their days panels carry a caveat note instead (see
# ``_cooldown_note``).
return (
f"uvx --exclude-newer {_DURATION_SENTINEL}"
" --exclude-newer-package libtmux-mcp=2099-01-01"
" libtmux-mcp"
)
if cooldown.id == "bypass":
return "uvx --no-config libtmux-mcp"
return "uvx libtmux-mcp"
if method.id == "pipx":
if cooldown.id == "days":
# pipx 1.8.0 bundles pip <26.1, which doesn't accept the
# duration form — emit an absolute date instead. JS recomputes
# ``today - savedDays`` on every page load.
return (
f"pipx run --pip-args=--uploaded-prior-to={_DATE_SENTINEL} libtmux-mcp"
)
# off + bypass (pipx default backend has no global cooldown)
# pipx's pip backend has no per-package cooldown override, and
# pipx's uv backend doesn't translate ``--uploaded-prior-to`` or
# ``--exclude-newer`` from ``--pip-args`` (see pipx's
# ``commands/run_uv.py::_UV_TRANSLATABLE_VALUE_FLAGS``). Both
# paths fail on fresh libtmux-mcp releases when a days-mode
# cutoff filters the target package out. All three modes emit
# the bare ``pipx run`` command; the per-cell ``_cooldown_note``
# redirects users to the uvx snippet for true cooldown
# enforcement.
return "pipx run libtmux-mcp"
# pip method: the cooldown applies on the prereq line above, not on
# the registered command. The register step is just ``libtmux-mcp``.
# pip method: same per-package-override limitation. The prereq line
# (``_pip_prereq_for``) emits the bare ``pip install``; the register
# step is just ``libtmux-mcp``.
return "libtmux-mcp"


Expand Down Expand Up @@ -343,18 +351,20 @@ def _json_body(method: Method, cooldown: Cooldown) -> str:
if method.id == "uvx":
command = "uvx"
if cooldown.id == "days":
args = f'"--exclude-newer", "{_DURATION_SENTINEL}", "libtmux-mcp"'
# See ``_tool_command`` for the rationale on the
# ``--exclude-newer-package libtmux-mcp=2099-01-01`` exempt.
args = (
f'"--exclude-newer", "{_DURATION_SENTINEL}",'
' "--exclude-newer-package", "libtmux-mcp=2099-01-01",'
' "libtmux-mcp"'
)
else:
args = '"libtmux-mcp"'
elif method.id == "pipx":
# All three modes emit the bare ``pipx run`` args (no
# ``--pip-args``) — see ``_tool_command`` for the reasoning.
command = "pipx"
if cooldown.id == "days":
args = (
'"run", "--pip-args=--uploaded-prior-to='
f'{_DATE_SENTINEL}", "libtmux-mcp"'
)
else:
args = '"run", "libtmux-mcp"'
args = '"run", "libtmux-mcp"'
else: # pip
command = "libtmux-mcp"
args = None
Expand Down Expand Up @@ -392,14 +402,17 @@ def _toml_body(method: Method, cooldown: Cooldown) -> str:
if method.id == "uvx":
command, args_inner = "uvx", '"libtmux-mcp"'
if cooldown.id == "days":
args_inner = f'"--exclude-newer", "{_DURATION_SENTINEL}", "libtmux-mcp"'
elif method.id == "pipx":
command, args_inner = "pipx", '"run", "libtmux-mcp"'
if cooldown.id == "days":
# See ``_tool_command`` for the rationale on the
# ``--exclude-newer-package libtmux-mcp=2099-01-01`` exempt.
args_inner = (
f'"run", "--pip-args=--uploaded-prior-to={_DATE_SENTINEL}",'
f'"--exclude-newer", "{_DURATION_SENTINEL}",'
' "--exclude-newer-package", "libtmux-mcp=2099-01-01",'
' "libtmux-mcp"'
)
elif method.id == "pipx":
# All three modes emit the same args — see ``_tool_command`` for
# the per-package-override rationale.
command, args_inner = "pipx", '"run", "libtmux-mcp"'
else: # pip
command, args_inner = "libtmux-mcp", None

Expand All @@ -415,19 +428,21 @@ def _toml_body(method: Method, cooldown: Cooldown) -> str:


def _cooldown_note(method: Method, cooldown: Cooldown) -> str | None:
"""Return a one-line caveat for cells where cooldown is a no-op."""
if cooldown.id != "bypass":
return None
if method.id == "pip":
return (
"pip has no global cooldown, so bypass is a no-op. Use this"
" when pairing the snippet with a uv-backed parent command."
)
if method.id == "pipx":
"""Return a one-line caveat for cells where the snippet has caveats."""
if method.id in {"pipx", "pip"} and cooldown.id in {"days", "bypass"}:
# pip's ``--uploaded-prior-to`` has no per-package override (so
# ``days`` mode would filter the target package out of the
# resolver for fresh releases) and there's no global cooldown
# for pip to bypass either. Both modes fall back to the bare
# command; the note redirects users to the uvx snippet, which
# carries ``--exclude-newer-package`` for true per-package
# cooldown enforcement.
return (
"pipx's default backend (pip) has no global cooldown."
" For uv-style cooldown control, install `pipx[uv]` and set"
" `UV_NO_CONFIG=1` in your shell."
"pip has no per-package cooldown override, so this snippet"
" runs without cooldown enforcement. Switch to the uvx tab —"
" it applies the cooldown to transitive deps via"
" `--exclude-newer` while exempting libtmux-mcp itself via"
" `--exclude-newer-package`."
)
return None

Expand Down
74 changes: 56 additions & 18 deletions tests/docs/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,17 @@ def test_body_for_uvx_days_inserts_duration_sentinel() -> None:

uv stores the value as ``ExcludeNewerValue::Relative(ExcludeNewerSpan)``
and re-evaluates ``now - N days`` on every resolver call, so a saved
``.mcp.json`` arg ``"P7D"`` stays fresh forever.
``.mcp.json`` arg ``"P7D"`` stays fresh forever. The
``--exclude-newer-package libtmux-mcp=2099-01-01`` exemption keeps
libtmux-mcp itself inside the resolver when the cutoff would
otherwise filter the package out (a fresh release vs a 7-day
cooldown window).
"""
client = CLIENTS[0]
body, language, note = _body_for(client, METHODS[0], client.scopes[0], _DAYS)
assert body == (
"claude mcp add tmux -- uvx --exclude-newer <COOLDOWN_DURATION> libtmux-mcp"
"claude mcp add tmux -- uvx --exclude-newer <COOLDOWN_DURATION>"
" --exclude-newer-package libtmux-mcp=2099-01-01 libtmux-mcp"
)
assert language == "console"
assert note is None
Expand All @@ -138,19 +143,20 @@ def test_body_for_uvx_bypass_inserts_no_config_flag() -> None:


def test_body_for_pipx_bypass_returns_caveat_note() -> None:
"""Pipx bypass renders identically to off and surfaces a no-op note."""
"""Pipx bypass renders identically to off and surfaces a caveat note."""
client = CLIENTS[0]
pipx = next(m for m in METHODS if m.id == "pipx")
body_off, _, note_off = _body_for(client, pipx, client.scopes[0], _OFF)
body_bp, _, note_bp = _body_for(client, pipx, client.scopes[0], _BYPASS)
assert body_off == body_bp # same command emitted
assert note_off is None
assert note_bp is not None
assert "pipx" in note_bp.lower()
# Note redirects users to the uvx tab for true cooldown support.
assert "uvx" in note_bp.lower()


def test_body_for_pip_bypass_returns_caveat_note() -> None:
"""Pip bypass renders identically to off and surfaces a no-op note."""
"""Pip bypass renders identically to off and surfaces a caveat note."""
client = CLIENTS[0]
pip = next(m for m in METHODS if m.id == "pip")
body_off, _, note_off = _body_for(client, pip, client.scopes[0], _OFF)
Expand All @@ -161,13 +167,37 @@ def test_body_for_pip_bypass_returns_caveat_note() -> None:
assert "pip" in note_bp.lower()


def test_body_for_pipx_days_uses_pip_args_form_with_absolute_date() -> None:
"""Pipx days uses absolute date (pipx 1.8.0's bundled pip rejects P<N>D)."""
def test_body_for_pipx_days_falls_back_to_bare_run() -> None:
"""Pipx days renders identically to off (no per-package cooldown override).

pipx's pip backend has no per-package ``--uploaded-prior-to`` flag,
so a days-mode cutoff would filter fresh ``libtmux-mcp`` releases
out of the resolver. The caveat note redirects users to the uvx tab.
"""
client = CLIENTS[0]
pipx = next(m for m in METHODS if m.id == "pipx")
body, language, _ = _body_for(client, pipx, client.scopes[0], _DAYS)
assert "--pip-args=--uploaded-prior-to=<COOLDOWN_DATE>" in body
body_off, _, _ = _body_for(client, pipx, client.scopes[0], _OFF)
body_days, language, note = _body_for(client, pipx, client.scopes[0], _DAYS)
assert body_days == body_off # bare command, no cooldown flag
assert "--pip-args" not in body_days
assert "--uploaded-prior-to" not in body_days
assert "<COOLDOWN_DATE>" not in body_days
assert language == "console"
assert note is not None
assert "uvx" in note.lower()


def test_body_for_pip_days_falls_back_to_bare_install() -> None:
"""Pip days renders identically to off (same per-package-override limit)."""
client = CLIENTS[0]
pip = next(m for m in METHODS if m.id == "pip")
body_off, _, _ = _body_for(client, pip, client.scopes[0], _OFF)
body_days, _, note = _body_for(client, pip, client.scopes[0], _DAYS)
assert body_days == body_off
assert "--uploaded-prior-to" not in body_days
assert "<COOLDOWN_DURATION>" not in body_days
assert note is not None
assert "uvx" in note.lower()


def test_body_for_json_client_off_returns_config_snippet() -> None:
Expand Down Expand Up @@ -237,19 +267,27 @@ def test_body_for_gemini_user_inserts_scope_flag() -> None:
assert language == "console"


def test_pip_panel_has_cooldown_aware_pip_prereq() -> None:
"""Panel.pip_prereq is set only for the pip method, with cooldown applied."""
def test_pip_panel_has_bare_pip_prereq_across_modes() -> None:
"""Pip panels emit the same bare ``pip install`` line across modes.

pip's ``--uploaded-prior-to`` has no per-package override (so a
days-mode cutoff would filter fresh ``libtmux-mcp`` releases out of
the resolver). All three modes fall back to the bare install line;
the per-panel cooldown note redirects users to the uvx snippet.
"""
panels = build_panels()
pip_panels = [p for p in panels if p.method.id == "pip"]
non_pip = [p for p in panels if p.method.id != "pip"]
assert all(p.pip_prereq is None for p in non_pip)
assert all(p.pip_prereq is not None for p in pip_panels)
days_pip = next(p for p in pip_panels if p.cooldown.id == "days")
off_pip = next(p for p in pip_panels if p.cooldown.id == "off")
assert days_pip.pip_prereq is not None
assert off_pip.pip_prereq is not None
assert "--uploaded-prior-to <COOLDOWN_DURATION>" in days_pip.pip_prereq
assert "--uploaded-prior-to" not in off_pip.pip_prereq
bypass_pip = next(p for p in pip_panels if p.cooldown.id == "bypass")
days_prereq = days_pip.pip_prereq
assert days_prereq is not None
assert days_prereq == off_pip.pip_prereq == bypass_pip.pip_prereq
assert "--uploaded-prior-to" not in days_prereq
assert "<COOLDOWN_DURATION>" not in days_prereq


def test_body_for_unknown_kind_raises() -> None:
Expand Down Expand Up @@ -342,10 +380,10 @@ def test_cooldown_days_slot_filter_registered_on_widget_render(
)
app = _build(make_app, real_widget_srcdir)
html = (pathlib.Path(app.outdir) / "index.html").read_text(encoding="utf-8")
# uvx + pip days bodies emit the duration slot; pipx days bodies
# emit the date slot. Both kinds must appear in any complete build.
# uvx days bodies emit the duration slot. pipx and pip days bodies
# fall back to the bare command (pip has no per-package cooldown
# override) so no date slot appears in any rendered snippet.
assert "data-cooldown-duration-slot" in html
assert "data-cooldown-date-slot" in html
# And no raw sentinel escapes to the rendered page.
assert "&lt;COOLDOWN_DURATION&gt;" not in html
assert "&lt;COOLDOWN_DATE&gt;" not in html
Expand Down