Skip to content

Commit 3b9a126

Browse files
committed
Merge branch 'openapi'
2 parents 6480843 + 24b7503 commit 3b9a126

23 files changed

+1468
-212
lines changed

.swiftpm/xcode/xcshareddata/xcschemes/SwiftMCPDemo.xcscheme

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,24 @@
5252
</BuildableProductRunnable>
5353
<CommandLineArguments>
5454
<CommandLineArgument
55-
argument = "--port 8081"
55+
argument = "httpsse"
56+
isEnabled = "YES">
57+
</CommandLineArgument>
58+
<CommandLineArgument
59+
argument = "--openapi"
5660
isEnabled = "YES">
5761
</CommandLineArgument>
5862
<CommandLineArgument
5963
argument = "--transport httpsse"
64+
isEnabled = "NO">
65+
</CommandLineArgument>
66+
<CommandLineArgument
67+
argument = "--port 8090"
6068
isEnabled = "YES">
6169
</CommandLineArgument>
6270
<CommandLineArgument
6371
argument = "--token secret"
64-
isEnabled = "NO">
72+
isEnabled = "YES">
6573
</CommandLineArgument>
6674
</CommandLineArguments>
6775
<EnvironmentVariables>

Demos/SwiftMCPDemo/Calculator.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import Foundation
22
import SwiftMCP
33

