Contents
MCP Connector API
One remote MCP server that gives Claude, ChatGPT and other AI clients the Nucleo tools of a user.
Server URL: https://mcp.nucleoplatform.com
The Nucleo connector is a remote Model Context Protocol server
(protocol version 2025-06-18, Streamable HTTP transport, JSON responses). Add it as a custom
connector in your AI client and sign in with your Nucleo account: the client then sees the tools of
every Nucleo module you can use in the active company — Brain, Catalog, Commerce, Intelligence —
with your own permissions. Delete tools are not exposed, with one exception: catalog_forget_memory
deletes an entry of Atomo's memory in Catalog. A few writes cannot be reversed automatically — that one,
merging duplicate contacts or tasks in Brain, and website CMS tools that the CMS marks as destructive —
and carry destructiveHint: true, so your AI client can ask you before running them.
Authentication is OAuth 2.1 with discovery: the server answers 401 with a pointer to its
protected-resource metadata (RFC 9728), which names Nucleo Core as the authorization server; the
client registers itself (RFC 7591), runs the authorization code flow with PKCE and sends
Authorization: Bearer <token> on every request. See the OAuth & OpenID Connect API.
Connect Claude (web, desktop, mobile): Settings → Connectors → Add custom connector → name
Nucleo, URL https://mcp.nucleoplatform.com → Connect and sign in.
Connect ChatGPT (developer mode): Settings → Connectors → Advanced → enable developer mode →
Create → MCP server URL https://mcp.nucleoplatform.com, authentication OAuth → sign in.
Authentication
nucleoOAuthOAuth 2.0 · authorizationCodeAccess token from Nucleo Core. MCP clients obtain it automatically: protected-resource metadata → authorization server metadata → dynamic client registration → authorization code + PKCE (S256) → token (8 h) + refresh token (30 days). The user approves the client on a consent screen. Tokens of self-registered clients carry
nucleo.ai_toolsand work only with the AI tools.authorize: https://auth.nucleoplatform.com/oauth/authorizetoken: https://auth.nucleoplatform.com/oauth/tokenscopes: openid, profile, email, offline_access
- Base URL
- https://mcp.nucleoplatform.comProduction. The root URL is the MCP endpoint.
- Who calls it
- AI assistants
- Endpoints
- 6
- OpenAPI 3.1 specification
- mcp.yaml
Discovery
OAuth protected-resource metadata (RFC 9728) that MCP clients read after a 401.
/.well-known/oauth-protected-resourceGet protected-resource metadata
RFC 9728 metadata. resource is the root URL of the host you called;
authorization_servers points to Nucleo Core, whose /.well-known/oauth-authorization-server
lists the registration, authorization and token endpoints. Cacheable for 5 minutes.
Responses
200Metadata.application/json
HeadersCache-Control
resourcestring (uri)requiredauthorization_serversarray of string (uri)requiredbearer_methods_supportedarray of stringOne ofheaderscopes_supportedarray of stringresource_namestring
curl -X GET https://mcp.nucleoplatform.com/.well-known/oauth-protected-resource \
-H 'Accept: application/json'const res = await fetch("https://mcp.nucleoplatform.com/.well-known/oauth-protected-resource", {
method: "GET",
headers: {
Accept: "application/json",
},
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('GET', 'https://mcp.nucleoplatform.com/.well-known/oauth-protected-resource', [
'headers' => [
'Accept' => 'application/json',
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"resource": "https://mcp.nucleoplatform.com",
"authorization_servers": [
"https://auth.nucleoplatform.com"
],
"bearer_methods_supported": [
"header"
],
"scopes_supported": [
"openid",
"profile",
"email"
],
"resource_name": "Nucleo"
}/.well-known/oauth-protected-resource/{resource}Get metadata for a resource path
Path-suffixed variant some clients request (e.g. /.well-known/oauth-protected-resource/mcp).
Same document as the root variant.
Path parameters
resourcestringrequiredExample:mcp
Responses
200Metadata.application/json
resourcestring (uri)requiredauthorization_serversarray of string (uri)requiredbearer_methods_supportedarray of stringOne ofheaderscopes_supportedarray of stringresource_namestring
curl -X GET https://mcp.nucleoplatform.com/.well-known/oauth-protected-resource/mcp \
-H 'Accept: application/json'const res = await fetch("https://mcp.nucleoplatform.com/.well-known/oauth-protected-resource/mcp", {
method: "GET",
headers: {
Accept: "application/json",
},
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('GET', 'https://mcp.nucleoplatform.com/.well-known/oauth-protected-resource/mcp', [
'headers' => [
'Accept' => 'application/json',
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"resource": "https://shop.acme.example",
"authorization_servers": [
"https://shop.acme.example"
],
"bearer_methods_supported": [
"header"
],
"scopes_supported": [
"string"
],
"resource_name": "string"
}MCP
The MCP endpoint. One JSON-RPC 2.0 request per HTTP POST, one JSON response.
Methods: initialize, ping, tools/list, tools/call. Notifications (notifications/*,
no id) are accepted with 202. Anything else → JSON-RPC error -32601.
Not supported in this version: server-initiated streams (GET → 405), SSE responses, JSON-RPC
batches, resources, prompts, sampling. The server is stateless: no Mcp-Session-Id.
Active company. Every module tool runs inside one company. At initialize the connector picks
it in this order: your only company → the company you chose earlier with nucleo_switch_company
→ the first company. The initialize result's instructions start with
ACTIVE COMPANY: "<name>" and the modules available there. Switching company is saved for you in
Nucleo and applies to every AI client you connect.
Tool names carry the module prefix: brain_, catalog_, commerce_, intelligence_; the two
connector tools start with nucleo_. A module's tools appear only if the company has the module,
the company has not switched it off for AI clients, it is set up, and you have access to it.
Definitions are cached for 60 seconds per person, company and module.
Annotations. Every tool carries the MCP annotations its module declares, passed through unchanged:
readOnlyHint (it only reads), destructiveHint (a write that cannot be reversed automatically, such
as merging duplicates in Brain), idempotentHint (true for reads) and openWorldHint (it reaches people
outside Nucleo, such as sending an email or a Slack message). A hint the module leaves out is derived
from whether the tool reads or writes, and a write tool is never presented as read-only. In the tool
tables below, Access shows them: read, write, write, destructive, write, open world.
Errors.
- Tool-level problems come back as a normal result with
isError: trueand a readable text: no permission (You do not have permission ... for this action.), invalid arguments, module not available in the company (with a hint to switch company), module unreachable or failing (Nucleo <Module> is not reachable right now (request <id>)— never internal details). - JSON-RPC errors:
-32700parse error (HTTP 400),-32601method not found,-32602unknown tool,-32603internal error,-32000module rate-limited (... busy, retry in N seconds).
Limits. Request body up to 2 MB. Module calls time out after 30 seconds. Modules rate-limit each person to 120 tool calls and 120 tool lists per minute (Catalog, Commerce, Intelligence). Token validation is cached for up to 60 seconds, but a token revoked in Nucleo (sign-out, disconnect, refresh) is refused immediately.
Privacy. Nucleo records which client you used, which tool, outcome and duration for the AI clients overview — never arguments or results.
/Server stream (not supported)
Server-initiated SSE streams are not offered. Always 405 with Allow: POST.
Responses
405Only POST is supported.application/json
HeadersAllow
errorstringmessagestring
curl -X GET https://mcp.nucleoplatform.com/const res = await fetch("https://mcp.nucleoplatform.com/", {
method: "GET",
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('GET', 'https://mcp.nucleoplatform.com/', [
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"error": "method_not_allowed",
"message": "Use POST with a JSON-RPC 2.0 body."
}/Send an MCP JSON-RPC request
The MCP endpoint (Streamable HTTP, POST only). Send Content-Type: application/json and
Authorization: Bearer <access token>. The response is always a single application/json
JSON-RPC message (202 with no body for notifications).
A missing, expired or revoked token gets 401 with
WWW-Authenticate: Bearer realm="Nucleo", resource_metadata="https://mcp.nucleoplatform.com/.well-known/oauth-protected-resource":
clients start the OAuth flow from there.
tools/call of a module tool is forwarded to the module with your token, the active company and
your language; the module's CallToolResult (content, structuredContent, isError) is passed
through unchanged.
Request bodyapplication/json
jsonrpc"2.0"requiredidstring | integerOmit for notifications.
methodstringrequiredOne ofinitializepingtools/listtools/callnotifications/initializednotifications/cancelledparamsobjectinitialize:protocolVersion,capabilities,clientInfo {name, version}.tools/call:name(prefixed tool name) andarguments(object matching the tool'sinputSchema).
Responses
200JSON-RPC response (result or error).application/json
jsonrpc"2.0"requiredidstring | integer | nullrequiredresultobjectinitialize→ InitializeResult;tools/list→{tools: Tool[]};tools/call→ CallToolResult;ping→{}.errorobjectChild attributes
codeintegerOne of-32700-32601-32602-32603-32000messagestring
202Notification accepted (no body).
400Body is not a JSON-RPC 2.0 request (also returned for batches).application/json
jsonrpc"2.0"requiredidstring | integer | nullrequiredresultobjectinitialize→ InitializeResult;tools/list→{tools: Tool[]};tools/call→ CallToolResult;ping→{}.errorobjectChild attributes
codeintegerOne of-32700-32601-32602-32603-32000messagestring
401Missing, invalid, expired or revoked token (or Nucleo Core temporarily unreachable).application/json
HeadersWWW-Authenticate
errorstringmessagestring
413Request body larger than 2 MB.
curl -X POST https://mcp.nucleoplatform.com/ \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "acme-agent",
"version": "1.4.0"
}
}
}'const res = await fetch("https://mcp.nucleoplatform.com/", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "acme-agent",
"version": "1.4.0"
}
}
}),
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('POST', 'https://mcp.nucleoplatform.com/', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
],
'json' => [
'jsonrpc' => '2.0',
'id' => 1,
'method' => 'initialize',
'params' => [
'protocolVersion' => '2025-06-18',
'capabilities' => (object) [],
'clientInfo' => [
'name' => 'acme-agent',
'version' => '1.4.0',
],
],
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": false
}
},
"serverInfo": {
"name": "nucleo",
"title": "Nucleo",
"version": "2.0.0"
},
"instructions": "ACTIVE COMPANY: \"Acme Apparel\" (the only company of the user). Modules available here: catalog, commerce. Nucleo is the platform of the signed-in person: tools are grouped by module through their prefix (brain_ = CRM, inbox, knowledge, documents, playbooks; catalog_ = products and assets; commerce_ = tickets, stores, orders; intelligence_ = KPIs and reports). Every tool runs inside the active company with the person's own permissions. Before any write tool, summarise what will change and ask for confirmation; tools marked destructive (destructiveHint) cannot be undone, so say so explicitly. Answer in the user's language (English)."
}
}{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "nucleo_list_companies",
"title": "List my companies",
"description": "List the companies the signed-in person can work in through Nucleo, with the modules available in each and which one is currently active. Call it when the user mentions a company that does not match the active one, or asks what they have access to.",
"inputSchema": {
"type": "object",
"properties": {}
},
"annotations": {
"readOnlyHint": true,
"destructiveHint": false,
"idempotentHint": true,
"openWorldHint": false
}
},
{
"name": "commerce_get_order",
"title": "Get order",
"description": "[Commerce] Orders (Nucleo OMS). One order in full: customer, address, items, ship-from location, holds, shipments with tracking, returns and anomalies.",
"inputSchema": {
"type": "object",
"properties": {
"order": {
"type": "string",
"maxLength": 120,
"description": "Order number (e.g. #1042) or id."
}
},
"required": [
"order"
]
},
"annotations": {
"readOnlyHint": true,
"destructiveHint": false,
"idempotentHint": true,
"openWorldHint": false
}
},
{
"name": "brain_merge_contacts",
"title": "Merge contacts",
"description": "[Brain] Unisce due contatti doppi: primary_contact_id resta, secondary_contact_id confluisce nel principale e finisce nel cestino. … Chiedi conferma; poi chiama con confirmed_by_user=true. …",
"inputSchema": {
"type": "object",
"properties": {
"primary_contact_id": {
"type": "integer",
"description": "Contatto che resta."
},
"secondary_contact_id": {
"type": "integer",
"description": "Contatto doppione da unire nel principale."
},
"confirmed_by_user": {
"type": "boolean",
"description": "true solo dopo il si' esplicito della persona."
}
},
"required": [
"primary_contact_id",
"secondary_contact_id",
"confirmed_by_user"
]
},
"annotations": {
"readOnlyHint": false,
"destructiveHint": true,
"idempotentHint": false,
"openWorldHint": false
}
}
]
}
}{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "Companies (● = active):\n● Acme Apparel (id 7) — modules: catalog, commerce\n○ Acme Outlet (id 9) — modules: catalog"
}
],
"structuredContent": {
"companies": [
{
"id": 7,
"slug": "acme",
"name": "Acme Apparel",
"modules": [
"catalog",
"commerce"
],
"active": true
},
{
"id": 9,
"slug": "acme-outlet",
"name": "Acme Outlet",
"modules": [
"catalog"
],
"active": false
}
],
"active_company_id": 7
},
"isError": false
}
}{
"jsonrpc": "2.0",
"id": 4,
"result": {
"content": [
{
"type": "text",
"text": "Active company is now \"Acme Outlet\" (id 9). Module tools now work inside it."
}
],
"structuredContent": {
"active_company_id": 9
},
"isError": false
}
}{
"jsonrpc": "2.0",
"id": 5,
"result": {
"content": [
{
"type": "text",
"text": "You do not have permission `oms.read` for this action."
}
],
"isError": true
}
}{
"jsonrpc": "2.0",
"id": 5,
"error": {
"code": -32602,
"message": "Unknown tool: commerce_search_invoices"
}
}{
"jsonrpc": "2.0",
"id": 8,
"error": {
"code": -32601,
"message": "Method not found: resources/list"
}
}{
"jsonrpc": "2.0",
"id": 5,
"error": {
"code": -32000,
"message": "Nucleo Commerce is busy, retry in 30 seconds."
}
}HTTP 202 — Notification accepted (no body).{
"jsonrpc": "2.0",
"id": null,
"error": {
"code": -32700,
"message": "Parse error: expected a JSON-RPC 2.0 request."
}
}{
"error": "unauthorized",
"message": "Sign in with Nucleo to use this connector."
}/mcpServer stream on alias (not supported)
Always 405 with Allow: POST.
Responses
405Only POST is supported.application/json
HeadersAllow
errorstringmessagestring
curl -X GET https://mcp.nucleoplatform.com/mcpconst res = await fetch("https://mcp.nucleoplatform.com/mcp", {
method: "GET",
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('GET', 'https://mcp.nucleoplatform.com/mcp', [
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"error": "method_not_allowed",
"message": "Use POST with a JSON-RPC 2.0 body."
}/mcpSend an MCP request (alias)
Same as POST /, for clients that expect an /mcp path.
Request bodyapplication/json
jsonrpc"2.0"requiredidstring | integerOmit for notifications.
methodstringrequiredOne ofinitializepingtools/listtools/callnotifications/initializednotifications/cancelledparamsobjectinitialize:protocolVersion,capabilities,clientInfo {name, version}.tools/call:name(prefixed tool name) andarguments(object matching the tool'sinputSchema).
Responses
200JSON-RPC response.application/json
jsonrpc"2.0"requiredidstring | integer | nullrequiredresultobjectinitialize→ InitializeResult;tools/list→{tools: Tool[]};tools/call→ CallToolResult;ping→{}.errorobjectChild attributes
codeintegerOne of-32700-32601-32602-32603-32000messagestring
202Notification accepted.
400Not a JSON-RPC 2.0 request.
401Missing, invalid, expired or revoked token (or Nucleo Core temporarily unreachable).application/json
HeadersWWW-Authenticate
errorstringmessagestring
curl -X POST https://mcp.nucleoplatform.com/mcp \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": "string",
"method": "initialize",
"params": {}
}'const res = await fetch("https://mcp.nucleoplatform.com/mcp", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"jsonrpc": "2.0",
"id": "string",
"method": "initialize",
"params": {}
}),
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('POST', 'https://mcp.nucleoplatform.com/mcp', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
],
'json' => [
'jsonrpc' => '2.0',
'id' => 'string',
'method' => 'initialize',
'params' => (object) [],
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"jsonrpc": "2.0",
"id": "string",
"result": {},
"error": {
"code": -32700,
"message": "string"
}
}HTTP 202 — Notification accepted.{
"error": "unauthorized",
"message": "Sign in with Nucleo to use this connector."
}