All documents

McpServerApp

spicegrinder-icon

© 2026 Obsvra. This document describes SpiceGrinder and is provided to help you evaluate and use it. It is not a license to reproduce, adapt, or use this material to build a competing product or service. Full terms: the SpiceGrinder EULA.

NAME

McpServerApp — MCP (Model Context Protocol) server exposing component discovery, model validation, and model analysis as tool calls

SYNOPSIS

java -cp spicegrinder.jar com.obsvra.spicegrinder.tools.McpServerApp

On a jpackage install:

mcp

Takes no arguments and no model file. Communicates over stdio by default using the MCP protocol (JSON-RPC), meant to be launched by an MCP host (Claude Desktop, Claude Code, or any other MCP-compatible client), not run interactively from a terminal. Set SPICEGRINDER_MCP_TRANSPORT=http to run a standalone HTTP server instead — for hosts whose connector UI only accepts a URL, not a local launch command; see TRANSPORT below.

DESCRIPTION

McpServerApp is a third surface over the same core classes library/validator/analyzer and the Service API already call directly (ComponentRegistry, ModelValidator, ModelAnalyzer) — not a wrapper around the CLI process, a peer of it. This is what makes SpiceGrinder usable from any MCP-aware AI host, not just one that happens to shell out to a CLI or know the REST API.

Six tools are exposed. list_components returns the identical JSON GET /v1/components does (both share ComponentDescriptorJson); get_allowlists returns the identical JSON GET /v1/allowlists does. validate_model and analyze_model take model text directly (no file path — the model’s actual XML/JSON content), matching how the Service API’s own POST /v1/models accepts inline model text.

generate/read_generation support two mutually exclusive modes, selected server-wide (see MODE SELECTION below): Proxy mode (default) is a thin HTTP client to an already-running ModelServiceApp, not a second generation engine — it inherits the Service’s own licensing, caching, and everything else it already does, for free. This McpServerApp process doesn’t manage or own that Service in any way; it just needs one reachable. Embedded mode generates data directly in this process instead, with no separate Service and no HTTP involved — see EMBEDDED MODE below. Both tools’ schemas are identical regardless of mode or build edition; only the dispatch differs.

Only components visible under the running artifact’s edition are ever returned by list_components — a Free build simply has fewer entries, same as returned by library.

Two MCP resources round out what a host with no local file access still can’t reach: the Model File Format Reference and the Customization Guide — see RESOURCES below.

TOOLS

list_components

ParameterTypeRequiredNotes
categorystringnogenerator or filter. Cannot be combined with component.
componentstringnoFull detail for exactly one component, by short name (e.g. Convert). Cannot be combined with category.

Result: {"edition": "...", "totalCount": N, "components": [...]} — see ComponentLibraryApp.md for the full per-component shape. An unknown or hidden component name, or both parameters given together, returns an error result (isError: true).

validate_model

ParameterTypeRequiredNotes
modelTextstringyesThe model’s actual content (XML or JSON, auto-detected) — not a file path.

Result:

{"valid": true, "rootName": "SixStats", "nodeCount": 6, "failures": []}

loadError is present (and rootName/nodeCount are null) when the model fails to parse or load. failures[] entries are {"nodeName": ..., "nodeType": ..., "message": ...} — one per node-level validate() failure, same as validator/ModelValidatorApp --json’s per-file shape, just without the batch wrapper or a file path (there isn’t one here).

Relative <Import> paths are not resolved for inline-text models — same limitation POST /v1/models already has, not something new introduced here.

analyze_model

ParameterTypeRequiredDefaultNotes
modelTextstringyes—The model’s actual content (XML or JSON, auto-detected).
includeThroughputbooleannotrueRun an observation throughput sample in addition to static complexity.
samplesintegerno5000Observations to sample for throughput.
isolatebooleannofalseAlso run the per-component isolated benchmark — expensive, many more generator calls than the throughput sample alone.
isolateRoundsintegerno10Interleaved measurement rounds for isolate.
isolateIterationsintegerno50Measured calls per node per round for isolate.
isolateWarmupRoundsintegerno3Discarded warmup rounds for isolate.
isolateThresholdnumberno3.0“Problem node” relative-multiplier cutoff for isolate.

Result:

{"rootName": "SixStats", "timestamp": "...", "complexity": {...}, "throughput": {...}|null, "isolatedBenchmark": {...}|null}

Same complexity/throughput/isolatedBenchmark shapes as analyzer/ModelAnalyzerApp --json (see ModelAnalyzerApp.md), minus the file-oriented fields (model, written) that don’t apply here. isolatedBenchmark’s shape matches the compact --isolate summary, not --isolate-raw’s full diagnostic dump — that stays out of MCP entirely, since it’s a local-machine diagnostic-stream concept not meaningful to a remote/agent caller.

get_allowlists

No parameters. Result identical to Service-API-Reference.md’s GET /v1/allowlists:

{"flatFilePathAllowlist": [...], "databaseUrlAllowlist": [...], "databaseAllowQuery": false, "serviceUrlAllowlist": [...]}

generate

