Custom connectors
Register your business's MCP server by its URL and choose which actions your Mets use, from code. It is the same connector as the Connections screen.
A custom connector is a remote MCP server —the one for your billing system, your ERP, your inventory, or a third party's such as Notion or Shopify— registered in your workspace. Meteor asks it for its list of actions, you choose which ones stay on, and your Mets use them like any other skill.
It is the same connector as the Connections screen: what you create over the API shows up there, and what someone creates there you see over the API.
Before you start
- The key needs
integrations:readto read andintegrations:manageto create and change. - The key has to belong to a workspace administrator, just like the screen. With the key of someone who is not one, you get
403even if it carries the scopes. - The server has to be on HTTPS and reachable from the internet. Addresses on internal networks are rejected.
Create one by its URL
import Met from '@meteor.ia/sdk';
const met = new Met(process.env.METEOR_API_KEY!);
const connector = await met.customConnectors.create({
name: 'Facturación',
url: 'https://mcp.mi-erp.com/mcp',
auth_mode: 'bearer', // 'none' | 'api_key' | 'bearer' | 'oauth'
secret: process.env.ERP_TOKEN!,
});
console.log(connector.id, connector.status); // '48211' 'connected'
for (const a of connector.actions) console.log(a.enabled ? '●' : '○', a.name, '→', a.key);
Before saving, Meteor asks the server for its list of actions with your credential. If the server does not answer or rejects the key, you get a 400 and nothing is created: a connector that exists is one that answered.
With auth_mode: 'api_key' the key goes in a header; X-API-Key by default, or the one you set in header. With 'none' no credential is sent.
Each action has two names: name is the server's and key is the one a Met sees (mcp_c_<slug>_<action>). The c_<slug> prefix comes from the connector's name and does not change afterwards.
From a template
For third parties' official MCP servers you do not need to type the URL:
const templates = await met.customConnectors.templates();
// [{ id: 'notion', name: 'Notion', auth_mode: 'oauth', asks_for: 'none', connector: null }, …]
await met.customConnectors.create({ template: 'freshdesk', subdomain: 'miempresa', secret: process.env.FRESHDESK_KEY! });
asks_for tells you whether the template asks for an extra value: subdomain or url (your own instance). If the workspace already created one, connector says which. In templates, whatever deletes, cancels or refunds money starts switched off.
Connectors with OAuth
An oauth connector is created over the API, but it is authorized in Met: consent needs a browser, and a key cannot do that. The response tells you so:
const c = await met.customConnectors.create({ name: 'Mi CRM', url: 'https://mcp.mi-crm.com/mcp', auth_mode: 'oauth' });
if (c.requires_authorization) {
console.log('Ask an administrator to authorize it in Met: Conexiones → Mi CRM → Autorizar.');
}
If the server does not allow automatic app registration, create your own app in that service and send its client_id (and client_secret, if it has one).
Switch actions on and off
await met.customConnectors.setDisabledActions(connector.id, ['borrar_factura', 'anular_factura']);
You send the complete list of switched-off actions; whatever is not on the list stays on. Each connector has room for the description of its actions (space): what does not fit is imported switched off (no_room: true). Switching off is always allowed; switching on one that does not fit answers 400 CUSTOM_CONNECTOR_NO_ROOM, and you have to switch another one off first.
Test and change the key
// Lists the actions again with the saved key and updates the status.
const tested = await met.customConnectors.test(connector.id);
// The new key is tested before it is saved: if the server rejects it, the previous one stays.
await met.customConnectors.updateCredentials(connector.id, { auth_mode: 'bearer', secret: newToken });
status uses the same vocabulary as the screen: connected, needs_reauth (the server rejected the credential), error or disconnected.
The credential cannot be read
The key or token is written and never returned: it is stored encrypted, and it only travels to the URL you created the connector with. credential.configured tells you whether there is one, and credential.updated_at when it was changed. If you need the key again, keep it on your side too.
The URL's query string does not come out either: some servers carry a key there.
The contact's phone
When a Met uses an action while handling a conversation, Meteor adds the X-Meteor-Contact-Phone header to that call with the phone of the conversation's contact, exactly as it is on their record (for example +573001112233). A flow node that runs inside a conversation does the same.
- Meteor sets it, not the model. An action's arguments (a
telefono, adocumento) are written by the Met, and a person can ask it to use someone else's. The header cannot be changed that way: it comes from the contact the conversation is with. If your server acts on a person's data, use the header and prefer it over the argument; if both arrive and they do not match, reject the call. - It only arrives when there is a contact with a phone. Without a conversation (
integrations.execute, a flow with no contact) or with a contact with no phone, the header does not arrive; it never arrives empty. Treat its absence as "I do not know who this is", not as permission. - Nobody else writes it. The credential cannot use that header name (
400 CUSTOM_CONNECTOR_HEADER_INVALID) and, if it carried it, Meteor replaces it. - The customer cannot change a phone that already exists. In a conversation with a customer, the Met saves the phone only if the contact does not have one yet (for example, in your website chat); if it already has one, it does not overwrite it even when asked to. The channel identifier is never changed. Your team can change both (the inbox, the API or a flow).
- Outside WhatsApp, the phone was given by the person. In the web chat, Messenger or Instagram, the phone the Met saved is the one the person typed: it is not verified. If your server needs certainty, verify it on its side (with a code, for example).
Use its actions from your code
The connector's id is also its key in integrations, so you can run an action without going through a Met (scope integrations:execute):
await met.integrations.execute(connector.id, 'crear_factura', { cliente: 'Panadería Luz', total: 120000 });
In Python
from meteor_ia import Met
met = Met("met_live_…")
c = met.custom_connectors.create(name="Facturación", url="https://mcp.mi-erp.com/mcp", auth_mode="bearer", secret=token)
met.custom_connectors.set_disabled_actions(c["id"], ["borrar_factura"])
What about met.integrations.register?
It keeps working the same. A custom connector is the recommended way to bring your MCP server: the credential is encrypted, you choose which actions are used, and the team sees it in Connections.