Hosted MCP servers

Put your tools at one URL for Claude, ChatGPT, Cursor or any MCP client.

A hosted MCP server puts a set of tools at one URL that any MCP client can use: Claude, ChatGPT, Cursor, your own agent. The tools are the ones a toolkit or an agent takes (a user's connections, single operations, web search and fetch), and NoClick runs them. Give each of your users a server over their own connections, and their assistant can work in their CRM without you running any MCP code.

This is different from the NoClick MCP server, which exposes the platform API itself to your own development tools.

Create a server

import noclick

with noclick.as_user("acme"):
    hubspot = noclick.Connection.from_name("hubspot", integration="hubspot", create_if_missing=True)
    server = noclick.McpServer.from_name("crm-tools", tools=[hubspot, "search"], create_if_missing=True)
    print(server.url)   # https://mcp.noclick.app/s/...

from_name with create_if_missing=True creates the server on first use and, after that, keeps its tools in step with your code. McpServer.create always creates, and raises noclick.Conflict if the name is taken; names are unique for each user, and for your account. A server created inside as_user belongs to that user and acts in the user's connections. Managing servers takes a key with the mcp_servers scope.

Tools are listed the way a toolkit lists them:

EntryServes
"hubspot", or a Connectionevery operation of the connection named hubspot
"hubspot.create_contact"one operation of it
"exa.search", or "exa"an operation that needs no account, or all of them, on NoClick's key
"search", "fetch"web search and a page's text
hubspot.only("search_contacts").bind(owner_id="7")an operation with some arguments fixed, out of every client's reach

A name is a connection in the server's scope first. When no connection has that name, it's the integration of that name, limited to its operations that need no account, so "exa.search" serves a web search with nothing connected. A connection named exa holding your own key takes over the same entry.

A server serves at most 64 tools, so name single operations of a large integration. tool_count is how many it serves right now.

Any client with the URL calls the tools, so fix what no client should choose with bind: the bound parameters leave each tool's schema and NoClick fills them on every call. In a server, {{user.id}} and the rest name the server's user; on an account server there's no user, so a tool bound to one isn't served.

A toolkit you already have becomes a server in one line:

toolkit = noclick.Toolkit(["hubspot", "search"], user="acme")
server = toolkit.mcp_server("crm-tools")

Connect a client

The URL is all a client needs; it speaks Streamable HTTP and needs no other key. The server answers each request on its own (no session state of its own), so any client that speaks MCP over HTTP works. A workflow's hosted MCP link (its urls entry of kind mcp) connects the same way. Below, <url> is the server's URL and <name> the name to give it in the client (such as crm-tools).

Anyone with the URL can call its tools, so keep it like a key: hand it to the one person or agent it's for.

Claude

Works on claude.ai, the Claude desktop app and mobile through custom connectors — Free (one connector), Pro, Max, Team and Enterprise.

  1. Open Customize → Connectors (on Team and Enterprise an Owner adds it under Organization settings → Connectors first).
  2. Click + and choose Add custom connector.
  3. Paste the server URL and click Add.
  4. In a chat, click + → Connectors and turn it on.

Then: claude.ai connects as soon as you click Add.

Claude Code

claude mcp add --transport http <name> <url>

Verify with claude mcp list (it connects and shows ✔ Connected) or /mcp inside a session. To share it with your team instead, add it to .mcp.json in the project root:

.mcp.json:

{
  "mcpServers": {
    "<name>": {
      "type": "http",
      "url": "<url>"
    }
  }
}

Then: Claude Code connects when a session starts — run claude, or restart a running one.

ChatGPT

Custom MCP connectors need developer mode (Plus, Pro, Business, Enterprise and Edu — set up on the web app; on team plans an admin must allow it first).

  1. Open Settings → Apps & Connectors.
  2. Under Advanced settings, toggle Developer mode on.
  3. Back in Apps & Connectors, click Create.
  4. Enter a name and paste the server URL as the MCP Server URL.
  5. Set Authentication to No Authentication, check "I trust this application", and save.
  6. In a chat, enable the connector from the + / tools menu.

Then: ChatGPT connects when you create the connector.

Codex

codex mcp add <name> --url <url>

The Codex app, the CLI and the IDE extension share this config. In the app you can add it instead under Settings → MCP servers → Add server: choose Streamable HTTP, paste the server URL, save, then Restart. Verify with codex mcp list, or /mcp inside a Codex session. Or configure it directly:

~/.codex/config.toml:

[mcp_servers.<name>]
url = "<url>"

Then: Codex connects when a session starts — run codex, or restart a running one (in the Codex app, Restart).

OpenCode

opencode mcp add

Choose Remote, paste the server URL, and answer No to OAuth — the link itself is the key. This only saves the config: OpenCode connects when it starts, and opencode mcp list checks (and connects) from your side. Or add it to opencode.json directly:

~/.config/opencode/opencode.json:

{
  "mcp": {
    "<name>": {
      "type": "remote",
      "url": "<url>",
      "enabled": true
    }
  }
}

Then: opencode mcp add only saves the config. OpenCode connects when it starts — run opencode, or restart a running one.

OpenClaw

openclaw mcp add <name> --url <url>

It probes the server before saving. Or add it to openclaw.json directly:

~/.openclaw/openclaw.json:

{
  "mcp": {
    "servers": {
      "<name>": {
        "transport": "streamable-http",
        "url": "<url>"
      }
    }
  }
}

Then: OpenClaw probes the server while adding it, so this lights up during openclaw mcp add.

Hermes

hermes mcp add <name> --url <url>

Answer no when it asks if the server requires authentication (the link itself is the key). Or add it to your profile config directly:

~/.hermes/config.yaml:

mcp_servers:
  <name>:
    url: "<url>"

Then: Hermes connects when it next starts a session.

Cursor

Add it manually to .cursor/mcp.json in your project (or ~/.cursor/mcp.json for all projects):

.cursor/mcp.json:

{
  "mcpServers": {
    "<name>": {
      "url": "<url>"
    }
  }
}

Cursor Settings → MCP shows a green dot and the tool list once it is live.

Then: Cursor connects as soon as you approve the install.

VS Code

code --add-mcp '{"name":"<name>","type":"http","url":"<url>"}'

Or from the Command Palette:

  1. CTRL/CMD + SHIFT + P and search for MCP: Add Server.
  2. Select HTTP (HTTP or Server-Sent Events).
  3. Paste the server URL and hit enter: <url>
  4. Enter the name <name> and hit enter.

Or add it to .vscode/mcp.json directly. MCP: List Servers shows it and can start it right away:

.vscode/mcp.json:

{
  "servers": {
    "<name>": {
      "type": "http",
      "url": "<url>"
    }
  }
}

Then: VS Code starts the server the first time agent chat needs it.

Windsurf

  1. Open Windsurf Settings → Cascade → MCP Servers (or the MCPs icon in the Cascade panel).
  2. Select Add custom server and enter the configuration below.
  3. Save, then refresh the servers list.

~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "<name>": {
      "serverUrl": "<url>"
    }
  }
}

