opik-mcp

tool-surface

Purpose

The tool surface is what every host session loads from this server: the read, list and schema tools, the initialize instructions, and the wire contract the conformance suite pins. write is in writes and read_skill in skills. This doc answers what a host receives, what a read or a list promises, what is refused, and which test holds each promise.

What it does now

The advertised surface

initialize

initialize returns the name opik-mcp and the text of render_instructions (src/opik_mcp/instructions.py): workspace and UI address, the default project if set, tool selection, the link rule, and today’s UTC date.

read

read(entity_type, id, project_id?, project_name?, since?, until?, fields?):

[read: trace <id> | 1,234 tok | open as a link named 'my-trace']
{"trace": {...}, "spans": [...], "spansTruncated": false, "spanBodies": "...", "url": "..."}

list

[list: trace | 1,234 tok | filters: error_info is_not_empty AND source = "sdk" | sort: duration desc | since: 1h (2026-09-24T10:03Z)]

An empty page with a zero total carries at most one hint, picked by empty_message (src/opik_mcp/read_list/list_empty_page.py) in this order: the entity’s page_note_fn note (Diagnostics); with since, the project’s last trace when it is before the window; under the sdk default, the rows other sources hold; with name, the rows without it; with filters, the rows without them. A failed probe is skipped.

A read carries a url, a url_absent sentence when the record has no page, or nothing when the UI base or workspace is unknown. A trace, span or thread page carries one url_template filled from each row: the entity’s row_link_template hook, turned into a page note by page_note_of in src/opik_mcp/read_list/decorations.py. An entity with no page of its own declares a view_page, and a case or prompt version a parent_page.

How it works

server/tools/ read / list (FastMCP tool, instrument_tool wrapper)
  -> read_list/read_tool.py run_read  |  read_list/list_tool.py run_list
  -> read_list/registry.py ENTITY_REGISTRY[entity_type]  (an EntityHandler)
  -> read_list/entities/<entity>.py  fetch_fn / list_fn / run_fn / link_fn / page_note_fn
  -> client/  -> Opik REST API

schema("list.*") goes writes/schema_tool.py run_schema → read_list/reference.py list_reference.

Where to start:

Root modules stay generic (ADR 0004); entity_names_at_root in tests/repo/ratchets.json is empty and stays so: a root table for one entity is a missing hook on EntityHandler.

decorations.block runs an optional part of a read under a deadline. A failure or timeout becomes {"error": "Could not load …"} and the read still returns. Its main user is project-overview.

Boundaries:

Decisions

Traps

Proven by

Log