Remove nonstandard read and list capabilities for resources (#15)

* Remove nonstandard read and list capabilities for server

* Update README
This commit is contained in:
Mattt 2025-03-17 04:17:23 -07:00 • committed by GitHub
commit a2a0044abb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 74 additions and 14 deletions

View file

@ -51,11 +51,11 @@ let server = Server(
name: "MyServer", name: "MyServer",
version: "1.0.0", version: "1.0.0",
capabilities: .init( capabilities: .init(
prompts: .init(),
resources: .init( resources: .init(
list: true,
read: true,
subscribe: true subscribe: true
) ),
tools: .init()
) )
) )

View file

@ -313,7 +313,7 @@ public actor Client {
// MARK: - Resources // MARK: - Resources
public func readResource(uri: String) async throws -> [Resource.Content] { public func readResource(uri: String) async throws -> [Resource.Content] {
_ = try checkCapability(\.resources?.read, "Resource reading") _ = try checkCapability(\.resources, "Resources")
let request = ReadResource.request(.init(uri: uri)) let request = ReadResource.request(.init(uri: uri))
let result = try await send(request) let result = try await send(request)
return result.contents return result.contents
@ -322,7 +322,7 @@ public actor Client {
public func listResources(cursor: String? = nil) async throws -> ( public func listResources(cursor: String? = nil) async throws -> (
resources: [Resource], nextCursor: String? resources: [Resource], nextCursor: String?
) { ) {
_ = try checkCapability(\.resources?.list, "Resource listing") _ = try checkCapability(\.resources, "Resources")
let request = ListResources.request(.init(cursor: cursor)) let request = ListResources.request(.init(cursor: cursor))
let result = try await send(request) let result = try await send(request)
return (resources: result.resources, nextCursor: result.nextCursor) return (resources: result.resources, nextCursor: result.nextCursor)

View file

@ -44,23 +44,15 @@ public actor Server {
public struct Capabilities: Hashable, Codable, Sendable { public struct Capabilities: Hashable, Codable, Sendable {
/// Resources capabilities /// Resources capabilities
public struct Resources: Hashable, Codable, Sendable { public struct Resources: Hashable, Codable, Sendable {
/// Whether the list of resources has changed
public var list: Bool?
/// Whether the resource can be read
public var read: Bool?
/// Whether the resource can be subscribed to /// Whether the resource can be subscribed to
public var subscribe: Bool? public var subscribe: Bool?
/// Whether the list of resources has changed /// Whether the list of resources has changed
public var listChanged: Bool? public var listChanged: Bool?
public init( public init(
list: Bool? = nil,
read: Bool? = nil,
subscribe: Bool? = nil, subscribe: Bool? = nil,
listChanged: Bool? = nil listChanged: Bool? = nil
) { ) {
self.list = list
self.read = read
self.subscribe = subscribe self.subscribe = subscribe
self.listChanged = listChanged self.listChanged = listChanged
} }

View file

@ -33,7 +33,11 @@ struct RoundtripTests {
let server = Server( let server = Server(
name: "TestServer", name: "TestServer",
version: "1.0.0", version: "1.0.0",
capabilities: .init(prompts: .init(), tools: .init()) capabilities: .init(
prompts: .init(),
resources: .init(),
tools: .init()
)
) )
await server.withMethodHandler(ListTools.self) { _ in await server.withMethodHandler(ListTools.self) { _ in
return ListTools.Result(tools: [ return ListTools.Result(tools: [
@ -61,6 +65,32 @@ struct RoundtripTests {
return CallTool.Result(content: [.text("\(a + b)")]) return CallTool.Result(content: [.text("\(a + b)")])
} }
// Add resource handlers to server
await server.withMethodHandler(ListResources.self) { _ in
return ListResources.Result(resources: [
Resource(
name: "Example Text",
uri: "test://example.txt",
description: "A test resource",
mimeType: "text/plain"
),
Resource(
name: "Test Data",
uri: "test://data.json",
description: "JSON test data",
mimeType: "application/json"
),
])
}
await server.withMethodHandler(ReadResource.self) { request in
guard request.uri == "test://example.txt" else {
return ReadResource.Result(contents: [.text("Resource not found", uri: request.uri)]
)
}
return ReadResource.Result(contents: [.text("Hello, World!", uri: request.uri)])
}
let client = Client(name: "TestClient", version: "1.0") let client = Client(name: "TestClient", version: "1.0")
try await server.start(transport: serverTransport) try await server.start(transport: serverTransport)
@ -104,8 +134,12 @@ struct RoundtripTests {
group.addTask { group.addTask {
try await Task.sleep(for: .seconds(1)) try await Task.sleep(for: .seconds(1))
listToolsTask.cancel() listToolsTask.cancel()
callToolTask.cancel()
throw CancellationError() throw CancellationError()
} }
group.addTask {
try await listToolsTask.value
}
group.addTask { group.addTask {
try await callToolTask.value try await callToolTask.value
} }
@ -113,6 +147,40 @@ struct RoundtripTests {
group.cancelAll() group.cancelAll()
} }
// Test listing resources
let listResourcesTask = Task {
let result = try await client.listResources()
#expect(result.resources.count == 2)
#expect(result.resources[0].uri == "test://example.txt")
#expect(result.resources[0].name == "Example Text")
#expect(result.resources[1].uri == "test://data.json")
#expect(result.resources[1].name == "Test Data")
}
// Test reading a resource
let readResourceTask = Task {
let result = try await client.readResource(uri: "test://example.txt")
#expect(result.count == 1)
#expect(result[0] == .text("Hello, World!", uri: "test://example.txt"))
}
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
try await Task.sleep(for: .seconds(1))
listResourcesTask.cancel()
readResourceTask.cancel()
throw CancellationError()
}
group.addTask {
try await listResourcesTask.value
}
group.addTask {
try await readResourceTask.value
}
try await group.next()
group.cancelAll()
}
await server.stop() await server.stop()
await client.disconnect() await client.disconnect()
try? clientToServerRead.close() try? clientToServerRead.close()