Mode is server-wide (see MODE SELECTION below) and doesn’t change this tool’s parameters — the same schema is used under Proxy or Embedded mode.

ParameterTypeRequiredDefaultNotes
modelTextstringyes—The model’s actual content (XML or JSON, auto-detected). Not a file path.
countintegeryes—Number of observations to generate.
seedintegerno—Force a specific RNG seed.
noSeedbooleannofalseIgnore the model’s own embedded seed and force a fresh random one.
formatstringnocsvcsv | tsv | plain.
keepResidentbooleannotruePin the registered model’s resident graph against LRU eviction on the Service. Proxy mode only — ignored under Embedded mode, which never keeps a resident graph.
previewLimitintegerno100Rows to include inline in the result (max 1000) — use read_generation for the rest.

Result is a bounded preview, not the raw data — large inline tool results are a real, observed problem across MCP hosts (truncation, context-window cost), so only the first previewLimit rows come back inline; the rest is retrieved via read_generation:

{"modelId": "m-...", "generationId": "g-...", "count": 500, "rowCount": 100, "totalCount": 500,
 "hasMore": true, "format": "csv", "seed": "...", "seedSource": "random", "preview": "row1\nrow2\n..."}

Under Proxy mode, generationId is Service-issued (g-...) and modelId is a real registered Service model id. Under Embedded mode, generationId is minted locally (e-...) and modelId is a synthetic, non-authoritative label (local-..., derived from modelText’s hash) — there’s no Service registration to point at.

Repeated generate calls with the same modelText reuse one Service-side registration under Proxy mode (cached in-process by McpServerApp, keyed by a SHA-256 of the text) instead of re-registering every time — this is what makes Proxy mode’s caching actually pay off across a session. Self-heals (re-registers once) if the cached registration was evicted by the Service’s own idle timeout in the meantime. Neither generate nor read_generation ever deletes the registration — it might be reused by later calls in the same session, or by other Service consumers entirely. Embedded mode has no equivalent registration or caching of the model itself — every call loads fresh and discards (see EMBEDDED MODE below).

read_generation

ParameterTypeRequiredDefaultNotes
generationIdstringyes—From a prior generate call.
offsetintegerno0Row offset to start reading from.
limitintegerno100Rows to read.

Routes automatically by generationId prefix, regardless of the server’s current mode setting: a Service-issued g-... id proxies to the Service’s own GET /v1/generate/{generationId}/data (see Service-API-Reference.md) — only readable from the Service process that produced it, so this only ever works against the same Service generate registered against. An Embedded-minted e-... id pages McpServerApp’s own local, in-process, bounded cache instead (see EMBEDDED MODE below) — only readable from the same McpServerApp process that produced it, and only while it’s still in that cache.

RESOURCES

MCP resources are a distinct primitive from tools — readable reference content a host can pull into context directly, not a callable action. Both are static, build-time-fixed snapshots of the real doc files, bundled onto the classpath at build time (not read from a filesystem path at runtime), so they’re available regardless of build edition or install layout:

URINameContent
spicegrinder://docs/model-file-format-referenceSpiceGrinder Model File Format ReferenceModel-File-Format-Reference.md — the XML/JSON model file format spec.
spicegrinder://docs/customizationSpiceGrinder Customization Guide (Pro)Customization Guide (included with the Pro download) — writing and registering custom generators/filters. Reference only; authoring custom components itself still requires Pro.

Both resources are available under Free too — reading about a Pro capability isn’t the same as using it, same reasoning list_components already applies to showing Pro-tier entries to a Free caller.

TRANSPORT

McpServerApp speaks either stdio (default, for a host that launches it as a local subprocess) or HTTP (for a host whose connector UI only accepts a URL, or for standing up one long-running server other MCP hosts on the network can reach). Tool and resource schemas are identical either way — this only changes how a host connects, never what’s available once connected.

VariableDefaultNotes
SPICEGRINDER_MCP_TRANSPORTstdiostdio or http. Anything else is treated as stdio.
SPICEGRINDER_MCP_HTTP_PORT8086HTTP mode only. Adjacent to but distinct from ModelServiceApp’s 8085 default.
SPICEGRINDER_MCP_HTTP_BIND127.0.0.1HTTP mode only. Loopback-only by default — same safe-by-default posture as ModelServiceApp.
SPICEGRINDER_MCP_HTTP_AUTH_TOKEN(none)HTTP mode only. If set, every request needs Authorization: Bearer <token>; unset means auth is disabled.

Each is also overridable via a matching -Dspicegrinder.mcp.* system property, for local testing. HTTP mode listens at http://<bind>:<port>/mcp and runs until killed — it’s meant to be started once, standalone (SPICEGRINDER_MCP_TRANSPORT=http mcp, or the equivalent java -cp form), not spawned per-session the way a host launches stdio mode. Available in both editions, same policy stdio already has.

Under the hood, HTTP mode runs the MCP SDK’s Streamable HTTP transport (HttpServletStreamableServerTransportProvider) on an embedded Jetty server — the SDK’s only server-side HTTP transport is Servlet-API-shaped, so this brings in Jetty + jakarta.servlet-api as real dependencies, unlike stdio mode which needs neither.

