Cursor mcp.json for the hosted Sume server: env interpolation or OAuth

Cursor reads .cursor/mcp.json with url and headers and supports ${env:NAME}. Pass the Sume key from the environment, or omit headers and use OAuth. Validated.

5 min readSume
All posts

In Cursor, put the hosted Sume server in .cursor/mcp.json (per project) or ~/.cursor/mcp.json (global) with url set to https://mcp.sume.com/mcp, and either add headers using ${env:SUME_API_KEY} or leave headers out and complete OAuth when Cursor prompts. The Cursor MCP documentation (read 2026-10-06) lists ${env:NAME}, ${userHome} and ${workspaceFolder} as interpolation forms, so the key does not need to be pasted into a file that gets committed.

The hosted quickstart shows the OAuth variant as the minimal entry, with only url, and says the consent page is on the MCP host.

Project file or global file?

Choose by who is holding the config, and write the choice in your contributing guide so it is made once rather than argued in every pull request. A project file is shared by everyone who clones the repository, so it should not contain a personal credential; with OAuth, each developer signs in as themselves and gets a read-only grant until they switch Write on. A global file belongs to one person, and the environment-variable form is reasonable there. If you commit .cursor/mcp.json, commit the ${env:...} form or the bare-URL form, never a literal key.

What do the two variants look like, and how can I test them?

import json

oauth_variant = {"mcpServers": {"sume": {"url": "https://mcp.sume.com/mcp"}}}
key_variant = {"mcpServers": {"sume": {
    "url": "https://mcp.sume.com/mcp",
    "headers": {"Authorization": "Bearer ${env:SUME_API_KEY}"},
}}}

def check(cfg):
    s = cfg["mcpServers"]["sume"]
    assert s["url"] == "https://mcp.sume.com/mcp"
    for value in s.get("headers", {}).values():
        assert "${env:" in value, "literal credential in config"
    return "headers" in s

assert check(oauth_variant) is False
assert check(key_variant) is True
literal = {"mcpServers": {"sume": {"url": "https://mcp.sume.com/mcp",
                                   "headers": {"Authorization": "Bearer sk_live"}}}}
try:
    check(literal)
except AssertionError:
    print("literal key rejected; both variants ok")
print(json.dumps(key_variant, indent=2))

How do I keep a literal key out of the repo?

Run a check like this in CI over any committed mcp.json. It rejects a header value without an ${env: reference, which is the easiest way to stop an accidental key from reaching a repository. Test the config on a clean machine at least once, because a file that works only on yours is often relying on a variable you forgot you had set. Remember that Cursor resolves the variable from the environment Cursor itself was started in; a variable exported in a terminal tab after Cursor launched may not be visible, so restart Cursor from a shell that has it, or set it at login.

What should I call first?

Finish with the read-only checks. Ask the agent to call tools_list once, as the quickstart suggests, then mcp_health, which reports the auth source and safety posture. The scopes are described in Sume hosted OAuth and API keys: OAuth means mcp:read unless you switch Write on, and an API key means the full tool set. Paid tools additionally require an idempotency_key, and Sume hosted tools and gates lists the optional dry_run and max_spend_usd guards. Use them from the first paid call, not the tenth.

A few Cursor-specific cautions follow from how project files work. A .cursor/mcp.json in a repository is read for everyone who opens it, so a hostile or careless pull request that changes the url could send your credentials to another host; review changes to that file as carefully as changes to a deploy script, and keep the url pinned to https://mcp.sume.com/mcp. The check above already asserts the exact address, which turns an edit to it into a failing test. Also avoid putting the key in ${userHome}-relative files that other tools sync to cloud storage. If you ever see a key in a commit, treat it as leaked: revoke it in the dashboard and create a new one rather than rewriting history and hoping nobody copied it. Finally, keep the checking script small and boring. A test that loads the JSON, asserts one URL and one header pattern is easy to review, runs in milliseconds and fails with a message that names the problem, which is the standard a guard like this has to meet if people are going to leave it enabled. If your team already lints JSON in CI, add it as one more case beside the existing ones.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume