Skip to content

feat: add mcpm update command for server update management - #315

Merged
JoJoJoJoJoJoJo merged 4 commits into
pathintegral-institute:mainfrom
DjodyKort:feat/update-command
Mar 27, 2026
Merged

feat: add mcpm update command for server update management#315
JoJoJoJoJoJoJo merged 4 commits into
pathintegral-institute:mainfrom
DjodyKort:feat/update-command

Conversation

@DjodyKort

@DjodyKort DjodyKort commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an mcpm update command that checks for and applies updates to installed MCP servers. This addresses a gap where mcpm can install and configure servers but has no way to update them — users must manually check for new versions and update each server individually.

  • Git-based servers: git fetch + git pull --ff-only (with --rebase opt-in), automatic post-update build steps (e.g. uv sync, npm install)
  • NPX/UVX servers: Informational display (auto-update at runtime)
  • HTTP remote servers: Skipped (nothing to update)
  • Source metadata tracking: New sources.json file tracks where each server came from, populated automatically on mcpm install/mcpm new and via mcpm update --init

New commands

mcpm update                    # Update all servers
mcpm update SERVER_NAME        # Update a specific server
mcpm update --check            # Dry run
mcpm update --rebase           # Use git rebase instead of fast-forward
mcpm update --init             # Scan and populate source metadata
mcpm update --init --force     # Re-detect all sources

Key design decisions

  • --ff-only by default instead of --rebase — safe, either succeeds cleanly or fails explicitly
  • Single sources.json in ~/.config/mcpm/ — consistent with existing servers.json pattern
  • Explicit post_update field instead of auto-detection — user confirms during --init
  • 30-second timeout on all git operations — prevents hangs on SSH auth issues
  • No new dependencies — uses subprocess, requests, pydantic, rich, click (all existing)

Files changed

New:

  • src/mcpm/commands/update.py — the update command
  • src/mcpm/core/source.py — source metadata models + manager + auto-detection
  • src/mcpm/utils/git.py — git operations with timeouts and error handling

Modified:

  • src/mcpm/cli.py — register update command
  • src/mcpm/commands/new.py — auto-populate source metadata on server creation
  • src/mcpm/commands/uninstall.py — clean up source metadata on server removal
  • README.md — document the new command

Tests (67 new):

  • tests/test_source.py — source detection + SourcesManager
  • tests/test_git_utils.py — git operations with real repos
  • tests/test_update.py — CLI, init, apply flow, hook integration

Test plan

  • uv run pytest tests/ — 252 tests pass (67 new, 0 broken)
  • uv run ruff check src/ tests/ — clean
  • Manual test: mcpm update --init correctly detects git/npx/remote/unknown servers
  • Manual test: mcpm update --check --verbose shows commits behind with summaries
  • Manual test: mcpm update SERVER --force pulls and runs post_update
  • Manual test: dirty repos, SSH failures, missing paths all handled gracefully

Summary by CodeRabbit

  • New Features

    • Added mcpm update command (init/re-detect sources, --check, --rebase, --force, non-interactive flow, post-update hooks); git servers default to fast‑forward pulls; NPX/UVX treated as runtime auto-updaters.
    • Auto-detect/persist server origin metadata; new now records sources and uninstall removes them.
    • New Git utilities for repo inspection and safe pulls.
  • Documentation

    • Added "Server Updates" section in README describing update workflows and options.
  • Tests

    • Added extensive tests for update flows, source detection/persistence, and git utilities.

Copilot AI review requested due to automatic review settings March 20, 2026 13:03
@qodo-code-review

Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Add mcpm update command for server update management with source tracking

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add mcpm update command for checking and applying updates to MCP servers
  - Supports git-based servers via git pull --ff-only or --rebase
  - NPX/UVX servers show auto-update info; HTTP remotes are skipped
  - Post-update build commands (e.g., uv sync, npm install) run automatically
• Implement source metadata tracking via ~/.config/mcpm/sources.json
  - Auto-detects server sources (git, npx, uvx, remote, unknown)
  - Populated on mcpm new, mcpm uninstall, and mcpm update --init
• Add git utilities with 30-second timeouts and comprehensive error handling
• Comprehensive test coverage with 67 new tests for update, source detection, and git operations
Diagram
flowchart LR
  A["Server Config"] -->|detect_source| B["Source Metadata"]
  B -->|SourcesManager| C["sources.json"]
  D["mcpm update"] -->|check status| E["git fetch/pull"]
  D -->|--init| F["Auto-detect & populate"]
  E -->|post_update| G["Build commands"]
  H["mcpm new"] -->|hook| C
  I["mcpm uninstall"] -->|hook| C
Loading

Grey Divider

File Changes

1. src/mcpm/commands/update.py ✨ Enhancement +405/-0

New update command with check, init, and apply flows

src/mcpm/commands/update.py


2. src/mcpm/core/source.py ✨ Enhancement +241/-0

Source metadata models and auto-detection logic

src/mcpm/core/source.py


3. src/mcpm/utils/git.py ✨ Enhancement +204/-0

Git operations with timeouts and error handling

src/mcpm/utils/git.py


View more (7)
4. src/mcpm/cli.py ✨ Enhancement +4/-2

Register update command in CLI

src/mcpm/cli.py


5. src/mcpm/commands/new.py ✨ Enhancement +9/-0

Auto-populate source metadata on server creation

src/mcpm/commands/new.py


6. src/mcpm/commands/uninstall.py ✨ Enhancement +7/-1

Clean up source metadata on server removal

src/mcpm/commands/uninstall.py


7. tests/test_update.py 🧪 Tests +569/-0

Comprehensive tests for update command and hooks

tests/test_update.py


8. tests/test_source.py 🧪 Tests +275/-0

Tests for source detection and SourcesManager

tests/test_source.py


9. tests/test_git_utils.py 🧪 Tests +267/-0

Tests for git operations with real and mocked repos

tests/test_git_utils.py


10. README.md 📝 Documentation +18/-0

Document update command and source tracking feature

README.md


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (2) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Action required

1. _suggest_post_update() suggests pip/poetry 📘 Rule violation ⚙ Maintainability
Description
The new update logic recommends poetry install and pip install -e . as post-update actions for
Python projects, introducing non-uv dependency management workflows. This conflicts with the
requirement to standardize dependency management actions on uv.
Code

src/mcpm/commands/update.py[R179-186]

+    if (repo_path / "pyproject.toml").exists():
+        # Check if uv is used (uv.lock present)
+        if (repo_path / "uv.lock").exists():
+            return "uv sync"
+        # Check for poetry
+        if (repo_path / "poetry.lock").exists():
+            return "poetry install"
+        return "pip install -e ."
Evidence
PR Compliance ID 1 forbids introducing dependency-management workflows using tools like pip or
poetry. The added _suggest_post_update() explicitly returns poetry install and `pip install -e
.` suggestions.

CLAUDE.md
src/mcpm/commands/update.py[179-186]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`mcpm update --init` can suggest `poetry install` and `pip install -e .` in `_suggest_post_update()`, which introduces non-`uv` dependency management workflows.

## Issue Context
Compliance requires Python dependency management actions to use `uv` rather than `pip`/`poetry`.

## Fix Focus Areas
- src/mcpm/commands/update.py[179-186]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. update lacks @click.help_option 📘 Rule violation ⚙ Maintainability
Description
The new update Click command defines help flags using context_settings instead of the required
@click.help_option("-h", "--help") decorator. This violates the CLI help consistency requirement.
Code

src/mcpm/commands/update.py[R44-51]

+@click.command(name="update", context_settings=dict(help_option_names=["-h", "--help"]))
+@click.argument("server_name", required=False)
+@click.option("--check", "--dry-run", "-c", "check_only", is_flag=True, help="Check for updates only, don't apply them")
+@click.option("--rebase", is_flag=True, help="Use git pull --rebase instead of --ff-only")
+@click.option("--force", is_flag=True, help="Skip confirmation prompts")
+@click.option("--verbose", "-V", is_flag=True, help="Show detailed output")
+@click.option("--init", "run_init", is_flag=True, help="Scan installed servers and populate source metadata")
+def update(server_name, check_only, rebase, force, verbose, run_init):
Evidence
PR Compliance ID 3 requires Click commands to include -h/--help via `@click.help_option("-h",
"--help"). The new command uses context_settings=dict(help_option_names=["-h", "--help"])` instead
of that decorator.

src/mcpm/commands/update.py[44-51]
Best Practice: Repository guidelines

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `update` command provides `-h/--help` via `context_settings`, but compliance requires using `@click.help_option("-h", "--help")`.

## Issue Context
The project enforces consistent help options across Click commands using the explicit decorator approach.

## Fix Focus Areas
- src/mcpm/commands/update.py[44-51]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Misleading update status output 🐞 Bug ✓ Correctness
Description
_run_update() prints "All servers are up to date" whenever no git servers are queued for update,
even if every server was skipped due to missing source metadata or unsupported source types. This
can incorrectly reassure users that update checks succeeded when they were never performed.
Code

src/mcpm/commands/update.py[R269-271]

+    if not updatable:
+        console.print("\n[green]All servers are up to date.[/]")
+        return
Evidence
The command explicitly continues when a server has no source metadata, but later unconditionally
claims everything is up to date if the updatable list is empty—this includes the case where
nothing was actually checked/eligible.

src/mcpm/commands/update.py[237-243]
src/mcpm/commands/update.py[268-271]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`mcpm update` prints `All servers are up to date` when `updatable` is empty, even if servers were skipped (e.g., missing metadata, remote/npx/unknown). This is incorrect/misleading output.

## Issue Context
During the scan loop, several branches `continue` without adding to `updatable`. If *all* servers are skipped, `updatable` remains empty and the command prints the same message as the true "checked and up-to-date" case.

## Fix Focus Areas
- src/mcpm/commands/update.py[237-276]

## Suggested fix approach
- Track counts like: `checked_git`, `skipped_missing_metadata`, `skipped_non_git_type`, `skipped_errors`, `skipped_dirty`, etc.
- Only print `All servers are up to date` when at least one update-eligible git server was actually checked.
- Otherwise print a summary like `No updatable servers found` or `0 git servers checked (N skipped: ... )` and keep the existing per-server lines.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (3)
4. sources.json I/O can crash 🐞 Bug ⛯ Reliability
Description
SourcesManager._load() only handles JSON parse errors and _save() does unguarded file writes;
common filesystem errors (permission denied, read-only FS, disk full) can raise and crash `mcpm
update/--init. This is inconsistent with GlobalConfigManager, which catches OSError` on save.
Code

src/mcpm/core/source.py[R187-212]

+    def _load(self) -> Dict[str, SourceMetadata]:
+        if not self.sources_path.exists():
+            return {}
+        try:
+            with open(self.sources_path, "r", encoding="utf-8") as f:
+                data = json.load(f) or {}
+        except json.JSONDecodeError as e:
+            logger.error(f"Error loading sources from {self.sources_path}: {e}")
+            return {}
+
+        sources = {}
+        for name, source_data in data.items():
+            try:
+                sources[name] = _source_adapter.validate_python(source_data)
+            except Exception as e:
+                logger.warning(f"Skipping invalid source entry '{name}': {e}")
+        return sources
+
+    def _save(self) -> None:
+        self.sources_path.parent.mkdir(parents=True, exist_ok=True)
+        data = {}
+        for name, source in self._sources.items():
+            data[name] = source.model_dump(mode="json", exclude_none=True)
+        with open(self.sources_path, "w", encoding="utf-8") as f:
+            json.dump(data, f, indent=2, default=str)
+
Evidence
SourcesManager does not catch OSError when reading or writing sources.json, so an I/O failure
will propagate as an exception. In contrast, the existing global config codebase pattern wraps JSON
writes in an except OSError block to prevent crashing the CLI.

src/mcpm/core/source.py[187-195]
src/mcpm/core/source.py[205-212]
src/mcpm/global_config.py[67-77]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`SourcesManager` can crash the CLI on basic filesystem failures because `_load()` does not catch `OSError` and `_save()` does not catch any exceptions.

## Issue Context
Other config managers in this repo (e.g., `GlobalConfigManager`) guard file writes with `try/except OSError` and log errors rather than crashing.

## Fix Focus Areas
- src/mcpm/core/source.py[187-212]
- src/mcpm/global_config.py[67-77]

