Skip to content

Use output converter MIME types for MCP resources#38

Merged
carldebilly merged 6 commits into
yllibed:mainfrom
autocarl:agent/issue-32-resource-mime-types
Jun 26, 2026
Merged

Use output converter MIME types for MCP resources#38
carldebilly merged 6 commits into
yllibed:mainfrom
autocarl:agent/issue-32-resource-mime-types

Conversation

@autocarl

@autocarl autocarl commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR closes #32 by making MCP command-backed resources advertise the MIME type that the Repl output pipeline actually emits.

The final design is intentionally converter-driven, not a per-resource label override: MCP resource reads currently force Repl commands through the JSON output path, so the truthful wire contract is json transformer output + application/json metadata.

Closes #32

What changed

  • Added IOutputTransformer.MimeType, defaulting to text/plain.
  • Added MIME metadata to the built-in output transformers:
    • JSON → application/json
    • Markdown → text/markdown
    • XML → application/xml
    • YAML → application/yaml
    • human/Spectre human output → text/plain
  • Updated command-backed MCP resource templates so resources/list advertises the forced MCP output MIME type instead of hard-coded text/plain.
  • Updated resources/read so successful command-backed resources return the same forced output MIME type with the rendered command result.
  • Kept MCP tool-call fallbacks separate from resource reads:
    • tool calls may still use human text fallbacks such as OK or paged summaries;
    • resource reads return protocol content only.
  • Made successful resource reads with no rendered command output return JSON null, preserving the application/json contract.
  • Suppressed low-level IReplIoContext.Output and IReplIoContext.Error writes during resource reads so side-channel command output/diagnostics are not mixed into the resource body.
  • Added in-process MCP coverage for resource MIME metadata, parameterized resource URIs, read errors, void/no-output reads, fallback separation, and side-channel output/error isolation.
  • Added an opt-in MCP Inspector CLI smoke test under dotnet test for the real wire contract.
  • Updated the MCP and testing docs to explain the actual assertion surface.

Design notes

This PR does not add .WithMimeType(...) or .AsResource(mimeType: ...).

That was considered, but a label-only API would let a resource claim text/markdown, text/html, or another media type while the current MCP command-backed resource implementation still emits the forced JSON output pipeline. That would make the protocol metadata less truthful, not more.

Non-JSON resource bodies should be handled by a future resource-specific converter/blob design where the declared MIME type and emitted bytes are controlled together.

Testing guidance

Repl.Testing remains the right layer for command behavior:

  • GetResult<T>() / TryGetResult<T>(...) validate handler return values before rendering.
  • ReadJson<T>() validates rendered JSON command output.

MCP resource MIME types are protocol metadata, so this PR validates them through:

  • the in-process MCP test fixture for default/local regression coverage;
  • the opt-in MCP Inspector CLI smoke test for end-to-end wire compatibility.

The Inspector smoke test is intentionally off by default:

REPL_RUN_MCP_INSPECTOR_TESTS=1 \
  dotnet test src/Repl.McpTests/Repl.McpTests.csproj -c Release \
  --filter 'TestCategory=ExternalToolchain'

This keeps the normal merge-gating path hermetic: dotnet test src/Repl.slnx does not require Node, npm, or npm registry access.

Test plan

  • dotnet test src/Repl.McpTests/Repl.McpTests.csproj -c Release --filter 'FullyQualifiedName~Given_McpResourceParameters|FullyQualifiedName~Given_McpInspectorCli' — 13 passed, 1 skipped opt-in Inspector smoke
  • REPL_RUN_MCP_INSPECTOR_TESTS=1 dotnet test src/Repl.McpTests/Repl.McpTests.csproj -c Release --filter 'TestCategory=ExternalToolchain' — 1 passed
  • dotnet build src/Repl.slnx -c Release -warnaserror --no-restore — 0 warnings/errors
  • dotnet test src/Repl.slnx -c Release — 989 passed, 1 skipped opt-in Inspector smoke
  • npx -y markdownlint-cli2 '**/*.md' — 0 errors
  • git diff --check
  • Local review panel emulation: clean across architect/security/skeptic/quality/style/operability/contract

