Both agents now get the MCP server without a separate launcher: home-manager owns one config file per agent next to each agent's own mutable state file (~/.codex/config.toml and ~/.claude.json stay untouched), and zsh aliases make the layered profile / --mcp-config call the default. Drop pkgs.chromium and --executable-path: the browsers bundled with playwright-mcp work on their own, so this removes a redundant chromium closure. --browser chrome would have looked for a branded Chrome under /opt/google. Add --output-dir so page snapshots stop landing in whatever repository the agent runs in, and give each agent its own profile so both can drive a browser in parallel. Verified on tnix: codex --profile playwright mcp list shows the server, and claude reports browser_navigate as available (NO in a control directory). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01543pvEuBqu1G4rimyXq3YA
84 lines
3.4 KiB
Nix
84 lines
3.4 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
let
|
|
# This is intentionally an explicit guard in addition to flake.lock. An
|
|
# input update must consciously update this value and repeat the MCP smoke
|
|
# test documented in docs/playwright-mcp.md.
|
|
playwrightMcpVersion = "0.0.76";
|
|
|
|
stateDir = "${config.home.homeDirectory}/.local/share/playwright-mcp";
|
|
|
|
# A Chromium user-data directory may be used by exactly one process at a
|
|
# time. Each agent therefore gets its own profile, so Codex and Claude may
|
|
# drive a browser in parallel instead of failing over a locked profile.
|
|
agents = [ "codex" "claude" ];
|
|
profileDir = agent: "${stateDir}/${agent}/profile";
|
|
|
|
# The browsers Playwright was tested against already ship with the
|
|
# playwright-mcp package; pkgs.chromium would be a second, redundant browser
|
|
# closure. "chrome" would instead look for a branded Google Chrome under
|
|
# /opt/google/chrome and fail.
|
|
mkServer = agent: {
|
|
command = "${pkgs.playwright-mcp}/bin/playwright-mcp";
|
|
args = [
|
|
"--browser"
|
|
"chromium"
|
|
"--user-data-dir"
|
|
(profileDir agent)
|
|
# Without this the server drops page snapshots into a .playwright-mcp
|
|
# directory below the current working directory, i.e. into whatever
|
|
# repository the agent happens to run in.
|
|
"--output-dir"
|
|
"${stateDir}/${agent}/output"
|
|
];
|
|
# The nixpkgs wrapper switches to an in-memory profile unless this is set,
|
|
# which would discard logins between sessions.
|
|
env.PLAYWRIGHT_MCP_USER_DATA_DIR = profileDir agent;
|
|
};
|
|
|
|
codexConfig = (pkgs.formats.toml { }).generate "codex-playwright.toml" {
|
|
mcp_servers.playwright = mkServer "codex" // {
|
|
startup_timeout_sec = 20;
|
|
tool_timeout_sec = 60;
|
|
};
|
|
};
|
|
|
|
claudeConfig = (pkgs.formats.json { }).generate "claude-playwright.json" {
|
|
mcpServers.playwright = mkServer "claude";
|
|
};
|
|
|
|
claudeConfigPath = "${config.home.homeDirectory}/.claude/mcp-playwright.json";
|
|
in
|
|
{
|
|
assertions = [
|
|
{
|
|
assertion = pkgs.playwright-mcp.version == playwrightMcpVersion;
|
|
message = "playwright-mcp changed from ${playwrightMcpVersion}; review its release notes, update the pin, and repeat the MCP smoke test.";
|
|
}
|
|
];
|
|
|
|
# Both agents read a file this module owns. Neither agent's own mutable
|
|
# state file is touched: Codex rewrites ~/.codex/config.toml (trust levels,
|
|
# model notices) and Claude rewrites ~/.claude.json, so home-manager must
|
|
# not own either. Codex layers a named profile on top of its base config,
|
|
# Claude takes an additional config file on the command line.
|
|
home.file.".codex/playwright.config.toml".source = codexConfig;
|
|
home.file.".claude/mcp-playwright.json".source = claudeConfig;
|
|
|
|
# Make it the default for the interactive shell rather than a separate
|
|
# command that has to be remembered. zsh does not re-expand an alias into
|
|
# itself, so these still call the real binaries.
|
|
programs.zsh.shellAliases = {
|
|
codex = "codex --profile playwright";
|
|
claude = "claude --mcp-config ${claudeConfigPath}";
|
|
};
|
|
|
|
# Browser profiles are runtime data, not Nix store artifacts; they only need
|
|
# to exist with private permissions before the first launch.
|
|
home.activation.playwrightMcpDirs =
|
|
lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
|
run ${pkgs.coreutils}/bin/install -d -m 0700 ${lib.escapeShellArg stateDir} \
|
|
${lib.concatMapStringsSep " " (agent:
|
|
lib.escapeShellArg "${stateDir}/${agent}") agents}
|
|
'';
|
|
}
|