Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed dividers that could not be dragged at all: the user list and privilege panes in Users & Roles, the trigger list in Structure, and the metrics and slow query panes in Server Dashboard. (#1872)
- Cancel now stops a connection attempt right away instead of letting it run on in the background. A cancelled connection is also dropped from the last session, so restarting no longer reconnects to a host you gave up on, and it can no longer interrupt a later successful connection. (#1358)
- Fixed a crash when clicking a column header on a table whose columns have comments. Sorting such a table quit the app immediately. (#1869)
- MCP tools no longer stop working after the server sits idle for 15 minutes, or after the MCP server is restarted from Settings. TablePro's bridge now starts a new session by itself instead of reusing a dead one, so agents like Claude Code and Cursor keep working without being turned off and on again. (#1881)

### Security

Expand Down
3 changes: 3 additions & 0 deletions TablePro.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -442,11 +442,14 @@
CLI/BridgeMain.swift,
CLI/BridgeProxy.swift,
CLI/Handshake.swift,
Core/Concurrency/OnceTask.swift,
Core/MCP/Session/MCPClock.swift,
Core/MCP/Transport/MCPBridgeLogger.swift,
Core/MCP/Transport/MCPMessageTransport.swift,
Core/MCP/Transport/MCPProtocolError.swift,
Core/MCP/Transport/MCPStdioMessageTransport.swift,
Core/MCP/Transport/MCPStreamableHttpClientTransport.swift,
Core/MCP/Transport/MCPUpstreamCredentials.swift,
Core/MCP/Wire/HttpRequestHead.swift,
Core/MCP/Wire/HttpResponseHead.swift,
Core/MCP/Wire/JsonRpcCodec.swift,
Expand Down
15 changes: 11 additions & 4 deletions TablePro/CLI/BridgeMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,27 @@ struct TableProMcpBridge {
exit(1)
}

guard let endpoint = handshake.endpoint() else {
guard let credentials = handshake.credentials() else {
logger.log(.error, "Handshake produced invalid endpoint")
emitFatalJsonRpcError(message: "Invalid MCP server endpoint")
exit(1)
}

let credentialsProvider = MCPCachedUpstreamCredentialsProvider(initial: credentials) {
let refreshed = try await acquirer.acquire()
guard let credentials = refreshed.credentials() else {
throw MCPHandshakeError.fileNotFound
}
logger.log(.info, "Re-acquired MCP handshake after upstream rejected the bridge")
return credentials
}

let upstream = MCPStreamableHttpClientTransport(
configuration: MCPStreamableHttpClientConfiguration(
endpoint: endpoint,
bearerToken: handshake.token,
tlsCertFingerprint: handshake.tlsCertFingerprint,
requestTimeout: .seconds(60),
serverInitiatedStream: false
),
credentialsProvider: credentialsProvider,
errorLogger: logger
)

Expand Down
9 changes: 9 additions & 0 deletions TablePro/CLI/Handshake.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,13 @@ extension MCPBridgeHandshake {
let scheme = (tls ?? false) ? "https" : "http"
return URL(string: "\(scheme)://127.0.0.1:\(port)/mcp")
}

func credentials() -> MCPUpstreamCredentials? {
guard let endpoint = endpoint() else { return nil }
return MCPUpstreamCredentials(
endpoint: endpoint,
bearerToken: token,
tlsCertFingerprint: tlsCertFingerprint
)
}
}
15 changes: 13 additions & 2 deletions TablePro/Core/MCP/MCPServerManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,11 @@ final class MCPServerManager {

startDispatchLoop(transport: newTransport, dispatcher: newDispatcher, generation: generation)
startStateLoop(transport: newTransport, generation: generation)
startSessionEventsLoop(sessionStore: newSessionStore, generation: generation)
startSessionEventsLoop(
sessionStore: newSessionStore,
authPolicy: services.authPolicy,
generation: generation
)
await registerRevocationObserver(
tokenStore: newTokenStore,
sessionStore: newSessionStore,
Expand Down Expand Up @@ -273,14 +277,21 @@ final class MCPServerManager {
}
}

private func startSessionEventsLoop(sessionStore: MCPSessionStore, generation: Int) {
private func startSessionEventsLoop(
sessionStore: MCPSessionStore,
authPolicy: MCPAuthPolicy,
generation: Int
) {
sessionEventsTask?.cancel()
sessionEventsTask = Task { [weak self] in
let stream = await sessionStore.events
for await event in stream {
guard let self else { return }
guard await self.isCurrentGeneration(generation) else { return }
Self.logger.debug("Session event: \(String(describing: event), privacy: .public)")
if case .terminated(let sessionId, _) = event {
await authPolicy.clearSession(sessionId.rawValue)
}
await self.refreshClients()
}
}
Expand Down
1 change: 1 addition & 0 deletions TablePro/Core/MCP/Transport/MCPMessageTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ public enum MCPTransportError: Error, Sendable, Equatable {
case invalidEndpoint
case authentication(httpStatus: Int, message: String)
case sessionExpired
case unreachable(detail: String)
case timeout
}
Loading
Loading