Build note

Designing MCP Tools for Multi-Step Work

Published Updated Florian Smeyers5 min read
MCPIntegrationsAutomation
On this page

Build note · 25 October 2026

A tool list can expose order search and order detail without explaining how the two fit together. If search returns id and detail expects orderId, a model may connect them, but the interface has left it another translation to make.

In Connecting Your Business with MCP Servers, we showed an ERP lookup demo using dummy data for an anonymous German clothing manufacturer. That article is not measured evidence of time savings. The richer order schemas and records below are synthetic design proposals, not the history of a shipped client implementation.

MCP standardises tool discovery and invocation. It does not guarantee good tool selection, correct reasoning or authorisation. Models can also compose tools outside MCP; the protocol is not what makes a sequence possible.

Give the Next Call a Clear Input

For an order interface, I would use order_id consistently in list results and detail arguments. The description should state what comes back and name the next lookup when detail is needed. For example, this synthetic tool definition uses the protocol's inputSchema field:

{
  "name": "search_orders",
  "description": "List open or partially shipped orders, ordered by promised_date. Returns order_id, status and promised_date. Use get_order_details with order_id for component deliveries. Follow next_cursor to continue.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "status": {
        "type": "string",
        "enum": ["open", "partially_shipped"]
      },
      "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 25},
      "cursor": {"type": "string"}
    },
    "required": ["status"],
    "additionalProperties": false
  }
}

The schema gives the client and model useful constraints. The implementation must still reject invalid values and enforce limits. default is an annotation, not code that fills in a missing argument. An enum does not prevent a model from proposing an invalid call. Other context, including user instructions and previous results, also affects tool selection.

Keep the list bounded, with enough evidence to choose the next lookup. Return a cursor when more results exist and mark any truncated fields explicitly. A compact result that omits decisive information is not sufficient just because it is small.

A Synthetic Order Chain

Suppose a user asks which open orders may miss their promised date. This invented response contains example identifiers, not client records:

{
  "items": [
    {"order_id": "EX-01", "status": "open", "promised_date": "2026-11-10"},
    {"order_id": "EX-02", "status": "open", "promised_date": "2026-11-12"}
  ],
  "next_cursor": null
}

Neither status nor the promised date proves an order is late. In this example the agent must fetch component detail for both orders, or use an authorised risk filter backed by actual delivery data. Choosing the first result is a lookup order, not a risk inference.

// Synthetic call
{"name": "get_order_details", "arguments": {"order_id": "EX-01"}}

// Synthetic result
{
  "order_id": "EX-01",
  "promised_date": "2026-11-10",
  "components": [
    {"component_id": "PART-A", "expected_delivery": "2026-11-13"}
  ]
}

Here the component date is later than the order's promised date. That supports flagging a possible risk, subject to stock, alternatives and the reliability of those dates. It does not establish the answer for EX-02 or authorise a message to the customer.

Constructed illustration of list_items feeding an example id into get_item.

Constructed illustration: example UI, tool names and ticket data, not a live capture or the manufacturer workflow. It illustrates only the identifier handoff, not a verified dependency or approval recommendation.

The same list-then-detail shape can be useful for tickets, orders or equipment. It still needs tests with the intended client, model and permissions, including missing records, stale data and partial results.

What the Ignition Artifact Actually Contains

The code reference for this note is a static inspection of server.py in the existing Ignition bundle on 25 October 2026. Its manifest identifies version 2.6.4; the archived server file is dated 18 February 2026. It contains 66 decorated MCP tools and 19 WebDev endpoint registrations in _setup_webdev_project(). Those are counts in that artifact, not a current gateway inventory or a verified breakdown into tool tiers.

The names include read_tag_values and write_tag_value, alongside get_active_alarms, browse_tags and get_alarm_journal. The pump investigation describes a simulated failure on a live demo gateway: a script wrote upset tag values to produce the alarm cascade. It is a demo of querying several sources, not a real plant incident or a client outcome.

The connector combines native gateway API calls with a generated WebDev bridge for additional operations. That creates separate access paths to review. Separating a read tool from a write tool in the menu is useful, but does not itself enforce separate permissions.

The Bridge Is Not a Read-Only Security Boundary

The inspected bundle generates WebDev method configuration with require-auth: false, require-https: false and no required roles. It includes write, SQL and function-execution endpoints. A read-only native gateway API key therefore does not establish that the generated bridge is read-only.

The system.* prefix check in function execution restricts a namespace; it is not a narrow capability allowlist. That namespace includes writes and configuration operations. Likewise, the resource path check is against the overall projects directory, not authorisation for one selected project. Neither check proves general execution or filesystem safety.

These are static findings about the downloadable artifact. They do not establish that any deployed gateway is publicly exposed; no gateway was probed for this note. I would not treat this bundle as evidence of a safe production rollout. Endpoint authentication, transport, operation-level authorisation and project access need independent verification in an isolated environment before use. Connector remediation is separate from this article.

Replacement and Retry Need Preconditions

The setup code imports its generated project using overwrite=true. That expresses replacement intent, not a harmless retry guarantee. Repeating setup can overwrite edits or a different version. It should require explicit authorisation, an inspected target and a recovery plan, not run as an unattended repair step.

Setting the same value twice may leave the same stored value, yet still trigger side effects or overwrite a newer operator change. Write design needs concurrency checks, operation-specific permissions and a policy for ambiguous network failures. Idempotency keys can help where the server implements deduplication; they do not make every physical or business action safe to repeat.

Return Safe Errors, Not Raw Exceptions

Structured errors help an application distinguish invalid input from a temporary failure. They do not guarantee recovery or prevent leakage. The inspected execution endpoint can return an exception and traceback; wrapping that in JSON does not sanitise it.

For a new implementation, I would return a stable code and a safe message, keeping internal diagnostics in access-controlled logs. This is a recommendation, not the bundle's shipped response shape:

{
  "error": {
    "code": "TAG_READ_FAILED",
    "message": "The requested tag values could not be read.",
    "retryable": false
  }
}

The caller should stop or take a tested recovery path, not assume that a readable error permits another action. Consistent identifiers and bounded results make a chain easier to follow. Server-side access controls and independently checked outcomes determine whether that chain is fit to use.

For a fixed, repeatable operation, a deterministic integration or CLI may be more appropriate than model-directed tool selection. The design question is what needs a decision between calls, and what should run the same way every time.

Want to discuss a project like this?

SFLOW helps companies implement practical AI solutions — from custom platforms to industrial integrations with your existing systems.

← All articles