## Suggested fix approach
- In `_load()`, catch `(json.JSONDecodeError, OSError)` and return `{}` after logging.
- In `_save()`, wrap the `open(...)/json.dump(...)` in `try/except OSError` and log a clear message.
- Consider writing atomically (write to temp + rename) to avoid partially-written JSON on interruption (optional but recommended).
- Ensure callers (update/init) behave gracefully if save fails (e.g., continue without crashing).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Git tests assume main branch 🐞 Bug ⛯ Reliability
Description
The git_repo_with_remote fixture pushes to origin main without creating/renaming the local
branch to main, which fails on systems where git’s default initial branch is master. This makes
the new git tests environment-dependent and potentially flaky.
Code

tests/test_git_utils.py[R53-58]

+    # Create initial commit and push
+    (local / "README.md").write_text("# Test")
+    subprocess.run(["git", "add", "."], cwd=str(local), capture_output=True)
+    subprocess.run(["git", "commit", "-m", "initial"], cwd=str(local), capture_output=True)
+    subprocess.run(["git", "push", "origin", "main"], cwd=str(local), capture_output=True)
+
Evidence
The fixture never runs git branch -M main (or similar) before git push origin main, so if the
current branch is not main the push can fail with src refspec main does not match any.

tests/test_git_utils.py[39-59]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`tests/test_git_utils.py` assumes the current branch is `main` and pushes `origin main`, which is not portable across git default-branch configurations.

## Issue Context
Depending on `init.defaultBranch` (or older git defaults), the initial branch may be `master`. The push will fail if `main` doesn’t exist locally.

## Fix Focus Areas
- tests/test_git_utils.py[39-59]

## Suggested fix approach
- After the first commit in the fixture, explicitly set the branch:
 - `git branch -M main`
 - then `git push -u origin main`
- Also consider asserting return codes in the fixture (or using `check=True`) so failures are caught at the point of setup.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. post_update shell injection 🐞 Bug ⛨ Security
Description
_run_post_update() executes post_update with shell=True, so sources.json content is
evaluated by a shell and can execute arbitrary commands. Since post_update is set from interactive
input (or auto-suggestion) and persisted, tampering or unsafe input turns mcpm update into an
arbitrary shell execution primitive.
Code

src/mcpm/commands/update.py[R368-375]

+        result = subprocess.run(
+            command,
+            shell=True,
+            cwd=str(cwd),
+            capture_output=True,
+            text=True,
+            timeout=120,
+        )
Evidence
post_update is accepted into detected.post_update during --init, persisted to sources.json
via SourcesManager.set, then executed during updates using subprocess.run(..., shell=True).

src/mcpm/commands/update.py[125-168]
src/mcpm/commands/update.py[307-312]
src/mcpm/commands/update.py[364-375]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`mcpm update` runs `post_update` with `shell=True`, enabling shell injection via `sources.json` or user-provided `post_update` input.

## Issue Context
`post_update` is stored and later executed automatically. Even though this is a local CLI, evaluating persisted config via a shell is a high-risk pattern.

## Fix Focus Areas
- src/mcpm/commands/update.py[125-168]
- src/mcpm/commands/update.py[307-312]
- src/mcpm/commands/update.py[364-375]

## Suggested fix approach
- Prefer `shell=False` execution:
 - Store `post_update` as a list of argv tokens (recommended) OR
 - Parse with `shlex.split()` and run with `shell=False`.
- If you want to keep supporting compound commands like `npm install && npm run build`, run them as a sequence of commands (split on `&&`) and execute each with `shell=False`, stopping on first failure.
- At minimum, if you must keep shell behavior, gate it behind an explicit opt-in (e.g., `--allow-shell-post-update`) and clearly warn users; but the safer default is `shell=False`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@coderabbitai

coderabbitai Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds persistent per-server source detection and persistence, Git inspection and pull helpers, a new mcpm update CLI command with --init/--check/apply flows (ff-only or rebase), post-update hook execution, and automatic source metadata handling in new and uninstall.

Changes

Cohort / File(s) Summary
Documentation
README.md
Added "Server Updates" section documenting mcpm update workflows (--init, --check, --rebase, --init --force), source-type behaviors, and marked the roadmap item complete.
CLI integration
src/mcpm/cli.py
Registered update subcommand via main.add_command(update.update) and imported update for command registration.
Update command
src/mcpm/commands/update.py
New Click command update(...) implementing --init (detect/upsert sources) and a two-phase check/apply update flow: source discovery, GitStatus checks, interactive/forced confirmation, pulls (ff-only or rebase), post-update execution, and timestamps.
Source detection & persistence
src/mcpm/core/source.py
New Pydantic source models (GitSource, NpxSource, UvxSource, RemoteSource, GithubReleaseSource, UnknownSource), detect_source() classification logic, and SourcesManager for sources.json CRUD plus mark_checked/mark_updated.
Git utilities
src/mcpm/utils/git.py
New git helper module with GitStatus/GitResult dataclasses and subprocess-backed helpers: is_git_repo, get_remote_url, get_default_branch, is_dirty, fetch, check_status, pull_ff_only, pull_rebase, including timeout and common-error mapping.
Integrations: new / uninstall
src/mcpm/commands/new.py, src/mcpm/commands/uninstall.py
new now non-fatally detects and persists source metadata after successful server creation; uninstall attempts to remove stored source metadata on successful uninstall (failures suppressed and logged).
Tests
tests/test_git_utils.py, tests/test_source.py, tests/test_update.py
Added extensive tests: real-git fixtures and mocked failure cases for git utils, source detection and SourcesManager persistence/robustness, and end-to-end CLI tests for mcpm update --init/--check/apply flows plus integration with new and uninstall.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CLI as "mcpm update CLI"
    participant SM as "SourcesManager"
    participant DS as "detect_source()"
    participant Git as "Git Utils"

    User->>CLI: mcpm update --init [--force]
    CLI->>SM: load servers & sources.json
    loop per server
        CLI->>DS: detect_source(server_config)
        DS->>Git: inspect paths/commands for git root
        Git-->>DS: repo metadata / not found
        DS-->>CLI: SourceMetadata
        CLI->>SM: set(server_name, SourceMetadata)
    end
    CLI-->>User: summary (created/updated/skipped)
