DEV Community

Emre Baykal
Emre Baykal

Posted on

Connecting Claude Code to the MCP server built into HPE Morpheus Enterprise

Every HPE Morpheus Enterprise and HPE VM Essentials appliance from 9.0 on has an MCP server built in, at /api/mcp. That means an AI assistant such as Claude Code can list instances, read a tenant, order a catalog item or check a job without anyone writing an API client first. I connected Claude Code to a Morpheus Enterprise appliance and a VM Essentials appliance in our lab, and this is the setup, step by step.

The part I cared about most was control. Reads should just work. Anything that changes the appliance should stop and ask me first, and that rule should be enforced by the tools, not by the assistant remembering it.

Tested with Morpheus Enterprise 9.1.0.1 (three app nodes behind HAProxy), VM Essentials 9.0.2 (single manager) and Claude Code 2.1 in VS Code on macOS. I ran the steps on macOS only; the commands are plain POSIX shell and should behave the same on Linux and in WSL.

How the Morpheus MCP server works

The client talks to https://<appliance>/api/mcp over HTTPS with the same bearer token you would use for the REST API. Each tool call is passed on to the REST API with that token, so the user's role decides what the assistant can see and do. MCP adds no permissions of its own.

There are more than a thousand tools, grouped in categories such as instances, clouds, catalog and admin. A new session does not get all of them. It starts with about 70: a few meta-tools plus one loader per category (use_instances_tools, use_admin_tools and so on), and the assistant calls a loader before it can use that category's tools. Write tools are left out of a category's starter set and are loaded on request, which keeps a casual question from turning into a change.

One request header matters a lot here. A session opened with X-Mcp-Read-Only-Tools: true is read-only on the appliance side: a write tool called in that session is refused with "Write operations are disabled for this agent (read-only mode)". We use that to build two connections to the same appliance.

Connection Header In Claude Code
morpheus-ro X-Mcp-Read-Only-Tools: true allowed, no prompt
morpheus none asks before every call

Before you start

You need a Morpheus Enterprise or VM Essentials appliance on 9.0 or later, Claude Code, and a Morpheus user whose role covers what you want the assistant to do. A System Admin works for a lab; for anything else, give the MCP user a role with only the permissions it needs, since the token carries exactly those rights.

Use the appliance's DNS name everywhere, the one in its certificate. Connecting by IP address fails the TLS name check even after you trust the certificate.

The commands work as written on macOS and Linux and need curl and openssl, which both normally have. On Windows, do everything inside WSL: install Claude Code in the WSL distribution and, if you use VS Code, open your folder through the WSL extension so the panel runs there too. A native Windows setup would need a PowerShell version of the header script in step 3, which this article does not cover.

The steps connect one appliance, Morpheus Enterprise or VM Essentials alike; the example name is morpheus.example.com, so replace it with yours.

Install Claude Code

Claude Code needs a paid Claude plan (Pro, Max, Team or Enterprise) or a Claude Console account; the free plan does not include it. Install the command-line tool, because the setup below uses it. The same command works on macOS, Linux and inside WSL:

curl -fsSL https://claude.ai/install.sh | bash
claude --version
Enter fullscreen mode Exit fullscreen mode

If claude --version answers command not found, the folder the installer wrote to is not on your PATH yet. Add it to your shell's startup file: ~/.zshrc on macOS, where zsh is the default shell, and ~/.bashrc on most Linux distributions and WSL. For zsh:

echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
Enter fullscreen mode Exit fullscreen mode

For bash, the same two lines with ~/.bashrc. Then start claude once and sign in through the browser.

If you would rather work in VS Code, add the Claude Code extension as well (Extensions, search "Claude Code"). The extension carries its own copy for the chat panel but does not put claude on your PATH, so keep the CLI for the commands in this article. Both use the same MCP configuration.

Step 1: Check the endpoint

Create an API key in Morpheus under User Settings > API Keys for the user from "Before you start". Then check that the MCP server answers:

MORPHEUS_TOKEN=paste-the-api-key-here
curl -s -X POST "https://morpheus.example.com/api/mcp" \
  -H "Authorization: Bearer $MORPHEUS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"1"}}}'
Enter fullscreen mode Exit fullscreen mode

You should get a JSON-RPC result with "serverInfo":{"name":"morpheus",...}. No output at all usually means the certificate is not trusted yet: add -k after curl -s for this check only, and step 2 makes Claude Code trust it properly.

Step 2: Trust a self-signed certificate

Skip this if the appliance has a certificate from a public or company CA that your machine already trusts.

Claude Code runs on Node and reads extra trusted certificates from NODE_EXTRA_CA_CERTS. Save the appliance certificate to a file and point that variable at it:

mkdir -p ~/.morpheus
echo | openssl s_client -connect morpheus.example.com:443 -servername morpheus.example.com 2>/dev/null | openssl x509 > ~/.morpheus/morpheus-ca.pem
openssl x509 -in ~/.morpheus/morpheus-ca.pem -noout -subject
echo "$HOME/.morpheus/morpheus-ca.pem"
Enter fullscreen mode Exit fullscreen mode

The openssl x509 line should print the appliance's certificate subject; an error there means the download did not work. Add the file to the env block of ~/.claude/settings.json, with the full path the echo printed (/Users/<you>/... on macOS, /home/<you>/... on Linux and WSL). If the file already has content, add the env key to it rather than replacing the file:

{
  "env": {
    "NODE_EXTRA_CA_CERTS": "/full/path/from/echo/.morpheus/morpheus-ca.pem"
  }
}
Enter fullscreen mode Exit fullscreen mode

Claude Code reads this at startup, so it takes effect after the restart in step 6.

Step 3: Keep the token out of the config

Claude Code can run a small script on every connection and send the headers it prints. That lets the API key live in a file only you can read, instead of in the MCP configuration. First the key file:

mkdir -p ~/.morpheus && chmod 700 ~/.morpheus
cat > ~/.morpheus/morpheus.env <<'EOF'
MORPHEUS_HOST=morpheus.example.com
MORPHEUS_TOKEN=paste-the-api-key-here
EOF
chmod 600 ~/.morpheus/morpheus.env
Enter fullscreen mode Exit fullscreen mode

Then the script. Save it with any editor as ~/.morpheus/mcp-headers.sh:

#!/bin/sh
# Prints the Authorization header for a Morpheus MCP connection.
# Claude Code runs it on every connection and passes the server URL.
ENV_FILE="$HOME/.morpheus/morpheus.env"
[ -r "$ENV_FILE" ] || { echo "cannot read $ENV_FILE" >&2; exit 1; }
. "$ENV_FILE"

# Only send the key to the host it belongs to.
URL_HOST=$(printf '%s' "$CLAUDE_CODE_MCP_SERVER_URL" | sed -E 's#^https://([^/:]+).*#\1#')
[ "$URL_HOST" = "$MORPHEUS_HOST" ] || { echo "refusing $URL_HOST" >&2; exit 1; }
[ -n "$MORPHEUS_TOKEN" ] || { echo "no MORPHEUS_TOKEN in $ENV_FILE" >&2; exit 1; }

printf '{"Authorization":"Bearer %s"}\n' "$MORPHEUS_TOKEN"
Enter fullscreen mode Exit fullscreen mode

The host check costs one line and means a typo in the configuration can never send this key to another appliance. Test it the way Claude Code will call it:

chmod 700 ~/.morpheus/mcp-headers.sh
CLAUDE_CODE_MCP_SERVER_URL=https://morpheus.example.com/api/mcp ~/.morpheus/mcp-headers.sh | cut -c1-30
Enter fullscreen mode Exit fullscreen mode