MODE SELECTION

generate/read_generation’s mode is set server-wide, not per-call:

VariableDefaultNotes
SPICEGRINDER_MCP_GENERATE_MODEproxyproxy or embedded. Anything else is treated as proxy.

Also overridable via a matching -Dspicegrinder.mcp.generate.mode system property, for local testing.

PROXY MODE

generate/read_generation need a reachable, already-running SpiceGrinder Service — McpServerApp doesn’t start, manage, or own one. Configured via environment variables (the natural place an MCP host sets per-server config — see CONNECTING A HOST below):

VariableDefaultNotes
SPICEGRINDER_MCP_SERVICE_URLhttp://localhost:8085ModelServiceApp’s own default bind.
SPICEGRINDER_MCP_SERVICE_TOKEN(none)Sent as Authorization: Bearer <token> when set — required if the Service has auth.token configured.

Each is also overridable via a matching -Dspicegrinder.mcp.service.url/-Dspicegrinder.mcp.service.authToken system property, for local testing. If no Service is reachable at the configured URL, generate/read_generation return a clear error result rather than a raw connection exception.

EMBEDDED MODE

generate loads the model and generates data directly in this process — the same core path grind uses (ModelIO, ModelLoader, Randoms, ObservationFormat) — with no separate Service process and no HTTP involved. Deliberately unmetered, exactly like grind itself: individual Pro-only components still gate normally when the model loads, but Embedded mode grants nothing a Free-tier user couldn’t already do by running grind directly, so unlike Proxy mode it needs no license-capacity accounting of its own, and works the same way in both editions.

Every call loads fresh and discards — no resident-graph caching across calls the way the Service does. That’s what lets it hold no state between calls beyond the small results cache below.

Output size is bounded by estimate, not by row count — bytes-per-row varies enormously by model, so a flat row-count cap alone would be a poor proxy for actual memory/response cost. Before committing to the full count, a small warm-up sample is generated and used to project the total output size; if the projection exceeds the cap, generate fails clearly before doing the rest of the work:

VariableDefaultNotes
SPICEGRINDER_MCP_EMBEDDED_MAX_BYTES50,000,000 (50 MB)Projected total output size cap, estimated from a warm-up sample.

Also overridable via a matching -Dspicegrinder.mcp.embedded.maxBytes system property. A generous fixed row-count backstop (2,000,000) additionally guards against a degenerate near-zero-byte-row model that could otherwise outrun the size estimate on row count alone.

Generated rows are kept in a small bounded in-process cache (at most 20 generations, keyed by the e-... id generate returns) so read_generation can page back through them — never written to disk, never shared with another process, and gone once evicted or once this McpServerApp process exits. mcp.embedded_generate.start/mcp.embedded_generate.stop audit events bracket each call (see LocalInstrumentation’s NDJSON audit log), matching the general guidance to run with --audit on anything that generates data.

CONNECTING A HOST

Claude Desktop (claude_desktop_config.json) — the env block is only needed to override defaults, e.g. pointing Proxy mode at a non-default Service, giving it a token, or switching to Embedded mode:

{
  "mcpServers": {
    "spicegrinder": {
      "command": "/path/to/mcp",
      "args": [],
      "env": {
        "SPICEGRINDER_MCP_SERVICE_URL": "http://localhost:8085",
        "SPICEGRINDER_MCP_SERVICE_TOKEN": "..."
      }
    }
  }
}

Or, for a single-user setup with no Service running at all:

{
  "mcpServers": {
    "spicegrinder": {
      "command": "/path/to/mcp",
      "args": [],
      "env": {
        "SPICEGRINDER_MCP_GENERATE_MODE": "embedded"
      }
    }
  }
}

On a raw (non-packaged) build, use "command": "java", "args": ["-cp", "/path/to/spicegrinder.jar", "com.obsvra.spicegrinder.tools.McpServerApp"] instead.

Claude Code:

claude mcp add spicegrinder --env SPICEGRINDER_MCP_SERVICE_URL=http://localhost:8085 -- /path/to/mcp

or, for Embedded mode:

claude mcp add spicegrinder --env SPICEGRINDER_MCP_GENERATE_MODE=embedded -- /path/to/mcp

or a project .mcp.json:

{
  "mcpServers": {
    "spicegrinder": {
      "type": "stdio",
      "command": "/path/to/mcp",
      "env": {
        "SPICEGRINDER_MCP_SERVICE_URL": "http://localhost:8085"
      }
    }
  }
}

A host whose connector UI only accepts a URL (not a local launch command) needs HTTP mode instead of stdio — start it standalone first:

SPICEGRINDER_MCP_TRANSPORT=http SPICEGRINDER_MCP_GENERATE_MODE=embedded mcp

then point the host’s connector at http://127.0.0.1:8086/mcp (or whatever SPICEGRINDER_MCP_HTTP_BIND/_PORT are set to). See TRANSPORT above for the full config table.

SEE ALSO

ComponentLibraryApp.md, ModelValidatorApp.md, ModelAnalyzerApp.md, Service-API-Reference.md