Skip to main content

ChangeX MCP server: edit a .docx through an MCP client and get native Word revisions plus a portable .changex provenance journal.

Project description

changex-mcp

The ChangeX MCP server: an MCP client (Claude Code/Desktop, OpenAI, Gemini CLI, …) opens a .docx, makes small intent-named edits, and gets back

  1. a Word file with native accept/reject revisions authored by the model, and
  2. a portable, hash-chained .changex provenance journal recording what changed, where, and (where known) why and by whom.

It is a thin FastMCP (stdio) wrapper around the changex-core spine. The server holds in-process, single-session-per-handle state; the journal is flushed to disk on every edit, and the edit sequence number is server-assigned so concurrent tool calls in one turn stay ordered and race-free.

Install / run

# zero-clone (recommended for end users):
uvx changex-mcp

# from this monorepo (dev): installs core from the workspace
uv sync
uv run changex-mcp

# or with pip:
pip install changex-mcp        # pulls in changex-core
python -m changex_mcp          # identical to the `changex-mcp` script

All three forms start the same stdio server. Setup is under 10 minutes: install, drop one of the config blocks below into your client, restart the client.

Remote HTTP transport (connector-URL clients)

Some clients don't spawn a local process — they connect to an MCP server over a URL. claude.ai custom connectors and ChatGPT app connectors are both URL-based. For those, run the same server over Streamable HTTP instead of stdio:

# loopback only (default host 127.0.0.1, port 9000, path /mcp) — no token needed
changex-mcp --http
# → serves http://127.0.0.1:9000/mcp

# pick host / port / path
changex-mcp --http --host 127.0.0.1 --port 9000 --path /mcp

Everything is also configurable by environment variable (CLI flags win over env):

Env var Meaning Default
CHANGEX_MCP_TRANSPORT stdio | http | sse stdio
CHANGEX_MCP_HOST HTTP bind host 127.0.0.1
CHANGEX_MCP_PORT HTTP bind port 9000
CHANGEX_MCP_PATH HTTP endpoint path /mcp
CHANGEX_MCP_TOKEN Bearer token (see security) (none)
CHANGEX_MCP_PUBLIC 1 to acknowledge a non-loopback bind (off)

The HTTP deps (starlette + uvicorn) ship with the SDK's cli extra; if you installed a minimal wheel, add them with pip install "changex-mcp[http]".

Connector URL shape

http://<host>:<port><path>          e.g.  http://127.0.0.1:9000/mcp

That URL is exactly what you paste into a claude.ai custom connector or a ChatGPT app connector. Authenticate with an Authorization: Bearer <CHANGEX_MCP_TOKEN> header (required for any non-loopback bind; optional but recommended on loopback).

Security: this server edits local files

Because the tools write .docx/.changex files on the host, the bind policy is fail-closed:

  • Default is loopback (127.0.0.1). A loopback bind needs no token.

  • Binding to a non-loopback host or 0.0.0.0 is refused unless you supply both:

    1. the explicit --public flag (or CHANGEX_MCP_PUBLIC=1), and
    2. a bearer token in CHANGEX_MCP_TOKEN.

    A public bind without a token aborts with a clear warning rather than silently exposing file-editing tools to the network:

    CHANGEX_MCP_TOKEN=$(openssl rand -hex 32) changex-mcp --http --host 0.0.0.0 --public
    

To reach a loopback HTTP server from a cloud client, put it behind a TLS reverse proxy / tunnel and keep the bearer token on — never expose the raw port.

Tools

Tool Purpose
open_tracked(path, agent_context?, author?) Open a .docx; returns {handle, summary, baseline_sha256, session_id}. Pass agent_context={"model","vendor"} so revisions are authored by your model.
get_outline(handle, cursor?, limit?) Bounded, paginated paragraph list → discover node_ids. Returns {nodes:[{node_id,kind,preview,style}], next_cursor, total}.
edit(handle, op, node_id, …) One small tracked edit. opreplace_text / insert_text_after / delete_text / set_paragraph_style. Returns {op_id, seq, node_id, provenance_source}.
reject(handle, op_id) Reject a change by op_id: the op is non-destructively reverted (the rejection itself is audited) and excluded from the next save_tracked, so its revision is genuinely absent from the saved .docx. Returns {op_id, status, reverted, active_ops, verified}.
accept(handle, op_id) Accept (un-reject) a previously rejected op_id so its revision is kept and reappears on the next save_tracked. Returns {op_id, status, reverted, active_ops, verified}.
save_tracked(handle, out) Write the native-revisions .docx as a pure projection of the journal's non-reverted events; returns {tracked_path, changex_path, ops, verified} (ops = active op count).
get_changes(handle) The structured provenance journal: {session_id, events:[…], count, verified}.
render_review(handle, fmt?) Human-readable redline; fmthtml / markdown. Returns {format, report}.

The edit contract (boundary-enforced, not just prompted)

edit is intent-dispatched on op; supply only that intent's fields:

replace_text        → node_id, before (exact current text), after
insert_text_after   → node_id, anchor (exact text to insert after), text
delete_text         → node_id, before (exact text to delete)
set_paragraph_style → node_id, style (new), before (current style name)

The server refuses:

  • before_mismatchbefore/anchor must match the node's current text exactly. This kills blind full-node overwrites.
  • split_required — an op rewriting >50% of a paragraph is rejected with a structured message instructing the model to split it into smaller replace_text edits. The error is the prompt.

Errors are returned as {"error": "<code>", "detail": "<message>"}.

Provenance: observed vs declared (honest)

MCP tool calls do not carry the user's prompt, conversation turn, model id, or vendor. So ChangeX splits provenance and labels each event with provenance_source:

  • observed (server-captured, not trusted from the agent): ts, session_id, tool_call_id (transport request id), and client_name / client_version from the MCP clientInfo handshake.
  • declared (agent-supplied, optional, may be null): agent (model id) and vendor — captured once at open_tracked via agent_context; plus optional per-edit rationale, prompt (hashed to prompt_sha256, never stored verbatim), and turn_id.

MCP client configuration (copy-paste)

These use uvx changex-mcp. If you installed with pip, replace the command with python and args with ["-m", "changex_mcp"].

Claude Code

claude mcp add changex -- uvx changex-mcp

…or add to ~/.claude.json (or the project .mcp.json):

{
  "mcpServers": {
    "changex": {
      "command": "uvx",
      "args": ["changex-mcp"]
    }
  }
}

Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json (macOS) / %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "changex": {
      "command": "uvx",
      "args": ["changex-mcp"]
    }
  }
}

OpenAI (Agents SDK / MCPServerStdio)

from agents.mcp import MCPServerStdio

changex = MCPServerStdio(
    params={
        "command": "uvx",
        "args": ["changex-mcp"],
    },
    cache_tools_list=True,
)
# then attach `changex` to your Agent(..., mcp_servers=[changex])

For the OpenAI Responses API hosted-MCP shape, point a stdio bridge at uvx changex-mcp.

Gemini CLI

~/.gemini/settings.json:

{
  "mcpServers": {
    "changex": {
      "command": "uvx",
      "args": ["changex-mcp"]
    }
  }
}

claude.ai custom connector (remote, URL-based)

Custom connectors dial a URL, so run the HTTP transport first:

export CHANGEX_MCP_TOKEN=$(openssl rand -hex 32)
changex-mcp --http            # → http://127.0.0.1:9000/mcp

Then in claude.ai → Settings → Connectors → Add custom connector:

  • URL: http://127.0.0.1:9000/mcp (or your TLS-tunneled public URL)
  • Authentication: header Authorization: Bearer <CHANGEX_MCP_TOKEN>

A cloud client can't reach 127.0.0.1 on your laptop directly — front the loopback server with a TLS tunnel/reverse proxy and use that HTTPS URL, keeping the bearer token on. Any non-loopback bind requires --public + the token.

ChatGPT app connector (remote, URL-based)

Same server, same URL shape. Start it:

