MCP server reference
Connect agents to Veristage catalogue, workflow, and AI tools through a governed MCP endpoint.
The Insight external API exposes a Model Context Protocol (MCP) server so a publisher's own AI agents and tools connect to Insight's catalogue and skills through one governed door. It is the same walls as the REST API: a per-publisher API key maps to a service user, and every tool call inherits that user's permissions, usergroup scoping, and the key's coarse scopes.
This document describes the MCP layer specifically. The tools are not in
openapi.yaml— MCP tools are discovered at runtime viatools/list, not declared as REST paths. The OpenAPI spec documents only thePOST /mcptransport envelope. This file is the authoritative human reference for the tool catalogue; the livetools/listresponse is the authoritative machine reference (and is filtered per key — see Scopes).
Endpoint & transport
| Endpoint | POST https://<tenant>.veristage.com/api/external/mcp |
| Transport | MCP streamable-HTTP, JSON-RPC 2.0 |
| Response | application/json (the server does not upgrade to SSE; clients that require SSE are still compatible because the spec permits a JSON-only server) |
| Auth | X-Api-Key: <key> header on every request (server-side only — never from a browser) |
| Server | insight-external-mcp v1.0.0 |
| Protocol versions | 2025-06-18 (latest/preferred), 2025-03-26, 2024-11-05 |
GET /mcp returns 405 (no server-initiated stream); DELETE /mcp tears a
session down. JSON-RPC batching (removed in the 2025-06-18 MCP revision) is not
supported.
Connection lifecycle
1. initialize → negotiate protocolVersion; server returns serverInfo +
capabilities and issues an `Mcp-Session-Id` response header
2. notifications/initialized (client → server, no response)
3. tools/list → the tools this key may call (scope-filtered)
4. tools/call → invoke a tool
ping → liveness checkEvery request after initialize must send the Mcp-Session-Id header
returned by initialize.
Sessions
- A session row is created by
initializeand bound to the API key that created it. A session id presented with a different key is rejected — one key cannot resume or hijack another's session. - TTL is 24 hours (86400s), extended on each request. Expired sessions are rejected and reaped opportunistically.
- Missing/expired/foreign session → JSON-RPC error
-32001(session not found).
Scopes & authorisation (two layers)
Every tool declares a required coarse scope: read, write, or
ai. The key's scopes (see the main API docs) gate access at two points:
tools/listreturns only the tools whose required scope the key holds. A read-only key never even sees the write/ai tools.tools/callre-checks the tool's required scope before dispatch; an out-of-scope call is rejected with JSON-RPC error-32002and never runs.
Underneath the scope check, the linked service user's own permissions and usergroup scoping still apply (defence in depth) — exactly as they would for a human user in the app. A key can never reach data or actions its service user can't.
Tool catalogue
Tools map 1:1 onto the Insight external REST services; nothing is reimplemented. Descriptions below are the caller-facing tool descriptions.
Read tools (read scope)
| Tool | Purpose | Key inputs |
|---|---|---|
search_catalogue | Search the catalogue (books, documents, images). | query (req), mode (keyword|vector|hybrid), limit (1–25), offset, type |
catalogue_query | Answer a structured catalogue question ("how many books by X", "list our 2023 titles"). Counts are always exact. | filters (list of {field, op, value}, ANDed), query (free-text → title contains), sort, limit |
get_resource | Read the key metadata for one resource by ref (trimmed projection, not the full record). | ref (req) |
read_document_content | Read a page of a resource's extracted full text (for RAG / summarisation). | ref (req), offset (0-based chars), limit (default 20000, max 100000) |
list_collections | List collections (projects) visible to the linked user. | limit (1–100), offset |
get_collection | List the resources in one collection. | ref (req), limit (1–100), offset |
get_job | Poll one previously-submitted job by ref. | ref (req) |
Write tools (write scope)
| Tool | Purpose | Key inputs |
|---|---|---|
create_resource | Create a resource of a named type with field values keyed by field name. | type (req, type name), fields |
update_resource_fields | Update field values (keyed by field name) on an existing resource. | ref (req), fields (req) |
add_to_collection | Add resources to a collection (per-ref status returned). | ref (req, collection), resourceRefs (req, max 100) |
submit_job | Submit a long-running job. | type (req), params (req; must include resource) |
submit_job types: proofreading, translate, adapt, style-coding,
corrections-check, accessibility-check, document-index, scan. All are
non-destructive — they write an alternative file and never modify the
original. corrections-check additionally takes compareResourceRef or
compareAltFileRef (the second document), validated for the caller's access.
AI generation tools (ai scope)
| Tool | Purpose | Key inputs |
|---|---|---|
generate_keywords | Research Amazon book keywords for seed terms. | keywords (req, 1–10), marketplace (e.g. us, uk, de) |
generate_field | Generate a metadata field's value for a resource (not persisted — apply with update_resource_fields). | resourceRef (req), fieldName or fieldRef |
Field names are instance-specific.
create_resource/update_resource_fieldstake fields keyed by name, and the available names differ per publisher/box. Discover them via the REST endpointGET /resources/fields(orsearch_catalogueresults) rather than assuming a fixed set.
Errors
Protocol-level failures are JSON-RPC errors:
| Code | Meaning |
|---|---|
-32700 | Parse error (malformed JSON) |
-32600 | Invalid request |
-32601 | Method not found |
-32602 | Invalid params / unknown tool |
-32001 | Session not found / expired / not owned by this key |
-32002 | Insufficient scope for the requested tool |
Tool-execution failures come back as an in-band MCP result with
isError: true (not a protocol error). Caller-facing conditions (validation,
not-found, permission) carry a useful message; any unexpected internal error is
logged server-side and returned only as the generic string
"An internal error occurred while executing this tool" — no SQL, file paths,
or exception text is ever leaked to the caller.
Worked example
KEY="$VERISTAGE_KEY"; HOST="https://$VERISTAGE_HOST/api/external"
# 1. initialize — capture the session id from the response header
SID=$(curl -sD - -o /tmp/init.json -X POST "$HOST/mcp" \
-H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
"protocolVersion":"2025-06-18",
"capabilities":{},
"clientInfo":{"name":"my-agent","version":"1.0"}}}' \
| grep -i '^Mcp-Session-Id:' | tr -d '\r' | awk '{print $2}')
# 2. announce initialised
curl -s -X POST "$HOST/mcp" -H "X-Api-Key: $KEY" -H "Mcp-Session-Id: $SID" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
# 3. list the tools this key may call
curl -s -X POST "$HOST/mcp" -H "X-Api-Key: $KEY" -H "Mcp-Session-Id: $SID" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
# 4. call one
curl -s -X POST "$HOST/mcp" -H "X-Api-Key: $KEY" -H "Mcp-Session-Id: $SID" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{
"name":"search_catalogue",
"arguments":{"query":"climate","mode":"hybrid","limit":5}}}'Standard MCP clients (Claude, the MCP Inspector, the official SDKs) handle this
handshake automatically — point them at POST /api/external/mcp with the
X-Api-Key header.