Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions SSEExample/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
159 changes: 159 additions & 0 deletions SSEExample/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 55 additions & 0 deletions SSEExample/Package.swift
Original file line number Diff line number Diff line change
@@ -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")
]
),
]
)
8 changes: 8 additions & 0 deletions SSEExample/Sources/Client/App.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//
// File.swift
// SSEExample
//
// Created by Steven Prichard on 2025-03-20.
//

import Foundation
5 changes: 5 additions & 0 deletions SSEExample/Sources/Client/openapi-generator-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
generate:
- types
- client
accessModifier: public
namingStrategy: idiomatic
49 changes: 49 additions & 0 deletions SSEExample/Sources/Client/openapi.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
24 changes: 24 additions & 0 deletions SSEExample/Sources/ClientApp/ClientApp.swift
Original file line number Diff line number Diff line change
@@ -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)")
}
}
47 changes: 47 additions & 0 deletions SSEExample/Sources/LibServer/SSEServer.swift
Original file line number Diff line number Diff line change
@@ -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()
}
}
14 changes: 14 additions & 0 deletions SSEExample/Sources/Server/Server.swift
Original file line number Diff line number Diff line change
@@ -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
)
}