Skip to content

install

install

One command that wires up both halves.

Installing used to be four steps across two worlds: pip install, work out which Houdini packages directory is the real one, write a JSON file there, then hand your MCP client a command line whose Python path you had to discover yourself. Every one of those steps fails quietly. Houdini skips a package file it cannot resolve without saying so, and Claude Desktop does not inherit your PATH, so a bare python in its config reads as "disconnected" with no explanation.

python -m fxhoudinimcp install                    # do both halves
python -m fxhoudinimcp install --dry-run          # say what it would do
python -m fxhoudinimcp install --houdini-dir DIR  # just this one directory
python -m fxhoudinimcp install --client none      # wire the client yourself

It asks nothing and it finishes. Two earlier versions did neither, in ways worth recording because both looked reasonable:

  • It refused to choose between candidate packages directories, and exited. The reasoning was sound, that a wrong choice is silent, and the conclusion was wrong, because writing the same file into all of them is never a wrong choice.
  • It reported a Claude Code entry pointing somewhere else, printed the two commands to repair it, and stopped. It knew both values and had already been told to install. Printing homework is not finishing.

What remains genuinely undecidable is still refused, not guessed: if no Houdini packages directory exists at all, this says so rather than inventing one, since a package file in a directory Houdini never reads is the silent no-op the whole command exists to prevent.

Functions:

Name Description
build_parser

The argument parser, exposed so the README's flag table can be checked.

claude_code_add_argv

The claude mcp add invocation, for running or for printing verbatim.

claude_code_current_command

The command Claude Code has registered for us, if any.

claude_code_remove_argv

The claude mcp remove invocation, for running or for printing.

client_command

The argv an MCP client should run to start this server.

config_file_note

Name the config file the Claude Code CLI actually wrote.

desktop_config_path

Claude Desktop's config file for this platform, whether or not it exists.

install_claude_code

Register with Claude Code via its own CLI. Returns report lines.

install_desktop

Register the server in Claude Desktop's config. Returns report lines.

pinned_port_warning

Warn when a config pins HOUDINI_PORT, which disables port discovery.

printable_argv

An argv a person can paste back into a shell.

repoint_claude_code

Replace a Claude Code entry that points at a different interpreter.

resolve_houdini_dirs

Where the package file goes. An empty list means there is nowhere yet.

Functions:

build_parser

build_parser() -> ArgumentParser

The argument parser, exposed so the README's flag table can be checked.

The table in the README is the first thing anyone reads, so a flag that is renamed here and not there is a documented lie. tests/test_install.py compares the two.

claude_code_add_argv

claude_code_add_argv(scope: str = 'user') -> list[str]

The claude mcp add invocation, for running or for printing verbatim.

claude_code_current_command

claude_code_current_command() -> str | None

The command Claude Code has registered for us, if any.

Read with claude mcp get, whose output is meant for humans, so this only looks for the "Command:" line rather than trying to parse the whole thing. Returns None when the server is not registered or the output is unfamiliar.

claude_code_remove_argv

claude_code_remove_argv(scope: str = 'user') -> list[str]

The claude mcp remove invocation, for running or for printing.

Lives next to its counterpart because install needs it too: claude mcp add cannot update an entry in place, so repointing one means removing it first. uninstall imports it from here.

client_command

client_command() -> list[str]

The argv an MCP client should run to start this server.

sys.executable, never a bare "python". Claude Desktop launches its servers without the user's shell environment, so a bare interpreter name resolves against a PATH that may not contain the Python this package is installed into. That failure surfaces only as "disconnected", which is why the README had to explain it; an absolute path removes the class of problem.

config_file_note

config_file_note(result: CompletedProcess) -> list[str]

Name the config file the Claude Code CLI actually wrote.

It already prints File modified: <path> and we were swallowing it, in favour of "registered with Claude Code (user scope)". That reads as complete and is not, because "user scope" is not one place: CLAUDE_CONFIG_DIR decides which profile is user scope, and a machine can have several. On one with a second profile, a correct, connected registration was invisible to every claude mcp get run from the other one, and looked like a failed install for an afternoon.

Naming the file costs one line and removes the ambiguity, so a report of success can be checked rather than believed.

desktop_config_path

desktop_config_path() -> Path | None

Claude Desktop's config file for this platform, whether or not it exists.

install_claude_code

install_claude_code(dry_run: bool) -> list[str]

Register with Claude Code via its own CLI. Returns report lines.

install_desktop

install_desktop(config: Path, command: list[str], dry_run: bool) -> list[str]

Register the server in Claude Desktop's config. Returns report lines.

pinned_port_warning

pinned_port_warning(entry: dict | None) -> list[str]

Warn when a config pins HOUDINI_PORT, which disables port discovery.

An explicit HOUDINI_PORT is honoured deliberately by the server: it means "this session, not whichever answers first". But it also switches off the scan of 8100-8115, so a second Houdini that moved itself to 8101 becomes unreachable. Worth saying out loud, since the value is usually left over from an older config rather than chosen.

printable_argv

printable_argv(argv: list[str]) -> str

An argv a person can paste back into a shell.

repoint_claude_code

repoint_claude_code() -> list[str]

Replace a Claude Code entry that points at a different interpreter.

claude mcp add has no --force, so an existing entry is an error rather than an update. This used to be reported back with the two commands needed to fix it, which was the installer declining to finish its own job: it knows the value that is there, it knows the one that should be, and the operator consented by running install. Printing homework instead is the same "four steps across two worlds" this command exists to remove.

Removing first is safe in a way a blind overwrite would not be, because the old value is read and reported before anything changes.

resolve_houdini_dirs

resolve_houdini_dirs(explicit: str | None) -> tuple[list[Path], str]

Where the package file goes. An empty list means there is nowhere yet.

Every candidate, not one of them. This went through two wrong answers before landing here, and the reasoning matters because it looks like carelessness.

The original code refused to choose, on the grounds that choosing wrongly is invisible: Houdini skips a package file it cannot resolve without a word, so the wrong directory produces silence rather than an error. True, and it made the command exit rather than finish. The second answer was to ask, which only moved the decision without removing it, and made the whole thing depend on there being a terminal.

Both missed that the premise does not apply to "all of them". The files are byte-identical and point at the same plugin, so whichever directory Houdini reads, it finds a correct one. There is no last-one-wins hazard between copies of the same file, which is the only reason ambiguity was dangerous. OneDrive's Documents redirection stops being a question to answer and becomes two paths that both work.

The cost is an MCP menu in a Houdini version you may not use, removable in one uninstall. That is a much smaller price than a command that stops.