Servers
Connect remote MCP servers so your Omniagent can call their tools during a session
An MCP server is an external service that exposes tools over the Model Context Protocol. Register one with the Napster API, attach it to an Omniagent, and the agent can call its tools mid-conversation — without you writing a tool definition or handling a single tool call.
How MCP servers differ from tools
Who executes the call, and what you have to build
Types of MCP server
What a server requires before it lets your agent in
Register a server
POST /public/mcp-servers and the fields it takes
Attach it to an agent
The mcp field on agents, channels and connections
Per-user authorization
Pass an end user's OAuth token for the session
MCP servers are supported on the Azure OpenAI and OpenAI providers. Azure OpenAI is the default, so this covers most projects — but if your Omniagent runs on a different provider, attaching an MCP server fails with 400 UnsupportedProvider.
MCP is not available on phone channels. A server attached to an agent does not apply on VoIP or SIP sessions, so the agent has no MCP tools on a call.
How MCP servers differ from tools
A custom tool is something you define and execute — you write the JSON Schema, and the call comes back to your client or your server to run. An MCP server is something you point at. The provider connects to it, discovers its tools, and executes the calls itself.
| Custom tools | MCP servers | |
|---|---|---|
| You define the schema | Yes | No — discovered from the server |
| You execute the call | Yes, in your client or on your server | No — executed provider-side |
| Attached via | functions | mcp |
| Tools per entry | One | Every tool the server exposes |
The practical consequence: attaching an MCP server can add many tools to a session at once. Use allowedTools to narrow that down.
MCP server IDs share a namespace with tool names. If a server and a tool have the same name, the session fails to start.
Types of MCP server
Most MCP servers you attach are ones somebody else built and runs — a vendor's server for their own product, or a public one. What differs between them is what the server requires before it will let your agent in. That determines how you register it, and how much your session code has to do.
| Type | What the server requires | Register with |
|---|---|---|
| Public | Nothing | url |
| Authenticated | A credential you hold | url + headers |
| Per-user | Each end user's own authorization | url + authorizationRequired: true |
The server's own documentation tells you which it is. If it gave you an API key, it is authenticated. If it sent you through a sign-in screen, it is per-user.
For Gmail, Google Drive, SharePoint and other common services, you don't need to register anything at all — attach a ready-made connector instead. Connectors require the OpenAI provider, so on Azure OpenAI register the service's own MCP server here as a per-user server.
Public
Open to anyone with the URL — documentation lookups, public reference data, search. Register it with a url and attach it. There is nothing to handle at session time.
Authenticated
The server sits behind an account you hold. You get an API key or token from whoever runs it, store it in headers when you register, and it is sent on every request.
Every session uses that one account, so the agent sees the same data no matter who is talking to it. That suits servers whose data is not tied to a person: catalogs, pricing, documentation, reference systems.
Use allowedTools to limit what the agent can reach through your account. A third-party server often exposes more tools than you need — including ones that write or delete — and by default the agent can call all of them.
Per-user
The server holds data that belongs to individual people — their mail, calendar, files, or records — so it requires each user to authorize access to their own. Register it with authorizationRequired: true and no credential, then pass that user's OAuth token when you open the session. The token is used for that session and never stored.
This is the only shape that can answer "what's on my calendar" correctly. It costs you more work: you run the sign-in flow and supply a token per session. See Per-user authorization.
Register a server
Registration stores the server's address and access rules against your project. It does not attach it to anything yet.
Send the server definition to POST /public/mcp-servers:
curl -X POST https://companion-api.napster.com/public/mcp-servers \
-H "X-Api-Key: $NAPSTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"id": "inventory",
"url": "https://mcp.acme.com/sse",
"headers": {
"Authorization": "Bearer sk_live_abc123"
},
"allowedTools": ["check_stock", "get_price"],
"requireApproval": "never",
"description": "Live product inventory and pricing",
"authorizationRequired": false,
"tags": {
"env": "prod"
}
}'The response is the stored server definition — the same fields you sent, plus any defaults the API filled in. There is no generated ID to capture, since id is the one you chose:
{
"id": "inventory",
"url": "https://mcp.acme.com/sse",
"headers": {
"Authorization": "Bearer sk_live_abc123"
},
"allowedTools": ["check_stock", "get_price"],
"requireApproval": "never",
"description": "Live product inventory and pricing",
"authorizationRequired": false,
"tags": {
"env": "prod"
}
}Fields
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | You choose it — it is not generated. Alphanumeric, underscores and hyphens only, max 48 characters. Must be unique within your project |
url | string | Yes | The MCP server's endpoint. Must be https:// |
headers | object | No | String-to-string map sent to the server on every request. Use this for a static credential |
allowedTools | array | No | Names of the tools the agent may call. Omit to expose every tool the server advertises |
requireApproval | string | No | never (default) or always. See Approval |
description | string | No | Stored for your own reference. It is not sent to the model and does not influence tool selection |
authorizationRequired | boolean | No | false by default. true means sessions must supply a per-user token. See Per-user authorization |
tags | object | No | String-to-string map, max 16 pairs. Keys and values up to 64 characters. Filters the list endpoint |
The id becomes the server's label everywhere the agent refers to it, and it cannot be changed. Renaming means deleting the server, recreating it, and re-attaching it wherever it was used.
Approval
requireApproval controls whether a tool call runs immediately or waits for a human decision.
With never — the default — the agent calls the server's tools as soon as it decides to.
With always, every call pauses and your client decides. The exchange runs over the session's data channel:
- You receive an
mcp_approval_requestevent naming the server, the tool and its arguments. - Present the choice to the user however you like.
- Answer with
send_mcp_approval, passing the request ID andapprove: trueorfalse.
A rejection cancels the call and the MCP server is never contacted.
You have 15 seconds to answer. After that the request is rejected automatically and the call never runs. Build the approval UI before you set always on a server your agent depends on — the agent gives no spoken indication that it is waiting.
Use always for tools with consequences — sending mail, issuing a refund, changing a record — and never for reads.
Attach it to an agent
A registered server does nothing until you attach it. Pass its ID in mcp.servers when you create or update an Omniagent:
curl -X PATCH https://companion-api.napster.com/public/agents/agent_abc123 \
-H "X-Api-Key: $NAPSTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mcp": {
"servers": ["inventory"]
}
}'Every session that agent runs now has the server attached. You can also set mcp on a channel configuration to override it for one channel, or on a raw connection to attach servers per session.
The mcp object holds a second list, connectors, for ready-made servers you don't register. Those require the OpenAI provider — see Connectors.
mcp is replaced wholesale, never merged. A channel configuration with its own mcp overrides the agent's completely, and a PATCH with mcp replaces the previous value. To add a server, send the full list including the ones already attached.
Per-user authorization
This is the mechanics of the per-user shape — two steps: register the server as needing a token, then supply the user's token per session.
Register it with authorizationRequired and no credential:
curl -X POST https://companion-api.napster.com/public/mcp-servers \
-H "X-Api-Key: $NAPSTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"id": "crm",
"url": "https://mcp.acme.com/crm",
"authorizationRequired": true,
"description": "Customer records, scoped to the signed-in user"
}'Attach it to the agent, then pass that user's token in mcp.authorizations when you open the session. The agent already carries the server, so the connection call sends only the token:
curl -X POST https://companion-api.napster.com/public/agents/agent_abc123/connections \
-H "X-Api-Key: $NAPSTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channelType": "webrtc",
"mcp": {
"authorizations": [
{
"mcpServerId": "crm",
"token": "ya29.a0AfH6..."
}
]
}
}'The token is used for that session only. It is never stored.
Running the OAuth flow is your responsibility. Napster never contacts the identity provider — you obtain the user's access token and pass it at connection time. Mint a fresh token per session rather than reusing a cached one, since a session can outlive a short expiry.
The rule is strict both ways
authorizationRequired decides whether a token is mandatory or forbidden — there is no middle ground:
| Server registered with | Session provides a token | Result |
|---|---|---|
authorizationRequired: true | Yes | Works |
authorizationRequired: true | No | 400 — the server requires a token |
authorizationRequired: false | Yes | 400 — the server does not accept a token |
authorizationRequired: false | No | Works, using headers |
A token for a server that is not attached to the same request is also rejected.
Manage registered servers
# List, with optional search and tag filters
curl "https://companion-api.napster.com/public/mcp-servers?pageSize=20&search=inventory" \
-H "X-Api-Key: $NAPSTER_API_KEY"
# Get one
curl https://companion-api.napster.com/public/mcp-servers/inventory \
-H "X-Api-Key: $NAPSTER_API_KEY"
# Delete
curl -X DELETE https://companion-api.napster.com/public/mcp-servers/inventory \
-H "X-Api-Key: $NAPSTER_API_KEY"Updates use PUT and replace the entire server:
curl -X PUT https://companion-api.napster.com/public/mcp-servers/inventory \
-H "X-Api-Key: $NAPSTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://mcp.acme.com/sse",
"headers": {
"Authorization": "Bearer sk_live_xyz789"
},
"allowedTools": ["check_stock", "get_price"],
"requireApproval": "never",
"description": "Live product inventory and pricing"
}'PUT is a full replace, not a patch. Any field you omit reverts to its default — requireApproval falls back to never and headers is cleared. Read the server first, change what you need, and send the whole object back.
Errors
Managing servers
| Endpoint | Status | Code | Cause |
|---|---|---|---|
POST /public/mcp-servers | 409 | McpServerAlreadyExists | An MCP server with that id already exists in your project |
POST /public/mcp-servers | 400 | McpServerValidationFailed | The definition was rejected — most often a missing or non-https url |
PUT /public/mcp-servers/{id} | 400 | McpServerNotFound | No server with that id in your project |
DELETE /public/mcp-servers/{id} | 400 | McpServerInUse | Still attached to an Omniagent or a channel configuration. Detach it everywhere first |
DELETE /public/mcp-servers/{id} | 400 | McpServerNotFound | No server with that id in your project |
Attaching servers
| Endpoint | Status | Code | Cause |
|---|---|---|---|
POST /public/agents, PATCH /public/agents/{id}, PUT /public/agents/{id}/channels/{type} | 400 | McpServerNotFound | An ID in mcp.servers or mcp.connectors does not exist |
Opening a session
| Endpoint | Status | Code | Cause |
|---|---|---|---|
POST /public/agents/{id}/connections | 400 | UnsupportedProvider | The Omniagent runs on a provider that does not support MCP servers |
POST /public/agents/{id}/connections | 400 | McpServerNotFound | An attached ID no longer exists, or a token was supplied for a server that is not attached |
A session also fails to start if two attached servers share a name, or if a server's name collides with one of the agent's tool names.