export CHANGEX_MCP_TOKEN=$(openssl rand -hex 32)
changex-mcp --http --host 127.0.0.1 --port 9000 --path /mcp

In ChatGPT → Settings → Connectors / Apps → Add (developer mode for a custom MCP app):

  • MCP server URL: https://<your-tunnel-host>/mcp (point your tunnel at the loopback http://127.0.0.1:9000/mcp)
  • Auth: bearer token = CHANGEX_MCP_TOKEN

End-to-end example (what the model calls)

// 1. open
open_tracked({ "path": "/abs/report.docx",
               "agent_context": { "model": "claude-opus-4-8", "vendor": "anthropic" } })
// → { "handle": "ab12…", "summary": {…}, "baseline_sha256": "…", "session_id": "…" }

// 2. discover node_ids
get_outline({ "handle": "ab12…" })
// → { "nodes": [ { "node_id": "p:00000002", "preview": "The quick brown fox…" } ], … }

// 3. smallest edits
edit({ "handle": "ab12…", "op": "replace_text",
       "node_id": "p:00000002", "before": "quick", "after": "swift",
       "rationale": "tighter wording" })
edit({ "handle": "ab12…", "op": "set_paragraph_style",
       "node_id": "p:00000001", "before": "Normal", "style": "Heading 1" })

// 4. review: reject (drop) or accept (restore) individual changes by op_id
reject({ "handle": "ab12…", "op_id": "…op-id-of-edit-2…" })
// → { "op_id": "…", "status": "rejected", "reverted": true, "active_ops": 1, "verified": true }
accept({ "handle": "ab12…", "op_id": "…op-id-of-edit-2…" })
// → { "op_id": "…", "status": "accepted", "reverted": false, "active_ops": 2, "verified": true }

// 5. save → native Word revisions + .changex (only non-reverted ops are rendered)
save_tracked({ "handle": "ab12…", "out": "/abs/report.tracked.docx" })
// → { "tracked_path": "…", "changex_path": "…/report.changex", "ops": 2, "verified": true }

// 6. review / audit
render_review({ "handle": "ab12…", "fmt": "markdown" })
get_changes({ "handle": "ab12…" })

Notes / limits

  • docx only — the MCP server opens .docx files. Within docx the full op set is available: text insert/delete/replace, paragraph insert/delete, style change, and (since v0.1.1) format.run (run-property revision) and node.move (paragraph move). The other formats (xlsx/csv/pptx/md) are tracked via the changex track CLI rather than this server.
  • Single-session per document. Opening the same file twice in one server is refused rather than left undefined.
  • The hash chain is tamper-evidence for accidental corruption / naive tampering, not adversarial integrity — signing is a later milestone.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

changex_mcp-0.1.14.tar.gz (23.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

changex_mcp-0.1.14-py3-none-any.whl (27.7 kB view details)

Uploaded Python 3

File details

Details for the file changex_mcp-0.1.14.tar.gz.

File metadata

  • Download URL: changex_mcp-0.1.14.tar.gz
  • Upload date:
  • Size: 23.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for changex_mcp-0.1.14.tar.gz
Algorithm Hash digest
SHA256 1c2fdbe64854a20305222ed023951fa5835beb1db019089e03978b74b5c8303d
MD5 ffd0a856e0365a4ff8d40b52a9d61867
BLAKE2b-256 08c149600029d4597374b713232b5500a69238f110e8ebef5dc6c2ea5d038066

See more details on using hashes here.

File details

Details for the file changex_mcp-0.1.14-py3-none-any.whl.

File metadata

  • Download URL: changex_mcp-0.1.14-py3-none-any.whl
  • Upload date:
  • Size: 27.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for changex_mcp-0.1.14-py3-none-any.whl
Algorithm Hash digest
SHA256 231388ae711887670aa228a4873cfbca7f9f89a73065f800de9a98138a30ae3f
MD5 ab17e95785ba25c6ad06d2c6b1e72472
BLAKE2b-256 b2fdff00af42de34ff0c97ca3b646f02564257ee8d20cf51205a95402d66962d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page