4+
/**
5+
A Calculator for simple math doing additionals, subtractions etc.
6+
*/
47
@MCPServer(name: "SwiftMCP Demo")
58
class Calculator {
69
/// Adds two integers and returns their sum
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import Foundation
2+
import ArgumentParser
3+
import SwiftMCP
4+
import Logging
5+
#if canImport(OSLog)
6+
import OSLog
7+
#endif
8+
9+
/**
10+
A command that starts an HTTP server with Server-Sent Events (SSE) support for SwiftMCP.
11+
12+
This mode provides a long-running HTTP server that supports:
13+
- Server-Sent Events (SSE) for real-time updates
14+
- JSON-RPC over HTTP POST for function calls
15+
- Optional bearer token authentication
16+
- Optional OpenAPI endpoints for AI plugin integration
17+
18+
Key Features:
19+
1. Server-Sent Events:
20+
- Connect to `/sse` endpoint for real-time updates
21+
- Receive function call results and notifications
22+
- Maintain persistent connections with clients
23+
24+
2. JSON-RPC Endpoints:
25+
- Send POST requests to `/<serverName>/<toolName>`
26+
- Standard JSON-RPC 2.0 request/response format
27+
- Support for batched requests
28+
29+
3. Security:
30+
- Optional bearer token authentication
31+
- CORS support for web clients
32+
- Secure error handling and validation
33+
34+
4. AI Plugin Support:
35+
- OpenAPI specification at `/openapi.json`
36+
- AI plugin manifest at `/.well-known/ai-plugin.json`
37+
- Compatible with AI plugin standards
38+
*/
39+
struct HTTPSSECommand: ParsableCommand {
40+
static var configuration = CommandConfiguration(
41+
commandName: "httpsse",
42+
abstract: "Start an HTTP server with Server-Sent Events (SSE) support",
43+
discussion: """
44+
Start an HTTP server that supports Server-Sent Events (SSE) and JSON-RPC.
45+
46+
Features:
47+
- Server-Sent Events endpoint at /sse
48+
- JSON-RPC endpoints at /<serverName>/<toolName>
49+
- Optional bearer token authentication
50+
- Optional OpenAPI endpoints for AI plugin integration
51+
52+
Examples:
53+
# Basic usage
54+
SwiftMCPDemo httpsse --port 8080
55+
56+
# With authentication
57+
SwiftMCPDemo httpsse --port 8080 --token my-secret-token
58+
59+
# With OpenAPI support
60+
SwiftMCPDemo httpsse --port 8080 --openapi
61+
"""
62+
)
63+
64+
@Option(name: .long, help: "The port to listen on")
65+
var port: Int
66+
67+
@Option(name: .long, help: "Bearer token for authorization")
68+
var token: String?
69+
70+
@Flag(name: .long, help: "Enable OpenAPI endpoints")
71+
var openapi: Bool = false
72+
73+
func run() throws {
74+
#if canImport(OSLog)
75+
LoggingSystem.bootstrapWithOSLog()
76+
#endif
77+
78+
let calculator = Calculator()
79+
let host = String.localHostname
80+
print("MCP Server \(calculator.serverName) (\(calculator.serverVersion)) started with HTTP+SSE transport on http://\(host):\(port)/sse")
81+
82+
let transport = HTTPSSETransport(server: calculator, port: port)
83+
84+
// Set up authorization handler if token is provided
85+
if let requiredToken = token {
86+
transport.authorizationHandler = { token in
87+
guard let token else {
88+
return .unauthorized("Missing bearer token")
89+
}
90+
91+
guard token == requiredToken else {
92+
return .unauthorized("Invalid bearer token")
93+
}
94+
95+
return .authorized
96+
}
97+
}
98+
99+
// Enable OpenAPI endpoints if requested
100+
transport.serveOpenAPI = openapi
101+
102+
// Set up signal handling to shut down the transport on Ctrl+C
103+
setupSignalHandler(transport: transport)
104+
105+
// Run the server (blocking)
106+
try transport.run()
107+
}
108+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import Foundation
2+
import ArgumentParser
3+
import SwiftMCP
4+
import Logging
5+
import NIOCore
6+
#if canImport(OSLog)
7+
import OSLog
8+
#endif
9+
10+
/**
11+
A command that processes JSON-RPC requests from standard input and writes responses to standard output.
12+
13+
This is the default mode of operation for the SwiftMCP demo. It's designed to:
14+
- Read JSON-RPC requests line by line from stdin
15+
- Process each request using the configured MCP server
16+
- Write JSON-RPC responses to stdout
17+
- Log status messages to stderr to avoid interfering with the JSON-RPC protocol
18+
19+
This mode is particularly useful for:
20+
- Integration with other tools via Unix pipes
21+
- Testing and debugging MCP functions
22+
- Scripting and automation
23+
*/
24+
struct StdioCommand: ParsableCommand {
25+
static var configuration = CommandConfiguration(
26+
commandName: "stdio",
27+
abstract: "Read JSON-RPC requests from stdin and write responses to stdout",
28+
discussion: """
29+
Read JSON-RPC requests from stdin and write responses to stdout.
30+
31+
This mode is perfect for integration with other tools via pipes.
32+
33+
Examples:
34+
# Basic usage
35+
SwiftMCPDemo stdio
36+
37+
# With pipe
38+
echo '{"jsonrpc": "2.0", "method": "add", "params": [1, 2]}' | SwiftMCPDemo stdio
39+
"""
40+
)
41+
42+
func run() throws {
43+
#if canImport(OSLog)
44+
LoggingSystem.bootstrapWithOSLog()
45+
#endif
46+
47+
let calculator = Calculator()
48+
49+
do {
50+
// need to output to stderror or else npx complains
51+
fputs("MCP Server \(calculator.serverName) (\(calculator.serverVersion)) started with Stdio transport\n", stderr)
52+
53+
let transport = StdioTransport(server: calculator)
54+
try transport.run()
55+
}
56+
catch let error as IOError {
57+
// Handle specific IO errors with more detail
58+
let errorMessage = """
59+
IO Error: \(error)
60+
Code: \(error.errnoCode)
61+
"""
62+
fputs("\(errorMessage)\n", stderr)
63+
Foundation.exit(1)
64+
}
65+
catch let error as ChannelError {
66+
// Handle specific channel errors
67+
fputs("Channel Error: \(error)\n", stderr)
68+
Foundation.exit(1)
69+
}
70+
catch {
71+
// Handle any other errors
72+
fputs("Error: \(error)\n", stderr)
73+
Foundation.exit(1)
74+
}
75+
}
76+
}

Demos/SwiftMCPDemo/MCPCommand.swift

Lines changed: 38 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -15,125 +15,48 @@ import Glibc
1515
import OSLog
1616
#endif
1717

18-
/// Command-line interface for the SwiftMCP demo
18+
/**
19+
Command-line interface for the SwiftMCP demo.
20+
21+
This is the main entry point for the SwiftMCP demo application. It provides two modes of operation:
22+
23+
- `stdio`: The default mode that processes JSON-RPC requests from standard input and writes responses to standard output.
24+
Perfect for integration with other tools via pipes.
25+
26+
- `httpsse`: Starts an HTTP server with Server-Sent Events (SSE) support, optionally with authentication and OpenAPI endpoints.
27+
Ideal for long-running services and AI plugin integration.
28+
29+
Example usage:
30+
```bash
31+
# Using stdio mode (default)
32+
echo '{"jsonrpc": "2.0", "method": "add", "params": [1, 2]}' | SwiftMCPDemo
33+
34+
# Using HTTP+SSE mode
35+
SwiftMCPDemo httpsse --port 8080 --token secret --openapi
36+
```
37+
*/
1938
@main
2039
struct MCPCommand: ParsableCommand {
21-
22-
enum TransportType: String, ExpressibleByArgument {
23-
case stdio
24-
case httpsse
25-
}
26-
27-
static var configuration = CommandConfiguration(
28-
commandName: "SwiftMCPDemo",
29-
abstract: "A utility for testing SwiftMCP functions",
30-
discussion: """
40+
static var configuration = CommandConfiguration(
41+
commandName: "SwiftMCPDemo",
42+
abstract: "A utility for testing SwiftMCP functions",
43+
discussion: """
3144
Process JSON-RPC requests for SwiftMCP functions.
3245
3346
The command can operate in two modes:
3447
35-
- stdio: Reads JSON-RPC requests from stdin and writes responses to stdout
36-
- httpsse: Starts an HTTP server with Server-Sent Events (SSE) support on the specified port
48+
1. stdio:
49+
- Reads JSON-RPC requests from stdin
50+
- Writes responses to stdout
51+
- Perfect for integration with other tools via pipes
52+
- Example: echo '{"jsonrpc": "2.0", "method": "add", "params": [1, 2]}' | SwiftMCPDemo stdio
3753
38-
When using httpsse mode with --token, requests must include a valid bearer token
39-
in the Authorization header.
40-
"""
41-
)
42-
43-
@Option(name: .long, help: "The transport type to use (stdio or httpsse)")
44-
var transport: TransportType = .stdio
45-
46-
@Option(name: .long, help: "The port to listen on (required when transport is HTTP+SSE)")
47-
var port: Int?
48-
49-
@Option(name: .long, help: "Bearer token for authorization (optional, HTTP+SSE only)")
50-
var token: String?
51-
52-
func validate() throws {
53-
if transport == .httpsse && port == nil {
54-
throw ValidationError("Port must be specified when using HTTP+SSE transport")
55-
}
56-
}
57-
58-
/// The main entry point for the command
59-
mutating func run() throws {
60-
61-
#if canImport(OSLog)
62-
LoggingSystem.bootstrapWithOSLog()
63-
#endif
64-
65-
// Check if transport type is specified
66-
if CommandLine.arguments.contains("--transport") == false {
67-
print(MCPCommand.helpMessage())
68-
Foundation.exit(0)
69-
}
70-
71-
let calculator = Calculator()
72-
73-
do {
74-
switch transport {
75-
76-
case .stdio:
77-
78-
// need to output to stderror or else npx complains
79-
fputs("MCP Server \(calculator.serverName) (\(calculator.serverVersion)) started with Stdio transport\n", stderr)
80-
81-
let transport = StdioTransport(server: calculator)
82-
try transport.run()
83-
84-
case .httpsse:
85-
86-
guard let port else {
87-
fatalError("Port should have been validated")
88-
}
89-
90-
let host = String.localHostname
91-
print("MCP Server \(calculator.serverName) (\(calculator.serverVersion)) started with HTTP+SSE transport on http://\(host):\(port)/sse")
92-
93-
let transport = HTTPSSETransport(server: calculator, port: port)
94-
95-
// Set up authorization handler if token is provided
96-
if let requiredToken = token {
97-
transport.authorizationHandler = { token in
98-
99-
guard let token else {
100-
return .unauthorized("Missing bearer token")
101-
}
102-
103-
guard token == requiredToken else {
104-
return .unauthorized("Invalid bearer token")
105-
}
106-
107-
return .authorized
108-
}
109-
}
110-
111-
// Set up signal handling to shut down the transport on Ctrl+C
112-
setupSignalHandler(transport: transport)
113-
114-
// Run the server (blocking)
115-
try transport.run()
116-
}
117-
118-
}
119-
catch let error as IOError {
120-
// Handle specific IO errors with more detail
121-
let errorMessage = """
122-
IO Error: \(error)
123-
Code: \(error.errnoCode)
124-
"""
125-
fputs("\(errorMessage)\n", stderr)
126-
Foundation.exit(1)
127-
}
128-
catch let error as ChannelError {
129-
// Handle specific channel errors
130-
fputs("Channel Error: \(error)\n", stderr)
131-
Foundation.exit(1)
132-
}
133-
catch {
134-
// Handle any other errors
135-
fputs("Error: \(error)\n", stderr)
136-
Foundation.exit(1)
137-
}
138-
}
139-
}
54+
2. httpsse:
55+
- Starts an HTTP server with Server-Sent Events (SSE) support
56+
- Supports bearer token authentication and OpenAPI endpoints
57+
- Example: SwiftMCPDemo httpsse --port 8080
58+
""",
59+
subcommands: [StdioCommand.self, HTTPSSECommand.self],
60+
defaultSubcommand: StdioCommand.self
61+
)
62+
}

0 commit comments

Comments
 (0)