Skip to content

v0.4.0 — per-tool response mapping

Choose a tag to compare

@keysersoft keysersoft released this 02 Aug 07:31
· 21 commits to main since this release
ddefade

New: Response Mapping

Tools can now shape the API response before it reaches the AI client. Optional, per tool, and off by default — a tool without a mapping behaves exactly as before.

Upstream endpoints routinely return far more than a tool needs. A Datto RMM device carries up to 300 UDF fields, IPs and remote-control URLs when the tool only wants hostname, site, OS and status. Every extra byte is billed to the agent's context window and exposed to a third-party model. A response mapping declares what a tool actually publishes, separately from what the API returns.

{
  "responseMapping": {
    "transform": {
      "select": {
        "page": { "count": "$.pageDetails.count", "totalCount": "$.pageDetails.totalCount" },
        "devices": {
          "$from": "$.devices[*]",
          "$select": {
            "id": "id",
            "hostname": "hostname",
            "category": "deviceType.category",
            "antivirusStatus": "antivirus.antivirusStatus"
          }
        },
        "source": "= datto-rmm"
      },
      "exclude": ["devices[*].udf"]
    }
  }
}

Two modes. select is a declarative output template — leaves are paths ($.a.b, items[*].id, list[-1].x), = literal statics, or { $from, $select } to reshape every element of an array. expression with "mode": "jmespath" gives you full JMESPath for computed values (length(devices)) and filters (devices[?online == `false`]). Both support include/exclude path pruning, maxBytes and fallbackToRaw.

A path that does not resolve leaves its key out rather than emitting null — that is what keeps mapped responses small.

Applies to every connector type: REST, SOAP, GraphQL, SQL and bridged MCP servers.

Preview against a real response. In the tool editor under Response Mapping, "Preview with last real response" maps the most recent recorded response for that tool — without calling the API again — and shows raw ↔ mapped side by side with the size delta. The tool playground gains Mapped/Raw tabs.

API. GET/PATCH /api/connectors/:id/tools/:toolId/response-mapping to read, set or clear a mapping (leaving cacheTtl and followUp untouched), and POST .../preview-mapping for a dry run. The tool test endpoint now returns mapped and size accounting alongside the unchanged raw result. A malformed transform is rejected with a 400 at save time instead of silently degrading at call time.

Safety. A mapping that fails never breaks a working tool: the raw response is returned and a warning is logged. Set fallbackToRaw: false if you would rather see the error.

Fixes

  • {"type": "json", "fields": [...]} finally works. This shape has been documented since the first release and was never implemented — any tool configured with it silently returned the full response. It now behaves as transform.include.
  • Saving a tool from the UI no longer wipes its response mapping. The editor rebuilt responseMapping from cacheTtl alone, and the connector page never passed the stored value in, so cacheTtl and followUp were discarded on the first save from the GUI. Same bug class as the bodyMapping fix in v0.3.9.
  • structuredContent was empty for every tool with a followUp workflow hint. It was rebuilt by re-parsing the result text, and the appended hint text broke that parse, silently yielding {}. The executor now hands back the object directly.
  • Editing a mapping takes effect immediately. The response cache now stores the raw upstream response (key prefix bumped to tool_cache:v2:) instead of the rendered text, so a change no longer waits out the remaining cacheTtl.
  • outputSchema now matches what clients receive: it is re-inferred from the mapped shape and dropped when the transform changes.

Upgrade notes

No migration. The configuration lives under the existing mcp_tools.response_mapping JSON column in a new transform key, so adapter JSON, import/export, catalog re-sync and the version fingerprint are unaffected.

Tools with a cacheTtl will take one extra cache miss on their first call after the upgrade, because of the cache key prefix bump. Everything else is unchanged.

Verification

81 new tests; full suite green at 207 suites / 3562 tests. Verified end to end against a live API through a real MCP session: unmapped output byte-identical, mapped output 4543 → 384 bytes (−92%), broken mappings falling back to raw without failing the call, and cache edits taking effect on the next call.