Value decoding changes data-URL-looking JSON strings into Value.data

Open Beginner friendly
#277 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
76/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
swift
Domain
api

Research direction

Start at the string branch of Value.init(from:) and inspect the existing Data.isDataURL and Data.parseDataURL calls. Run the focused typed-text regression and control tests, then the complete SDK suite; done means generic JSON strings remain Value.string while explicit data encoding and typed image/audio serialization remain unchanged.

Written by the indexing model from the issue text.

Description

bug

Describe the bug

This is my first contribution to this repository, so I may be missing some intended design context. I have tried to verify the behavior carefully and would appreciate correction if this conversion is intentional.

Value.init(from:) currently interprets a JSON string as Value.data whenever the string looks like a data URL and can be parsed successfully.

For example, this valid JSON string:

"data:text/plain,Hello%20World"

is decoded as binary data rather than:

Value.string("data:text/plain,Hello%20World")

Re-encoding the decoded value changes the string to:

data:text/plain;base64,SGVsbG8gV29ybGQ=

This changes both the Value case and the original string’s spelling.

The same behavior can affect explicitly typed MCP text content. A CallTool.Result containing:

.text(
    text: "data:text/plain,Hello%20World",
    annotations: nil,
    _meta: nil
)

is rewritten when it passes through the SDK’s generic Value conversion. The recovered content is still tagged as text, but its text payload has changed.

To Reproduce

import Foundation
import MCP

let json = Data(#""data:text/plain,Hello%20World""#.utf8)
let decoded = try JSONDecoder().decode(Value.self, from: json)

print(decoded.stringValue as Any)

let reencoded = try JSONEncoder().encode(decoded)
let recoveredString = try JSONDecoder().decode(String.self, from: reencoded)
print(recoveredString)

Current behavior:

nil
data:text/plain;base64,SGVsbG8gV29ybGQ=

The behavior comes from the string branch of Value.init(from:), which calls Data.isDataURL and Data.parseDataURL before deciding between .data and .string.

Strings that do not form parseable data URLs remain .string. For example, Hello%20World by itself is unaffected. The conversion is triggered by complete data-URL-looking strings such as:

data:text/plain,Hello%20World
data:text/plain,Hello World
data:text/plain;base64,SGVsbG8=
data:,

Expected behavior

I believe generic JSON decoding should follow the JSON wire type:

  • Every JSON string decoded through Value remains Value.string.
  • Data-URL interpretation remains explicitly available through Data.parseDataURL.
  • Explicitly constructed Value.data values continue to encode using the existing data-URL representation.
  • Typed image and audio content remains unchanged because those MCP content types have explicit data and mimeType fields.

A generic JSON string does not carry enough information to distinguish:

Value.string("data:text/plain;base64,SGVsbG8=")

from an encoded:

Value.data(mimeType: "text/plain", Data("Hello".utf8))

because both have the same JSON representation.

Logs

A focused typed-text regression currently fails with:

text → "data:text/plain;base64,SGVsbG8gV29ybGQ="
original → "data:text/plain,Hello%20World"

With the original decoder implementation, 6 of 8 focused regression and control tests fail. The two unaffected controls verify explicit Value.data encoding and typed image/audio serialization.

Changing the generic string branch to assign .string(value) makes all 8 focused tests pass. The complete SDK test plan also passes: 559 tests, 0 failures.

Additional context

I encountered this through a downstream MCP server that needs to preserve literal text exactly:

https://github.com/bitbemol/second-brain-mcp

The downstream project currently vendors the Swift SDK solely to avoid this transformation.

I have a minimal patch and regression tests ready. The explicit data-URL parsing helpers are preserved.

There is a compatibility consideration: callers that rely on generic Value decoding to recognize data URLs would instead need to call Data.parseDataURL explicitly. If the current implicit decoding is intentional API behavior, I would appreciate guidance on how ordinary JSON strings with the same spelling should be represented without being transformed.

Tested against Swift SDK main at a0ae212 and release 0.12.1.

AI assistance disclosure: An LLM initially identified this behavior while I was diagnosing the downstream issue. Codex assisted with repository analysis, regression-test development, and drafting this report. I reviewed the affected code, reproduced the behavior with the original implementation in Xcode, and verified the proposed behavior with the focused and complete test suites.

Dominant language
Swift
Stars
1.5k
Forks
243
PR merge metrics
No merged PRs in 30d

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from modelcontextprotocol/swift-sdk

All issues in modelcontextprotocol/swift-sdk

Similar issues

More Swift issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.