Skip to content

feat(mcp-cli-bridge): stdin JSON payload mode for multi-argument tool calls#29636

Merged
pelikhan merged 3 commits intomainfrom
copilot/improve-mcp-cli-bridge
May 1, 2026
Merged

feat(mcp-cli-bridge): stdin JSON payload mode for multi-argument tool calls#29636
pelikhan merged 3 commits intomainfrom
copilot/improve-mcp-cli-bridge

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 1, 2026

Agents generating complex multi-argument payloads (e.g. add_comment with a multiline body) had to rely on per-field - stdin substitution, which only covers one argument at a time. This adds a JSON payload mode so the entire argument object can be piped at once.

Changes

  • hasStdinJsonPayload(args) — new exported predicate; returns true when:
    • args = ['.'] — explicit sentinel (always reads stdin)
    • args = [] and !process.stdin.isTTY — implicit when stdin is piped
  • parseToolArgs — before the flag-parsing loop, checks for JSON payload mode: parses stdinContent as a JSON object, applies schema key normalization (dash↔underscore), and returns properties directly. Falls through to normal parsing on invalid JSON or non-object input.
  • main()readStdinSync() is now also triggered by hasStdinJsonPayload, not just hasStdinPlaceholder.
  • mcp_cli_tools_prompt.md — updated builtin agent prompt to document the JSON payload mode, position it as the preferred approach for multi-argument calls, and distinguish it from the single-field - substitution.

Usage

# Explicit sentinel — preferred for any multi-argument call
printf '{"issue_number":42,"body":"### Title\n\nMultiline body."}' \
  | safeoutputs add_comment .

# Works with any tool
printf '{"title":"Fix: something","body":"Details here","labels":["bug"]}' \
  | safeoutputs create_issue .

# Implicit (no args, piped stdin)
cat payload.json | safeoutputs add_comment

# Single-field substitution (legacy, for one multiline parameter)
printf 'multiline\ncontent' | safeoutputs add_comment --issue_number 42 --body -

Values from JSON preserve their native types — no string coercion is applied. Schema key normalization (e.g. issue-numberissue_number) is applied to JSON keys.

Copilot AI and others added 2 commits May 1, 2026 21:12
When reading from stdin, support two new modes:
- '.' sentinel: `safeoutputs add_comment . < payload.json`
- no args with piped stdin: `echo '{...}' | safeoutputs add_comment`

The piped JSON object's properties are extracted as tool arguments,
with schema key normalization applied. This makes it easy for agents
to pass complex multi-argument payloads like add-comment without
shell quoting issues.

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/d4d45a9b-d190-45d3-9b19-16b0ac40c0b1

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/d4d45a9b-d190-45d3-9b19-16b0ac40c0b1

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan
Copy link
Copy Markdown
Collaborator

pelikhan commented May 1, 2026

@copilot optimize for agentic tool calling efficiency. Update builtin prompt to explain this behavior.

@pelikhan pelikhan marked this pull request as ready for review May 1, 2026 21:45
Copilot AI review requested due to automatic review settings May 1, 2026 21:45
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a stdin JSON payload mode to mcp_cli_bridge so entire multi-argument tool inputs can be piped as a single JSON object (via . sentinel or implicitly when stdin is piped and no args are provided), improving ergonomics for complex/multiline payloads.

Changes:

  • Introduces hasStdinJsonPayload(args) to detect JSON-payload stdin usage (['.'] or [] with non-TTY stdin).
  • Extends parseToolArgs to parse stdin as a JSON object (with schema-key normalization) before falling back to flag parsing.
  • Updates main() to pre-read stdin for JSON payload mode, and adds/updates Vitest coverage for the new behavior.
Show a summary per file
File Description
actions/setup/js/mcp_cli_bridge.cjs Adds JSON-payload stdin mode detection and parsing, and wires stdin pre-read into main().
actions/setup/js/mcp_cli_bridge.test.cjs Adds test coverage for JSON payload mode triggers, parsing behavior, and key normalization/type preservation.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 1

Comment on lines +554 to +558
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
for (const [key, value] of Object.entries(parsed)) {
const canonicalKey = resolveSchemaPropertyKey(key, schemaProperties, normalizedSchemaKeyMap, ambiguousNormalizedSchemaKeys);
result[canonicalKey] = value;
}
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

🧪 Test Quality Sentinel Report

Test Quality Score: 77/100

⚠️ Acceptable — with one note on test inflation

Metric Value
New/modified tests analyzed 14
✅ Design tests (behavioral contracts) 14 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 8 (57%)
Duplicate test clusters 0
Test inflation detected ⚠️ YES — 121 test lines added vs 49 production lines (ratio ≈ 2.5:1)
🚨 Coding-guideline violations None

Test Classification Details

View All 14 Classified Tests
Test File Classification Issues Detected
returns true for '.' sentinel mcp_cli_bridge.test.cjs ✅ Design None
returns true for empty args when stdin is not a TTY mcp_cli_bridge.test.cjs ✅ Design Edge case — non-TTY detection
returns false for empty args when stdin is a TTY mcp_cli_bridge.test.cjs ✅ Design Edge case — TTY guard
returns false when args contain flags mcp_cli_bridge.test.cjs ✅ Design Edge case — flag prefix
returns false when args has more than just '.' mcp_cli_bridge.test.cjs ✅ Design Edge case — mixed args
parses stdin JSON object when '.' sentinel is used mcp_cli_bridge.test.cjs ✅ Design None
parses stdin JSON object when no args and stdinContent is provided mcp_cli_bridge.test.cjs ✅ Design None
preserves types from JSON payload without coercion mcp_cli_bridge.test.cjs ✅ Design None
normalizes dashed JSON keys to schema underscore keys mcp_cli_bridge.test.cjs ✅ Design None
falls through to empty args when stdinContent is null and sentinel is used mcp_cli_bridge.test.cjs ✅ Design Edge case — null guard
falls through to empty args when stdinContent is empty string mcp_cli_bridge.test.cjs ✅ Design Edge case — empty string
falls through to normal parsing when stdinContent is not valid JSON mcp_cli_bridge.test.cjs ✅ Design Edge case — invalid JSON
falls through when JSON is an array rather than an object mcp_cli_bridge.test.cjs ✅ Design Edge case — wrong JSON type
handles multiline JSON payload mcp_cli_bridge.test.cjs ✅ Design None