carldebilly

This comment was marked as resolved.

@autocarl autocarl force-pushed the agent/issue-32-resource-mime-types branch from bd20936 to a28e2a8 Compare June 26, 2026 00:11
@autocarl

Copy link
Copy Markdown
Contributor Author

Addressed the review feedback in the updated head a28e2a8.

What changed:

  • IOutputTransformer now advertises MimeType.
  • Built-in transformers declare their actual MIME types:
    • JSON → application/json
    • Markdown → text/markdown
    • XML → application/xml
    • YAML → application/yaml
    • Human/Spectre → text/plain
  • MCP resource default MIME is now derived from the forced MCP output transformer (json), so undeclared resources advertise application/json.
  • AsResource(mimeType: "...") remains an explicit override, with resolution order: explicit override → forced converter MIME → text/plain fallback.
  • ResourceMimeType is carried on ReplDocCommand and then ReplDocResource, removing the fragile dictionary keyed by route path.
  • Dropped the misleading markdown-resource test/example because MCP still forces JSON today.

Not addressed in this PR by design:

  • per-resource output converter selection;
  • binary resource contents;
  • changing MCP tool output representation.

Local verification and CI are both green.

@carldebilly carldebilly marked this pull request as ready for review June 26, 2026 00:26
carldebilly

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

Comment thread src/Repl.Core/CommandBuilder.cs Outdated
@autocarl autocarl changed the title Allow MCP resources to declare MIME types Use output converter MIME types for MCP resources Jun 26, 2026
@autocarl

Copy link
Copy Markdown
Contributor Author

Addressed this review round in 3ece773.

Changes made:

  • Removed AsResource(mimeType) entirely. Resource MIME is now converter-driven, not declaration-driven.
  • Removed ResourceMimeType from ReplDocCommand, avoiding the positional record constructor / deconstruct break.
  • Moved forced-output MIME resolution to McpToolAdapter.ForcedOutputMimeType.
  • Changed resource reads to bypass tool text rendering (BuildToolResult), so PagedResultTextMode summaries and the tool OK placeholder do not relabel resource bodies.
  • Added regression tests for:
    • resource templates/read results advertising application/json from the forced JSON converter;
    • resource reads staying serialized JSON in summary-only paged mode;
    • no-output handlers reading as JSON null rather than tool OK text.
  • Added docs for the wire-observable text/plainapplication/json default change and the current limitation around non-JSON resource bodies.

Verification:

  • dotnet test src/Repl.McpTests/Repl.McpTests.csproj -c Release --filter 'FullyQualifiedName~Given_McpResourceParameters'
  • dotnet test src/Repl.Tests/Repl.Tests.csproj -c Release --filter 'FullyQualifiedName~Given_CommandBuilderEnrichment|FullyQualifiedName~Given_OutputTransformerMimeTypes'
  • dotnet test src/Repl.McpTests/Repl.McpTests.csproj -c Release --filter 'FullyQualifiedName~Given_McpResourceParameters|FullyQualifiedName~Given_McpServerEndToEnd'
  • dotnet test src/Repl.SpectreTests/Repl.SpectreTests.csproj -c Release --filter 'FullyQualifiedName~Given_SpectreOutputTransformerMimeTypes'
  • dotnet test src/Repl.slnx -c Release
  • git diff --check
  • local review panel: clean across architect/security/skeptic/quality/style/operability/contract

@carldebilly

Copy link
Copy Markdown
Member

@codex Please review again

chatgpt-codex-connector[bot]

This comment was marked as resolved.

carldebilly

This comment was marked as resolved.

@autocarl

Copy link
Copy Markdown
Contributor Author

Addressed the latest non-blocking review notes in ae7f593.

