Skip to main content
Glama
rustam-zarifov

mto-mcp-server

mto-mcp-server

An MCP server that reconciles a piping MTO (Material Take-Off) against a Plant 3D SCSA export, for industrial EPC projects.

The problem

MTOs drift out of sync with the model as a piping design evolves. Bringing one back in line is a recurring, mechanical-but-error-prone task: diff two Excel exports with different shapes, decide what changed, update quantities, add new items, and — carefully — remove items that dropped out of the design. Doing this by hand across hundreds of rows and 8 piping categories (pipes, elbows, tees, reducers, flanges, valves, blind flanges, gaskets) is slow and easy to get subtly wrong.

Related MCP server: procurement-tools

Why MCP instead of a script

The reconciliation logic itself could be a plain script. What makes this an agent tool rather than a script is the shape of the two calls:

  • diff_mto_scsa only ever computes and reports. It's built so an LLM can call it, read the result, and reason about it in natural language — "SCSA shows 4 of these, MTO has 3, want me to bump it?" — before anything is written.

  • apply_mto_update is split into a safe path (quantity corrections, new items — additive/corrective) and a gated path (removals — zeroing a quantity because the item left the design, which is destructive). The gate is two independent flags, dry_run and include_removals, not a single "confirm" toggle, so an agent (or a human driving it) can preview everything with dry_run=True, apply the safe part, and only zero items out once include_removals=True is passed explicitly and deliberately.

That two-tool shape — compute-and-explain, then gated-write — is the actual point of the project: it's the difference between "a script that mutates your MTO" and "a tool an agent can be trusted to drive."

Scope

This project demonstrates the reconciliation logic end-to-end against real elbow (Отвод) data — the two tools, the 3 scenarios, and the confirm-gate on removals are all built and tested against real MTO/SCSA files. Category-specific matching for other categories (valve check-type synonyms, reducer family+OD-pair+line) exists as draft code in matching.py but is intentionally not wired in — see "Known limitations" below. Everything not elbow-specific (normalization, the diff/apply split, the write logic) is category-agnostic and applies to any category once its match key is validated.

Built on prior work

The core normalization and categorization logic is ported, not rewritten, from rustam-zarifov/mto-scsa-reconciliation — a 6-script toolkit built from real EPC piping reconciliation work, covering all 7 major categories with a color-coded Excel report as output. That toolkit's raw-file assumptions (a 26-column master_mto.xlsm with section headers, feeding an intermediate MTO_Lookup_Table; a header-named SCSA export) don't match the simpler raw exports this project was tested against, so the I/O layer (io_excel.py) here is new. What's ported:

  • normalize.py's classify_category() — the keyword-based category rules, straight from that project's categorize() (now covering fasteners, insulation, and structural steel too, not just the 8 piping categories, since the keyword list came over wholesale).

  • normalize.py's Cyrillic х/Latin x unification, from that project's norm().

  • domain_addons.py — GOST year-format normalization, line-code recovery, OD→DN lookup, and pipe-segment splitting, ported in full from 4_normalization_addons.py. Not wired into matching yet — elbows don't hit any of these cases; they're here for when gasket/pipe/flange matching gets built out.

  • apply.py's _redistribute_group() — proportional redistribution across multiple MTO rows sharing one match key, from that project's 5_apply_fixes.py, replacing an earlier "dump into one row, zero the rest" approach.

One thing that did not port: that project's match key is (name, material), using a Материал column present on both sides of its pipeline. This project's raw MTO export has no comparable material column (only a line code, which turned out to be unreliable — see below), so the match key here stays name-only until that gap is closed.

The 3 scenarios

Scenario

Condition

Action

Qty mismatch

item in both, quantities differ (either direction)

update MTO qty to match SCSA

Removed

item in MTO, no longer in SCSA

zero the MTO qty (gated, see above)

New

item in SCSA, not yet in MTO

insert into MTO with SCSA's qty

Items with matching quantities in both files produce no diff entry (counted in summary.matched only) — this is checked directly in the test suite, not assumed.

Install

pip install -e .

Requires mcp>=1.9.0,<2.0.0. Note: pip install "mcp[cli]" on its own pulls the 2.x line, which drags in a much heavier dependency set (opentelemetry, jsonschema, pyjwt>=2.10) that isn't needed for a local stdio server. This project pins <2.0.0 deliberately.

Run

mto-mcp-server

or point a Claude Desktop / Claude Code MCP config at python -m mto_mcp_server.server.

Tools

diff_mto_scsa(mto_path, scsa_path, category=None)

Read-only. Returns:

{
  "summary": {"matched": 1, "qty_mismatch": 2, "removed": 2, "new": 2},
  "qty_mismatch": [{"name": "...", "line": "...", "mto_qty": 3, "scsa_qty": 4, ...}],
  "removed":      [{"name": "...", "mto_qty": 1, ...}],
  "new":          [{"name": "...", "scsa_qty": 1, ...}]
}

category optionally restricts to one of the categories classify_category() produces — the 8 piping categories (pipe, elbow, tee, reducer, flange, valve, blind_flange, gasket) plus the extra buckets that came along with the ported keyword rules (stud_bolt, bolt, nut, washer, gate_valve, safety_valve, ball_valve, steam_trap, pipe_support, insulation, structural_steel). Omit it to diff everything.

apply_mto_update(mto_path, scsa_path, category=None, include_removals=False, dry_run=False, output_path=None)

Re-derives the diff internally from mto_path/scsa_path every call — it does not accept a pre-computed diff object from the caller. That keeps the tool safe against a stale or hand-edited diff being replayed, and keeps diff_mto_scsa a true preview with no side effects to trust.

  • dry_run=True → compute and report, write nothing.

  • dry_run=False, include_removals=False (default) → applies qty fixes and inserts; removals are reported but not written.

  • dry_run=False, include_removals=True → also zeroes removed items.

  • output_path → write to a different file instead of overwriting mto_path in place.

Matching design

  • Normalization: strips °, a trailing ?, collapses whitespace, and unifies Cyrillic "х" / Latin "x" (a real bug source in dimension strings like "160x5") before any comparison.

  • Default match key is name-only, not name+line. In the real SCSA export, line-code columns are populated for some standards (GOST) and empty for others (ASME) — see tests/fixtures/scsa_test.xlsx. Requiring line in the key would silently drop real matches. Line is still carried through on every diff entry as display metadata.

  • Valves and reducers: the spec calls for check-type valve synonym folding (Обратный клапан = Обратный затвор) and family+OD-pair+line matching for reducers, but there was no real valve or reducer data available to validate either against — only elbow data was available. Both key functions exist as drafts in matching.py (valve_key, reducer_key) but are not wired in — see the _KEY_FUNCS comment. Both categories currently fall through to the name-only default key. Wiring either back in is a one-line change once there's real data to check it against.

  • GOST/ASME cross-referencing falls out of the name-only key for free — the standard field is read but deliberately excluded from the key.

Testing

tests/test_diff.py runs against real (small) MTO/SCSA export pairs in tests/fixtures/, not synthetic data. Expected values were derived by hand-tracing the fixture files first, then asserted — not read off the code's own output. Covers: exact-match (no false flag), qty mismatch in both directions, removed items, new items, dry-run (no write), apply without removals (removed items untouched), apply with removals (zeroed), and new-item insertion (qty + next POZ number).

pip install pytest
pytest tests/ -v

Known limitations / next steps

  • Category-specific matching (valve check-type synonyms, reducer family+OD-pair+line) is drafted but not validated against real data — see above. Demo and tests currently cover elbow reconciliation only.

  • The match key is name-only, not (name, material) like the ported toolkit's proven engine — this project's raw MTO export doesn't expose a comparable material field yet. See "Built on prior work" above.

  • domain_addons.py is ported but not wired into any matching path yet.

  • Column positions in io_excel.py (MTO_COLUMNS, SCSA_COLUMNS) are verified against the two fixture files only; a different SCSA export layout would need the indices adjusted.

Available Tools

2 tools
apply_mto_updateA

Write the MTO/SCSA reconciliation into the MTO file.

Always re-derives the diff from the two paths (never trust a caller-supplied diff blob). Quantity corrections and new items are applied whenever dry_run=False - they're additive/corrective, not destructive. Removals (zeroing a quantity because the item left the project) are ONLY written when include_removals=True as well.

Recommended flow: call with dry_run=True first to preview writes, then call again with dry_run=False, and include_removals=True only once you've reviewed the removed list.

Args: mto_path: path to the MTO .xlsx file to update. scsa_path: path to the SCSA .xlsx export (source of truth). category: optional category filter, same as diff_mto_scsa. include_removals: if True, zero out items no longer in SCSA. dry_run: if True, compute and report but write nothing. output_path: write to a different file instead of overwriting mto_path. Defaults to overwriting mto_path in place.

