Quiddity.Mcp (ExQuiddity v0.1.2)

Copy Markdown View Source

A Model Context Protocol server over stdio: a STEP part, for an assistant.

One JSON-RPC message per line in, one out — the stdio transport, which is what a client launches as a process. No sessions, no server-sent events, no batches: every request is self-contained, which is what lets a client treat it as a pipe.

What the tools are for

quiddity answers "what is this part?" and this server exposes that, plus the geometry underneath it. The questions worth asking are different enough to be separate tools:

  • what is it? — quiddity_part, quiddity_features, quiddity_face;
  • what is here? — quiddity_probe and quiddity_sense: a ray, a point, and a camera frame rendered against the exact B-rep;
  • how do two parts differ? — quiddity_correspond matches the features of one part against another's by name and size.

quiddity_export_mjcf is the one tool that writes: it puts the part on disk as a MuJoCo model.

Parts are opened once

Calls name a path; the server keeps the parsed part, so a second question about the same file does not re-read it. The cache is this process's — restarting the server costs a re-read, and nothing on disk depends on it.

A tool that cannot answer answers: a refusal is text saying what was wrong and what to try, never a dropped connection.

Summary

Types

What the server remembers between messages: the parts it has read.

Functions

One message: the reply (or nil for a notification) and the state after it.

The protocol revisions this server speaks, newest first.

Reads JSON-RPC messages from stdin and writes answers to stdout until stdin closes.

Types

state()

@type state() :: %{parts: %{optional(String.t()) => Quiddity.Part.t()}}

What the server remembers between messages: the parts it has read.

Functions

handle(line, state)

@spec handle(String.t(), state()) :: {String.t() | nil, state()}

One message: the reply (or nil for a notification) and the state after it.

nil for a blank line, and for a line that is not JSON — a transport that crashes on garbage is a transport nobody can debug.

protocol_versions()

@spec protocol_versions() :: [String.t()]

The protocol revisions this server speaks, newest first.

serve()

@spec serve() :: :ok

Reads JSON-RPC messages from stdin and writes answers to stdout until stdin closes.

Notifications are answered with nothing, which is what the transport says: a notifications/initialized gets no reply.