Skip to main content
A Model Context Protocol (MCP) server is an external service that exposes a set of tools over a standard wire protocol. ModuleX can connect to one as a credential, discover the tools it advertises, and make those tools callable from a Tool node, the AI Composer, and the Assistant — exactly like a built-in catalog integration, but without writing any code. This is the second of the two ways to add tools to ModuleX. The first is to author a package integration with the @tool contract (see Build an integration). Connecting an MCP server is the lighter-weight path: you point ModuleX at a server URL, it lists the server’s tools, and it stores them on a credential your organization can use.
There is no in-app builder for authoring integrations today. You add your own tools either by publishing a package to the modulex-integrations catalog (code) or by connecting an external MCP server (this page). Both paths are code/configuration only.

How it works

When you register an MCP server, ModuleX connects to it once, lists its tools, and persists the connection plus the discovered tool definitions as a single credential. At run time, a Tool node that references that credential reconnects to the server, loads the live tools, and invokes the one you named.
1

Register the server

Call POST /credentials/mcp-server with the server URL (and any auth headers). ModuleX connects, discovers the tools, and stores them on a new credential whose integration_name is the literal mcp_server and whose auth_type is custom.
2

Inspect the discovered tools

The create response includes the discovered tools in credentials_metadata. You can also read them any time with GET /credentials/{credential_id}/mcp-tools.
3

Use a tool in a workflow

In a Tool node, set tool.integration_name to mcp_server, set tool.credential_id to your MCP credential, and set tool.service_name to the MCP tool’s name.
4

Refresh when the server changes

When the server adds or removes tools, call POST /credentials/{credential_id}/refresh-discovery to re-list and update the stored tool definitions.
🎬 MEDIA PLACEHOLDER · MX-MEDIA-4200 · [IMAGE] [IMAGE_DESCRIPTION]: Diagram of the MCP credential lifecycle in ModuleX. [IMAGE_DETAILS]: Three-stage flow — (1) POST /credentials/mcp-server → ModuleX connects to the external MCP server and discovers tools; (2) tools stored on an mcp_server credential (auth_type: custom) in the organization; (3) a Tool node references the credential by credential_id and service_name, ModuleX reconnects at run time and invokes the tool. Show the server URL masked. Light theme, 16:9, labelled arrows.

What a discovered tool looks like

Each tool ModuleX discovers is reduced to three fields, captured at discovery time and stored on the credential:
name
string
The MCP tool’s name, as advertised by the server. This is the value you put in a Tool node’s service_name.
description
string
The tool’s human/LLM-facing description (empty string if the server provides none).
input_schema
object
The tool’s input parameters as a JSON Schema object (typically {type, properties, required}). ModuleX reads this from the tool’s args_schema (or args / tool_call_schema fallbacks); if the server advertises no schema, this is an empty object {}.
At run time, ModuleX parses each tool’s MCP TextContent response — a shape like [{"type": "text", "text": "{...}"}] — and decodes the stringified JSON so that an MCP tool returns structured data just like a built-in tool. This parsing is handled internally by MCPToolWrapper; you do not configure it.

Transport and protocol

ModuleX connects to the server with the langchain-mcp-adapters client over a configurable transport. Two transports exist in the protocol — sse and streamable_http — but the REST route does not expose a transport field: a server registered through POST /credentials/mcp-server always uses the default streamable_http transport. The discovered server_info.protocol_version ModuleX records is 2024-11-05.
Because the create route fixes the transport to streamable_http, an MCP server that speaks only the older sse transport cannot currently be registered through the public API. If you need sse, raise it with support — it is set on the stored credential’s auth_data, not on the request body. (TBD: a public way to choose the transport at registration time.)

Authentication

Every ModuleX API request authenticates with your API key as a bearer token plus your organization context header. See API authentication for the full model.
Request headers
Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-Organization-ID: org_xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
All three MCP routes require the caller to be an owner or admin of the organization. The member role cannot register or refresh MCP servers. See Roles & permissions. The MCP server itself is authenticated separately, with the headers you supply on registration. ModuleX sends those headers to the server on every connection — for example a bearer token the server expects:
headers field
{
  "Authorization": "Bearer your-mcp-server-token"
}
The server URL and headers can carry secrets (some hosted MCP servers encode a per-user token in the URL path or query). ModuleX encrypts them at rest on the credential and masks the URL in logs to scheme and host only — for example https://mcp.example.com. The full URL is never logged.