ParametersJSON Schema
NameRequiredDescriptionDefault
dry_runNo
categoryNo
mto_pathYes
scsa_pathYes
output_pathNo
include_removalsNo

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full behavioral disclosure burden, and it does so thoroughly. It explains that the diff is always re-derived, that corrections are additive/corrective rather than destructive, that removals only happen when include_removals=True, and that output_path defaults to overwriting the input file.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately sized for a complex mutation tool. Every sentence earns its place: purpose, safety behavior, recommended workflow, and parameter explanations are clearly separated and front-loaded, with no filler or redundancy beyond minor repetition of defaults.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a six-parameter mutation tool with no annotations and no output schema, the description is highly complete: it covers behavior, parameter meanings, defaults, and workflow. It only stops short of describing the return/report format for dry_run, which would help an agent act on the preview.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must fully compensate, and it does. Every parameter is explained with meaningful context: mto_path and scsa_path roles, dry_run behavior, include_removals semantics, category matching diff_mto_scsa, and output_path's overwrite default.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Write the MTO/SCSA reconciliation into the MTO file.' It clearly differentiates this apply/write tool from the sibling diff_mto_scsa, which is a diff/report tool, by making the write action explicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The recommended flow gives explicit sequencing: call with dry_run=True first, then dry_run=False, and only include_removals=True after reviewing removals. It does not explicitly name diff_mto_scsa as the alternative for previewing, but the conditional guidance around dry_run and include_removals still provides clear usage direction.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

diff_mto_scsaA

Compare an MTO file against a Plant 3D SCSA export.

Read-only - never writes anything. Returns counts and item lists for the 3 scenarios: qty_mismatch, removed (in MTO, not in SCSA anymore), new (in SCSA, not yet in MTO). Items with matching qty in both are counted in summary.matched but not listed individually.

Args: mto_path: path to the MTO .xlsx file. scsa_path: path to the SCSA .xlsx export. category: optional filter - one of pipe/elbow/tee/reducer/flange/ valve/blind_flange/gasket/stud_bolt/bolt/nut/washer/ gate_valve/safety_valve/ball_valve/steam_trap/pipe_support/ insulation/structural_steel. Omit to diff everything.

ParametersJSON Schema
NameRequiredDescriptionDefault
categoryNo
mto_pathYes
scsa_pathYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full behavioral burden and meets it: it discloses read-only side effects, the exact scenarios returned (qty_mismatch, removed, new), and that matching items only appear as a count in summary.matched, not as a list. This goes well beyond a generic 'compare files' statement.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is efficiently front-loaded with the core action and safety characteristic, then organized into scenario and argument lists. Every sentence adds information; there is no filler or repetition of schema titles.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a three-parameter comparison tool with no output schema, the description covers inputs, optional filtering, output scenarios, and the special handling of matched items. An agent has enough context to invoke it and interpret the result categories correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must explain the parameters itself. It does so thoroughly: mto_path and scsa_path specify file types, and category is listed with the full set of allowed values plus the default behavior when omitted.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource ('Compare an MTO file against a Plant 3D SCSA export') and names three concrete diff scenarios. This makes the tool's purpose immediately clear and distinguishes it from the sibling apply_mto_update, which implies writing changes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description states this tool is 'Read-only - never writes anything', signaling not to use it for modifications and implying apply_mto_update is the writing counterpart. It also explains the optional category filter and its omission behavior. However, it never names apply_mto_update explicitly as the alternative for applying changes.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updatesv0.1.0
    • First observedapply_mto_update
    • First observeddiff_mto_scsa

TDQS

A4.8/5.0

Scored across 2 tools

Disambiguation5/5

The two tools have clearly distinct roles: diff_mto_scsa is explicitly read-only comparison, while apply_mto_update is write-back with dry-run and removal safeguards. There is no realistic ambiguity about which tool to select for a given task.

Naming Consistency5/5

Both names follow a consistent verb_noun snake_case pattern: diff_mto_scsa and apply_mto_update. The shared 'mto' prefix ties them to the same domain, and the verbs clearly signal read vs. write actions.

Tool Count4/5

Two tools is slightly below the typical 3-15 range, but this is a deliberately narrow reconciliation workflow where diff and apply are exactly the needed operations. The small count is reasonable and neither tool is redundant.

Completeness5/5

The server covers the full diff-then-apply lifecycle: it can identify mismatches, removed items, and new items, and then safely write corrections back, including controlled removal handling. Parametrized categories, dry-run preview, and output_path prevent dead ends.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    Connects Hermes to procurement pricing tools for BOM review, mock market data, cost calculations, and Excel report generation.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Validates Excel (.xlsx) workbooks against reviewed, lockable acceptance contracts, catching errors and outputting structured issues that agents can repair.
    1
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables managing CSVBox importer sheets through MCP tools, including creating, replacing, patching, generating, validating, and scaffolding importers from any MCP-compatible client.
    9
    36 npm
    MIT