feat: add fromjson template filter for consuming MCP tool results - #580
Merged
Jason Robert (jrob5756) merged 4 commits intoOct 1, 2026
Merged
Jason Robert (jrob5756) merged 4 commits into
Jason Robert (jrob5756) merged 4 commits into
Conversation
MCP steps return the raw MCP envelope, and many widely used MCP servers return data only as a JSON string in content[0].text without populating structuredContent. With no inverse of the existing json filter, downstream steps could not parse that text inside arguments, routes, or stdin templates, blocking deterministic read-modify-write workflows. Register a built-in fromjson filter on TemplateRenderer: it parses a JSON string into native data and raises a TemplateError with a specific suggestion on non-string input or invalid JSON. render() previously re-wrapped any TemplateError raised by a filter under its generic handler, replacing the specific suggestion with "Check template and context for errors"; it now re-raises filter-raised TemplateErrors unchanged (attaching the template when missing) so the filter's own error contract reaches the caller. Closes microsoft#579
The integration test added for microsoft#579 hand-built a context dict using a steps.<name>.output prefix, which WorkflowContext never creates: prior steps are exposed directly by name (ctx[agent] = {"output": ...}). Build the context through WorkflowContext.store()/build_for_agent() and use the canonical get_mr.output.content[0].text path so the test pins the shape the engine actually produces.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #580 +/- ##
=======================================
Coverage ? 92.94%
=======================================
Files ? 198
Lines ? 32033
Branches ? 0
=======================================
Hits ? 29772
Misses ? 2261
Partials ? 0 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Jason Robert (jrob5756)
left a comment
Collaborator
There was a problem hiding this comment.
Thanks for adding the fromjson filter for MCP tool results. I have one recommended clarification about passing parsed collections to MCP arguments and route conditions.
1 approved findings: 1 inline, 0 in this review body.
A bare {{ ... }} interpolation of a parsed collection stringifies it
with Python repr before the MCP argument YAML-coercion pass, turning
null into "None" and escaping embedded newlines; an empty list
renders as the truthy string "[]" in route conditions. Apply the
review feedback: keep parsed values JSON via tojson when they feed
arguments, and test the collection (length > 0) in route conditions.
Regression coverage for the MCP arguments guidance: bare interpolation of a parsed collection corrupts it through YAML coercion while tojson round-trips exactly, and an empty list interpolated bare is truthy in route conditions while length > 0 is not.
Jason Robert (jrob5756)
approved these changes
Oct 1, 2026
Jason Robert (jrob5756)
left a comment
Collaborator
There was a problem hiding this comment.
LGTM, thanks for adding the fromjson template filter to consume MCP tool results!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #579.
Summary
MCP steps (
type: mcp) return the raw MCP envelope, and many widely used MCP servers return data only as a JSON string incontent[0].textand never populatestructuredContent. Jinja2 shipstojsonbut no inverse, andTemplateRendererregistered only thejson(dumps) anddefaultfilters — so downstream steps could not parse a previous MCP tool's JSON text insidearguments, routes, orstdintemplates, blocking deterministic read-modify-write workflows (the issue's GitLabupdate_merge_requestlabel-merge chain is the concrete case).This PR registers a built-in
fromjsonfilter — the inverse of the existingjsonfilter:On non-string input or invalid JSON it raises
TemplateErrorwith a specific suggestion, consistent with the renderer's error contract.One extra fix found during review
TemplateRenderer.render()re-wrapped anyTemplateErrorraised by a filter under its genericexcept Exceptionhandler, replacing the filter's specificsuggestionwith "Check template and context for errors" and burying the real cause.render()now re-raises filter-raisedTemplateErrors unchanged (attaching the rendered template for context when missing), so the filter's own error contract reaches the caller. Regression tests pin the structured contract (suggestion,template_string, and theJSONDecodeErrorcause).Changes
src/conductor/executor/template.py—fromjsonfilter registration + implementation;TemplateError-preserving branch inrender()tests/test_executor/test_template.py— 6 unit tests: object/array/scalar parsing, composition withdefault+ concat, both error paths with the structured contract assertedtests/test_executor/test_mcp_step.py— integration regression: a prior MCP step'scontent[0].textparsed withfromjson, merged, serialized withtojson, arriving at the tool as a native list via argument auto-coercion (the issue's exact pattern)docs/workflow-syntax.md— MCP Steps section documents parsingcontent[0].textwithfromjsonwhen a server returns nostructuredContentplugins/conductor/skills/conductor/references/{yaml-schema,authoring}.md—fromjsonadded to both### Filtersblockschangelog.d/579.added.md— towncrier fragmentVerification
tests/test_executor/+tests/test_config/: 2172 passed-n 4): 11024 passed; the single failure (tests/test_cli/test_status.py::TestTerminalCostCellBilling::test_status_table_renders_the_label) is a pre-existing parallel-isolation flake — it fails identically on cleanmainunder the same command and passes in isolation with and without this changemake check(ruff + ty): clean