swift-sdk/Tests/MCPTests/MetaFieldsTests.swift
Maksym Mova 6112a3995a
V0.11.x (#199)
* Support 2025-06-18 Spec (#167)

* Support OpenAI _meta for Tool

* Update init

* Add title and outputSchema

* Concurreny issue

* General fields

* Add Generate Fields to Server elements

* 4 tab spacing

* Update Version

* Elicitation

* Format

* More tests and inits

* Elicitation in README

* Fix orphaned test

* Add title to more structs

* Public init

* Fix meta validation and make tests more generic

* Format and make Result properties public again

* Revert more visibility changes

* feat: 2025-11-25 phase 1, includes Version, Icon, Progress (#181)

* chore: fixed unit tests

* feat: added icon and progess base structs

* feat: added progress notification and meta to callTool

* feat: added tests

* feat: simplified RequestMeta encoding/decoding

* tests: added unit test to track progress

* chore: rebased v0.11.x

* feat: implemented cancellation

* feat: added overloaded callTool method

* fix: encoding Response _meta fix

* chore: added old version to the versions list

* feat: 2025-11-25 phase 2; added Logging and Completions (#196)

* feat: added Logging and Completions

* feat: updated EmbeddedResource for the latest spec

* chore: changed encoding of meta in Prompts to encodeIfPresent

* feat: compliance tests + http server transport (#197)

* feat: http server transport

* feat: added http server transport

* feat: removed unused files

* feat: added conformance tests

* feat: improved conformace testing

* chore: removed redundant files

* feat/2025-11-25: sampling, elicitation, roots (#198)

* feat: Roots implementation

* feat: added unit testing

* feat: improved unit tests and fixed NetworkTransport crash

* feat: implemented sampling and elicitation

* feat: improved sampling tests

* feat: improved elicitation tests

* feat: improved confirmance tests

* feat: improved Prompts unit tests

* feat: removed test code

* feat: updated conformance tests

* chore: removed redundant commas

* chore: removed 6.0.3 swift version from ci

* chore: improved unit tests a little bit

* chore: update README.md

* chore: add server progress tracking example to README.md

* chore: updated README.md

* chore: updated transports table formatting

* chore: updated MockNetworkConnection

---------

Co-authored-by: Austin Evans <austinjevans@me.com>
Co-authored-by: yehorsobko <yehorsobko@macpaw.com>
2026-02-19 13:09:31 +02:00

351 lines
13 KiB
Swift

import Testing
import class Foundation.JSONDecoder
import class Foundation.JSONEncoder
import class Foundation.JSONSerialization
@testable import MCP
@Suite("Meta Fields")
struct MetaFieldsTests {
private struct Payload: Codable, Hashable, Sendable {
let message: String
}
private enum TestMethod: Method {
static let name = "test.general"
typealias Parameters = Payload
typealias Result = Payload
}
@Test("Encoding includes meta and custom fields")
func testEncodingGeneralFields() throws {
let meta: Metadata = Metadata(additionalFields: ["vendor.example/request-id": .string("abc123")])
let request = Request<TestMethod>(
id: 42,
method: TestMethod.name,
params: Payload(message: "hello"),
_meta: meta
)
let data = try JSONEncoder().encode(request)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let metaObject = json?["_meta"] as? [String: Any]
#expect(metaObject?["vendor.example/request-id"] as? String == "abc123")
}
@Test("Decoding restores general fields")
func testDecodingGeneralFields() throws {
let payload: [String: Any] = [
"jsonrpc": "2.0",
"id": 7,
"method": TestMethod.name,
"params": ["message": "hi"],
"_meta": ["vendor.example/session": "s42"],
"custom-data": ["value": 1],
]
let data = try JSONSerialization.data(withJSONObject: payload)
let decoded = try JSONDecoder().decode(Request<TestMethod>.self, from: data)
let metaValue = decoded._meta?["vendor.example/session"]
#expect(metaValue == .string("s42"))
}
@Test("Response encoding includes general fields")
func testResponseGeneralFields() throws {
let meta = Metadata(additionalFields: ["vendor.example/status": .string("partial")])
let response = Response<TestMethod>(
id: 99,
result: .success(Payload(message: "ok")),
_meta: meta
)
let data = try JSONEncoder().encode(response)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let metaObject = json?["_meta"] as? [String: Any]
#expect(metaObject?["vendor.example/status"] as? String == "partial")
let decoded = try JSONDecoder().decode(Response<TestMethod>.self, from: data)
#expect(decoded._meta?["vendor.example/status"] == .string("partial"))
}
@Test("Tool encoding and decoding with general fields")
func testToolGeneralFields() throws {
let meta = Metadata(additionalFields: [
"vendor.example/outputTemplate": .string("ui://widget/kanban-board.html")
])
let tool = Tool(
name: "kanban-board",
title: "Kanban Board",
description: "Display kanban widget",
inputSchema: try Value(["type": "object"]),
_meta: meta
)
let data = try JSONEncoder().encode(tool)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let metaObject = json?["_meta"] as? [String: Any]
#expect(
metaObject?["vendor.example/outputTemplate"] as? String
== "ui://widget/kanban-board.html")
let decoded = try JSONDecoder().decode(Tool.self, from: data)
#expect(
decoded._meta?["vendor.example/outputTemplate"]
== .string("ui://widget/kanban-board.html")
)
}
@Test("Meta keys allow nested prefixes")
func testMetaKeyNestedPrefixes() throws {
let meta = Metadata(additionalFields: [
"vendor.example/toolInvocation/invoking": .bool(true)
])
let tool = Tool(
name: "invoke",
description: "Invoke tool",
inputSchema: [:],
_meta: meta
)
let data = try JSONEncoder().encode(tool)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let metaObject = json?["_meta"] as? [String: Any]
#expect(metaObject?["vendor.example/toolInvocation/invoking"] as? Bool == true)
let decoded = try JSONDecoder().decode(Tool.self, from: data)
#expect(decoded._meta?["vendor.example/toolInvocation/invoking"] == .bool(true))
}
@Test("Resource content encodes meta")
func testResourceContentGeneralFields() throws {
let meta = Metadata(additionalFields: [
"vendor.example/widgetPrefersBorder": .bool(true)
])
let content = Resource.Content.text(
"<div>Widget</div>",
uri: "ui://widget/kanban-board.html",
mimeType: "text/html",
_meta: meta
)
let data = try JSONEncoder().encode(content)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let metaObject = json?["_meta"] as? [String: Any]
#expect(metaObject?["vendor.example/widgetPrefersBorder"] as? Bool == true)
let decoded = try JSONDecoder().decode(Resource.Content.self, from: data)
#expect(decoded._meta?["vendor.example/widgetPrefersBorder"] == .bool(true))
}
@Test("Initialize.Result encoding with meta")
func testInitializeResultGeneralFields() throws {
let meta = Metadata(additionalFields: ["vendor.example/build": .string("v1.0.0")])
let result = Initialize.Result(
protocolVersion: "2024-11-05",
capabilities: Server.Capabilities(),
serverInfo: Server.Info(name: "test", version: "1.0"),
instructions: "Test server",
_meta: meta
)
let data = try JSONEncoder().encode(result)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let metaObject = json?["_meta"] as? [String: Any]
#expect(metaObject?["vendor.example/build"] as? String == "v1.0.0")
let decoded = try JSONDecoder().decode(Initialize.Result.self, from: data)
#expect(decoded._meta?["vendor.example/build"] == .string("v1.0.0"))
}
@Test("ListTools.Result encoding with meta")
func testListToolsResultGeneralFields() throws {
let meta = Metadata(additionalFields: ["vendor.example/page": .int(1)])
let tool = Tool(
name: "test",
description: "A test tool",
inputSchema: try Value(["type": "object"])
)
let result = ListTools.Result(
tools: [tool],
nextCursor: "page2",
_meta: meta
)
let data = try JSONEncoder().encode(result)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let metaObject = json?["_meta"] as? [String: Any]
#expect(metaObject?["vendor.example/page"] as? Int == 1)
let decoded = try JSONDecoder().decode(ListTools.Result.self, from: data)
#expect(decoded._meta?["vendor.example/page"] == .int(1))
}
@Test("CallTool.Result encoding with meta")
func testCallToolResultGeneralFields() throws {
let meta = Metadata(additionalFields: ["vendor.example/executionTime": .int(150)])
let result = CallTool.Result(
content: [.text("Result data")],
isError: false,
_meta: meta
)
let data = try JSONEncoder().encode(result)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let metaObject = json?["_meta"] as? [String: Any]
#expect(metaObject?["vendor.example/executionTime"] as? Int == 150)
let decoded = try JSONDecoder().decode(CallTool.Result.self, from: data)
#expect(decoded._meta?["vendor.example/executionTime"] == .int(150))
}
@Test("ListResources.Result encoding with meta")
func testListResourcesResultGeneralFields() throws {
let meta = Metadata(additionalFields: ["vendor.example/cacheControl": .string("max-age=3600")])
let resource = Resource(
name: "test.txt",
uri: "file://test.txt",
description: "Test resource",
mimeType: "text/plain"
)
let result = ListResources.Result(
resources: [resource],
nextCursor: nil,
_meta: meta
)
let data = try JSONEncoder().encode(result)
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
let metaObject = json["_meta"] as! [String: Any]
#expect(metaObject["vendor.example/cacheControl"] as? String == "max-age=3600")
let decoded = try JSONDecoder().decode(ListResources.Result.self, from: data)
#expect(decoded._meta?["vendor.example/cacheControl"] == Value.string("max-age=3600"))
}
@Test("ReadResource.Result encoding with meta")
func testReadResourceResultGeneralFields() throws {
let meta = Metadata(additionalFields: ["vendor.example/encoding": .string("utf-8")])
let result = ReadResource.Result(
contents: [.text("file contents", uri: "file://test.txt")],
_meta: meta
)
let data = try JSONEncoder().encode(result)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let metaObject = json?["_meta"] as? [String: Any]
#expect(metaObject?["vendor.example/encoding"] as? String == "utf-8")
let decoded = try JSONDecoder().decode(ReadResource.Result.self, from: data)
#expect(decoded._meta?["vendor.example/encoding"] == .string("utf-8"))
}
@Test("ListPrompts.Result encoding with meta")
func testListPromptsResultGeneralFields() throws {
let meta = Metadata(additionalFields: ["vendor.example/category": .string("system")])
let prompt = Prompt(
name: "greeting",
description: "A greeting prompt"
)
let result = ListPrompts.Result(
prompts: [prompt],
nextCursor: nil,
_meta: meta
)
let data = try JSONEncoder().encode(result)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let metaObject = json?["_meta"] as? [String: Any]
#expect(metaObject?["vendor.example/category"] as? String == "system")
let decoded = try JSONDecoder().decode(ListPrompts.Result.self, from: data)
#expect(decoded._meta?["vendor.example/category"] == .string("system"))
}
@Test("GetPrompt.Result encoding with meta")
func testGetPromptResultGeneralFields() throws {
let meta = Metadata(additionalFields: ["vendor.example/version": .int(2)])
let message = Prompt.Message.user("Hello")
let result = GetPrompt.Result(
description: "A test prompt",
messages: [message],
_meta: meta
)
let data = try JSONEncoder().encode(result)
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
let metaObject = json["_meta"] as! [String: Any]
#expect(metaObject["vendor.example/version"] as? Int == 2)
let decoded = try JSONDecoder().decode(GetPrompt.Result.self, from: data)
#expect(decoded._meta?["vendor.example/version"] == Value.int(2))
}
@Test("CreateSamplingMessage.Result encoding with meta")
func testSamplingResultGeneralFields() throws {
let meta = Metadata(additionalFields: ["vendor.example/model-version": .string("gpt-4-0613")])
let result = CreateSamplingMessage.Result(
model: "gpt-4",
stopReason: .endTurn,
role: .assistant,
content: .text("Hello!"),
_meta: meta
)
let data = try JSONEncoder().encode(result)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let metaObject = json?["_meta"] as? [String: Any]
#expect(metaObject?["vendor.example/model-version"] as? String == "gpt-4-0613")
let decoded = try JSONDecoder().decode(CreateSamplingMessage.Result.self, from: data)
#expect(decoded._meta?["vendor.example/model-version"] == .string("gpt-4-0613"))
}
@Test("CreateElicitation.Result encoding with meta")
func testElicitationResultGeneralFields() throws {
let meta = Metadata(additionalFields: ["vendor.example/timestamp": .int(1_640_000_000)])
let result = CreateElicitation.Result(
action: .accept,
content: ["response": .string("user input")],
_meta: meta
)
let data = try JSONEncoder().encode(result)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let metaObject = json?["_meta"] as? [String: Any]
#expect(metaObject?["vendor.example/timestamp"] as? Int == 1_640_000_000)
let decoded = try JSONDecoder().decode(CreateElicitation.Result.self, from: data)
#expect(decoded._meta?["vendor.example/timestamp"] == .int(1_640_000_000))
}
}