Skip to content

Commit e841b4d

Browse files
committed
Checkpoint
1 parent f7e83dc commit e841b4d

File tree

3 files changed

+143
-133
lines changed

3 files changed

+143
-133
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
</CommandLineArgument>
7070
<CommandLineArgument
7171
argument = "--token secret"
72-
isEnabled = "YES">
72+
isEnabled = "NO">
7373
</CommandLineArgument>
7474
</CommandLineArguments>
7575
<EnvironmentVariables>

Sources/SwiftMCP/Transport/Channel+SSE.swift

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ extension Channel {
99
/// - Returns: An EventLoopFuture that completes when the message has been written and flushed
1010
func sendSSE(_ message: LosslessStringConvertible) {
1111
guard self.isActive else {
12-
Logger(label: "com.cocoanetics.SwiftMCP.Transport").warning("Attempted to send SSE message on inactive channel")
12+
1313
return
1414
}
1515

@@ -19,23 +19,8 @@ extension Channel {
1919

2020
let part = HTTPServerResponsePart.body(.byteBuffer(buffer))
2121

22-
// Create a promise to track the write operation
23-
let promise = self.eventLoop.makePromise(of: Void.self)
24-
25-
// Set up promise completion handler
26-
promise.futureResult.whenComplete { result in
27-
switch result {
28-
case .success:
29-
Logger(label: "com.cocoanetics.SwiftMCP.Transport").debug("SSE message sent successfully")
30-
case .failure(let error):
31-
Logger(label: "com.cocoanetics.SwiftMCP.Transport").error("Failed to send SSE message: \(error)")
32-
// Close the channel on write failure
33-
self.close(promise: nil)
34-
}
35-
}
36-
3722
// Write with promise
38-
self.write(part, promise: promise)
23+
self.write(part, promise: nil)
3924
self.flush()
4025
}
4126
}

Sources/SwiftMCP/Transport/HTTPLogger.swift

Lines changed: 140 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,160 @@
1-
import Foundation
2-
import Logging
3-
import NIOCore
4-
import NIOHTTP1
5-
import NIOConcurrencyHelpers
1+
import Foundation
2+
import Logging
3+
import NIOCore
4+
import NIOHTTP1
5+
import NIOConcurrencyHelpers
66

7-
/// A channel handler that logs both incoming and outgoing HTTP messages
8-
final class HTTPLogger: ChannelDuplexHandler {
9-
typealias InboundIn = HTTPServerRequestPart
10-
typealias InboundOut = HTTPServerRequestPart
11-
typealias OutboundIn = HTTPServerResponsePart
12-
typealias OutboundOut = HTTPServerResponsePart
7+
/// A channel handler that logs both incoming and outgoing HTTP messages
8+
final class HTTPLogger: ChannelDuplexHandler {
9+
typealias InboundIn = HTTPServerRequestPart
10+
typealias InboundOut = HTTPServerRequestPart
11+
typealias OutboundIn = HTTPServerResponsePart
12+
typealias OutboundOut = HTTPServerResponsePart
1313

14-
private let httpLogger = Logger(label: "com.cocoanetics.SwiftMCP.HTTP")
15-
private let lock = NIOLock()
16-
17-
// Track current request/response state
18-
private var currentRequestHead: HTTPRequestHead?
19-
private var currentRequestBody = ""
20-
private var currentResponseHead: HTTPResponseHead?
21-
private var currentResponseBody = ""
22-
23-
/// Log incoming requests and forward them to the next handler
24-
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
25-
let reqPart = unwrapInboundIn(data)
14+
private let httpLogger = Logger(label: "com.cocoanetics.SwiftMCP.HTTP")
15+
private let sseLogger = Logger(label: "com.cocoanetics.SwiftMCP.SSE")
16+
private let lock = NIOLock()
2617

27-
lock.withLock {
28-
switch reqPart {
29-
case .head(let head):
30-
// Log previous request if exists
31-
logCurrentRequest()
32-
33-
currentRequestHead = head
34-
currentRequestBody = ""
35-
36-
case .body(let buffer):
37-
if let str = buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes) {
38-
currentRequestBody += str
18+
// Track current request/response state
19+
private var currentRequestHead: HTTPRequestHead?
20+
private var currentRequestBody = ""
21+
private var currentResponseHead: HTTPResponseHead?
22+
private var currentResponseBody = ""
23+
private var isSSEConnection = false
24+
25+
/// Log incoming requests and forward them to the next handler
26+
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
27+
let reqPart = unwrapInboundIn(data)
28+
29+
lock.withLock {
30+
switch reqPart {
31+
case .head(let head):
32+
// Check if this is an SSE connection
33+
if head.uri.hasPrefix("/sse") {
34+
isSSEConnection = true
35+
}
36+
37+
// Log previous request if exists
38+
logCurrentRequest()
39+
40+
currentRequestHead = head
41+
currentRequestBody = ""
42+
43+
case .body(let buffer):
44+
if let str = buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes) {
45+
currentRequestBody += str
46+
}
47+
48+
case .end:
49+
logCurrentRequest()
50+
currentRequestHead = nil
51+
currentRequestBody = ""
3952
}
40-
41-
case .end:
42-
logCurrentRequest()
43-
currentRequestHead = nil
44-
currentRequestBody = ""
4553
}
54+
55+
context.fireChannelRead(data)
4656
}
4757

48-
context.fireChannelRead(data)
49-
}
50-
51-
/// Log outgoing responses and forward them to the next handler
52-
func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
53-
let resPart = unwrapOutboundIn(data)
54-
55-
lock.withLock {
56-
switch resPart {
57-
case .head(let head):
58-
// Log previous response if exists
59-
logCurrentResponse()
60-
61-
currentResponseHead = head
62-
currentResponseBody = ""
63-
64-
case .body(let body):
65-
if case .byteBuffer(let buffer) = body {
66-
if let str = buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes) {
67-
currentResponseBody += str
58+
/// Log outgoing responses and forward them to the next handler
59+
func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
60+
let resPart = unwrapOutboundIn(data)
61+
62+
lock.withLock {
63+
switch resPart {
64+
case .head(let head):
65+
// For SSE connections, only log the initial response headers
66+
if isSSEConnection {
67+
var log = "HTTP/\(head.version.major).\(head.version.minor) \(head.status.code) \(head.status.reasonPhrase)\n"
68+
head.headers.forEach { log += "\($0.name): \($0.value)\n" }
69+
log += "\n"
70+
sseLogger.info("Connection Established:\n\(log)")
71+
} else {
72+
// Log previous response if exists
73+
logCurrentResponse()
74+
currentResponseHead = head
75+
currentResponseBody = ""
76+
}
77+
78+
case .body(let body):
79+
if case .byteBuffer(let buffer) = body {
80+
if let str = buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes) {
81+
if isSSEConnection {
82+
// For SSE, log each message immediately
83+
sseLogger.trace("Message: \(str)")
84+
} else {
85+
currentResponseBody += str
86+
}
87+
}
88+
}
89+
90+
case .end:
91+
if !isSSEConnection {
92+
logCurrentResponse()
93+
currentResponseHead = nil
94+
currentResponseBody = ""
6895
}
6996
}
70-
71-
case .end:
72-
logCurrentResponse()
73-
currentResponseHead = nil
74-
currentResponseBody = ""
7597
}
98+
99+
context.write(data, promise: promise)
76100
}
77101

78-
context.write(data, promise: promise)
79-
}
80-
81-
func flush(context: ChannelHandlerContext) {
82-
context.flush()
83-
}
84-
85-
private func logCurrentRequest() {
86-
guard let head = currentRequestHead else { return }
87-
88-
var log = "\(head.method) \(head.uri) HTTP/\(head.version.major).\(head.version.minor)\n"
89-
head.headers.forEach { log += "\($0.name): \($0.value)\n" }
90-
log += "\n" // Empty line after headers
91-
92-
if !currentRequestBody.isEmpty {
93-
log += currentRequestBody + "\n"
102+
func flush(context: ChannelHandlerContext) {
103+
context.flush()
94104
}
95105

96-
httpLogger.trace("\(log)")
97-
}
98-
99-
private func logCurrentResponse() {
100-
guard let head = currentResponseHead else { return }
101-
102-
var log = "HTTP/\(head.version.major).\(head.version.minor) \(head.status.code) \(head.status.reasonPhrase)\n"
103-
head.headers.forEach { log += "\($0.name): \($0.value)\n" }
104-
log += "\n" // Empty line after headers
106+
private func logCurrentRequest() {
107+
guard let head = currentRequestHead else { return }
108+
109+
var log = "\(head.method) \(head.uri) HTTP/\(head.version.major).\(head.version.minor)\n"
110+
head.headers.forEach { log += "\($0.name): \($0.value)\n" }
111+
log += "\n" // Empty line after headers
112+
113+
if !currentRequestBody.isEmpty {
114+
log += currentRequestBody + "\n"
115+
}
116+
117+
httpLogger.info("\(log)")
118+
}
105119

106-
if !currentResponseBody.isEmpty {
107-
log += currentResponseBody + "\n"
120+
private func logCurrentResponse() {
121+
guard let head = currentResponseHead else { return }
122+
123+
var log = "HTTP/\(head.version.major).\(head.version.minor) \(head.status.code) \(head.status.reasonPhrase)\n"
124+
head.headers.forEach { log += "\($0.name): \($0.value)\n" }
125+
log += "\n" // Empty line after headers
126+
127+
if !currentResponseBody.isEmpty {
128+
log += currentResponseBody + "\n"
129+
}
130+
131+
httpLogger.info("\(log)")
108132
}
109133

110-
httpLogger.trace("\(log)")
111-
}
112-
113-
func handlerAdded(context: ChannelHandlerContext) {
114-
lock.withLock {
115-
currentRequestHead = nil
116-
currentRequestBody = ""
117-
currentResponseHead = nil
118-
currentResponseBody = ""
134+
func handlerAdded(context: ChannelHandlerContext) {
135+
lock.withLock {
136+
currentRequestHead = nil
137+
currentRequestBody = ""
138+
currentResponseHead = nil
139+
currentResponseBody = ""
140+
isSSEConnection = false
141+
}
119142
}
120-
}
121-
122-
func handlerRemoved(context: ChannelHandlerContext) {
123-
lock.withLock {
124-
// Log any pending messages
125-
logCurrentRequest()
126-
logCurrentResponse()
127-
128-
// Clear state
129-
currentRequestHead = nil
130-
currentRequestBody = ""
131-
currentResponseHead = nil
132-
currentResponseBody = ""
143+
144+
func handlerRemoved(context: ChannelHandlerContext) {
145+
lock.withLock {
146+
// Log any pending messages
147+
logCurrentRequest()
148+
if !isSSEConnection {
149+
logCurrentResponse()
150+
}
151+
152+
// Clear state
153+
currentRequestHead = nil
154+
currentRequestBody = ""
155+
currentResponseHead = nil
156+
currentResponseBody = ""
157+
isSSEConnection = false
158+
}
133159
}
134160
}
135-
}

0 commit comments

Comments
 (0)