Loading
sequenceDiagram
    actor User
    participant CLI as "mcpm update CLI"
    participant SM as "SourcesManager"
    participant Git as "Git Utils"
    participant Shell as "Post-update subprocess"

    User->>CLI: mcpm update [--check] [--rebase] [server]
    CLI->>SM: load servers & sources.json
    loop Phase 1: evaluate each GitSource
        CLI->>Git: is_git_repo(path)
        Git-->>CLI: valid?
        alt valid & clean
            CLI->>Git: fetch(remote)
            Git-->>CLI: fetch result
            CLI->>Git: check_status(branch)
            Git-->>CLI: commits_behind / summaries
        end
    end
    alt --check
        CLI-->>User: report availability
    else apply
        User->>CLI: confirm (if prompted)
        loop Phase 2: for each updatable server
            alt --rebase
                CLI->>Git: pull_rebase(path)
            else
                CLI->>Git: pull_ff_only(path)
            end
            Git-->>CLI: pull result
            alt success
                CLI->>Shell: run post_update (with timeout)
                Shell-->>CLI: cmd result
                CLI->>SM: mark_updated(server_name)
            end
        end
        CLI-->>User: update summary
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Review effort 4/5

Suggested reviewers

  • calmini

Poem

🐰 I hopped through trees of code to find each source,
I sniffed the branches, traced the git‑root course.
I pulled fresh commits with ff or gentle rebase,
Ran tiny post‑update carrots, then cleaned the place.
Servers wake up smiling — hooray, a tidy space!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.49% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically describes the main feature being added: the mcpm update command for server update management, which is the central objective of this changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an mcpm update CLI command plus supporting source-metadata tracking so installed MCP servers can be checked/updated (primarily via git), with informational handling for npx/uvx and skipping for remote servers.

Changes:

  • Introduce mcpm update command with --check, --rebase, and --init flows.
  • Add sources.json support (models + detection + persistence) to track server origins and update behavior.
  • Add git utility layer (fetch/status/pull with timeouts) and comprehensive tests for source detection, git behavior, and update flows.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/mcpm/commands/update.py Implements the update/check/init flows and ties them to git + sources metadata.
src/mcpm/core/source.py Adds source metadata models, auto-detection, and a SourcesManager for sources.json.
src/mcpm/utils/git.py Adds subprocess-based git operations with timeouts and structured results.
src/mcpm/cli.py Registers the new update command in the main CLI.
src/mcpm/commands/new.py Auto-populates source metadata on server creation.
src/mcpm/commands/uninstall.py Removes source metadata on server uninstall.
tests/test_update.py Covers CLI behavior, init flow, update application flow, and new/uninstall hooks.
tests/test_source.py Covers source detection and SourcesManager persistence/error-handling.
tests/test_git_utils.py Covers real-repo git operations (fetch/status/pull) plus timeout/error handling.
README.md Documents the new mcpm update functionality and flags.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/mcpm/utils/git.py Outdated
Comment thread tests/test_git_utils.py
Comment thread src/mcpm/commands/update.py
Comment thread src/mcpm/commands/update.py
Comment thread src/mcpm/core/source.py Outdated
Comment thread src/mcpm/commands/new.py Outdated
Comment thread src/mcpm/commands/uninstall.py Outdated
Comment thread README.md
Comment thread src/mcpm/commands/update.py Outdated
Comment thread src/mcpm/commands/update.py
Comment thread src/mcpm/commands/update.py
Comment thread src/mcpm/core/source.py
Comment thread tests/test_git_utils.py
Comment thread src/mcpm/commands/update.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (2)
src/mcpm/core/source.py (2)

137-143: GitSource created for non-existent directories may cause issues later.

When dir_path.exists() returns False, the code still returns GitSource(path=directory) at line 143. This could cause issues during update operations when the path doesn't exist.

Consider returning UnknownSource instead when the directory doesn't exist, or adding validation during update operations (which appears to be handled in update.py with "path not found" checks).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcpm/core/source.py` around lines 137 - 143, The branch that handles a
provided directory currently returns GitSource(path=directory) even when
Path(directory).exists() is False; update _resolve logic (the block containing
dir_path, _find_git_root, and GitSource) so that if dir_path.exists() is False
you return UnknownSource(path=directory) (or otherwise mark it as unknown)
instead of constructing a GitSource; reference GitSource, UnknownSource and
_find_git_root so the change is applied in the same code path and keep
update.py's existing "path not found" validation consistent with this returned
UnknownSource.

94-105: Incomplete version suffix stripping for npm packages.

The function only strips @latest suffix but doesn't handle other version specifiers like @1.0.0, @^2.0.0, or @~1.0.0. This could result in version numbers being included in the package name.

♻️ Proposed fix to handle arbitrary version suffixes
 def _extract_npx_package(args: List[str]) -> Optional[str]:
     """Extract the npm package name from npx args, stripping flags like -y."""
     for arg in args:
         if not arg.startswith("-"):
