diff --git a/SSEExample/.gitignore b/SSEExample/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/SSEExample/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/SSEExample/Package.resolved b/SSEExample/Package.resolved new file mode 100644 index 0000000..85a142d --- /dev/null +++ b/SSEExample/Package.resolved @@ -0,0 +1,159 @@ +{ + "originHash" : "1ced3efda8fe966fc0bc1246d256613eb05764b6a1b37a9d74438c7bbd5deabe", + "pins" : [ + { + "identity" : "anycodable", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Flight-School/AnyCodable", + "state" : { + "revision" : "862808b2070cd908cb04f9aafe7de83d35f81b05", + "version" : "0.6.7" + } + }, + { + "identity" : "openapikit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mattpolzin/OpenAPIKit", + "state" : { + "revision" : "fd27b297993923c9a1725dc62553d06579c7a33e", + "version" : "3.4.2" + } + }, + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms", + "state" : { + "revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023", + "version" : "1.2.1" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "41982a3656a71c768319979febd796c6fd111d5c", + "version" : "1.5.0" + } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", + "version" : "1.1.4" + } + }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types", + "state" : { + "revision" : "ef18d829e8b92d731ad27bb81583edd2094d1ce3", + "version" : "1.3.1" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "3d8596ed08bd13520157f0355e35caed215ffbfa", + "version" : "1.6.3" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "c51907a839e63ebf0ba2076bba73dd96436bd1b9", + "version" : "2.81.0" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", + "version" : "1.0.3" + } + }, + { + "identity" : "swift-openapi-generator", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-openapi-generator", + "state" : { + "revision" : "755c0ec69bd667aa4e8ba50c8b710585d302879e", + "version" : "1.7.1" + } + }, + { + "identity" : "swift-openapi-runtime", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-openapi-runtime", + "state" : { + "revision" : "81c309c7b43cd56b2d2b90ca0170f17ff3d0c433", + "version" : "1.8.1" + } + }, + { + "identity" : "swift-openapi-urlsession", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-openapi-urlsession", + "state" : { + "revision" : "9bf4c712ad7989d6a91dbe68748b8829a50837e4", + "version" : "1.0.2" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "0687f71944021d616d34d922343dcef086855920", + "version" : "600.0.1" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "a34201439c74b53f0fd71ef11741af7e7caf01e1", + "version" : "1.4.2" + } + }, + { + "identity" : "swiftmcp", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Cocoanetics/SwiftMCP.git", + "state" : { + "branch" : "swift6", + "revision" : "6555f7a3658e7fe9c54eb6ed98654d2e254cb8c5" + } + }, + { + "identity" : "yams", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/Yams", + "state" : { + "revision" : "b4b8042411dc7bbb696300a34a4bf3ba1b7ad19b", + "version" : "5.3.1" + } + } + ], + "version" : 3 +} diff --git a/SSEExample/Package.swift b/SSEExample/Package.swift new file mode 100644 index 0000000..f68707a --- /dev/null +++ b/SSEExample/Package.swift @@ -0,0 +1,55 @@ +// swift-tools-version: 6.1 + +import PackageDescription + +let package = Package( + name: "SSEExample", + platforms: [ + .macOS(.v15) + ], + products: [ + .library( + name: "Client", + targets: ["Client"] + ) + ], + dependencies: [ + .package(url: "https://github.com/Cocoanetics/SwiftMCP.git", branch: "swift6"), + .package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.0"), + + // Client + .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.6.0"), + .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.7.0"), + .package(url: "https://github.com/apple/swift-openapi-urlsession", from: "1.0.0"), + ], + targets: [ + .target( + name: "Client", + dependencies: [ + .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"), + .product(name: "OpenAPIURLSession", package: "swift-openapi-urlsession"), + ], + plugins: [.plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator")] + ), + .target( + name: "LibServer", + dependencies: [ + .product(name: "SwiftMCP", package: "SwiftMCP"), + .product(name: "ArgumentParser", package: "swift-argument-parser"), + ] + ), + .executableTarget( + name: "Server", + dependencies: [ + .byName(name: "LibServer"), + .product(name: "ArgumentParser", package: "swift-argument-parser"), + ] + ), + .executableTarget( + name: "ClientApp", + dependencies: [ + .target(name: "Client") + ] + ), + ] +) diff --git a/SSEExample/Sources/Client/App.swift b/SSEExample/Sources/Client/App.swift new file mode 100644 index 0000000..37e40dc --- /dev/null +++ b/SSEExample/Sources/Client/App.swift @@ -0,0 +1,8 @@ +// +// File.swift +// SSEExample +// +// Created by Steven Prichard on 2025-03-20. +// + +import Foundation diff --git a/SSEExample/Sources/Client/openapi-generator-config.yaml b/SSEExample/Sources/Client/openapi-generator-config.yaml new file mode 100644 index 0000000..8e60f91 --- /dev/null +++ b/SSEExample/Sources/Client/openapi-generator-config.yaml @@ -0,0 +1,5 @@ +generate: + - types + - client +accessModifier: public +namingStrategy: idiomatic diff --git a/SSEExample/Sources/Client/openapi.json b/SSEExample/Sources/Client/openapi.json new file mode 100644 index 0000000..f8a7172 --- /dev/null +++ b/SSEExample/Sources/Client/openapi.json @@ -0,0 +1,49 @@ +{ + "info" : { + "description" : "API for SSEServer providing various tools.", + "title" : "SSEServer", + "version" : "0.0.1" + }, + "openapi" : "3.1.0", + "paths" : { + "\/sseserver\/ping" : { + "post" : { + "description" : "A tool to test if this is working", + "operationId" : "ping", + "requestBody" : { + "content" : { + "application\/json" : { + "schema" : { + "description" : "A tool to test if this is working", + "properties" : { + + }, + "type" : "object" + } + } + }, + "required" : true + }, + "responses" : { + "200" : { + "content" : { + "application\/json" : { + "schema" : { + "type" : "string" + } + } + }, + "description" : "The returned value of the tool" + } + }, + "summary" : "ping" + } + } + }, + "servers" : [ + { + "description" : "Production Server", + "url" : "http:\/\/127.0.0.1" + } + ] +} diff --git a/SSEExample/Sources/ClientApp/ClientApp.swift b/SSEExample/Sources/ClientApp/ClientApp.swift new file mode 100644 index 0000000..8a82744 --- /dev/null +++ b/SSEExample/Sources/ClientApp/ClientApp.swift @@ -0,0 +1,24 @@ +// +// ClientApp.swift +// SSEExample +// +// Created by Steven Prichard on 2025-03-20. +// + +import Client +import Foundation +import OpenAPIRuntime +import OpenAPIURLSession + +@main +struct ClientApp { + static func main() async throws { + let client = Client( + serverURL: URL(string: "http://localhost:8080")!, + transport: URLSessionTransport() + ) + + let response = try await client.ping(.init(body: .json(.init()))) + print("ℹ️ Response: \(response)") + } +} diff --git a/SSEExample/Sources/LibServer/SSEServer.swift b/SSEExample/Sources/LibServer/SSEServer.swift new file mode 100644 index 0000000..c00d314 --- /dev/null +++ b/SSEExample/Sources/LibServer/SSEServer.swift @@ -0,0 +1,47 @@ +// +// SSEServer.swift +// SSEExample +// +// Created by Steven Prichard on 2025-03-20. +// + +import ArgumentParser +@preconcurrency import SwiftMCP + +@MCPServer(name: "SSEServer", version: "0.0.1") +actor SSEServer { + init() {} + + @MCPTool(description: "A tool to test if this is working") + func ping() -> String { + return "pong" + } + + +} + +public struct SSEServerCommand: AsyncParsableCommand { + public init() {} + + @Option(name: .long, help: "The hostname to listen on") + var hostname: String = "127.0.0.1" + + @Option(name: .long, help: "The port to listen on") + var port: Int = 8080 + + @Option(name: .long, help: "Bearer token for authorization") + var token: String? + + public func run() async throws { + let server = SSEServer() + + let transport = HTTPSSETransport( + server: server, + host: hostname, + port: port + ) + transport.serveOpenAPI = true // TODO: Accept this as a flag + + try await transport.run() + } +} diff --git a/SSEExample/Sources/Server/Server.swift b/SSEExample/Sources/Server/Server.swift new file mode 100644 index 0000000..aaf5893 --- /dev/null +++ b/SSEExample/Sources/Server/Server.swift @@ -0,0 +1,14 @@ +import LibServer +import ArgumentParser + +@main +struct ServerCommand: AsyncParsableCommand { + static let configuration: CommandConfiguration = .init( + commandName: "server", + abstract: "Start an HTTP server with Server-Sent Events (SSE) support", + subcommands: [ + SSEServerCommand.self + ], + defaultSubcommand: SSEServerCommand.self + ) +}