McpServerApp
![]()
© 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
| Parameter | Type | Required | Notes |
|---|---|---|---|
category | string | no | generator or filter. Cannot be combined with component. |
component | string | no | Full 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
| Parameter | Type | Required | Notes |
|---|---|---|---|
modelText | string | yes | The 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
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
modelText | string | yes | — | The model’s actual content (XML or JSON, auto-detected). |
includeThroughput | boolean | no | true | Run an observation throughput sample in addition to static complexity. |
samples | integer | no | 5000 | Observations to sample for throughput. |
isolate | boolean | no | false | Also run the per-component isolated benchmark — expensive, many more generator calls than the throughput sample alone. |
isolateRounds | integer | no | 10 | Interleaved measurement rounds for isolate. |
isolateIterations | integer | no | 50 | Measured calls per node per round for isolate. |
isolateWarmupRounds | integer | no | 3 | Discarded warmup rounds for isolate. |
isolateThreshold | number | no | 3.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.
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
modelText | string | yes | — | The model’s actual content (XML or JSON, auto-detected). Not a file path. |
count | integer | yes | — | Number of observations to generate. |
seed | integer | no | — | Force a specific RNG seed. |
noSeed | boolean | no | false | Ignore the model’s own embedded seed and force a fresh random one. |
format | string | no | csv | csv | tsv | plain. |
keepResident | boolean | no | true | Pin 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. |
previewLimit | integer | no | 100 | Rows 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
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
generationId | string | yes | — | From a prior generate call. |
offset | integer | no | 0 | Row offset to start reading from. |
limit | integer | no | 100 | Rows 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:
| URI | Name | Content |
|---|---|---|
spicegrinder://docs/model-file-format-reference | SpiceGrinder Model File Format Reference | Model-File-Format-Reference.md — the XML/JSON model file format spec. |
spicegrinder://docs/customization | SpiceGrinder 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.
| Variable | Default | Notes |
|---|---|---|
SPICEGRINDER_MCP_TRANSPORT | stdio | stdio or http. Anything else is treated as stdio. |
SPICEGRINDER_MCP_HTTP_PORT | 8086 | HTTP mode only. Adjacent to but distinct from ModelServiceApp’s 8085 default. |
SPICEGRINDER_MCP_HTTP_BIND | 127.0.0.1 | HTTP 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:
| Variable | Default | Notes |
|---|---|---|
SPICEGRINDER_MCP_GENERATE_MODE | proxy | proxy 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):
| Variable | Default | Notes |
|---|---|---|
SPICEGRINDER_MCP_SERVICE_URL | http://localhost:8085 | ModelServiceApp’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:
| Variable | Default | Notes |
|---|---|---|
SPICEGRINDER_MCP_EMBEDDED_MAX_BYTES | 50,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