-            # Strip `@version` suffix if present (e.g. `@upstash/context7-mcp`@latest -> `@upstash/context7-mcp`)
-            name = arg.split("@latest")[0] if arg.endswith("@latest") else arg
-            # Handle scoped packages: `@scope/name`@version
-            if name.startswith("@") and "@" in name[1:]:
-                parts = name[1:].split("@")
-                name = f"@{parts[0]}"
+            # Handle scoped packages: `@scope/name` or `@scope/name`@version
+            if arg.startswith("@"):
+                # Split after the scope: `@scope/name`@version -> ["scope/name", "version"]
+                rest = arg[1:]  # Remove leading @
+                if "/" in rest:
+                    scope_and_name = rest.split("@")[0]  # Take part before any version
+                    name = f"@{scope_and_name}"
+                else:
+                    name = arg  # Malformed, return as-is
+            else:
+                # Unscoped package: name or name@version
+                name = arg.split("@")[0]
             return name
     return None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcpm/core/source.py` around lines 94 - 105, The _extract_npx_package
function only strips "@latest" and misses other version suffixes; update it to
remove any trailing version specifier after the package name by trimming the
substring from the last "@" that denotes a version (not the scope separator).
Concretely, in _extract_npx_package when you compute name, detect scoped
packages (name.startswith("@")) and if so find the last "@" position and remove
everything from that position onward only if that "@" comes after the scope
slash, otherwise keep the scope/name; for unscoped packages simply split on the
first "@" and take the left side. Ensure the modified logic preserves scoped
package names like "@scope/name" and returns None for flag-only args.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/mcpm/commands/update.py`:
- Around line 116-121: detected.remote_url is assigned from
git_utils.get_remote_url and then printed/stored verbatim, which can leak
embedded credentials; before assigning/printing/saving replace any userinfo in
URLs (username:password@) with a redacted token (e.g., <redacted>) or normalize
to credential-free form using a sanitizer function (e.g., sanitize_remote_url)
and use that sanitized value when setting detected.remote_url and when calling
console.print or writing to sources.json; update git_utils.get_remote_url usage
sites (including where console.print is called for detected.remote_url and the
other occurrence at line ~168) to call the sanitizer so no plaintext credentials
are persisted or echoed.
- Around line 240-242: The summary shows a green "up to date" even when all
candidates were skipped/errored because `updatable` is empty; change the logic
in the update command (e.g., around the `updatable` usage and the summary print
blocks near the `console.print` shown and the later summary at lines ~269-271
and ~335-345) to track skipped/errored packages (introduce a boolean like
`had_skips_or_errors` or counters for `skipped`/`failed`) and only print the
green success/"up to date" summary when there were zero skips/errors and
`updatable` is empty; otherwise print a non-success summary (warning/error)
indicating how many were skipped/failed and avoid the misleading green message.
Ensure the checks reference the existing variables `source`, `updatable`, and
current summary printing code paths so skipped/error cases no longer appear as
"up to date."

In `@src/mcpm/utils/git.py`:
- Around line 94-100: The is_dirty function currently treats a non-zero git
status exit as clean; update it to fail closed by treating any non-zero exit the
same as an exception: after calling _run_git in is_dirty, check
result.returncode (or use result.check_returncode()) and if it's non-zero return
True (assume dirty) instead of proceeding to interpret stdout; continue to catch
subprocess.TimeoutExpired, FileNotFoundError (and optionally
subprocess.CalledProcessError) and return True on those errors.

In `@tests/test_git_utils.py`:
- Around line 23-36: The git_repo fixture creates a repo without guaranteeing
the branch name, causing flaky tests on environments with different git
defaults; update the git_repo fixture to explicitly set a consistent branch
(e.g., "main") right after init (either by using git init with a default branch
option or by creating and checking out "main" with git checkout -b main) so
subsequent operations rely on a known branch name; ensure the change is applied
within the git_repo fixture where subprocess.run calls perform git init/commit.
- Around line 158-164: The test fails because the remote fixture creates
origin/main while test_up_to_date and check_status assume origin/master; update
the git_repo_with_remote fixture (or the setup in test_up_to_date) to create a
remote tracking branch matching the expected name (origin/master) or change the
test to use origin/main, and then call check_status with the explicit branch
parameter (e.g., check_status(local, branch="main" or "master") ) so
check_status compares against the correct remote branch after fetch; ensure you
still invoke fetch(local) before calling check_status to populate the remote
ref.
- Around line 39-59: The fixture pushes to "main" but the default branch can be
"master" in some environments; update the git_repo_with_remote (and the git_repo
fixture) to explicitly create and use a known branch (e.g., "main"): when
creating the bare remote or local repo set the initial branch (use git init
--initial-branch main or, for older git, run git symbolic-ref HEAD
refs/heads/main after init), ensure the cloned local repo is on that branch
before committing/pushing, and push that explicit branch (use the same branch
name variable for add/commit/push) so check_status looking for
origin/main/origin/master is consistent across environments.
- Around line 191-207: The test failure occurs because the second clone's push
doesn’t set the upstream and/or branch names diverge, causing
pull_ff_only(local) to see divergent histories; update the fixture or test so
the second clone pushes with upstream tracking (use git push -u origin main or
equivalent when pushing from the "second" clone) and ensure both clones use the
same branch name (e.g., "main") before committing, and make the pull call
operate on that branch by ensuring fetch(local) and pull_ff_only(local) target
the same branch; locate this logic around test_pull_success, the
git_repo_with_remote fixture, the "second" clone setup, fetch, and pull_ff_only
to apply the changes.

---

Nitpick comments:
In `@src/mcpm/core/source.py`:
- Around line 137-143: The branch that handles a provided directory currently
returns GitSource(path=directory) even when Path(directory).exists() is False;
update _resolve logic (the block containing dir_path, _find_git_root, and
GitSource) so that if dir_path.exists() is False you return
UnknownSource(path=directory) (or otherwise mark it as unknown) instead of
constructing a GitSource; reference GitSource, UnknownSource and _find_git_root
so the change is applied in the same code path and keep update.py's existing
"path not found" validation consistent with this returned UnknownSource.
- Around line 94-105: The _extract_npx_package function only strips "@latest"
and misses other version suffixes; update it to remove any trailing version
specifier after the package name by trimming the substring from the last "@"
that denotes a version (not the scope separator). Concretely, in
_extract_npx_package when you compute name, detect scoped packages
(name.startswith("@")) and if so find the last "@" position and remove
everything from that position onward only if that "@" comes after the scope
slash, otherwise keep the scope/name; for unscoped packages simply split on the
first "@" and take the left side. Ensure the modified logic preserves scoped
package names like "@scope/name" and returns None for flag-only args.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e95eb587-b06f-48a3-b702-e5f110944418

📥 Commits

Reviewing files that changed from the base of the PR and between 3a21496 and 42029e1.

📒 Files selected for processing (10)
  • README.md
  • src/mcpm/cli.py
  • src/mcpm/commands/new.py
  • src/mcpm/commands/uninstall.py
  • src/mcpm/commands/update.py
  • src/mcpm/core/source.py
  • src/mcpm/utils/git.py
  • tests/test_git_utils.py
  • tests/test_source.py
  • tests/test_update.py

Comment thread src/mcpm/commands/update.py
Comment thread src/mcpm/commands/update.py
Comment thread src/mcpm/utils/git.py
Comment thread tests/test_git_utils.py
Comment thread tests/test_git_utils.py
Comment thread tests/test_git_utils.py
Comment thread tests/test_git_utils.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/test_git_utils.py (1)