Flagged Tests — Requires Review

No tests were flagged for behavioral quality issues. All 14 new tests verify observable, user-facing contracts of the new hasStdinJsonPayload and parseToolArgs (stdin JSON mode) feature.

i️ Test Inflation Note

File: mcp_cli_bridge.test.cjs
Ratio: 121 test lines added vs 49 production lines (≈ 2.5:1, threshold 2:1)

This is a mild overage and is not a serious concern here — the new feature has three distinct behavioral dimensions worth thorough coverage (sentinel detection, TTY detection, and JSON parsing variants). The 14 tests map cleanly onto distinct scenarios with no obvious redundancy. No action required, but worth being aware of.


Scoring Breakdown

Component Score Rationale
Behavioral Coverage 40/40 All 14 tests are design tests (100%)
Error/Edge Case Coverage ~17/30 8/14 tests cover edge/error cases (57%)
Low Duplication 20/20 No duplicate clusters detected
Proportional Growth 0/10 Inflation ratio 2.5:1 exceeds 2:1 threshold
Total 77/100

The missing edge-case points are because 6 tests cover only happy-path parsing scenarios (JSON object with valid keys). These are still good tests — they verify key behavioral invariants — but they don't include an error case. Adding one or two table-driven rows with invalid/boundary inputs to the parsing tests would push the score higher in future iterations.


Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 0 tests
  • 🟨 JavaScript (*.test.cjs): 14 tests (vitest)

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%). All new tests verify behavioral contracts of the new stdin JSON payload feature.


📖 Understanding Test Classifications

Design Tests (High Value) verify what the system does:

  • Assert on observable outputs, return values, or state changes
  • Cover error paths and boundary conditions
  • Would catch a behavioral regression if deleted
  • Remain valid even after internal refactoring

Implementation Tests (Low Value) verify how the system does it:

  • Assert on internal function calls (mocking internals)
  • Only test the happy path with typical inputs
  • Break during legitimate refactoring even when behavior is correct
  • Give false assurance: they pass even when the system is wrong

Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.

References: §25234542649

🧪 Test quality analysis by Test Quality Sentinel · ● 535K ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 77/100. Test quality is acceptable — 0% of new tests are implementation tests (threshold: 30%). All 14 new tests verify behavioral contracts of the stdin JSON payload feature.

Adds documentation for the new JSON stdin payload mode (.sentinel),
positions it as the preferred approach for multi-argument tool calls,
and reorganises the stdin-piping section to distinguish between the
full-payload mode and single-field - substitution.

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/9908e4bf-ffe8-4094-9fd7-978a8149566a

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 1, 2026

@copilot optimize for agentic tool calling efficiency. Update builtin prompt to explain this behavior.

Done in d081c45. Updated mcp_cli_tools_prompt.md to:

  • Add a "Multiline and Multi-Argument Payloads (JSON stdin)" section that positions the . sentinel as the preferred approach for any multi-argument call — single pipe operation, no shell quoting issues, native type preservation
  • Explain why agents should prefer it (structured payload, one operation, no repeated --key value flags)
  • Demote the old - single-field substitution to a secondary "Single-Parameter stdin Substitution" section for cases where only one field needs piped content
  • Update the Notes bullet list to lead with the JSON payload mode recommendation

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw IF/RW4LlR7HxMOaW/usr/bin/runc (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v6
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq [.object.sha, .object.type] | @tsv (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq [.object.sha, .object.type] | @tsv get --local est.lock.yml copilot.originalinfocmp (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq [.object.sha, .object.type] | @tsv get --local ock.yml copilot.originalinfocmp (http block)
  • https://api.github.com/repos/actions/download-artifact/git/ref/tags/v8
    • Triggering command: /usr/bin/gh gh api /repos/actions/download-artifact/git/ref/tags/v8 --jq [.object.sha, .object.type] | @tsv -dirty&#34; -o gh-aw ./cmd/gh-aw cfg 64/pkg/tool/linux_amd64/vet credential.helpe/usr/bin/gh (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/download-artifact/git/ref/tags/v8 --jq [.object.sha, .object.type] | @tsv get cfg 64/pkg/tool/linux_amd64/vet credential.usern/usr/bin/gh (http block)
  • https://api.github.com/repos/actions/setup-node/git/ref/tags/v6
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v6 --jq [.object.sha, .object.type] | @tsv (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v6 --jq [.object.sha, .object.type] | @tsv get --local r: $owner, name: $name) { hasDiscussionsEnabled } } copilot.originalgit (http block)
  • https://api.github.com/repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq [.object.sha, .object.type] | @tsv (http block)
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq [.object.sha, .object.type] | @tsv diff.go export.go x_amd64/compile (http block)
  • https://api.github.com/repos/github/gh-aw
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw --jq .default_branch --local credential.helper ache/go/1.25.8/x64/pkg/tool/linux_amd64/vet (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants