MCP Server
KINTSUGI ships an optional Model Context Protocol (MCP) server that turns the signal-isolation toolbox into tools an AI assistant can drive. Connect the MCP client you use (see Connect your client) to a KINTSUGI project, and the assistant can load channels, inspect them, suggest and apply autofluorescence subtraction, score the result, save it, and record what worked so the next session starts from it. KINTSUGI itself (CLI, notebooks, Snakemake workflow) does not need the server or any assistant.
What is different about this server
Declarative, dependency-free registry.
kintsugi.mcp.tool_specs.TOOL_SPECSlists every tool (name, handler, title, description, behaviour annotations) without importing the SDK, sokintsugi mcp tools, the docs generator and the tests work without the optionalmcpextra.Schemas derived from the handlers and enforced. Parameter descriptions, enums and numeric ranges live in the handler signatures (
Annotated[int, Field(description=..., ge=1, le=99)],Literal[...]). The SDK advertises them and rejects violations before the handler runs; there is no second, hand-written copy that can drift.Structured output on every tool. Results arrive as
structuredContent(machine-readable) plus a text block, and the tools the model reasons over most (get_image_stats,assess_quality,suggest_with_learning,get_session_state) declare typed output schemas.Path safety as a design rule. Every path the model supplies is confined to the project (
ensure_withinfollows symlinks), filenames must be bare names, numeric clamps reject NaN/inf/bool, and model checkpoints are only loaded from directories the user controls. See Security model.A learning loop. Parameters the user approves are stored in SQLite (project and user-wide), normalised by tissue and marker aliases, and recommended back with a confidence that combines match quality, outcome, recency and consistency.
kintsugi mcp pretrainbootstraps the database from existing processed data.Tiered automation. Heuristic suggestion → learned recall → Random Forest prediction → Optuna search → parameter-free SMO background estimation, all as tools, so the assistant can escalate when the cheap option is not enough.
Evaluated, not assumed.
evals/mcp/harness.pymeasures the tool surface (token cost, descriptions, titles, annotations, task coverage) and a strict structural gate runs in CI; an optional agentic pass measures whether a model picks the right tool for realistic requests, through the Anthropic Messages API or any OpenAI-compatible endpoint (including local models).
Sixty-second setup
pip install -e ".[mcp]" # or: kintsugi install mcp (needs mcp>=2.0)
kintsugi mcp clients # the clients it can configure
kintsugi mcp config /path/to/project --client NAME # configure yours (repeatable)
kintsugi init /path/to/project --mcp-client NAME # or while creating a project
The MCP server is optional, and no client is configured unless you choose it.
kintsugi mcp config without --client asks which client(s) in a terminal and
otherwise fails with the list; kintsugi init without --mcp-client asks
(default: none) in a terminal and otherwise creates the project with no MCP
configuration, and kintsugi init --force refreshes only the clients the project
already has. --client all configures every client below, --print-only shows the
configuration without writing, and kintsugi mcp clients lists the registry. A
client that keeps its configuration in the project gets that file and an
AGENTS.md for the assistant; a client configured per user or in the IDE gets the
entry to paste. Connect your client lists every client and
what to do after configuring it.
Existing files are merged (other servers and settings are kept; an existing
kintsugi entry is replaced only with --force), nothing is written outside the
project, and the generated files are added to the project’s .gitignore because
they hold machine-specific absolute paths: collaborators run kintsugi mcp config
on their own machine. AGENTS.md is created only when absent; an existing one only
has its generated tool list refreshed. kintsugi mcp config refuses a directory without
kintsugi_project.json (the server would refuse it as --project-dir).
Every client gets the same server entry in its own format. For example,
--client claude-code writes these two files:
// .mcp.json — the server definition Claude Code reads
{
"mcpServers": {
"kintsugi": {
"type": "stdio",
"command": "/path/to/env/bin/kintsugi",
"args": ["mcp", "start", "--project-dir", "/path/to/project"]
}
}
}
// .claude/settings.local.json — approves exactly this server
{ "enabledMcpjsonServers": ["kintsugi"] }
command is the absolute path of the kintsugi executable in the environment
you ran the command from, so the client can start the server even when that
environment’s bin/ is not on PATH. --project-dir tells the server which
project project_path defaults to. MCP clients differ in the working directory
and environment they give a server (several ignore a cwd field, and some pass
on almost no environment variables), so the project travels in args, which
every client supports. Where a client can set environment variables,
KINTSUGI_PROJECT_DIR does the same; without either, the working directory is
used only if it contains kintsugi_project.json, and tools that need a project
otherwise ask for project_path instead of guessing. The server speaks JSON-RPC over stdio; nothing else is written to
stdout (diagnostics go to stderr), and a test spawns the real server over a
pipe to keep it that way.
Connect your client
kintsugi mcp config <project> --client NAME (or kintsugi init --mcp-client NAME)
writes or prints the configuration; then follow the last column. The table is
generated from the registry the command uses:
Client |
|
Configuration |
After configuring |
Client docs |
|---|---|---|---|---|
Claude Code |
|
|
Open the project folder, accept the trust dialog, then run /mcp. |
|
VS Code (GitHub Copilot) |
|
|
Open the folder, trust the workspace, and start ‘kintsugi’ from MCP: List Servers. |
|
Cursor |
|
|
Open the folder; enable ‘kintsugi’ under Settings > MCP if prompted. |
|
Gemini CLI |
|
|
Run ‘gemini’ in the project folder (trust it with ‘gemini trust’ if asked), then /mcp. |
|
Antigravity |
|
|
Open the folder as the workspace; the server appears under MCP servers. |
|
OpenAI Codex CLI |
|
|
Mark the project trusted (Codex loads .codex/config.toml only in trusted projects), then run ‘codex’ there and /mcp. |
|
Zed |
|
|
Open the folder (trust the worktree); the server is listed in the Agent Panel settings. |
|
Claude Desktop |
|
snippet to paste: claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/; Windows: %APPDATA%\Claude) |
Merge the entry into mcpServers and restart Claude Desktop. |
|
Windsurf / Devin Desktop |
|
snippet to paste: mcp_config.json (~/.config/devin/ or ~/.codeium/; Windows: %APPDATA%\devin) |
Merge the entry into mcpServers, then refresh the MCP servers list. |
|
Cline |
|
snippet to paste: cline_mcp_settings.json (open it from Cline > MCP Servers > Configure) |
Merge the entry into mcpServers and save; Cline restarts the server. |
|
JetBrains AI Assistant |
|
snippet to paste: Settings > Tools > AI Assistant > Model Context Protocol (MCP) > Add |
Choose STDIO, paste the JSON, and set the working directory to the project folder. |
11 clients. Generated from kintsugi.mcp.clients by kintsugi mcp tools --write; do not edit by hand.
Instructions file: every client above except Claude Desktop reads AGENTS.md
(Gemini CLI through the context.fileName entry kintsugi mcp config adds;
Claude Code reads CLAUDE.md first, which can be the single line @AGENTS.md).
Claude Code also has its own command for the same entry:
claude mcp add --scope project --transport stdio kintsugi -- kintsugi mcp start --project-dir /path/to/project.
Any other client
Any MCP client that can start a local stdio server works. Give it:
command: the absolute path of
kintsugiin the environment where KINTSUGI is installed (which kintsugi, orwhere kintsugion Windows), so the client does not depend on itsPATH;args:
["mcp", "start", "--project-dir", "/absolute/path/to/project"].
In the common mcpServers JSON form:
{
"mcpServers": {
"kintsugi": {
"command": "/path/to/env/bin/kintsugi",
"args": ["mcp", "start", "--project-dir", "/path/to/project"]
}
}
}
To try the server without any assistant, the MCP Inspector lists and calls its tools:
npx @modelcontextprotocol/inspector --cli "$(which kintsugi)" mcp start \
--project-dir /path/to/project --method tools/list
From Python, the mcp SDK starts it the same way:
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
params = StdioServerParameters(
command="/path/to/env/bin/kintsugi",
args=["mcp", "start", "--project-dir", "/path/to/project"],
)
async def main():
async with stdio_client(params) as (read, write), ClientSession(read, write) as session:
await session.initialize()
print([tool.name for tool in (await session.list_tools()).tools])
asyncio.run(main())
Architecture
flowchart LR
CC[MCP client<br/>your AI assistant] <-- "JSON-RPC over stdio" --> S[MCPServer<br/>kintsugi mcp start]
S --> R[tool_specs.py<br/>34 tools: name, title,<br/>description, annotations]
S --> ADP["handler adapter<br/>{'error': ...} → isError"]
ADP --> T1[tools/signal_isolation]
ADP --> T2[tools/quality_assessment]
ADP --> T3[tools/visualization]
ADP --> T4[tools/workflow]
ADP --> T5[tools/learning]
T1 & T2 & T3 & T4 --> SS[(session state<br/>loaded images, history)]
T5 --> DB[(SQLite learning DBs<br/>project + ~/.kintsugi)]
T1 & T4 --> FS[(project files<br/>data/raw, data/processed)]
S -. resources .-> SS
S -. resources .-> DB
S -. prompt .-> P[signal_isolation_workflow]
PS[path_safety.py] -.guards.-> T1 & T4
kintsugi.mcp.serverbuilds theMCPServer(name, title, version, website, instructions), registers every tool from the registry with its title and annotations, wraps each handler so an in-band{"error": ...}becomes an MCP tool error, and registers the resources and the prompt.kintsugi.mcp.tools.*are plainasyncfunctions returning dicts. They are SDK-agnostic, which is why the unit tests can call them directly and why the Jupyter tuners inkintsugi.assist.param_tunercan share them.Session state lives in server memory for the lifetime of the client session: loaded channels, intermediate results and per-channel history.
get_session_stateshows it,unload_channelfrees it.Workflow state persists per project in
<project>/.kintsugi/(see Workflow state);get_workflow_statusshows it.kintsugi.mcp.path_safetyholds the checks a JSON schema cannot express.kintsugi.assist.parameter_learningis the learning engine behind thelearningtools.
Tools
The table is generated from the registry by kintsugi mcp tools --write docs/mcp.md; CI fails if it drifts.
Tool |
Title |
Category |
Read-only |
Destructive |
Description |
|---|---|---|---|---|---|
|
Load channel |
Signal isolation |
no |
no |
Load a channel image from a KINTSUGI project. Returns image metadata and prepares it for processing. |
|
Subtract blank (autofluorescence) |
Signal isolation |
no |
no |
Subtract autofluorescence/blank channel from signal. Uses optimized Dask operations for large images. |
|
Preview weighted subtraction |
Signal isolation |
yes |
no |
Preview the per-intensity-range weights for weighted autofluorescence subtraction WITHOUT applying it. Use this to verify the plan before calling subtract_blank with method=’weighted’. |
|
Denoise (basic filters) |
Signal isolation |
no |
no |
Apply denoising filters (percentile, uniform, median) to reduce noise. |
|
Apply CLAHE contrast |
Signal isolation |
no |
no |
Apply Contrast Limited Adaptive Histogram Equalization to enhance local contrast. |
|
Clean background |
Signal isolation |
no |
no |
Remove background and small objects from the image. |
|
Gaussian background subtraction |
Signal isolation |
no |
no |
Subtract Gaussian-blurred version to remove structured background/autofluorescence. |
|
Denoise (advanced) |
Signal isolation |
no |
no |
Apply advanced denoising: adaptive (auto-select best filter), bilateral (edge- preserving), nlm (non-local means), bm3d (block matching 3D), n2v (self-supervised Noise2Void), or patch_svd. |
|
Assess quality |
Quality assessment |
yes |
no |
Assess channel quality with heuristic metrics (SNR, contrast, dynamic range, background) and flag channels that need manual review. Returns a 0-1 quality score, the metrics and recommendations. |
|
Compute SNR |
Quality assessment |
yes |
no |
Compute Signal-to-Noise Ratio for a channel. |
|
Batch quality assessment |
Quality assessment |
yes |
no |
Assess several loaded channels at once: scores sorted worst-first and a review queue of the channels below the threshold. |
|
Image statistics |
Visualization |
yes |
no |
Get statistics for a loaded image (min, max, mean, std, histogram). |
|
Thumbnail preview |
Visualization |
yes |
no |
Get a downsampled thumbnail of the image for preview. |
|
Compare two channels |
Visualization |
yes |
no |
Compare two loaded channels statistically (means, difference, correlation, SSIM) with a one-line interpretation; useful for before/after checks. |
|
Intensity profile |
Visualization |
yes |
no |
Intensity values along a horizontal or vertical line through a loaded channel, for spotting gradients or uneven illumination. |
|
List project channels |
Workflow |
yes |
no |
List all available channels in the current project/cycle. |
|
Save processed channel |
Workflow |
no |
yes |
Save the processed channel to the project output directory. |
|
Processing history |
Workflow |
yes |
no |
Get the processing history for a channel, showing all operations applied. |
|
Suggest parameters (heuristic) |
Workflow |
yes |
no |
Rule-based suggestions for every operation from the channel’s intensity statistics, without the learning database. Prefer suggest_with_learning when the tissue type and marker are known. |
|
Generate Jupyter tuning cell |
Workflow |
yes |
no |
Generate a Jupyter notebook cell for interactive parameter tuning with Kview2 widgets. |
|
Cluster channels |
Signal isolation |
no |
no |
Cluster channels by image feature similarity. Groups channels that share similar intensity profiles, SNR, and texture so parameters can be tuned once per cluster. |
|
Propagate cluster parameters |
Signal isolation |
no |
yes |
Apply tuned signal isolation parameters from a representative channel to all members of a cluster. |
|
Session state |
Workflow |
yes |
no |
Show what this server session holds: the project root it serves, every loaded channel with shape/dtype/metadata, per-channel processing step counts, and memory usage. |
|
Unload channel |
Workflow |
no |
no |
Free server memory by dropping a loaded channel (or everything with clear_all). Files on disk are untouched. |
|
Workflow status |
Workflow |
yes |
no |
Show the project’s persistent workflow state, which survives server restarts: every channel’s status (pending, processing, review, approved, rejected, needs_adjustment), quality score and step count, plus the processing and review queues. Call it first to resume earlier work. |
|
Record review decision |
Workflow |
no |
no |
Record a review decision other than approval (rejected, needs_adjustment, or pending to reset) for a channel in the project’s workflow state; works for channels that are no longer loaded. Use approve_and_learn to approve. |
|
Learned parameters |
Parameter learning |
yes |
no |
Get recommended parameters based on learned history from similar tissue/marker combinations. Returns parameters with confidence scores. |
|
Record parameters |
Parameter learning |
no |
no |
Record a successful parameter set for future learning. Call after user approves processing results. |
|
Suggest parameters (learned + heuristic) |
Parameter learning |
yes |
no |
Get comprehensive parameter suggestions combining image analysis AND learned history. This is the primary recommendation tool. |
|
Approve and learn |
Parameter learning |
no |
no |
Approve processing results and record ALL parameters for learning. Call when user approves final processed result. |
|
Learning database statistics |
Parameter learning |
yes |
no |
Get statistics about the parameter learning database. |
|
Optimize parameters (Optuna) |
Signal isolation |
no |
no |
Find optimal signal isolation parameters via Bayesian optimization (Optuna). Automatically searches the parameter space — no manual slider adjustment needed. |
|
Predict parameters (Random Forest) |
Signal isolation |
yes |
no |
Predict signal isolation parameters from image features using a trained Random Forest model. Includes confidence scores and uncertainty estimates. |
|
Estimate background (SMO) |
Signal isolation |
no |
no |
Estimate and subtract background using SMO (Silver Mountain Operator). Parameter- free — no blank channel needed. |
34 tools. Generated by kintsugi mcp tools --write; do not edit by hand.
Every tool returns structuredContent. “Read-only” and “Destructive” are the
MCP behaviour annotations: read-only tools never write files and never change the
processing history (assess_quality only annotates the image’s session metadata
with its quality_score, which save_processed and approve_and_learn persist);
destructive ones overwrite files on disk (save_processed,
propagate_parameters). The full parameter contract of each tool (descriptions, enums,
ranges) is what the server advertises in tools/list; kintsugi mcp tools
prints the registry, and python evals/mcp/harness.py prints the advertised
schemas’ size and quality.
Resources and prompt
Kind |
URI / name |
Content |
|---|---|---|
resource |
|
Loaded channels, shape/dtype/metadata, history counts, memory |
resource template |
|
Every operation applied to a channel |
resource |
|
Learning database statistics |
resource |
|
Image files in the project the server was started for |
resource |
|
Persistent per-channel status and queues of the project (survives restarts) |
prompt |
|
Guided checklist for one marker/blank pair |
Progress and images
Long-running tools (subtract_blank with method="weighted",
denoise_advanced, optimize_parameters, cluster_channels,
propagate_parameters) run their CPU work on a worker thread and send MCP
progress notifications, so the client stays responsive and can cancel.
get_thumbnail returns the preview as an image content block the model can
actually look at, alongside the structured metadata; pass
include_data_uri=true only if you also want the base64 in the JSON.
The learning loop
suggest_with_learningcombines image analysis with records for the same tissue and marker. Records are scored by exact or family match on tissue and marker (+3 / +1.5 to +2), quality improvement, image-characteristic similarity, a 30-day recency decay, and a 1.5x weight for project-local records; confidence combines record count, exact matches, average improvement and parameter consistency (coefficient of variation).subtract_blank(and the other processing tools) apply the parameters;assess_qualityscores the result.approve_and_learnrecords every operation’s parameters with the before and after quality, normalising tissue and marker names (CD3e→cd3,DAPI-01→dapi, whole-token matching soCD31never becomescd3).Next time, the recommendation confidence crosses the “learned” tier (> 0.5) and the learned values take precedence over the heuristic.
Databases: <project>/.kintsugi/parameter_learning.db and
~/.kintsugi/parameter_learning.db. kintsugi mcp pretrain <project> seeds
them from existing processed pairs; those bootstrap rows are visible to
recommendations at reduced weight and can never push a recommendation above
the 0.5 tier on their own. An existing <project>/.claude_workflow/ from an
earlier release is read as is and moved to .kintsugi/ on the first write.
Both databases are created when parameters are first recorded; looking up
recommendations or statistics never creates them.
Workflow state
Everything in the session is lost when the client stops the server; what matters
for picking up the work again is kept per project in <project>/.kintsugi/
(kintsugi.assist.workflow_state.WorkflowState):
Event |
Recorded |
|---|---|
|
the channel is registered ( |
|
a step with its parameters; status |
|
a step with the output path; status |
|
status |
|
status |
Steps of a derived image (cyc001_CD3_subtracted_denoised) are recorded on the
channel it came from (cyc001_CD3): every image carries project, cycle and
root in its metadata, inherited from the image it was computed from.
get_workflow_status (and the kintsugi://project/workflow resource, and
kintsugi mcp status <project> on the command line) lists every channel with its
status, quality score, step count and last operation, plus the processing and
review queues; get_processing_history falls back to these persisted steps for a
channel that is no longer loaded, and set_review_decision works on such a
channel by name.
workflow_state.json holds the channels and the sessions that touched them; every
change is also appended to the audit_log table of workflow_audit.db. Nothing is
written until the first change, read-only tools never write, and state is only
kept for directories that are KINTSUGI projects (kintsugi_project.json), so a
model-supplied project_path cannot create it elsewhere. Changes are
read-modify-write under an OS file lock (fcntl.lockf, which also works over NFS;
msvcrt.locking on Windows) with atomic replacement, so two clients (or a
client and a notebook) on one project do not overwrite each other.
Security model
The server is a local, single-user stdio subprocess started by the MCP client with the user’s own permissions; there is no network listener, authentication or multi-tenancy, so the relevant threat is a model-supplied argument escaping the project.
Risk |
Control |
|---|---|
Path traversal / symlink escape |
|
Loading a model file the model chose |
|
Resource exhaustion |
Schema bounds ( |
Over-broad approval |
The generated |
Stdout corruption |
The CLI banner and project loader write to stderr/logging; |
Writing where the client happened to start the server |
Without |
Not applicable to this server, and deliberately absent: OAuth or API keys, rate
limiting, DNS-rebinding protection, streamable HTTP. See SECURITY.md for how
to report a vulnerability.
Why the tools are not prefixed kintsugi_
The 34 names are valid, unique, verb-first snake_case names, and MCP clients
namespace tools by server (Claude Code, for example, as mcp__kintsugi__<tool>). A prefix would
duplicate that namespace while breaking existing permission rules, notebooks
and evaluation tasks. The server’s instructions and each tool’s title carry
the context instead.
Evaluation
python evals/mcp/harness.py # structural pass, no network
python evals/mcp/harness.py --strict # what CI runs on every MCP change
ANTHROPIC_API_KEY=... python evals/mcp/harness.py --agentic --provider anthropic --model MODEL
OPENAI_API_KEY=... python evals/mcp/harness.py --agentic --provider openai --model MODEL
OPENAI_BASE_URL=http://localhost:11434/v1 \
python evals/mcp/harness.py --agentic --provider openai --model MODEL # local model
The structural pass reports the token cost of the advertised tool definitions,
missing descriptions, titles or annotations, confusable tool groups, eval tasks
that name absent tools, and tools no task targets. --strict fails on any of
those and on documentation drift, so the table above, the registry and the
server can never disagree. The committed baseline lives in
evals/mcp/baseline.json; evals/mcp/README.md explains the numbers.
The agentic pass offers the advertised tools to a model for each of the eval
tasks and scores the first tool it calls. --provider anthropic uses the
Messages API; --provider openai uses Chat Completions, which OpenAI and many
other platforms and local servers (Ollama, vLLM) serve, selected with
OPENAI_BASE_URL or --base-url. The adapters use only the standard library.
There is no default provider or model: pass --provider and --model, or answer
the prompts in a terminal; elsewhere a missing choice is a usage error. See evals/mcp/README.md for the details and the
weekly CI job’s settings.
A recorded session
The transcript below is produced by python evals/mcp/demo.py, which creates a
synthetic project, drives the real server through the in-process client and
prints what each tool returned. tests/test_mcp_demo.py runs it in CI.
Server kintsugi 1.2.3, 34 tools, project <project>.
1. list_channels {"cycle": "1"}
{
"status": "success",
"project": "<project>",
"total_channels": 2,
"channels": [
{
"cycle": "cyc001",
"channel": "Blank",
"source": "raw",
"path": "<project>/data/raw/cyc001/Blank.tif",
"size_mb": 0.125
},
{
"cycle": "cyc001",
"channel": "CD3",
"source": "raw",
"path": "<project>/data/raw/cyc001/CD3.tif",
"size_mb": 0.125
}
],
"currently_loaded": []
}
2. load_channel {"cycle": "1", "channel": "CD3"}
{
"status": "loaded",
"name": "cyc001_CD3",
"shape": [
256,
256
],
"dtype": "uint16",
"source": "<project>/data/raw/cyc001/CD3.tif",
"stats": {
"min": 366.0,
"max": 4087.0,
"mean": 721.256,
"std": 533.247
}
}
3. load_channel {"cycle": "1", "channel": "Blank"}
{
"status": "loaded",
"name": "cyc001_Blank",
"shape": [
256,
256
],
"dtype": "uint16",
"source": "<project>/data/raw/cyc001/Blank.tif",
"stats": {
"min": 365.0,
"max": 796.0,
"mean": 542.652,
"std": 81.496
}
}
4. get_image_stats {"channel": "cyc001_CD3"}
{
"status": "success",
"channel": "cyc001_CD3",
"statistics": {
"shape": [
256,
256
],
"dtype": "uint16",
"min": 296.0,
"max": 4680.0,
"mean": 729.46,
"std": 540.832,
"median": 562.0,
"percentiles": {
"p1": 387.0,
"p5": 426.0,
"p10": 448.0,
"p25": 491.0,
"p50": 562.0,
"p75": 673.0,
"p90": 1144.0,
"p95": 1953.25,
"p99": 3286.65
},
"histogram": {
"counts": [
531,
10902,
20851,
13821,
7667,
2134,
"... (50 items)"
],
"bin_edges": [
296.0,
383.68,
471.36,
559.04,
646.72,
734.4,
"... (51 items)"
],
"bin_centers": [
339.84,
427.52,
515.2,
602.88,
690.56,
778.24,
"... (50 items)"
]
},
"zero_pixels": 0,
"zero_ratio": 0.0,
"saturated_pixels": 0,
"saturation_ratio": 0.0,
"nonzero_mean": 729.46,
"nonzero_std": 540.832,
"nonzero_median": 562.0,
"nonzero_ratio": 1.0
}
}
5. get_thumbnail {"channel": "cyc001_CD3", "max_size": 64}
content: image block (image/png, 3996 base64 chars) + text
{
"status": "success",
"channel": "cyc001_CD3",
"thumbnail": {
"width": 64,
"height": 64,
"format": "png"
},
"original_shape": [
256,
256
]
}
6. suggest_with_learning {"channel": "cyc001_CD3", "tissue_type": "tonsil", "marker_name": "CD3", "blank_channel": "cyc001_Blank"}
{
"status": "success",
"channel": "cyc001_CD3",
"tissue_type": "tonsil",
"tissue_type_normalized": "tonsil",
"marker_name": "CD3",
"marker_name_normalized": "cd3",
"image_characteristics": {
"snr": 49.23,
"dynamic_range": 4384.0,
"sparsity": 0.9,
"background_level": 448.0
},
"operations": {
"blank_subtraction": {
"recommended_parameters": {
"blank_scale_factor": 1.0,
"blank_clip_factor": 423,
"smooth_low": false,
"smooth_high": false,
"erosion": 1
},
"confidence": 0.3,
"source": "heuristic",
"rationale": "Blank mean=545, Signal mean=729",
"learned_records": 0
},
"denoise": {
"recommended_parameters": {
"method": "median",
"filter_size": 1
},
"confidence": 0.3,
"source": "heuristic",
"rationale": "Low noise (std=23) - minimal filtering",
"learned_records": 0
},
"clahe": {
"recommended_parameters": {
"clip_limit": 0.02,
"tile_grid_size": 50,
"nbins": 128
},
"confidence": 0.3,
"source": "heuristic",
"rationale": "Low dynamic range (2900) - aggressive CLAHE",
"learned_records": 0
},
"clean_background": {
"recommended_parameters": {
"background_threshold": 224,
"remove_small_objects": true,
"min_object_size": 30
},
"confidence": 0.3,
"source": "heuristic",
"rationale": "Background level at p10=448",
"learned_records": 0
},
"gaussian_subtract": {
"recommended_parameters": {
"sigma": 10,
"scale_factor": 0.2
},
"confidence": 0.3,
"source": "heuristic",
"rationale": "Standard Gaussian subtraction",
"learned_records": 0
}
},
"suggested_order": [
"clahe",
"clean_background",
"blank_subtraction",
"denoise",
"gaussian_subtract"
]
}
7. analyze_weighted_subtraction {"signal_channel": "cyc001_CD3", "blank_channel": "cyc001_Blank", "n_ranges": 3}
{
"status": "success",
"method": "weighted",
"n_ranges": 3,
"range_method": "percentile",
"base_scale_factor": 0.69,
"blank_clip_factor": 457,
"transition_width": 0.1,
"ranges": [
{
"lower_bound": 296.0,
"upper_bound": 512.0,
"weight": 0.826,
"label": "background",
"pixel_fraction": 0.33
},
{
"lower_bound": 512.0,
"upper_bound": 628.0,
"weight": 0.783,
"label": "dim",
"pixel_fraction": 0.335
},
{
"lower_bound": 628.0,
"upper_bound": 4680.0,
"weight": 0.914,
"label": "bright",
"pixel_fraction": 0.336
}
],
"confidence": 0.75,
"analysis": {
"signal_blank_correlation": 0.247,
"af_contribution": 0.392
},
"tissue_type": "unknown",
"marker_name": null
}
8. subtract_blank {"signal_channel": "cyc001_CD3", "blank_channel": "cyc001_Blank", "method": "weighted", "n_ranges": 3, "tissue_type": "tonsil", "marker_name": "CD3"}
{
"status": "success",
"output_name": "cyc001_CD3_subtracted",
"method": "weighted",
"shape": [
256,
256
],
"stats": {
"min": 0.0,
"max": 4186.0,
"mean": 311.459,
"std": 522.008
},
"parameters_used": {
"method": "weighted",
"blank_clip_factor": 457,
"base_scale_factor": 1.0,
"n_ranges": 3,
"range_method": "percentile",
"transition_width": 0.1
},
"range_metrics": [
{
"label": "background",
"weight": 0.826,
"pixel_count": 21631,
"snr": 21.85,
"af_removal": 1,
"signal_preservation": 0.901
},
{
"label": "dim",
"weight": 0.783,
"pixel_count": 21919,
"snr": 16.749,
"af_removal": 1,
"signal_preservation": 0.992
},
{
"label": "bright",
"weight": 0.914,
"pixel_count": 21985,
"snr": 47.53,
"af_removal": 0,
"signal_preservation": 0.985
}
],
"quality": {
"snr_improvement": 0.001,
"af_removal": 1,
"signal_preservation": 0.959,
"residual_correlation": -0.036,
"quality_score": 0.849
}
}
9. assess_quality {"channel": "cyc001_CD3_subtracted"}
{
"status": "success",
"channel": "cyc001_CD3_subtracted",
"quality_score": 0.773,
"requires_review": true,
"metrics": {
"min_intensity": 0.0,
"max_intensity": 4186.0,
"mean_intensity": 311.459,
"std_intensity": 522.008,
"dynamic_range": 4186.0,
"snr": 35.861,
"signal_background_ratio": 24.111,
"coefficient_of_variation": 1.676,
"histogram_entropy": 5.229,
"saturation_ratio": 0.041,
"sharpness": 131.683,
"autofluorescence_score": 0.041
},
"recommendations": [
"\u26a0 Image quality is acceptable. Some processing recommended.",
"Low dynamic range. CLAHE may help enhance contrast."
],
"threshold_used": 0.85
}
10. save_processed {"channel": "cyc001_CD3_subtracted", "output_name": "CD3_isolated"}
{
"status": "success",
"output_path": "<project>/data/processed/signal_isolated/CD3_isolated.tiff",
"history_path": "<project>/data/processed/signal_isolated/CD3_isolated_history.json",
"format": "tiff",
"shape": [
256,
256
]
}
11. approve_and_learn {"channel": "cyc001_CD3_subtracted", "tissue_type": "tonsil", "marker_name": "CD3", "operations_params": {"blank_subtraction": {"method": "weighted", "n_ranges": 3}}, "quality_before": 0.4, "quality_after": 0.8}
{
"status": "success",
"message": "Processing approved and parameters recorded for learning",
"workflow_channel": "cyc001_CD3",
"tissue_type": "tonsil",
"marker_name": "CD3",
"quality_improvement": 0.4,
"operations_recorded": [
{
"operation": "blank_subtraction",
"recorded": true,
"record_id": 1
}
]
}
12. get_learned_parameters {"tissue_type": "tonsil", "marker_name": "CD3", "operation": "blank_subtraction"}
{
"status": "success",
"tissue_type": "tonsil",
"tissue_type_normalized": "tonsil",
"marker_name": "CD3",
"marker_name_normalized": "cd3",
"operation": "blank_subtraction",
"recommended_parameters": {
"method": "weighted",
"n_ranges": 3
},
"confidence": 0.444,
"confidence_level": "low",
"based_on_records": 1,
"approved_records": 1,
"bootstrap_records": 0,
"similar_records": [
{
"tissue": "tonsil",
"marker": "CD3",
"quality_improvement": 0.4,
"score": 12.45,
"parameters": {
"method": "weighted",
"n_ranges": 3
}
}
]
}
13. get_session_state {}
{
"status": "success",
"project_root": "<project>",
"loaded_images": {
"cyc001_CD3": {
"shape": [
256,
256
],
"dtype": "uint16",
"metadata": {
"source_path": "<project>/data/raw/cyc001/CD3.tif",
"cycle": "cyc001",
"channel": "CD3",
"project": "<project>",
"root": "cyc001_CD3"
}
},
"cyc001_Blank": {
"shape": [
256,
256
],
"dtype": "uint16",
"metadata": {
"source_path": "<project>/data/raw/cyc001/Blank.tif",
"cycle": "cyc001",
"channel": "Blank",
"project": "<project>",
"root": "cyc001_Blank"
}
},
"cyc001_CD3_subtracted": {
"shape": [
256,
256
],
"dtype": "uint16",
"metadata": {
"source": "cyc001_CD3",
"blank": "cyc001_Blank",
"operation": "subtract_blank_weighted",
"project": "<project>",
"cycle": "cyc001",
"channel": "CD3",
"root": "cyc001_CD3",
"quality_score": 0.773,
"saved_path": "<project>/data/processed/signal_isolated/CD3_isolated.tiff"
}
}
},
"total_loaded": 3,
"processing_history": {
"cyc001_CD3": 0,
"cyc001_Blank": 0,
"cyc001_CD3_subtracted": 1
},
"memory_usage": {
"total_mb": 0.375,
"total_gb": 0.0
},
"workflow": {
"session_id": "<session>",
"sessions": 1,
"total_channels": 2,
"status_breakdown": {
"approved": 1,
"pending": 1
},
"processing_queue": [
"cyc001_Blank"
],
"review_queue": [],
"approved": [
"cyc001_CD3"
]
}
}
14. get_workflow_status {}
{
"session_id": "<session>",
"sessions": 1,
"total_channels": 2,
"status_breakdown": {
"approved": 1,
"pending": 1
},
"processing_queue": [
"cyc001_Blank"
],
"review_queue": [],
"approved": [
"cyc001_CD3"
],
"status": "success",
"project_root": "<project>",
"channels": {
"cyc001_CD3": {
"status": "approved",
"cycle": "cyc001",
"quality_score": 0.8,
"steps": 2,
"last_operation": "saved",
"review_notes": "",
"modified": "<time>"
},
"cyc001_Blank": {
"status": "pending",
"cycle": "cyc001",
"quality_score": null,
"steps": 0,
"last_operation": null,
"review_notes": "",
"modified": "<time>"
}
}
}
Compatibility
Component |
Supported |
|---|---|
|
2.x ( |
Python |
3.10, 3.11, 3.12 |
Client |
Any MCP client that speaks stdio; |
Transport |
stdio only |
Troubleshooting
Executable not found in $PATH: kintsugi— the client’s entry names a barekintsugithat is not on the client’sPATH; runkintsugi mcp config <project> --client NAME --forcefrom the environment where KINTSUGI is installed, so the entry carries the absolute path of the executable.The server is listed but not started, or “Pending approval” — the client has not approved it yet: trust the folder or workspace and enable
kintsugias the last column of Connect your client describes. In Claude Code,.claude/settings.local.jsonapprovals apply only in a trusted folder, andclaude mcp reset-project-choicesclears earlier decisions.mcp 1.x is installed but KINTSUGI requires mcp>=2.0— runpip install "mcp>=2.0,<3"in the environment the server starts from.A tool call fails with a validation error — the message names the parameter and the allowed values; the schema is the contract.
Running the server by hand —
kintsugi mcp startreads JSON-RPC on stdin; pipe aninitializerequest to see the handshake.