Changes:

  • Added a dotnet test-owned MCP Inspector CLI smoke test (Given_McpInspectorCli) that runs npx @modelcontextprotocol/inspector@0.22.0 against the sample MCP server and asserts:
    • resources/list advertises application/json for command-backed resources;
    • resources/read returns application/json;
    • the returned resource text is parseable JSON.
  • Kept this inside Repl.McpTests rather than adding a separate CI workflow step. The test project references the sample MCP server with ReferenceOutputAssembly="false" so the sample is built before the smoke test runs.
  • Added resource-read regression tests for:
    • Results.Exit(0) with no payload → JSON null / application/json;
    • failed resource command → McpException;
    • unknown adapter resource route → text/plain error result.
  • Made forced-output MIME lookup use TryGetValue with a friendly InvalidOperationException if the required json transformer is missing.
  • Updated docs:
    • mcp-reference.md: MCP Inspector CLI examples for resources/list and resources/read MIME checks;
    • testing-toolkit.md: clarified Repl.Testing return-value/rendered-output assertions vs MCP wire metadata.

Verification:

  • dotnet test src/Repl.McpTests/Repl.McpTests.csproj -c Release --filter 'FullyQualifiedName~Given_McpResourceParameters|FullyQualifiedName~Given_McpInspectorCli'
  • dotnet build src/Repl.slnx -c Release -warnaserror --no-restore
  • dotnet test src/Repl.slnx -c Release
  • npx -y @modelcontextprotocol/inspector@0.22.0 --cli dotnet samples/08-mcp-server/bin/Release/net10.0/McpServerSample.dll mcp serve --method resources/read --uri repl://contacts
  • npx -y markdownlint-cli2 '**/*.md'
  • git diff --check

@autocarl

Copy link
Copy Markdown
Contributor Author

@codex Please review again after ae7f593

chatgpt-codex-connector[bot]

This comment was marked as resolved.

carldebilly

This comment was marked as resolved.

@autocarl

Copy link
Copy Markdown
Contributor Author

Addressed the latest review round in b18fc03.

Changes:

  • Gated the MCP Inspector smoke test:
    • [TestCategory("ExternalToolchain")]
    • skipped by default via Assert.Inconclusive unless REPL_RUN_MCP_INSPECTOR_TESTS=1 is set
    • still runnable through dotnet test, but no Node/npm/network dependency on the normal solution test path
  • Hardened Inspector process launch:
    • resolves npx and dotnet to absolute paths
    • clears inherited env and passes a minimal environment
    • removed the ineffective Start().Should().BeTrue() assertion
  • Removed hard-coded net10.0 from the sample DLL lookup by globbing the sample output directory.
  • Decoupled the Inspector smoke assertion from Alice / sample array contents; it now checks parseable JSON body + MIME metadata.
  • Fixed the mixed-output resource edge case: command-backed resource reads now suppress low-level IReplIoContext.Output side-channel writes, so application/json only labels the rendered JSON return payload.
  • Documented the opt-in Inspector command and the side-channel resource behavior.

Verification:

  • dotnet test src/Repl.McpTests/Repl.McpTests.csproj -c Release --filter 'FullyQualifiedName~Given_McpResourceParameters|FullyQualifiedName~Given_McpInspectorCli' — 13 passed, 1 skipped
  • REPL_RUN_MCP_INSPECTOR_TESTS=1 dotnet test src/Repl.McpTests/Repl.McpTests.csproj -c Release --filter 'TestCategory=ExternalToolchain' — 1 passed
  • dotnet build src/Repl.slnx -c Release -warnaserror --no-restore — 0 warnings/errors
  • dotnet test src/Repl.slnx -c Release — 989 passed, 1 skipped
  • npx -y markdownlint-cli2 '**/*.md' — 0 errors
  • git diff --check
  • local review panel emulation clean across architect/security/skeptic/quality/style/operability/contract

@autocarl

Copy link
Copy Markdown
Contributor Author

@codex Please review again after b18fc03

chatgpt-codex-connector[bot]

This comment was marked as resolved.

@carldebilly carldebilly merged commit 5a3a62c into yllibed:main Jun 26, 2026
6 checks passed
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.

Allow MCP resources to declare MIME types

2 participants