MCP server
Preview: it works, but expect rough edges. matmul-mcp is an MCP server that lets Claude Code, Claude Desktop, Cursor, VS Code or any other MCP client find and launch GPU machines on MatMul. Ask for "the cheapest H100 under $3/hr with an SSH command" and the assistant drives the MatMul API for you. It can't launch anything until you've approved a price quote.
What it does
The server runs on your own machine over stdio. Your MCP client starts it as a subprocess, and it calls the MatMul API (https://api.matmul.cloud/v1) with your API key. There's no hosted MCP endpoint yet. Through it, an assistant can:
- search live GPU offers across neoclouds, cheapest first;
- check your prepaid balance, burn rate and runway;
- quote and then launch an instance: a plain VM (Ubuntu with the NVIDIA driver) or a Docker image;
- wait until it's running and give you the SSH command;
- list and terminate your instances, and manage your SSH public keys;
- make a Stripe checkout link for adding credit, which you open and pay yourself.
Instances bill per hour from your prepaid balance until they're terminated, the same as instances launched from the console or the CLI.
Install
You need uv (for uvx) or Python 3.10+ with pip. The config snippets below use uvx, which fetches and runs the server without a separate install step:
uvx matmul-mcp --version # run without installing (recommended) pip install matmul-mcp # or install the matmul-mcp command
If you install with pip, use matmul-mcp as the command in your client config instead of uvx with ["matmul-mcp"].
Authentication: your API key
The server acts as you, with your own API key. Create one on the CLI & API page of the console at app.matmul.cloud/app/cli. Keys start with lmk_. Keys belong to a person, so your role in the organization (owner, admin, member or viewer) applies to what the assistant can do, the same as in the console.
The server looks for the key in this order:
- the
MATMUL_API_KEYenvironment variable, which you set in the client config below; - the key saved by
matmul login --api-key <key>in~/.config/matmul/config.json(or the file named byMATMUL_CONFIG).
Other settings, also environment variables:
MATMUL_MCP_MAX_HOURLY_USD: optional hard cap in dollars per hour. The server refuses to quote or launch any instance above it, whatever the assistant asks for, and filtersfind_gpusto it. Recommended.MATMUL_API_URL: API base URL. Defaults tohttps://api.matmul.cloud; a trailing/v1is accepted.MATMUL_MCP_ENABLE_JOBS: set to1to add the managed-jobs tools (off by default; see below).
Treat the key like a password. It's stored in plain text in most client config files, so keep those files out of git. VS Code can prompt for it instead (below).
Set up your client
In each snippet, replace lmk_... with your key. You can leave out MATMUL_API_KEY if you've run matmul login on this machine. The cap of $5/hr is an example: set your own, or leave it out.
Claude Code
claude mcp add matmul \ -e MATMUL_API_KEY=lmk_... \ -e MATMUL_MCP_MAX_HOURLY_USD=5 \ -- uvx matmul-mcp
Add --scope user to make it available in every project. Check it with claude mcp list, or /mcp inside a session.
Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows), then restart Claude Desktop:
{
"mcpServers": {
"matmul": {
"command": "uvx",
"args": ["matmul-mcp"],
"env": {
"MATMUL_API_KEY": "lmk_...",
"MATMUL_MCP_MAX_HOURLY_USD": "5"
}
}
}
}Cursor
Edit ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project):
{
"mcpServers": {
"matmul": {
"command": "uvx",
"args": ["matmul-mcp"],
"env": {
"MATMUL_API_KEY": "lmk_...",
"MATMUL_MCP_MAX_HOURLY_USD": "5"
}
}
}
}VS Code (GitHub Copilot agent mode)
Add .vscode/mcp.json to your workspace. VS Code prompts for the key the first time the server starts and stores it securely, so the key never sits in the file:
{
"inputs": [
{ "type": "promptString", "id": "matmul-key", "description": "MatMul API key", "password": true }
],
"servers": {
"matmul": {
"type": "stdio",
"command": "uvx",
"args": ["matmul-mcp"],
"env": {
"MATMUL_API_KEY": "${input:matmul-key}",
"MATMUL_MCP_MAX_HOURLY_USD": "5"
}
}
}
}How launches are confirmed
launch_instance rents a real machine that bills every hour until it's terminated, so it always takes two calls:
- Quote. The first call launches nothing. It picks the cheapest live offer at or under your price ceiling and returns a quote: the offer, its estimated $/hr, the ceiling, the image (or plain VM), the SSH key, your balance and runway at that price, and a line saying it bills until terminated. It also returns a
confirm_token. - Confirm. The assistant shows you the quote and asks. Only a second call carrying that token, with the same arguments, launches the machine.
The token is what makes the quote a required step:
- It's random (
lq_...), so the only way to get one is the quoting call, and that call's result, with the price, is in the conversation you see. - It's bound to the request: name, GPU, count, region, image, keep-alive, SSH key and ceiling must all match, and the launch uses the offer that was quoted. A mismatch uses up the token.
- It works once and expires after 10 minutes.
The server's instructions tell the assistant to get your explicit yes before the second call. The token proves a quote was shown; it can't prove you said yes. If your client lets you approve tool calls one by one, keep approval on for launch_instance.
The price guard
max_hourly_usd is required on every launch. The assistant is told to use the budget you gave and not invent one. It's sent to the API as the max_hourly_price_cents price guard: if the live price has risen above it by the time the machine is created, the API refuses the launch. The error starts with "Price guard:" and says nothing was launched and nothing was charged, and the assistant can get a fresh quote or ask whether to raise the ceiling.
MATMUL_MCP_MAX_HOURLY_USD is a second limit on top. It lives in your client config, so the assistant can't change it, and the server checks it on both the quote and the launch.
No automatic top-ups
add_credit only returns a Stripe checkout link for you to open and pay in your browser. The server never pays for anything. If a launch fails because the balance is too low or the account is frozen, the error says so and points at get_balance and add_credit.
Tools
Read tools are marked read-only, and terminate_instance (and cancel_job) are marked destructive, so clients that auto-approve reads still ask before those calls. Prices are in US dollars per hour for the whole machine.
find_gpus
find_gpus(gpu?, max_hourly_usd?, region?, count?, limit=10): live GPU offers, cheapest first, up to 100. Read-only and free. Offers change minute to minute, so the launch re-checks the price. Filtered to MATMUL_MCP_MAX_HOURLY_USD when it's set.
get_balance
get_balance(include_ledger?, ledger_limit=10): prepaid balance, whether the account is frozen (and the grace time left), new-account limits if your account isn't verified yet, running instances, burn rate and runway in hours. Optionally recent charges and credits. Read-only.
add_credit
add_credit(amount_usd): returns a Stripe checkout URL. It charges nothing itself; you open the link and pay. The API's minimum and maximum top-up apply. The balance updates a few seconds after payment.
list_instances and get_instance
list_instances(include_deleted?): your organization's instances with status, GPU, region, image, $/hr, accrued cost and SSH command, plus the total burn rate. get_instance(id_or_name): one instance. New instances take a few minutes to provision; the assistant polls this until the status is running and then gives you ssh_command. For an instance launched with an image, container_shell_command opens a shell inside the container (the SSH command lands on the host VM). Both are read-only and warn if the account is frozen.
launch_instance
launch_instance(name, gpu, max_hourly_usd, image?, keep_alive?, ssh_key?, count=1, region?, confirm_token?): quote, then launch, as described above. Spends money.
imageis a Docker image such aspytorch/pytorch:latest; leave it out for a plain VM.keep_alivekeeps an interactive image running and needs an image.ssh_keyis a key id or name. You can leave it out when exactly one key is registered.countis GPUs per machine (1 to 16). Leave outregionto take the cheapest anywhere.
terminate_instance
terminate_instance(id_or_name): deletes the instance and stops its billing from that moment. Returns the accrued cost. Destructive: the machine and everything on its disks are gone. The assistant is told to confirm with you first unless you just asked for exactly this.
list_ssh_keys and add_ssh_key
list_ssh_keys() lists the public keys installed on new instances. add_ssh_key(name, public_key) registers one. It must be a public key line (ssh-ed25519 ..., ssh-rsa ... or ecdsa-..., as in ~/.ssh/id_ed25519.pub); the server refuses anything that looks like a private key.
Managed jobs (off by default)
With MATMUL_MCP_ENABLE_JOBS=1 the server adds run_job, list_jobs, job_logs and cancel_job (destructive). They're opt-in because run_job doesn't take a price ceiling yet (the API and CLI do), and managed jobs aren't switched on for MatMul accounts yet. Leave them off unless you're testing jobs.
Resources and a prompt
matmul://instances: your non-deleted instances, as JSON.matmul://balance: balance and frozen state, as JSON.- Prompt
gpu_dev_box(gpu, max_hourly_usd, image): walks the assistant through find, add key, quote, confirm, wait and SSH. In Claude Code it shows up as a slash command under the server's name.
Example prompts
- "What are the three cheapest H100s available right now?"
- "Find me the cheapest L40S under $1.50/hr and launch a plain VM called dev. Show me the quote first."
- "Launch pytorch/pytorch:latest on an A100 under $2/hr with keep_alive, then give me the SSH command when it's running."
- "Add my public key from ~/.ssh/id_ed25519.pub as laptop."
- "How much is my balance, and how long will it last at the current burn rate?"
- "What's running and what has it cost so far? Terminate anything called test-*."
- "I need $50 more credit: give me the checkout link."
Troubleshooting
- The server doesn't start, or the client can't find
uvx. GUI apps such as Claude Desktop often don't see your shell'sPATH. Use the absolute path fromwhich uvxas the command. Runuvx matmul-mcp --versionin a terminal to check it runs at all. - "Not authenticated with MatMul". The API rejected the key (401). Check
MATMUL_API_KEYin the client config, or runmatmul login --api-key <key>, and restart the client. Create a new key at app.matmul.cloud/app/cli if the old one was revoked. - "MatMul refused this for your role". Your role in the organization doesn't allow the action (403). Ask an owner or admin.
- "MatMul account can't spend right now". The balance is too low or the account is frozen. Ask the assistant for
get_balance, then add credit through the console oradd_credit. - "Price guard: the live price is now above your max_hourly_usd". The live price rose above your
max_hourly_usdbetween the quote and the launch. Get a fresh quote, or raise the ceiling if you're happy to pay more. - "above this server's MATMUL_MCP_MAX_HOURLY_USD cap". The launch asked for more than your cap. Pick a cheaper GPU, or raise the cap in the client config and restart.
- "that confirm_token is unknown, expired or already used", or "issued for different launch parameters". Tokens last 10 minutes, work once and only for the quoted arguments. Ask for a fresh quote. Restarting the client also clears pending quotes.
- "No SSH key is registered" or "Several SSH keys are registered". Add your public key with
add_ssh_key, or name the key to use. - "No 1x H100 available at or under ...". Nothing matches right now. Try
find_gpusfor current prices, another GPU or region, or a higher ceiling. - "Cannot reach the MatMul API". Check your network and, if you set it,
MATMUL_API_URL. "The upstream GPU supplier is unavailable" means a provider error; try again shortly and check Status. - The tools don't show up. Restart the client after editing its config, and check the JSON is valid. To test the server on its own, run it in the MCP Inspector:
MATMUL_API_KEY=lmk_... npx @modelcontextprotocol/inspector uvx matmul-mcp. - Upgrading from Lemnos. The product used to be called Lemnos. The old
LEMNOS_*variable names still work when theMATMUL_*one isn't set, and a key saved in~/.config/lemnos/config.jsonis still read.
Still stuck? See Support. New to MatMul? Start with the docs overview or how to rent a cloud GPU in 5 minutes.
← All docs