Register an MCP server

POST /credentials/mcp-server connects to the server, discovers its tools, and creates one mcp_server credential.

Request body

server_url
string
required
The MCP server endpoint URL, for example https://mcp.example.com/mcp. ModuleX connects to this URL during discovery and again on every run.
headers
object
Optional HTTP headers sent to the MCP server on every request, including auth — for example {"Authorization": "Bearer token"}. Keys and values are strings. Omit for an unauthenticated server.
display_name
string
Optional label for the credential. If omitted, ModuleX generates one from the server’s reported name and tool count, for example MCP Server - example (8 tools).
make_default
boolean
default:"false"
Whether to make this the default credential for the mcp_server integration in this organization. A Tool node that omits credential_id cannot resolve an MCP server, so for MCP this mostly affects which credential the app preselects.
There is no transport_type field on this request — the route always registers the server with the streamable_http transport (see Transport and protocol).
curl https://api.modulex.dev/credentials/mcp-server \
  -X POST \
  -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "X-Organization-ID: org_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "server_url": "https://mcp.example.com/mcp",
    "headers": { "Authorization": "Bearer your-mcp-server-token" },
    "display_name": "Example docs MCP",
    "make_default": false
  }'

Response

A successful call returns 201-style credential data shaped as MCPServerCredentialResponse. This is a narrower shape than the standard credential response — it omits updated_at, integration_type, last_used_at, and expires_at.
credential_id
string (UUID)
The new credential’s ID. Use it as tool.credential_id in a Tool node and as the path parameter for refresh and list calls.
integration_name
string
Always mcp_server.
display_name
string
The label, either the one you supplied or the generated default.
auth_type
string
Always custom for MCP server credentials.
is_default
boolean
Whether this is the default mcp_server credential for the organization.
created_at
string | null
ISO 8601 creation timestamp.
credentials_metadata
object
The discovery metadata, including the discovered tools.
Example response
{
  "credential_id": "9f2a6b1c-4d3e-4a8f-9c10-2e7b5a1d8f33",
  "integration_name": "mcp_server",
  "display_name": "Example docs MCP",
  "auth_type": "custom",
  "is_default": false,
  "created_at": "2026-06-21T10:14:00.000Z",
  "credentials_metadata": {
    "discovered_at": "2026-06-21T10:14:00.000Z",
    "last_refreshed_at": "2026-06-21T10:14:00.000Z",
    "refresh_count": 0,
    "server_info": {
      "name": "example-docs",
      "version": "unknown",
      "protocol_version": "2024-11-05",
      "url": "https://mcp.example.com/mcp",
      "transport_type": "streamable_http"
    },
    "tools": [
      {
        "name": "search_documents",
        "description": "Full-text search across the docs corpus.",
        "input_schema": {
          "type": "object",
          "properties": {
            "query": { "type": "string" },
            "limit": { "type": "integer" }
          },
          "required": ["query"]
        }
      }
    ],
    "resources": [],
    "prompts": [],
    "tool_count": 1
  }
}

List discovered tools

GET /credentials/{credential_id}/mcp-tools returns the tools currently stored on the credential. It reads the persisted credentials_metadata.tools — it does not reconnect to the server (use refresh for that).
credential_id
string (UUID)
required
The MCP server credential’s ID.
curl https://api.modulex.dev/credentials/9f2a6b1c-4d3e-4a8f-9c10-2e7b5a1d8f33/mcp-tools \
  -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "X-Organization-ID: org_xxxxxxxxxxxxxxxxxxxxxxxx"

Response

credential_id
string (UUID)
The credential queried.
tools
array
The discovered tool descriptors, each {name, description, input_schema}.
total_count
integer
The number of tools returned.

Refresh tool discovery

POST /credentials/{credential_id}/refresh-discovery reconnects to the server, re-lists its tools, updates the stored credentials_metadata, and returns a summary of what changed. Run it after the server adds or removes tools. It is valid only for mcp_server credentials; any other credential returns 400.
credential_id
string (UUID)
required
The MCP server credential to refresh.
This route takes no request body — the server URL, headers, and transport are read from the stored credential.
curl https://api.modulex.dev/credentials/9f2a6b1c-4d3e-4a8f-9c10-2e7b5a1d8f33/refresh-discovery \
  -X POST \
  -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "X-Organization-ID: org_xxxxxxxxxxxxxxxxxxxxxxxx"

Response

credential_id
string (UUID)
The credential refreshed.
refreshed_at
string
ISO 8601 timestamp of this refresh.
changes
object
A diff against the previous tool set.
total_tools
integer
The tool count after the refresh.
success
boolean
Whether the refresh completed.
Example response
{
  "credential_id": "9f2a6b1c-4d3e-4a8f-9c10-2e7b5a1d8f33",
  "refreshed_at": "2026-06-21T11:02:13.000Z",
  "changes": {
    "added": ["summarize_document"],
    "removed": [],
    "total_before": 1,
    "total_after": 2
  },
  "total_tools": 2,
  "success": true
}

Use an MCP tool in a workflow

A Tool node calls an MCP tool the same way it calls a catalog action, with three differences: integration_name is the literal mcp_server, credential_id is required (the URL, headers, and transport live on the credential, not the catalog), and service_name is the MCP tool’s name.
Tool node — MCP tool definition
{
  "tool": {
    "integration_name": "mcp_server",
    "service_name": "search_documents",
    "credential_id": "9f2a6b1c-4d3e-4a8f-9c10-2e7b5a1d8f33"
  },
  "input_mapping": {
    "query": "{{intake_1.question}}",
    "limit": 5
  }
}
At run time ModuleX loads the credential, connects to the server, lists its tools, selects the one matching service_name, and invokes it with the mapped inputs. The output is parsed back into structured data, so you reference it like any other node output. See the Tool node reference for input_mapping, parameter defaults and overrides, and the output shape.

Errors

These routes use the standard {detail} HTTPException envelope (a string or, for the org rate limit, an object) — not the flat billing DenialEnvelope. The MCP credential routes are CRUD-style and have no per-call credit gate (the credit gate applies to modulex_key resolution at execution time, not to these routes). For the complete error model, see Errors & status codes.
StatusWhenDetail
400Connection or discovery failed; or refresh called on a non-mcp_server credentialCredentialValidationError message
401 / 403Missing/invalid API key, or caller is not an owner/adminauth dependency message
404Credential not found in this organization (refresh / list)CredentialNotFoundError message
429Organization-class API rate limit (fail-open; includes X-RateLimit-* headers)rate-limit object
500Internal failure persisting the credentialCredentialServiceError message
A connection or discovery failure surfaces as 400 with a Failed to connect to MCP server: ... message. Verify the server URL is reachable and that any auth header the server expects is present and correct before retrying.

SDK reference

Every operation maps to a method in both official SDKs.
REST routePython (modulex-python)JavaScript (modulex-js)
POST /credentials/mcp-servercredentials.create_mcp_server(...)credentials.mcpServer(...)
POST /credentials/{id}/refresh-discoverycredentials.refresh_mcp_discovery(id)credentials.refreshDiscovery(id)
GET /credentials/{id}/mcp-toolscredentials.mcp_tools(id)credentials.mcpTools(id)
Parity note: the JS MCPServerCredentialResponse mirrors the backend’s narrower MCP shape (it omits updated_at, integration_type, last_used_at, and expires_at relative to a standard credential response). For the full cross-language matrix, see the SDK ⇄ API parity matrix.

Limitations

POST /credentials/mcp-server always registers a server with the streamable_http transport; the sse transport is not selectable through the request body. (TBD: a public way to choose the transport.)
The tool list is captured at registration and persisted on the credential. A GET .../mcp-tools call reads that snapshot; it does not reconnect. Run refresh to pick up tools the server has added or removed.
MCP resources and prompts are recorded as empty arrays; only tools are discovered and usable today.
Registering, refreshing, and listing MCP servers require the owner or admin role. The member role is retired and cannot perform these actions — see Roles & permissions.
For other documented gaps, see Known limitations.

Tool node

Wire an MCP tool into a workflow with integration_name: mcp_server.

Build an integration

The code path: author a package integration with the @tool contract.

Credentials & OAuth2

How ModuleX stores and resolves credentials, including custom types.

Managing credentials

Create, scope, and rotate credentials in the app and via API.