You should see {"Authorization":"Bearer followed by the start of the key.

Step 4: Add the connections

Run these from the folder you work in with Claude Code. Local scope keeps the entries in ~/.claude.json for that folder only, out of any repository:

H="$HOME/.morpheus/mcp-headers.sh"

claude mcp add-json --scope local morpheus-ro \
  "{\"type\":\"http\",\"url\":\"https://morpheus.example.com/api/mcp\",\"headers\":{\"X-Mcp-Read-Only-Tools\":\"true\"},\"headersHelper\":\"$H\"}"

claude mcp add-json --scope local morpheus \
  "{\"type\":\"http\",\"url\":\"https://morpheus.example.com/api/mcp\",\"headersHelper\":\"$H\"}"
Enter fullscreen mode Exit fullscreen mode

Step 5: Set the permissions

Create .claude/settings.local.json in the same folder (mkdir -p .claude first; if the file exists, add the permissions key to it). If the folder is a git repository, keep this file out of git:

{
  "permissions": {
    "allow": ["mcp__morpheus-ro__*"],
    "ask":   ["mcp__morpheus__*"]
  }
}
Enter fullscreen mode Exit fullscreen mode

The read-only connection runs without interrupting you, and the appliance itself refuses writes on it. Every call on the full connection shows a prompt, so the moment of approval is the moment you see the exact tool and arguments. Keep the full connection out of allow; the whole design rests on that.

Step 6: Trust the folder and restart

Claude Code runs a headersHelper only in a folder you have trusted. Start claude once from a terminal in the same folder as step 4, accept the trust question, and leave with /exit. If you use the VS Code panel, reload the window afterwards (Command Palette, Developer: Reload Window) so it picks up the new settings.

Check the connections:

claude mcp list
Enter fullscreen mode Exit fullscreen mode

Both should show Connected. Then ask Claude something like "who am I on morpheus-ro?". It loads the admin tools and answers with your user, without a prompt. Ask the same through the full connection and you should get a permission prompt.

What it looks like in use

You talk to Claude in plain language, in the terminal (claude) or in the VS Code panel, and it picks the tools itself; naming the connection in the question ("on morpheus-ro") tells it which one to use. Typing /mcp shows the connections, their state and tool counts, with a reconnect action for each.

Two questions from our lab, the first on a VM Essentials appliance and the second on Morpheus Enterprise, with the answers shortened and the names changed:

How is the HVM cluster on morpheus-ro doing?

Claude Code loads the clusters category on morpheus-ro, calls list_clusters and answers without asking anything:

Cluster prod-hvm (HVM 1.3 Cluster on HVM/Ubuntu 24.04): status ok, synced a minute ago.
Hosts: hvm-node01, hvm-node02, hvm-node03, all online.
CPU: 8% average, 15% peak.
Memory: 54 GB in use of 810 GB; 767 GB (95%) is reserved.
Storage: 1.9 TB used of 319 TB.
Enter fullscreen mode Exit fullscreen mode

Which catalog items have to do with Kubernetes on morpheus-ro?

7 enabled items:
- Build Kubernetes Cluster (instance)
- Kubernetes Namespace (workflow, Self Service)
- Kubernetes Metallb Config (workflow)
- Kubernetes NFS Storage Class Conf. (workflow)
- K8s Web Info, Voting App, Grafana (blueprints)
Enter fullscreen mode Exit fullscreen mode

A long result, such as the full catalog, comes back as a preview with an artifact ID, and Claude Code pages through the rest with get_result_excerpt only when it needs to. That keeps a big list from filling the conversation.

A change takes the other path. On the VM Essentials appliance:

Create a small Ubuntu 24.04 VM called demo-vm on the HVM cluster, 1 CPU and 2 GB.

Claude first works out the options on morpheus-ro: the cloud, group and layout, the plans for that layout, the network and the image, mostly by reading an existing Ubuntu VM. Then it switches to the full morpheus connection and stops at a permission prompt for create_instance with the request it is about to send:

{
  "zoneId": 1,
  "instance": {"name": "demo-vm", "site": {"id": 1}, "instanceType": {"code": "ubuntu"},
               "layout": {"id": 216}, "plan": {"id": 19}},
  "config": {"resourcePoolId": "pool-1", "createUser": true, "imageId": 239},
  "servicePlanOptions": {"maxCores": 1, "coresPerSocket": 1},
  "volumes": [{"id": -1, "rootVolume": true, "name": "root", "size": 20, "storageType": 1, "datastoreId": 16}],
  "networkInterfaces": [{"network": {"id": "network-4"}}]
}
Enter fullscreen mode Exit fullscreen mode

Nothing reaches the appliance until you accept. If the appliance rejects the request, nothing is created and its message comes back as text; on ours it asked for coresPerSocket, which Claude added before asking again. After the second approval:

Instance 464 demo-vm created, status pending.
About a minute later, on morpheus-ro: demo-vm is running,
1 CPU / 2 GB, 20 GB root disk, an address from the VLAN's IP pool.
Enter fullscreen mode Exit fullscreen mode

Deleting goes through the same prompt, and the delete_instance tool takes the instance name as well as its ID; the appliance refuses the call if the two do not match. On morpheus-ro none of these calls would get through, because the appliance itself refuses writes on that connection.

If the appliance is behind a load balancer

As far as I can tell, an MCP session lives only on the app node that opened it; that is what our three-node appliance shows, at least. Claude Code keeps more than one connection open per session, and if the load balancer spreads them across nodes, the other nodes answer "Session not found" and the client keeps reconnecting.

Pin MCP traffic by its session header. On HAProxy, in the backend that serves the app nodes:

    stick-table type string len 64 size 10k expire 1h
    stick store-response res.hdr(Mcp-Session-Id)
    stick match req.hdr(Mcp-Session-Id)
Enter fullscreen mode Exit fullscreen mode

Only requests carrying Mcp-Session-Id are pinned, so the UI and the REST API keep their normal balancing. One hour matches the MCP session idle timeout. Check with haproxy -c -f /etc/haproxy/haproxy.cfg, then systemctl reload haproxy. A single-node appliance does not need this.

Day to day

After a restart Claude Code reconnects on its own and the script supplies the key again. If the network or the VPN drops for longer than a moment, the connections can end up failed; once you are back on the network, type /mcp reconnect all in Claude Code, or reconnect them one by one from /mcp. Sessions that sit idle for an hour are closed by the appliance and reopened by the client without you noticing. If Claude says a tool it needs is not available in a category it already loaded, reconnect that connection from /mcp; a fresh session starts with all the loaders again.

The API key is what expires eventually. Create a new one in Morpheus, paste it into the env file, and the next connection uses it.

Rollback

claude mcp remove --scope local morpheus-ro
claude mcp remove --scope local morpheus
Enter fullscreen mode Exit fullscreen mode

Then remove the NODE_EXTRA_CA_CERTS line, .claude/settings.local.json and the ~/.morpheus files, and delete the API key in Morpheus.

Checklist

  1. /api/mcp answers initialize.
  2. The certificate is trusted, and you connect by the name in it.
  3. The API key sits in a mode 600 file, and the header script refuses other hosts.
  4. Two connections, read-only and full.
  5. Read-only allowed, full set to ask.
  6. Folder trusted, Claude Code restarted, claude mcp list shows both connected.
  7. Behind a load balancer: MCP sessions pinned by Mcp-Session-Id.

Top comments (0)