Value decoding changes data-URL-looking JSON strings into Value.data
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 76/100
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
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
ValueremainsValue.string. - Data-URL interpretation remains explicitly available through
Data.parseDataURL. - Explicitly constructed
Value.datavalues continue to encode using the existing data-URL representation. - Typed image and audio content remains unchanged because those MCP content types have explicit
dataandmimeTypefields.
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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from modelcontextprotocol/swift-sdk
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
enhancement
Difficulty 1/5 Under an hour Newbie friendliness 78/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
Difficulty 3/5 1-2 days Newbie friendliness 72/100
modelcontextprotocol/swift-sdk#287 · 1 comment · 1 reaction ·
All issues in modelcontextprotocol/swift-sdk
Similar issues
-
type: docs
Difficulty 1/5 Under an hour Newbie friendliness 95/100
googleapis/google-cloud-swift#971 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
bitcoindevkit/bdk-ffi#1125 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
mozilla-mobile/firefox-ios#35743 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
manaflow-ai/cmux#13417 ·
-
triage
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
ionic-team/capacitor#8616 ·