166-184: Consider explicitly specifying the branch for git push commands in test setup.

The git push command on line 177 (and similar occurrences on lines 202, 220, 256) relies on git's default push behavior. While the fixture now properly creates the main branch, the second clone's push may still behave unexpectedly in some environments where push.default is not set to current or simple.

♻️ Proposed fix for consistency
         (second / "new.txt").write_text("new content")
         subprocess.run(["git", "add", "."], cwd=str(second), capture_output=True)
         subprocess.run(["git", "commit", "-m", "new commit"], cwd=str(second), capture_output=True)
-        subprocess.run(["git", "push"], cwd=str(second), capture_output=True)
+        subprocess.run(["git", "push", "origin", "main"], cwd=str(second), capture_output=True)

Apply similar changes to lines 202, 220, and 256.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_git_utils.py` around lines 166 - 184, In test_commits_behind (and
the other test setups that perform pushes), the subprocess.run(["git", "push"])
calls rely on git config defaults; change them to explicitly push the intended
branch (e.g., subprocess.run(["git", "push", "origin", "main"], ...)) so the
second clone pushes the correct branch; update the pushes in the
test_commits_behind function and the similar push calls referenced around the
other tests (the other occurrences on the same test file) to use "origin" and
the "main" branch, leaving the surrounding calls to fetch(local) and
check_status(local) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/test_git_utils.py`:
- Around line 166-184: In test_commits_behind (and the other test setups that
perform pushes), the subprocess.run(["git", "push"]) calls rely on git config
defaults; change them to explicitly push the intended branch (e.g.,
subprocess.run(["git", "push", "origin", "main"], ...)) so the second clone
pushes the correct branch; update the pushes in the test_commits_behind function
and the similar push calls referenced around the other tests (the other
occurrences on the same test file) to use "origin" and the "main" branch,
leaving the surrounding calls to fetch(local) and check_status(local) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 725ce242-10b1-487c-b19f-d8b867fa2c32

📥 Commits

Reviewing files that changed from the base of the PR and between 42029e1 and 60a920d.

📒 Files selected for processing (3)
  • tests/test_git_utils.py
  • tests/test_source.py
  • tests/test_update.py

@DjodyKort
DjodyKort force-pushed the feat/update-command branch from 60a920d to 776c808 Compare March 20, 2026 13:41
Adds source metadata tracking (sources.json) and a new update command
that checks git-based servers for available updates and applies them.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/mcpm/core/source.py`:
- Around line 74-81: The current _find_git_root preserves relative parents so
relative --directory values (like ".") get persisted into GitSource.path; update
the logic to resolve paths to absolute before walking and when persisting
GitSource.path: in _find_git_root(start_path: Path) call start_path =
start_path.resolve() (and use resolved parents) so the returned repo root is
absolute, and ensure the code that constructs/saves GitSource.path (the place
that writes the raw --directory value) stores str(path.resolve()) or the
resolved root returned by _find_git_root rather than the original raw string;
update any code that calls _find_git_root or assigns GitSource.path to use the
resolved Path to avoid saving relative paths.
- Around line 197-205: After loading JSON into data in the SourcesManager (where
code opens self.sources_path and assigns json.load(f) to data), validate that
data is a dict before iterating; if not, log an error including the path and the
unexpected type and return {} so the file is treated as corrupted metadata.
Specifically, after the json.load assignment and before the for name,
source_data in data.items() loop, add an isinstance(data, dict) check and handle
non-dict payloads by logging and returning an empty dict.
- Around line 216-219: The loop that serializes self._sources into data before
writing sources_path currently includes GitSource.remote_url verbatim, risking
credential leakage; update the serialization step in that method to redact or
omit userinfo from remote_url before calling source.model_dump(mode="json") (or
post-process the dumped dict): parse each source's remote_url (identify
GitSource or attribute remote_url), remove username/password/userinfo from the
URL (or set remote_url to None or a masked value like
"https://<redacted>@host/..."), then serialize the sanitized dict into data and
json.dump it to sources_path so no credentials are persisted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9dd8bc74-2c3d-4c3b-97ae-9fa477442946

📥 Commits

Reviewing files that changed from the base of the PR and between 60a920d and 776c808.

📒 Files selected for processing (10)
  • README.md
  • src/mcpm/cli.py
  • src/mcpm/commands/new.py
  • src/mcpm/commands/uninstall.py
  • src/mcpm/commands/update.py
  • src/mcpm/core/source.py
  • src/mcpm/utils/git.py
  • tests/test_git_utils.py
  • tests/test_source.py
  • tests/test_update.py
✅ Files skipped from review due to trivial changes (2)
  • README.md
  • tests/test_git_utils.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/mcpm/commands/uninstall.py
  • src/mcpm/commands/new.py
  • tests/test_source.py
  • src/mcpm/cli.py
  • src/mcpm/commands/update.py

Comment thread src/mcpm/core/source.py
Comment thread src/mcpm/core/source.py
Comment thread src/mcpm/core/source.py
@DjodyKort
DjodyKort force-pushed the feat/update-command branch from 776c808 to 7149d7f Compare March 20, 2026 14:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/mcpm/core/source.py (2)

74-80: ⚠️ Potential issue | 🟠 Major

Resolve repo paths before persisting GitSource.path.

Relative --directory values like . or ~/repo stay unresolved here. With --directory ., _find_git_root() never checks the current directory and Line 150 stores a cwd-dependent path, so mcpm update only works from the same working directory.

🐛 Suggested fix
 def _find_git_root(start_path: Path) -> Optional[Path]:
     """Walk up from start_path looking for a .git directory."""
-    current = start_path if start_path.is_dir() else start_path.parent
+    start_path = start_path.expanduser().resolve(strict=False)
+    current = start_path if start_path.is_dir() else start_path.parent
     while current != current.parent:
         if (current / ".git").exists():
             return current
         current = current.parent
+    if (current / ".git").exists():
+        return current
     return None
@@
     if command == "uv" and args and args[0] == "run":
         directory = _extract_directory_from_args(args)
         if directory:
-            dir_path = Path(directory)
+            dir_path = Path(directory).expanduser().resolve(strict=False)
             if dir_path.exists():
                 git_root = _find_git_root(dir_path)
                 if git_root:
                     return GitSource(path=str(git_root))
-            return GitSource(path=directory)
+            return GitSource(path=str(dir_path))

Also applies to: 141-150

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcpm/core/source.py` around lines 74 - 80, The code fails to resolve
user/relative paths before searching and persisting GitSource.path, causing
cwd-dependent behavior; update _find_git_root to accept and operate on a
resolved absolute Path (call Path.expanduser().resolve() on start_path and use
that as the initial current), and ensure wherever GitSource.path is
assigned/stored (the code around the GitSource creation/assignment near lines
141-150) you persist the resolved absolute path rather than the original
relative input so that repo detection and stored paths are independent of the
current working directory.

197-205: ⚠️ Potential issue | 🟠 Major

Reject non-object sources.json payloads up front.

Truthy non-object JSON (["x"], "foo", 123) reaches Line 205 and blows up on data.items(), while falsy payloads are currently masked by or {}. Treat anything other than a JSON object as corrupted metadata and return {}.

🛡️ Suggested fix
         try:
             with open(self.sources_path, "r", encoding="utf-8") as f:
-                data = json.load(f) or {}
+                data = json.load(f)
         except (json.JSONDecodeError, OSError) as e:
             logger.error(f"Error loading sources from {self.sources_path}: {e}")
             return {}
+        if not isinstance(data, dict):
+            logger.error(
+                "Error loading sources from %s: expected a JSON object, got %s",
+                self.sources_path,
+                type(data).__name__,
+            )
+            return {}
 
         sources = {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcpm/core/source.py` around lines 197 - 205, After loading JSON into data
in the code that opens self.sources_path (inside the method in
src/mcpm/core/source.py), validate that data is a dict/object before using
data.items(): replace the current "data = json.load(f) or {}" pattern with
loading into data and then check "if not isinstance(data, dict):
logger.error(f'Invalid/corrupted sources.json at {self.sources_path}: expected
JSON object, got {type(data).__name__}'); return {}" so non-object JSON payloads
(lists, strings, numbers, etc.) are rejected up front and you avoid calling
data.items() on invalid types when building sources.
src/mcpm/commands/update.py (1)

116-121: ⚠️ Potential issue | 🟠 Major

Redact credentials before storing or echoing remote_url.

git remote get-url can return https://user:token@host/.... Assigning it directly here leaks secrets twice: Line 121 prints them, and Line 168 persists them through sources.set(). Sanitize the URL before assigning it to detected.remote_url.

🔒 Example approach
from urllib.parse import urlsplit, urlunsplit

def _sanitize_remote_url(url: str) -> str:
    if "://" not in url:
        return url

    parts = urlsplit(url)
    if not (parts.username or parts.password):
        return url

    host = parts.hostname or ""
    if parts.port:
        host = f"{host}:{parts.port}"

    return urlunsplit((parts.scheme, host, parts.path, parts.query, parts.fragment))
-                detected.remote_url = git_utils.get_remote_url(repo_path)
+                remote_url = git_utils.get_remote_url(repo_path)
+                detected.remote_url = _sanitize_remote_url(remote_url) if remote_url else None

Also applies to: 168-168

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcpm/commands/update.py` around lines 116 - 121, Sanitize remote URLs
returned by git_utils.get_remote_url before assigning to detected.remote_url and
before persisting via sources.set to avoid leaking credentials; implement a
small helper (e.g., _sanitize_remote_url) that uses
urllib.parse.urlsplit/urlunsplit to strip username/password (preserve scheme,
host[:port], path, query, fragment) and call it when setting detected.remote_url
and when printing/storing values so no credentials from git remote URLs are
echoed or saved.
🧹 Nitpick comments (1)
tests/test_git_utils.py (1)

23-59: Make the git scaffolding fail fast.

These setup calls ignore their exit codes. If init, commit, or push fails, the test keeps running against a half-built repo and the eventual assertion points at the wrong thing. Add check=True here; the same pattern is worth applying to the later second-clone setup blocks too.

♻️ Example
-    subprocess.run(["git", "init", "-b", "main"], cwd=str(repo), capture_output=True)
+    subprocess.run(["git", "init", "-b", "main"], cwd=str(repo), capture_output=True, check=True)
 ...
-    subprocess.run(["git", "commit", "-m", "initial"], cwd=str(repo), capture_output=True)
+    subprocess.run(["git", "commit", "-m", "initial"], cwd=str(repo), capture_output=True, check=True)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_git_utils.py` around lines 23 - 59, The subprocess.run calls in
the git_repo and git_repo_with_remote fixtures do not check exit codes and can
leave tests running against a partially set-up repo; update every subprocess.run
invocation inside the git_repo and git_repo_with_remote fixtures (including the
git init, git config, git add, git commit, git clone, git push, and the bare
repo init calls) to pass check=True so failures raise immediately and the
fixture fails fast.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/mcpm/commands/update.py`:
- Around line 314-324: The code currently calls sources.mark_updated(name) and
increments success_count before verifying the post-update hook; change the flow
so that sources.mark_updated(name) and success_count += 1 only occur after
verifying post_success: if source.post_update is set, run
_run_post_update(source.post_update, repo_path) and only when it returns True
call sources.mark_updated(name) and increment success_count; if there is no
post_update, keep the original behavior (mark and count immediately). Ensure the
failure branch still prints the manual-run hint and does not mark the source or
increment success_count.

---

Duplicate comments:
In `@src/mcpm/commands/update.py`:
- Around line 116-121: Sanitize remote URLs returned by git_utils.get_remote_url
before assigning to detected.remote_url and before persisting via sources.set to
avoid leaking credentials; implement a small helper (e.g., _sanitize_remote_url)
that uses urllib.parse.urlsplit/urlunsplit to strip username/password (preserve
scheme, host[:port], path, query, fragment) and call it when setting
detected.remote_url and when printing/storing values so no credentials from git
remote URLs are echoed or saved.

In `@src/mcpm/core/source.py`:
- Around line 74-80: The code fails to resolve user/relative paths before
searching and persisting GitSource.path, causing cwd-dependent behavior; update
_find_git_root to accept and operate on a resolved absolute Path (call
Path.expanduser().resolve() on start_path and use that as the initial current),
and ensure wherever GitSource.path is assigned/stored (the code around the
GitSource creation/assignment near lines 141-150) you persist the resolved
absolute path rather than the original relative input so that repo detection and
stored paths are independent of the current working directory.
- Around line 197-205: After loading JSON into data in the code that opens
self.sources_path (inside the method in src/mcpm/core/source.py), validate that
data is a dict/object before using data.items(): replace the current "data =
json.load(f) or {}" pattern with loading into data and then check "if not
isinstance(data, dict): logger.error(f'Invalid/corrupted sources.json at
{self.sources_path}: expected JSON object, got {type(data).__name__}'); return
{}" so non-object JSON payloads (lists, strings, numbers, etc.) are rejected up
front and you avoid calling data.items() on invalid types when building sources.

---

Nitpick comments:
In `@tests/test_git_utils.py`:
- Around line 23-59: The subprocess.run calls in the git_repo and
git_repo_with_remote fixtures do not check exit codes and can leave tests
running against a partially set-up repo; update every subprocess.run invocation
inside the git_repo and git_repo_with_remote fixtures (including the git init,
git config, git add, git commit, git clone, git push, and the bare repo init
calls) to pass check=True so failures raise immediately and the fixture fails
fast.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 740a5c18-aad4-4510-a778-6f638f8d74b6

📥 Commits

Reviewing files that changed from the base of the PR and between 776c808 and 7149d7f.

📒 Files selected for processing (10)
  • README.md
  • src/mcpm/cli.py
  • src/mcpm/commands/new.py
  • src/mcpm/commands/uninstall.py
  • src/mcpm/commands/update.py
  • src/mcpm/core/source.py
  • src/mcpm/utils/git.py
  • tests/test_git_utils.py
  • tests/test_source.py
  • tests/test_update.py
✅ Files skipped from review due to trivial changes (2)
  • README.md
  • tests/test_update.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/mcpm/cli.py
  • src/mcpm/commands/uninstall.py
  • src/mcpm/commands/new.py
  • tests/test_source.py
  • src/mcpm/utils/git.py

Comment thread src/mcpm/commands/update.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/mcpm/commands/update.py (1)

332-332: Consider adding type hints for better maintainability.

The helper functions _check_git_server and _source_type_label lack type annotations.

♻️ Suggested type hints
-def _check_git_server(name, source, sources, updatable, verbose) -> bool:
+def _check_git_server(
+    name: str,
+    source: GitSource,
+    sources: SourcesManager,
+    updatable: list,
+    verbose: bool,
+) -> bool:
-def _source_type_label(source) -> str:
+def _source_type_label(source: object) -> str:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcpm/commands/update.py` at line 332, Add type annotations to the helper
functions: change _check_git_server to def _check_git_server(name: str, source:
Mapping[str, Any], sources: Sequence[Mapping[str, Any]], updatable: bool,
verbose: bool) -> bool and change _source_type_label to def
_source_type_label(source: Mapping[str, Any]) -> str (or similar if source is a
specific dict shape use Dict[str, Any]); import the needed typing symbols
(Mapping, Sequence, Any, Dict) at the top of the module and update any related
references to satisfy the new signatures.
tests/test_update.py (1)

436-436: Platform-dependent test command.

The false command used here exists on Unix but not Windows. If Windows CI/dev support is needed, consider using a cross-platform alternative.

♻️ Cross-platform alternative
-        mgr.set("my-server", GitSource(path=str(repo_dir), branch="main", post_update="false"))
+        mgr.set("my-server", GitSource(path=str(repo_dir), branch="main", post_update="exit 1"))

Or mock _run_post_update directly if subprocess execution is not being tested.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_update.py` at line 436, The test uses the Unix-only "false"
command in mgr.set(..., GitSource(..., post_update="false")), which breaks on
Windows; either change the post_update to a cross-platform invocation (e.g., use
the running Python interpreter via sys.executable to run a short one-liner) or
avoid spawning a subprocess by mocking the internal helper (_run_post_update) so
the test does not rely on platform-specific shell commands; update the test to
use GitSource and mgr.set but supply a platform-neutral post_update command or
add a mock for _run_post_update.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/mcpm/commands/update.py`:
- Line 332: Add type annotations to the helper functions: change
_check_git_server to def _check_git_server(name: str, source: Mapping[str, Any],
sources: Sequence[Mapping[str, Any]], updatable: bool, verbose: bool) -> bool
and change _source_type_label to def _source_type_label(source: Mapping[str,
Any]) -> str (or similar if source is a specific dict shape use Dict[str, Any]);
import the needed typing symbols (Mapping, Sequence, Any, Dict) at the top of
the module and update any related references to satisfy the new signatures.

In `@tests/test_update.py`:
- Line 436: The test uses the Unix-only "false" command in mgr.set(...,
GitSource(..., post_update="false")), which breaks on Windows; either change the
post_update to a cross-platform invocation (e.g., use the running Python
interpreter via sys.executable to run a short one-liner) or avoid spawning a
subprocess by mocking the internal helper (_run_post_update) so the test does
not rely on platform-specific shell commands; update the test to use GitSource
and mgr.set but supply a platform-neutral post_update command or add a mock for
_run_post_update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f8fe7af6-3c79-4a1e-8a9f-d21902db0dc1

📥 Commits

Reviewing files that changed from the base of the PR and between 7149d7f and 04c0ad0.

📒 Files selected for processing (2)
  • src/mcpm/commands/update.py
  • tests/test_update.py

@JoJoJoJoJoJoJo JoJoJoJoJoJoJo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea! Thanks for your contribution ❤️

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@JoJoJoJoJoJoJo
JoJoJoJoJoJoJo merged commit efc2916 into pathintegral-institute:main Mar 27, 2026
4 checks passed
mcpm-semantic-release Bot pushed a commit that referenced this pull request Mar 27, 2026
# [2.14.0](v2.13.0...v2.14.0) (2026-03-27)

### Features

* add `mcpm update` command for server update management ([#315](#315)) ([efc2916](efc2916))
@mcpm-semantic-release

Copy link
Copy Markdown

🎉 This PR is included in version 2.14.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@DjodyKort
DjodyKort deleted the feat/update-command branch April 4, 2026 11:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants