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 via tools/list, not declared as REST paths. The OpenAPI spec documents only the POST /mcp transport envelope. This file is the authoritative human reference for the tool catalogue; the live tools/list response is the authoritative machine reference (and is filtered per key — see Scopes).

Endpoint & transport

EndpointPOST https://<tenant>.veristage.com/api/external/mcp
TransportMCP streamable-HTTP, JSON-RPC 2.0
Responseapplication/json (the server does not upgrade to SSE; clients that require SSE are still compatible because the spec permits a JSON-only server)
AuthX-Api-Key: <key> header on every request (server-side only — never from a browser)
Serverinsight-external-mcp v1.0.0
Protocol versions2025-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 check

Every request after initialize must send the Mcp-Session-Id header returned by initialize.

Sessions

  • A session row is created by initialize and 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:

  1. tools/list returns only the tools whose required scope the key holds. A read-only key never even sees the write/ai tools.
  2. tools/call re-checks the tool's required scope before dispatch; an out-of-scope call is rejected with JSON-RPC error -32002 and 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)

ToolPurposeKey inputs
search_catalogueSearch the catalogue (books, documents, images).query (req), mode (keyword|vector|hybrid), limit (1–25), offset, type
catalogue_queryAnswer 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_resourceRead the key metadata for one resource by ref (trimmed projection, not the full record).ref (req)
read_document_contentRead a page of a resource's extracted full text (for RAG / summarisation).ref (req), offset (0-based chars), limit (default 20000, max 100000)
list_collectionsList collections (projects) visible to the linked user.limit (1–100), offset
get_collectionList the resources in one collection.ref (req), limit (1–100), offset
get_jobPoll one previously-submitted job by ref.ref (req)

Write tools (write scope)

ToolPurposeKey inputs
create_resourceCreate a resource of a named type with field values keyed by field name.type (req, type name), fields
update_resource_fieldsUpdate field values (keyed by field name) on an existing resource.ref (req), fields (req)
add_to_collectionAdd resources to a collection (per-ref status returned).ref (req, collection), resourceRefs (req, max 100)
submit_jobSubmit 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)

ToolPurposeKey inputs
generate_keywordsResearch Amazon book keywords for seed terms.keywords (req, 1–10), marketplace (e.g. us, uk, de)
generate_fieldGenerate 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_fields take fields keyed by name, and the available names differ per publisher/box. Discover them via the REST endpoint GET /resources/fields (or search_catalogue results) rather than assuming a fixed set.

Errors

Protocol-level failures are JSON-RPC errors:

CodeMeaning
-32700Parse error (malformed JSON)
-32600Invalid request
-32601Method not found
-32602Invalid params / unknown tool
-32001Session not found / expired / not owned by this key
-32002Insufficient 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.

On this page