Then: Windsurf connects when you save the config and refresh the MCP servers list.

Zed

  1. CMD + , to open Zed settings.
  2. Add the server under context_servers:

settings.json:

{
  "context_servers": {
    "<name>": {
      "url": "<url>"
    }
  }
}

Settings → AI → MCP Servers shows a green dot ("Server is active") once it connects. Older Zed builds without remote support can bridge through mcp-remote:

settings.json (legacy bridge):

{
  "context_servers": {
    "<name>": {
      "source": "custom",
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "<url>"
      ],
      "env": {}
    }
  }
}

Then: Zed connects when you save settings.json.

Others

Any client that speaks Streamable HTTP just needs the URL — if a JSON config asks for a transport type, use "streamable-http".

Standard config:

{
  "mcpServers": {
    "<name>": {
      "type": "streamable-http",
      "url": "<url>"
    }
  }
}

Stdio-only clients can bridge through mcp-remote:

mcp-remote bridge:

Command: npx
Arguments: -y mcp-remote <url>
Environment: none

Then: Most clients connect when they start — restart yours after saving the config.

Is it working?

Whether a server works has two sides, and they fail differently:

  • The server side. tool_count above zero means NoClick is serving tools. Zero means none of its entries resolve right now, usually because its connection isn't connected yet: send the user the connection's link.
  • The client side. clients() lists every MCP client that has connected, as it named itself, and when it was last seen:
server = noclick.McpServer.from_name("crm-tools", user="acme")
print(server.tool_count, server.last_client_at)
for client in server.clients():
    print(client.name, client.version, client.last_seen_at, client.tool_count)

No clients means nobody has connected yet: most clients connect only when a session starts, so start a new chat after adding the URL. A client whose tool_count is below the server's listed its tools before you added some, and will see them in its next session.

Change the tools

server = noclick.McpServer.from_name("crm-tools", user="acme")
server.update(tools=["hubspot", "search", "fetch"])

The URL stays the same. Connected clients see the new tools when they next list them.

Rotate or delete it

server = noclick.McpServer.from_name("crm-tools", user="acme")
server.rotate()
print(server.url)   # the new URL; the old one stops answering at once
server.delete()

Rotate when a URL has gone somewhere it shouldn't, then give the client the new one. Deleting a server stops its URL; the connections it served stay.

What a call does

A tool call through the server is the same as a toolkit call: it runs in the connections of the user the server belongs to, under your policy, as the key that created the server, so that key's spend cap and rate limit apply. A call your policy denies, or holds for approval, answers the client with an error it can read, and an approval reaches its approver as usual. A client can call only the tools the server serves. Each call is recorded as a tool call with source="mcp_server" and the server's id, so you can see what a client did:

server = noclick.McpServer.from_name("crm-tools", user="acme")
for call in noclick.ToolCall.list(source="mcp_server", user="acme", limit=20):
    if call.mcp_server_id == server.id:
        print(call.tool_name, call.status, call.created_at)

While its user is suspended, a server serves no tools, and once the user is deleted its URL stops answering.

Limits and billing

  • An account has up to 100 servers, each serving up to 64 tools.
  • The server itself is free. Calls bill like toolkit calls: connection tools run in your users' own accounts, and search and fetch count toward your usage and the user's spend cap.

Errors

ErrorWhen
noclick.ConflictA server of the same user already has that name (existing_id names it).
noclick.InvalidRequestMore than 64 tools, or an entry that isn't a connection, an operation or a capability.
noclick.NotFoundNo server by that name or id.
noclick.PermissionDeniedThe key lacks the mcp_servers scope.
noclick.QuotaExceededThe account already has 100 servers.

Every method is in the McpServer reference, from McpServer.from_name to McpServer.clients.