MCP Connectors & Custom Actions API
This content is not available in your language yet.
This page documents every GraphQL operation for managing MCP connectors and custom actions — the two ways a workflow’s tool/ACTION nodes reach out to third-party systems. For the conceptual model (what MCP connectors are, how they differ from custom actions, and known limitations), see MCP Connectors. This page is strictly the field-by-field CRUD reference.
All operations on this page require JWT dashboard authentication (AuthJwtGuard) — none of them are reachable with an API key. Every request must also include the x-huat-platform: customer header. See API Reference Overview for general request conventions, Agents API for agent management, and Workflows API for how a connector or action gets referenced by a workflow node.
MCP connectors
Section titled “MCP connectors”An MCP connector represents a registered third-party MCP tool server — an endpoint plus credential that a workflow’s tool node can call.
mcpConnector
Section titled “mcpConnector”Fetches a single MCP connector by id.
Auth: JWT dashboard auth only.
Arguments
Section titled “Arguments”| Name | Type | Required | Description |
|---|---|---|---|
id | Int! | Yes | The connector’s id. |
Returns
Section titled “Returns”McpConnectorDto (nullable).
| Field | Type | Description |
|---|---|---|
id | Int! | Connector id. |
name | String! | Display name. |
serverUrl | String! | The MCP server’s endpoint URL. |
createdAt | DateTime! | Creation timestamp. |
updatedAt | DateTime! | Last update timestamp. |
Note the API key used to authenticate to the third-party server is write-only — it’s accepted on create/update but never returned in any query response.
Example request
Section titled “Example request”query GetMcpConnector($id: Int!) { mcpConnector(id: $id) { id name serverUrl }}{ "id": 7 }Headers:
Authorization: Bearer <your-jwt>x-huat-platform: customerExample response
Section titled “Example response”{ "data": { "mcpConnector": { "id": 7, "name": "Library Catalog", "serverUrl": "https://tools.example.com/mcp" } }}mcpConnectors
Section titled “mcpConnectors”Lists every MCP connector registered for the caller’s tenant.
Auth: JWT dashboard auth only.
Arguments
Section titled “Arguments”None.
Returns
Section titled “Returns”[McpConnectorDto!]! — same shape as mcpConnector above.
Example request
Section titled “Example request”query ListMcpConnectors { mcpConnectors { id name serverUrl }}Headers:
Authorization: Bearer <your-jwt>x-huat-platform: customerExample response
Section titled “Example response”{ "data": { "mcpConnectors": [ { "id": 7, "name": "Library Catalog", "serverUrl": "https://tools.example.com/mcp" }, { "id": 9, "name": "Ticketing System", "serverUrl": "https://ticketing.example.com/mcp" } ] }}listMcpTools
Section titled “listMcpTools”Lists the tool schemas exposed by a given MCP connector — what an agent/workflow using this connector can actually call. This is the recommended way to discover exactly what a tool workflow node pointed at a given connector can do, rather than guessing tool names and argument shapes from documentation or trial and error.
Auth: JWT dashboard auth only.
Arguments
Section titled “Arguments”| Name | Type | Required | Description |
|---|---|---|---|
connectorId | Int! | Yes | The MCP connector to introspect. |
Returns
Section titled “Returns”[McpToolSchemaDto!]!
| Field | Type | Description |
|---|---|---|
name | String! | Tool name, as exposed by the MCP server. |
description | String | Human-readable description of what the tool does. |
inputSchema | JSON | The tool’s JSON Schema for its input arguments. |
Example request
Section titled “Example request”query ListMcpTools($connectorId: Int!) { listMcpTools(connectorId: $connectorId) { name description inputSchema }}{ "connectorId": 7 }Headers:
Authorization: Bearer <your-jwt>x-huat-platform: customerExample response
Section titled “Example response”{ "data": { "listMcpTools": [ { "name": "search_catalog", "description": "Search the library catalog by title, author, or keyword.", "inputSchema": { "type": "object", "properties": { "query": { "type": "string" }, "limit": { "type": "integer", "default": 10 } }, "required": ["query"] } }, { "name": "get_availability", "description": "Check whether a specific item is currently available to borrow.", "inputSchema": { "type": "object", "properties": { "itemId": { "type": "string" } }, "required": ["itemId"] } } ] }}Use the returned name values and inputSchema shapes when configuring a tool node in a workflow — see Workflows Overview for how tool nodes reference a connector and tool name.
createMcpConnector
Section titled “createMcpConnector”Registers a new MCP connector (third-party tool server credential/endpoint) for the caller’s tenant.
Auth: JWT dashboard auth only.
Arguments
Section titled “Arguments”| Name | Type | Required | Description |
|---|---|---|---|
input.name | String! | Yes | Display name for the connector. |
input.serverUrl | String! | Yes | The MCP server’s endpoint URL. |
input.apiKey | String! | Yes | Credential used to authenticate to the MCP server. Stored securely; never returned by any query. |
Returns
Section titled “Returns”McpConnectorDto! — the newly created connector (without the API key).
Example request
Section titled “Example request”mutation CreateMcpConnector($input: CreateMcpConnectorInput!) { createMcpConnector(input: $input) { id name serverUrl }}{ "input": { "name": "Library Catalog", "serverUrl": "https://tools.example.com/mcp", "apiKey": "REPLACE_WITH_YOUR_MCP_SERVER_API_KEY" }}Headers:
Authorization: Bearer <your-jwt>x-huat-platform: customerExample response
Section titled “Example response”{ "data": { "createMcpConnector": { "id": 7, "name": "Library Catalog", "serverUrl": "https://tools.example.com/mcp" } }}updateMcpConnector
Section titled “updateMcpConnector”Updates an MCP connector owned by the caller’s tenant.
Auth: JWT dashboard auth only.
Arguments
Section titled “Arguments”| Name | Type | Required | Description |
|---|---|---|---|
input.id | Int! | Yes | Connector to update. |
input.name | String | No | New display name. |
input.serverUrl | String | No | New endpoint URL. |
Note apiKey cannot be changed via updateMcpConnector — it is only accepted on create. To rotate the credential, delete and re-create the connector (which also means updating any workflow tool node that referenced the old connector id).
Returns
Section titled “Returns”McpConnectorDto! — the updated connector.
Example request
Section titled “Example request”mutation UpdateMcpConnector($input: UpdateMcpConnectorInput!) { updateMcpConnector(input: $input) { id name serverUrl }}{ "input": { "id": 7, "name": "Library Catalog v2", "serverUrl": "https://tools-v2.example.com/mcp" }}Headers:
Authorization: Bearer <your-jwt>x-huat-platform: customerExample response
Section titled “Example response”{ "data": { "updateMcpConnector": { "id": 7, "name": "Library Catalog v2", "serverUrl": "https://tools-v2.example.com/mcp" } }}deleteMcpConnector
Section titled “deleteMcpConnector”Deletes an MCP connector owned by the caller’s tenant.
Auth: JWT dashboard auth only.
Arguments
Section titled “Arguments”| Name | Type | Required | Description |
|---|---|---|---|
id | Int! | Yes | Connector to delete. |
Returns
Section titled “Returns”Boolean! — true on success.
Deleting a connector that’s still referenced by a published workflow’s tool node will break that node at runtime — check listMcpTools/your workflow graphs before deleting a connector that may be in active use.
Example request
Section titled “Example request”mutation DeleteMcpConnector($id: Int!) { deleteMcpConnector(id: $id)}{ "id": 9 }Headers:
Authorization: Bearer <your-jwt>x-huat-platform: customerExample response
Section titled “Example response”{ "data": { "deleteMcpConnector": true } }Custom actions
Section titled “Custom actions”A custom action is a named, reusable REST call definition — URL, HTTP method, optional bearer-token credential, and optional extra static headers — for use by ACTION workflow nodes. Use custom actions for simple REST integrations that don’t warrant standing up a full MCP server.
Credential sources
Section titled “Credential sources”Every custom action’s credentialSource field (returned on CustomActionDto) records which mechanism authenticates its outgoing calls. It’s read-only for most values — set indirectly by which other input fields you supply, or by Wetel provisioning — with one exception: SESSION_OMNICHAT/SESSION_AISCRM/SESSION_NORTIA can be set directly via a credentialSource input field on createCustomAction/updateCustomAction.
| Value | Set via | Who can create one |
|---|---|---|
null | createCustomAction with bearerToken | Any tenant, self-serve — a plain static bearer token. |
"LARK_TENANT" | createCustomAction with larkAppId/larkAppSecret | Any tenant, self-serve — see Using Lark above. |
"OMNICHAT" / "NORTIA" / "AISCRM" | Not exposed on createCustomAction today | Wetel-provisioned only — a tenant-wide shared account credential for one of Wetel’s certified partners. |
"SESSION_OMNICHAT" / "SESSION_AISCRM" / "SESSION_NORTIA" | createCustomAction/updateCustomAction with input.credentialSource set to that value directly | Any tenant, self-serve — see Sessions API: sdkVerifyWebbyxOneIdentity. |
Setting credentialSource to SESSION_OMNICHAT/SESSION_AISCRM/SESSION_NORTIA doesn’t store a credential on the action itself — it opts the action into resolving its credential live, per call, from whichever end user is currently signed in to that conversation via WebbyX One (see the mutation itself below). It’s mutually exclusive with bearerToken/larkAppId+larkAppSecret — supplying both is rejected. credentialSource is the only field on either input that accepts one of these three values directly; "OMNICHAT", "NORTIA", "AISCRM", and "LARK_TENANT" remain rejected if passed here.
The three self-serve paths that matter most in practice today: a plain bearer token or your own Lark app cover any REST endpoint you control. For calling OmniChat, aisCRM, or Nortia, the realistic self-serve path is SESSION_OMNICHAT/SESSION_AISCRM/SESSION_NORTIA — create (or update) a custom action with credentialSource set to one of those three values, then have your end users sign in via WebbyX One and link their product identity (sdkVerifyWebbyxOneIdentity’s productTickets), rather than asking Wetel to provision a tenant-wide shared account.
This matters directly for the certified operation catalog — a certified operation’s compatibleCredentialSources names which of these values it’s meant to pair with, and the action node it’s wired to must actually carry one of them.
As of 2026-09-08, url and method are immutable on an action whose credentialSource is a Wetel-provisioned shared credential ("OMNICHAT" / "NORTIA" / "AISCRM" / "LARK_TENANT") — updateCustomAction rejects any attempt to change either field, telling you to create a new action instead. This prevents such an action from being repointed at an arbitrary endpoint while still sending Wetel’s own shared partner credential on every call. SESSION_OMNICHAT/SESSION_AISCRM/SESSION_NORTIA and a plain bearerToken-credentialed action are unaffected — url/method stay freely editable on those.
Credential header name
Section titled “Credential header name”By default, a custom action’s stored bearerToken renders as Authorization: Bearer <token> on every call. Some third-party APIs instead authenticate with a static, differently-named secret header — Nortia’s REST Integration API uses X-Client-Secret; X-Api-Key is common generally. credentialSecretHeaderName on createCustomAction/updateCustomAction covers this: set it to the header name the target API expects, and the stored secret is sent under that header instead, as the raw value with no Bearer prefix, and no Authorization header is sent at all for that call.
It only applies to the plain self-service bearerToken credential (credentialSource: null). It has no effect on any Wetel-provisioned shared credential ("OMNICHAT"/"NORTIA"/"AISCRM"/"LARK_TENANT") or session-identity source ("SESSION_OMNICHAT"/"SESSION_AISCRM"/"SESSION_NORTIA") — each of those builds its own Authorization: Bearer header server-side and never reads this field.
Create-time rule: credentialSecretHeaderName is rejected unless bearerToken is supplied in the same call — a header name with no secret to send under it would be a silent no-op.
mutation CreateActionWithCustomHeader($input: CreateCustomActionInput!) { createCustomAction(input: $input) { id name credentialSecretHeaderName }}{ "input": { "name": "Partner API (X-Api-Key auth)", "url": "https://api.partner.example/v1/records", "method": "POST", "bearerToken": "REPLACE_WITH_YOUR_API_KEY", "credentialSecretHeaderName": "X-Api-Key" }}Update-time rule: on an existing action, credentialSecretHeaderName can be changed to a different header name, or cleared back to the default Authorization: Bearer behavior by passing null. It’s rejected if the action has no stored bearerToken at all, or if the action’s credentialSource is one of the Wetel-provisioned/session-identity values above (those never consult this field).
Extra static headers
Section titled “Extra static headers”extraHeaders exists for a target API that needs more than one static header alongside Authorization (or the custom credential header above) — the motivating case is a partner API requiring both a credential header and a separate, non-secret identifier header like x-organization-id: <org uid>. It’s a plain { [key: string]: string } map merged into every outgoing request’s headers first; the credential header (Authorization: Bearer <token> by default, or <credentialSecretHeaderName>: <token> if set) is applied afterward and wins on a same-named collision.
extraHeaders is not a secret-storage mechanism, and this is worth repeating: never put a secret in it. It’s stored and returned as plain JSON — anyone who can query customActions for your tenant sees these values verbatim, unlike bearerToken, which is write-only and never returned by any query. It exists for non-sensitive request metadata a target API requires, not for credentials. Before adding a header here, ask: would I be comfortable with a tenant admin viewing this value in a query response? If the answer is no — an API key, a token, anything that must stay secret — it does not belong in extraHeaders. Use bearerToken (with credentialSecretHeaderName if the target needs a non-Authorization header) for the one credential a custom action does support; a target API needing more than one secret header isn’t a fit for a custom action today.
Using Lark
Section titled “Using Lark”Custom actions have first-class support for Lark (Feishu internationally) — send messages, create/query Base records, route approvals, and more, all via Lark’s Open Platform REST API, using your own Lark app, not a shared or third-party credential.
Step 1 — create a Lark app. In Lark’s Developer Console (or open.feishu.cn if your org is on Feishu), create a custom app and note its App ID and App Secret from the app’s “Credentials & Basic Info” page.
Step 2 — register a custom action with larkAppId/larkAppSecret/larkRegion instead of bearerToken:
mutation CreateLarkAction($input: CreateCustomActionInput!) { createCustomAction(input: $input) { id name url method credentialSource }}{ "input": { "name": "Send Lark Message", "url": "https://open.larksuite.com/open-apis/im/v1/messages?receive_id_type=open_id", "method": "POST", "larkAppId": "cli_xxxxxxxxxxxxxxxx", "larkAppSecret": "REPLACE_WITH_YOUR_LARK_APP_SECRET", "larkRegion": "LARK_SUITE" }}Wetel handles the rest: it exchanges your app credentials for a Lark tenant_access_token (Lark’s own short-lived, ~2-hour token) on every call, refreshing it automatically before it expires. Your ACTION workflow node just references this custom action’s id — no token management on your side.
larkAppId/larkAppSecret are write-only, same as bearerToken — accepted on createCustomAction, never returned by customActions or any other query. Supply either bearerToken OR larkAppId+larkAppSecret, never both — the mutation rejects the request if both are present, since they resolve to different (mutually exclusive) credential mechanisms server-side.
larkAppId/larkAppSecret must be supplied together — providing one without the other is rejected. larkRegion is optional and defaults to LARK_SUITE (international) if you don’t set it; use FEISHU if your Lark org is on Feishu’s China deployment.
A few real Lark Open Platform endpoints to build against, each its own createCustomAction call:
| Action | Endpoint | Notes |
|---|---|---|
| Send a message | POST /open-apis/im/v1/messages | To a user, group, or your app’s own bot chat. |
| Create/query a Base record | POST/GET /open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records | Lark Base is Airtable-like structured data — a lightweight CRM target. |
| Create an approval instance | POST /open-apis/approval/v4/instances | Route something to a human for sign-off. |
| Look up a user by email/mobile | POST /open-apis/contact/v3/users/batch_get_id | Usually a prerequisite before sending a message — Lark’s messaging API needs an internal user id, not an email. |
See Lark’s Open Platform API reference for the full endpoint list and request/response shapes — Wetel is a transport for these calls, not a wrapper around them, so any endpoint your app is authorized for works the same way.
customActions
Section titled “customActions”Lists every custom action registered for the caller’s tenant.
Auth: JWT dashboard auth only.
Arguments
Section titled “Arguments”None.
Returns
Section titled “Returns”[CustomActionDto!]!
| Field | Type | Description |
|---|---|---|
id | Int! | Action id. |
name | String! | Display name. |
description | String | Optional description. |
url | String! | Target REST endpoint. |
method | String! | HTTP method (e.g. GET, POST). |
argsSchema | JSON | JSON Schema describing the arguments an ACTION node collects/passes for this call. |
extraHeaders | JSON | Additional static headers (a plain { [key: string]: string } map) sent on every call to this action, merged with (and never overriding) the credential header built from the bearer token. See “Extra static headers” below. |
credentialSecretHeaderName | String | Header name the stored bearerToken is sent under, if set (e.g. "X-Api-Key"). null = the default Authorization: Bearer <token> behavior. See “Credential header name” below. |
credentialSource | String | Which mechanism authenticates this action’s calls — null, "LARK_TENANT", "OMNICHAT", "NORTIA", "AISCRM", "SESSION_OMNICHAT", "SESSION_AISCRM", or "SESSION_NORTIA". Not directly settable except via input.credentialSource (see below) for the three SESSION_* values — every other value is derived from other input fields or Wetel-provisioned. See Credential sources above. |
createdAt | DateTime! | Creation timestamp. |
updatedAt | DateTime! | Last update timestamp. |
The bearer token (if set) is write-only — it’s accepted on create/update but never returned by this or any query. extraHeaders, unlike the bearer token, is not treated as a secret — it’s stored and returned as-is, so it round-trips back out of customActions/createCustomAction/updateCustomAction unchanged. See the callout below before putting anything in it.
Example request
Section titled “Example request”query ListCustomActions { customActions { id name url method argsSchema extraHeaders }}Headers:
Authorization: Bearer <your-jwt>x-huat-platform: customerExample response
Section titled “Example response”{ "data": { "customActions": [ { "id": 3, "name": "Create Support Ticket", "url": "https://api.example.com/tickets", "method": "POST", "argsSchema": { "type": "object", "properties": { "subject": { "type": "string" }, "body": { "type": "string" } }, "required": ["subject", "body"] } } ] }}createCustomAction
Section titled “createCustomAction”Registers a new custom action for the caller’s tenant, for use by ACTION workflow nodes.
Auth: JWT dashboard auth only.
Arguments
Section titled “Arguments”| Name | Type | Required | Description |
|---|---|---|---|
input.name | String! | Yes | Display name. |
input.url | String! | Yes | Target REST endpoint. |
input.method | String | No | HTTP method (defaults applied server-side if omitted). |
input.description | String | No | Optional description. |
input.argsSchema | JSON | No | JSON Schema describing the call’s arguments. |
input.bearerToken | String | No | Bearer credential sent with the request. Stored securely; never returned by any query. Mutually exclusive with larkAppId/larkAppSecret below. |
input.credentialSecretHeaderName | String | No | Send the stored bearerToken under this header name instead of Authorization: Bearer, e.g. "X-Api-Key". See “Credential header name” below. Rejected unless bearerToken is supplied in the same call. |
input.extraHeaders | JSON | No | Additional static headers ({ [key: string]: string }), merged onto every call alongside the credential header. See “Extra static headers” below — never put a real credential in here. |
input.larkAppId | String | No | Your own Lark app’s App ID. See “Using Lark” below. Must be supplied together with larkAppSecret. |
input.larkAppSecret | String | No | Your own Lark app’s App Secret. Stored securely; never returned by any query. |
input.larkRegion | String | No | FEISHU or LARK_SUITE — which Lark deployment your app is on. Defaults to LARK_SUITE if omitted. |
input.credentialSource | String | No | Set to "SESSION_OMNICHAT", "SESSION_AISCRM", or "SESSION_NORTIA" to opt this action into the session-identity-driven credential flow instead of a stored bearer token — see Credential sources above. Any other value ("OMNICHAT", "NORTIA", "AISCRM", "LARK_TENANT") is rejected here. Mutually exclusive with bearerToken/larkAppId+larkAppSecret. |
Returns
Section titled “Returns”CustomActionDto! — the newly created action (without the bearer token).
Example request
Section titled “Example request”mutation CreateCustomAction($input: CreateCustomActionInput!) { createCustomAction(input: $input) { id name url method extraHeaders }}{ "input": { "name": "Create Support Ticket", "url": "https://api.example.com/tickets", "method": "POST", "description": "Opens a new support ticket in the external helpdesk.", "argsSchema": { "type": "object", "properties": { "subject": { "type": "string" }, "body": { "type": "string" } }, "required": ["subject", "body"] }, "bearerToken": "REPLACE_WITH_YOUR_HELPDESK_API_TOKEN", "extraHeaders": { "x-organization-id": "org-123" } }}Headers:
Authorization: Bearer <your-jwt>x-huat-platform: customerExample response
Section titled “Example response”{ "data": { "createCustomAction": { "id": 3, "name": "Create Support Ticket", "url": "https://api.example.com/tickets", "method": "POST" } }}Example: session-identity-driven action (OmniChat/aisCRM)
Section titled “Example: session-identity-driven action (OmniChat/aisCRM)”No bearerToken — the credential resolves live, per call, from whoever is signed in to the conversation via WebbyX One (see Credential sources above and sdkVerifyWebbyxOneIdentity):
{ "input": { "name": "OmniChat (session identity)", "url": "https://api.omnichat.example/graphql", "method": "POST", "credentialSource": "SESSION_OMNICHAT" }}updateCustomAction
Section titled “updateCustomAction”Updates a custom action owned by the caller’s tenant.
Auth: JWT dashboard auth only.
Arguments
Section titled “Arguments”| Name | Type | Required | Description |
|---|---|---|---|
input.id | Int! | Yes | Action to update. |
input.name | String | No | New display name. |
input.url | String | No | New target endpoint. Rejected if the action’s credentialSource is a Wetel-provisioned shared credential (OMNICHAT/NORTIA/AISCRM/LARK_TENANT) — see Credential sources above. Also checked against the same private/internal-address (SSRF) guard as every other tenant-controlled outbound URL, and rejects redirect-only targets at call time. |
input.method | String | No | New HTTP method. Same immutability restriction as input.url for a shared-credential action. |
input.description | String | No | New description. |
input.argsSchema | JSON | No | New arguments schema. |
input.extraHeaders | JSON | No | Replaces the action’s entire extraHeaders map. Omit the field to leave the existing value unchanged; pass {} (or null) to clear it — this is a full replace, not a per-key merge. |
input.credentialSecretHeaderName | String | No | Retarget the action’s stored secret onto a different header name, or pass null to clear back to the default Authorization: Bearer behavior. See “Credential header name” above. Rejected if the action has no stored bearerToken, or if its credentialSource is a Wetel-provisioned/session-identity value. |
input.credentialSource | String | No | Switch this action onto (or between) "SESSION_OMNICHAT"/"SESSION_AISCRM"/"SESSION_NORTIA" after creation. Same restrictions as createCustomAction — see Credential sources above. Switching onto a SESSION_* source clears any stale stored credential the action previously had. |
Note bearerToken cannot be changed via updateCustomAction — it is only accepted on create. To rotate the credential, delete and re-create the action (and update any workflow ACTION node that referenced the old action id).
Returns
Section titled “Returns”CustomActionDto! — the updated action.
Example request
Section titled “Example request”mutation UpdateCustomAction($input: UpdateCustomActionInput!) { updateCustomAction(input: $input) { id name url method }}{ "input": { "id": 3, "url": "https://api.example.com/v2/tickets", "description": "Opens a new support ticket in the external helpdesk (v2 endpoint)." }}Headers:
Authorization: Bearer <your-jwt>x-huat-platform: customerExample response
Section titled “Example response”{ "data": { "updateCustomAction": { "id": 3, "name": "Create Support Ticket", "url": "https://api.example.com/v2/tickets", "method": "POST" } }}deleteCustomAction
Section titled “deleteCustomAction”Deletes a custom action owned by the caller’s tenant.
Auth: JWT dashboard auth only.
Arguments
Section titled “Arguments”| Name | Type | Required | Description |
|---|---|---|---|
id | Int! | Yes | Action to delete. |
Returns
Section titled “Returns”Boolean! — true on success.
As with deleteMcpConnector, deleting an action still referenced by a published workflow’s ACTION node will break that node at runtime.
Example request
Section titled “Example request”mutation DeleteCustomAction($id: Int!) { deleteCustomAction(id: $id)}{ "id": 3 }Headers:
Authorization: Bearer <your-jwt>x-huat-platform: customerExample response
Section titled “Example response”{ "data": { "deleteCustomAction": true } }Deployment tips: PoC vs. production
Section titled “Deployment tips: PoC vs. production”For a proof-of-concept / demo
Section titled “For a proof-of-concept / demo”- For a quick demo, a custom action is usually faster to stand up than a full MCP connector — if the third-party system only needs one or two REST calls, skip building/hosting an MCP server and use
createCustomActiondirectly. - Always run
listMcpToolsagainst a connector once, right after creating it and before wiring it into a workflow — confirming the actual tool names/schemas up front avoids a live demo failure caused by a guessed tool name that doesn’t exist. - Use a scoped-down, disposable credential (API key or bearer token) for demo connectors/actions rather than a production credential — demo environments get shared, screen-recorded, and sometimes left running longer than intended.
- Have a fallback in mind for any workflow branch that depends on a demo connector/action — a flaky third-party demo endpoint is a common source of “it worked in rehearsal” failures.
For production
Section titled “For production”- Rotate credentials on a schedule: since neither
updateMcpConnectornorupdateCustomActioncan change the stored secret, rotating a production credential means delete-and-recreate — plan for the brief window where any workflow node referencing the old id needs to be repointed to the new one, and do this as a single coordinated change (update the connector/action, thenupdateWorkflow+publishWorkflowon every affected workflow) rather than leaving a workflow pointed at a deleted id. - Before deleting any MCP connector or custom action, check every workflow for a
tool/ACTIONnode referencing it — there’s no built-in reference check, so a stale id in a live workflow only surfaces as a runtime failure, visible viaworkflowRun’snodeTrace(see Workflows API) rather than at delete time. - Treat third-party MCP servers and REST endpoints backing production actions as external dependencies with their own SLAs — apply the same monitoring/alerting discipline you’d apply to any other production integration, since a workflow node calling out to a down third party fails the whole conversation turn, not just that node.
- Because these operations are JWT-only (no API-key path), any infrastructure-as-code or CI/CD process that provisions connectors/actions as part of a deployment pipeline needs a JWT-authenticated service identity — factor this into your automation design early rather than discovering the API-key gap mid-rollout.
- Since
apiKey/bearerTokenare write-only and never returned by any query, keep your own secure record of which credential is currently active for each connector/action (e.g. in your own secrets manager) — the Wetel API itself gives you no way to retrieve or diff a previously-set credential.
See Workflows API for how connectors and actions are referenced from tool/ACTION nodes, MCP Connectors for the conceptual guide and known limitations, and API Reference Overview for general auth and header conventions.