Metadata-Version: 2.4
Name: cnx-mcp
Version: 0.2.0
Summary: Local MCP stdio bridge for cnx — GPU rental negotiation agent (proxies to the live cnx /mcp HTTP endpoint; signs and pays locally via a disposable wallet keystore so the harness/LLM never sees a private key)
License: MIT
Keywords: compute,gpu,mcp,negotiation,x402
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: mcp<2,>=1.28
Requires-Dist: x402[evm,httpx]==2.15.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest-mock>=3.14; extra == 'dev'
Requires-Dist: pytest>=8.3.3; extra == 'dev'
Description-Content-Type: text/markdown

# cnx-mcp

A real, installable **local MCP stdio server** for [cnx](../README.md), the
GPU rental negotiation agent. It bridges any MCP-compatible host (Claude
Desktop, Claude Code, etc.) to the live cnx backend over stdio, so you can
add cnx to a host with one line instead of hand-rolling an HTTP bridge.

This package is a **thin protocol bridge, not a reimplementation**. On every
`tools/list` and `tools/call` request it forwards straight through to the
live cnx `/mcp` HTTP endpoint

```
https://ddlcl1en1j.execute-api.us-east-1.amazonaws.com/production/mcp
```

and reshapes the JSON-RPC response into the MCP types the SDK's stdio
transport expects. There are **no hardcoded tool schemas** in this package -
`list_tools()` always reflects whatever the live backend currently serves,
so this bridge can never drift out of sync with the real tool set.

## Install & run

**Not yet published to PyPI.** Once published, any of these will work:

```bash
# No install - run directly
uvx cnx-mcp

# Same idea, via pipx
pipx run cnx-mcp

# Install then run
pip install cnx-mcp
cnx-mcp
```

Until then, run it from a local checkout of this repo:

```bash
# From the repo root
pip install -e ./mcp-server
cnx-mcp

# or, without a persistent install, via uv:
uvx --from ./mcp-server cnx-mcp
```

## Configuring an MCP host

Add cnx to your host's MCP server config. For Claude Desktop
(`claude_desktop_config.json`), once `cnx-mcp` is published:

```json
{
  "mcpServers": {
    "cnx": {
      "command": "uvx",
      "args": ["cnx-mcp"]
    }
  }
}
```

Or, if you installed it with `pip install cnx-mcp`:

```json
{
  "mcpServers": {
    "cnx": {
      "command": "cnx-mcp"
    }
  }
}
```

**Before publish**, point a host at a local checkout instead:

```json
{
  "mcpServers": {
    "cnx": {
      "command": "uvx",
      "args": ["--from", "/absolute/path/to/mcp-server", "cnx-mcp"]
    }
  }
}
```

Claude Code (`.mcp.json` or `claude mcp add`) uses the same
`command`/`args` shape.

## Pointing at a different backend

By default `cnx-mcp` talks to the live production endpoint. Override it with
`CNX_MCP_BASE_URL` - useful for local development or a staging deployment
without needing a new release of this package:

```json
{
  "mcpServers": {
    "cnx": {
      "command": "uvx",
      "args": ["cnx-mcp"],
      "env": {
        "CNX_MCP_BASE_URL": "https://your-staging-endpoint.example.com/mcp"
      }
    }
  }
}
```

Or from the shell:

```bash
CNX_MCP_BASE_URL=http://localhost:3000/mcp cnx-mcp
```

## Available tools

Most of the tool list is discovered dynamically from the live backend on
every `tools/list` call - this README is not the source of truth for it. As
of this writing the backend serves 5 tools: `rent_compute`,
`negotiate_price`, `submit_payment`, `check_rental_status`,
`report_unreachable`. See the main repo's `SKILLS.md` for the current,
authoritative description of each.

One additional tool, **`pay_and_confirm`**, is added locally by this
package - the backend doesn't know about it. See the next section.

## Paying without ever handing your private key to the harness

Settling a real rental means signing an x402 EIP-3009 payment
authorization. Doing that with `submit_payment` directly requires the
calling harness/LLM to hold your private key in its own context, which is
exactly the thing you don't want to hand an autonomous agent.

`pay_and_confirm` solves this by moving signing into this local process,
outside the harness's reach entirely:

1. **One-time setup**, at your own terminal (never through the harness):

   ```bash
   cnx-mcp init-wallet
   ```

   This generates a fresh, disposable EOA keypair, asks you to set a
   password (interactively - never as a command argument or tool call),
   and writes an encrypted keystore to `~/.cnx/keystore.json` (override
   with `CNX_WALLET_KEYSTORE_PATH`). It prints the new public address -
   fund that address yourself with only what you intend to spend on
   rentals. **This should be a purpose-built rental wallet, not your main
   wallet** - there is no separate software spend cap, so the funded
   balance IS the cap: `pay_and_confirm` will pay anything up to whatever
   the wallet actually holds.

2. Set `CNX_WALLET_PASSWORD` in your own shell or MCP host config before
   launching the harness - e.g.:

   ```json
   {
     "mcpServers": {
       "cnx": {
         "command": "uvx",
         "args": ["cnx-mcp"],
         "env": { "CNX_WALLET_PASSWORD": "your-password" }
       }
     }
   }
   ```

3. From here, the harness's normal flow just works: call `rent_compute`
   with `mode="execute"`, then call `pay_and_confirm` with the same
   `task_id` right after. This bridge caches the payment requirement it
   already saw pass through in step one, signs it locally using the
   unlocked keystore, submits it via the backend's existing
   `submit_payment` tool, and returns the receipt - all without the
   harness ever seeing the requirement's signature material or the key
   itself.

**What this protects against, and what it doesn't:** the private key never
enters the LLM/harness's context or token stream - only a `task_id`
argument crosses that boundary, and `pay_and_confirm`'s schema has no
password/key parameter of any kind. This protects against the LLM (or a
prompt-injected tool result) ever seeing or exfiltrating the key. It does
**not** protect against a fully compromised host machine - anything with
read access to this process's memory, or to the keystore file plus your
password, can still get the key. Treat the wallet as disposable and fund
it accordingly.

If you'd rather sign payments yourself from a bare terminal instead of via
MCP, see the main repo's `scripts/cnx_pay.py`, which this tool's signing
path is ported from.

## Development

```bash
cd mcp-server
python3 -m venv .venv
.venv/bin/pip install -e '.[dev]'
.venv/bin/pytest tests/ -v
```

## Publishing

Publishing to PyPI (`uv publish` / `twine upload`) is a deliberate manual
step, not automated by this package or its build tooling. Verify the PyPI
name `cnx-mcp` is still available and the built package passes review before
publishing.
