* fix: review fixes of the latest changes * fix: updated unit tests * chore: more conformance compliance improvements * chore: reverted protocol changes * chore: more compliance improvements * chore: more conformance compliance improvements * chore: improvements to the backcompat and updated to Tool unit tests * chore: ci update
297 lines
11 KiB
Swift
297 lines
11 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("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(text: "Result data", annotations: nil, _meta: nil)],
|
|
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))
|
|
}
|
|
}
|