On this page

The Nviti MCP server lets an MCP client manage resources in the company assigned to an API key. It uses the same BREAD permissions, validation, resource fields, and audit records as the REST API.

Update Nviti records with Codex CLI and MCP Watch on YouTube

Prepare a key

Open Settings → API Keys for the company you want to manage. Create a key with the operations the client needs and copy its secret. New keys have no permissions until you assign them. Your account also needs API integration subscription access and the corresponding resource access.

For a client that only looks up contacts, assign contacts:browse and contacts:read. For a client that updates contact details, also assign contacts:edit. Add and Delete are separate grants. See API Settings for the permission matrix and key lifecycle.

Connect a client

Use a client that supports a remote MCP server over Streamable HTTP with custom Bearer headers.

Setting Value
Server name A descriptive local name, such as Nviti contacts.
URL https://api.nviti.ng/api/v1/account/mcp, or the MCP endpoint displayed in API settings.
Transport Streamable HTTP.
Authentication header Authorization: Bearer <API_KEY>.

Store the actual secret in the client's credential storage. The exact configuration format depends on the client. Nviti currently uses Bearer authentication for this connection; an OAuth account sign-in flow is not provided. A client that requires OAuth and cannot send a Bearer header cannot use this endpoint directly.

After connecting, let the client initialize the server and discover its tools. The key fixes the company context. Do not add company_id, tenant_id, or other company selectors to tool arguments.

Connect Codex CLI

With Codex CLI installed and signed in, enter your Nviti key into an environment variable in the terminal where you will run Codex:

bash
read -rsp 'Nviti API key: ' NVITI_API_KEY
export NVITI_API_KEY
printf '\n'

codex mcp add nviti \
  --url 'https://api.nviti.ng/api/v1/account/mcp' \
  --bearer-token-env-var NVITI_API_KEY

codex mcp get nviti
codex

Use the endpoint displayed in API settings for your installation. The configuration stores the environment variable's name; start Codex from a terminal where that variable is available. This setup was verified with Codex CLI 0.159.3. See the official Codex MCP documentation for current configuration options.

For the tutorial's two updates, grant Browse, Read, and Edit for Contacts and Contact groups. Then give Codex a precise instruction:

Find the unique contact named Ada Okafor and read it. Change only its email to [email protected], preserving its name and phone number. Find the unique contact group named Bloom VIP Customers and read it. Change only its description to “VIP studio customers: send a personal follow-up within one business day.” Preserve the group name. Read both records again and report the saved values. Stop if either name is ambiguous.

Replace these fictional records and values with your intended changes. Approve the requested write tools in your Codex session. Codex's tool approval policy and Nviti's API permissions both apply: an Edit grant does not override a client's policy that prevents writes. The video uses a dedicated demo profile that approves only the two Edit tools; the Nviti key grants no Add or Delete operations.

In the workspace UI, API contacts appear under Visitors, and contact_groups appear under Custom Audiences. Open those records to verify the saved changes independently of the client's response.

Discover permitted tools

Tool names follow {action}_{scope}. For example:

Grant Tool Purpose
contacts:browse browse_contacts List/search contact summaries.
contacts:read read_contacts Retrieve supported contact details.
contacts:edit edit_contacts Change supported contact attributes.
contacts:add add_contacts Create a contact.
contacts:delete delete_contacts Remove a contact.

Only effective grants appear in tools/list. Each tool includes a description, inputSchema, and annotations describing whether it reads or changes data. The scope reference lists all supported scopes and fields.

Tool discovery is paginated. Follow result.nextCursor until it is absent and combine the result.tools arrays. The default page contains up to 15 tools, so a fully permitted key needs several discovery requests. A client that reads only the first page will miss tools. This cursor is separate from the numeric page used to browse resources.

Refresh tool discovery after changing key permissions or owner access. A downloaded MCP schema export contains the full permitted tool list at export time, while live discovery reflects current access. Every tool execution checks access again.

Protocol walkthrough

Most MCP clients handle the following exchange automatically. These examples show the JSON-RPC messages for a custom client.

Send requests as POST to the MCP URL with Authorization, Content-Type: application/json, and Accept: application/json, text/event-stream. Give each request a unique id. Notifications omit id.

Initialize

bash
export NVITI_MCP_URL='https://api.nviti.ng/api/v1/account/mcp'
export NVITI_API_KEY='<YOUR_API_KEY>'

curl --fail-with-body --silent --show-error --include \
  --request POST "$NVITI_MCP_URL" \
  -H "Authorization: Bearer $NVITI_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"My Nviti integration","version":"1.0.0"}}}'

The response identifies Nviti account management. Use the returned result.protocolVersion in the MCP-Protocol-Version header on subsequent requests. If the response supplies MCP-Session-Id, also send that header on subsequent requests. Then send the initialized notification:

json
{"jsonrpc":"2.0","method":"notifications/initialized"}

List tools

json
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}

If the response contains result.nextCursor, send another request using that exact value:

json
{"jsonrpc":"2.0","id":3,"method":"tools/list","params":{"cursor":"<NEXT_CURSOR_FROM_RESPONSE>"}}

Continue until no cursor remains. Treat cursors as opaque values, and restart discovery from the first page after changing permissions.

Browse contacts

json
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "browse_contacts",
    "arguments": {"page":1,"per_page":20,"search":"Ada"}
  }
}

The result's structuredContent has the REST response shape: a data array and pagination meta. Browse returns IDs, names, and timestamps. Call read_contacts with {"id":42} to retrieve the supported details for an actual ID from Browse.

Edit a contact

json
{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "edit_contacts",
    "arguments": {
      "id": 42,
      "attributes": {"name":"Ada Okafor","email":"[email protected]"}
    }
  }
}

Replace the example ID with the intended contact ID before calling this tool. MCP Edit nests changed fields inside attributes; REST Edit uses a flat body. Edit returns only changed fields, ID, and timestamps, even if the key lacks Read access.

Add uses the same nested attributes without an ID. For example, the arguments for add_contacts are:

json
{"attributes":{"name":"Ada Okafor","email":"[email protected]"}}

Read and Delete use only an ID, such as {"id":42}. Delete changes real company data; check the intended resource before calling it. Creating and editing require the fields and constraints in the scope reference.

Handle results and errors

A successful call has result.isError: false and the resource response in result.structuredContent. It also includes a text representation in result.content.

Check all three layers of a response:

  1. HTTP status: authentication, subscription access, Origin restrictions, and rate limiting can reject the request before MCP execution.
  2. JSON-RPC error: an unavailable tool or malformed protocol request can return an error even with HTTP 200. An unavailable tool uses code -32602.
  3. result.isError: validation, missing resources, and denied resource operations can return HTTP 200 with a tool error in result.content.

For validation errors, text content contains JSON with code: VALIDATION_ERROR and an errors map. A missing resource produces a RESOURCE_NOT_FOUND message. Do not treat HTTP 200 alone as a successful operation.

REST and MCP share the default 60-request-per-minute limit per key. Subscription request allowances and tenant-wide API rate limits also apply. Respect Retry-After on 429 responses. Rotating or revoking a key immediately invalidates its previous secret and derived tokens; update the client's credential and reconnect after rotation.

Troubleshoot a connection

Symptom Check
401 response Bearer header, secret, expiry, revocation, and company assignment. Ordinary mobile/session tokens are not account-management credentials.
403 response API integration subscription access, company membership, and any browser Origin header.
No tools Assign resource grants, confirm the owner's current access, and refresh discovery.
Some tools missing Follow nextCursor; also check supported actions and the key's effective grants.
Unknown tool after a permission change Refresh tools/list and discard cached tool definitions.
Validation error Use the nested attributes object for Add/Edit, positive integer IDs, and only supported fields.
HTTP 422 with errors.subscription Review the account's remaining API request allowance and subscription limits.

For browser clients, the server accepts only configured Origins. Application and API-server Origins are allowed by default. Administrators can add exact Origins through ACCOUNT_MCP_ALLOWED_ORIGINS, a comma-separated list such as https://tools.example.com,https://portal.example.com. This setting validates the Origin; browser hosting must also provide the required CORS behavior. Clients that do not send an Origin header are accepted by the Origin check.

Administrators can set ACCOUNT_API_BASE_URL to change the address displayed in exports. This changes the advertised address; the installation must already serve the API at that address.