Skip to content

Commit c3ae777

Browse files
committed
fix: fail faster when mcp servers fail to connect
1 parent 5c40cfa commit c3ae777

File tree

11 files changed

+171
-71
lines changed

11 files changed

+171
-71
lines changed

src/components/UserInput.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const UserInput: React.FC = () => {
4646

4747
return (
4848
<Box>
49-
<BlinkCaret />
49+
<BlinkCaret enabled={!store.isProcessing} />
5050

5151
<TextInput
5252
value={store.input}

src/hooks/useAgent.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import { messageTypes, runAgentLoop } from "utils/runAgentLoop"
66
export function useAgent() {
77
const messageQueue = AgentStore.useStoreState((state) => state.messageQueue)
88
const config = AgentStore.useStoreState((state) => state.config)
9+
const mcpServers = AgentStore.useStoreState((state) => state.mcpServers)
910
const actions = AgentStore.useStoreActions((actions) => actions)
1011
const currentAssistantMessageRef = useRef("")
1112
const sessionIdRef = useRef<string | undefined>(undefined)
1213
const abortControllerRef = useRef<AbortController | undefined>(undefined)
13-
const connectedServersRef = useRef<Set<string>>(new Set())
14+
const inferredServersRef = useRef<Set<string>>(new Set())
1415

1516
const runQuery = useCallback(
1617
async (userMessage: string) => {
@@ -32,7 +33,8 @@ export function useAgent() {
3233
const agentLoop = runAgentLoop({
3334
abortController,
3435
config,
35-
connectedServers: connectedServersRef.current,
36+
inferredServers: inferredServersRef.current,
37+
mcpServers,
3638
messageQueue,
3739
onToolPermissionRequest: (toolName, input) => {
3840
actions.setPendingToolPermission({ toolName, input })

src/mcp/getAgentStatus.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ export const getAgentStatus = async (mcpServer?: McpServer) => {
77
const config = await loadConfig()
88
const messageQueue = new MessageQueue()
99
const abortController = new AbortController()
10-
const connectedServers = new Set<string>()
10+
const inferredServers = new Set<string>()
1111

1212
const agentLoop = runAgentLoop({
1313
abortController,
1414
config,
15-
connectedServers,
15+
inferredServers,
1616
messageQueue,
1717
userMessage: "status",
1818
})

src/mcp/getMcpServer.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
2+
import type { McpServerStatus } from "store"
23
import { registerAskAgentTool } from "mcp/tools/askAgent"
34
import { registerAskAgentSlackbotTool } from "mcp/tools/askAgentSlackbot"
45
import { registerGetAgentStatusTool } from "mcp/tools/getAgentStatus"
@@ -10,8 +11,11 @@ export const getMcpServer = () => {
1011
// Map thread IDs to Claude Agent SDK session IDs for per-thread isolation
1112
const threadSessions = new Map<string, string>()
1213

13-
// Map session IDs to connected MCP servers for persistence across requests
14-
const sessionConnectedServers = new Map<string, Set<string>>()
14+
// Map session IDs to inferred MCP servers for persistence across requests
15+
const sessionInferredServers = new Map<string, Set<string>>()
16+
17+
// Map session IDs to MCP server statuses for persistence across requests
18+
const sessionMcpServers = new Map<string, McpServerStatus[]>()
1519

1620
const mcpServer = new McpServer(
1721
{
@@ -32,7 +36,8 @@ export const getMcpServer = () => {
3236
get sessionId() {
3337
return sessionId
3438
},
35-
sessionConnectedServers,
39+
sessionInferredServers,
40+
sessionMcpServers,
3641
onSessionIdUpdate: (newSessionId) => {
3742
sessionId = newSessionId
3843
},
@@ -43,7 +48,8 @@ export const getMcpServer = () => {
4348
mcpServer,
4449
context: {
4550
threadSessions,
46-
sessionConnectedServers,
51+
sessionInferredServers,
52+
sessionMcpServers,
4753
},
4854
})
4955

src/mcp/runStandaloneAgentLoop.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
2+
import type { McpServerStatus } from "store"
23
import { loadConfig } from "utils/loadConfig"
34
import { log } from "utils/logger"
45
import { MessageQueue } from "utils/MessageQueue"
56
import { contentTypes, messageTypes, runAgentLoop } from "utils/runAgentLoop"
67

78
interface RunQueryOptions {
89
additionalSystemPrompt?: string
9-
existingConnectedServers?: Set<string>
10+
existingInferredServers?: Set<string>
11+
existingMcpServers?: McpServerStatus[]
1012
mcpServer: McpServer
1113
onSessionIdReceived?: (sessionId: string) => void
1214
prompt: string
@@ -15,7 +17,8 @@ interface RunQueryOptions {
1517

1618
export const runStandaloneAgentLoop = async ({
1719
additionalSystemPrompt,
18-
existingConnectedServers,
20+
existingInferredServers,
21+
existingMcpServers,
1922
mcpServer,
2023
onSessionIdReceived,
2124
prompt,
@@ -25,17 +28,16 @@ export const runStandaloneAgentLoop = async ({
2528
const messageQueue = new MessageQueue()
2629
const streamEnabled = config.stream ?? false
2730

28-
const connectedServers = existingConnectedServers ?? new Set<string>()
31+
const inferredServers = existingInferredServers ?? new Set<string>()
2932
const abortController = new AbortController()
3033

3134
const agentLoop = runAgentLoop({
3235
abortController,
3336
additionalSystemPrompt,
3437
config,
35-
connectedServers,
38+
inferredServers,
39+
mcpServers: existingMcpServers,
3640
messageQueue,
37-
sessionId,
38-
userMessage: prompt,
3941
onServerConnection: async (status) => {
4042
await mcpServer.sendLoggingMessage({
4143
level: "info",
@@ -45,6 +47,8 @@ export const runStandaloneAgentLoop = async ({
4547
}),
4648
})
4749
},
50+
sessionId,
51+
userMessage: prompt,
4852
})
4953

5054
let finalResponse = ""
@@ -155,7 +159,7 @@ export const runStandaloneAgentLoop = async ({
155159

156160
return {
157161
response: finalResponse,
158-
connectedServers,
162+
inferredServers,
159163
}
160164
}
161165
}
@@ -166,6 +170,6 @@ export const runStandaloneAgentLoop = async ({
166170

167171
return {
168172
response: finalResponse,
169-
connectedServers,
173+
inferredServers,
170174
}
171175
}

src/mcp/tools/askAgent.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
2+
import type { McpServerStatus } from "store"
23
import { runStandaloneAgentLoop } from "mcp/runStandaloneAgentLoop"
34
import { z } from "zod"
45

56
export interface AskAgentContext {
67
sessionId?: string
7-
sessionConnectedServers: Map<string, Set<string>>
8+
sessionInferredServers: Map<string, Set<string>>
9+
sessionMcpServers: Map<string, McpServerStatus[]>
810
onSessionIdUpdate: (sessionId: string) => void
911
}
1012

@@ -27,23 +29,28 @@ export const registerAskAgentTool = ({
2729
},
2830
},
2931
async ({ query }) => {
30-
const existingConnectedServers = context.sessionId
31-
? context.sessionConnectedServers.get(context.sessionId)
32+
const existingInferredServers = context.sessionId
33+
? context.sessionInferredServers.get(context.sessionId)
3234
: undefined
3335

34-
const { response, connectedServers } = await runStandaloneAgentLoop({
36+
const existingMcpServers = context.sessionId
37+
? context.sessionMcpServers.get(context.sessionId)
38+
: undefined
39+
40+
const { response, inferredServers } = await runStandaloneAgentLoop({
3541
prompt: query,
3642
mcpServer,
3743
sessionId: context.sessionId,
38-
existingConnectedServers,
44+
existingInferredServers,
45+
existingMcpServers,
3946
onSessionIdReceived: (newSessionId) => {
4047
context.onSessionIdUpdate(newSessionId)
4148
},
4249
})
4350

44-
// Update the session's connected servers
51+
// Update the session's inferred servers
4552
if (context.sessionId) {
46-
context.sessionConnectedServers.set(context.sessionId, connectedServers)
53+
context.sessionInferredServers.set(context.sessionId, inferredServers)
4754
}
4855

4956
return {

src/mcp/tools/askAgentSlackbot.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
2+
import type { McpServerStatus } from "store"
23
import { runStandaloneAgentLoop } from "mcp/runStandaloneAgentLoop"
34
import { z } from "zod"
45

56
export interface AskAgentSlackbotContext {
67
threadSessions: Map<string, string>
7-
sessionConnectedServers: Map<string, Set<string>>
8+
sessionInferredServers: Map<string, Set<string>>
9+
sessionMcpServers: Map<string, McpServerStatus[]>
810
}
911

1012
export interface RegisterAskAgentSlackbotToolProps {
@@ -41,29 +43,34 @@ export const registerAskAgentSlackbotTool = ({
4143
? context.threadSessions.get(threadId)
4244
: undefined
4345

44-
const existingConnectedServers = existingSessionId
45-
? context.sessionConnectedServers.get(existingSessionId)
46+
const existingInferredServers = existingSessionId
47+
? context.sessionInferredServers.get(existingSessionId)
4648
: undefined
4749

48-
const { response, connectedServers } = await runStandaloneAgentLoop({
50+
const existingMcpServers = existingSessionId
51+
? context.sessionMcpServers.get(existingSessionId)
52+
: undefined
53+
54+
const { response, inferredServers } = await runStandaloneAgentLoop({
4955
prompt: query,
5056
mcpServer,
5157
sessionId: existingSessionId,
5258
additionalSystemPrompt: systemPrompt,
53-
existingConnectedServers,
59+
existingInferredServers,
60+
existingMcpServers,
5461
onSessionIdReceived: (newSessionId) => {
5562
if (threadId) {
5663
context.threadSessions.set(threadId, newSessionId)
5764
}
5865
},
5966
})
6067

61-
// Update the session's connected servers
68+
// Update the session's inferred servers
6269
if (existingSessionId || threadId) {
6370
const sessionId =
6471
existingSessionId || context.threadSessions.get(threadId!)
6572
if (sessionId) {
66-
context.sessionConnectedServers.set(sessionId, connectedServers)
73+
context.sessionInferredServers.set(sessionId, inferredServers)
6774
}
6875
}
6976

src/utils/__tests__/getPrompt.test.ts

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -145,47 +145,80 @@ describe("buildSystemPrompt", () => {
145145
expect(prompt).toContain("GitHub instructions")
146146
})
147147

148-
test("should include connected MCP servers in system prompt", async () => {
148+
test("should include inferred MCP servers in system prompt", async () => {
149149
const config: AgentChatConfig = {
150150
mcpServers: {},
151151
}
152-
const connectedServers = new Set(["github", "gitlab"])
152+
const inferredServers = new Set(["github", "gitlab"])
153153

154154
const prompt = await buildSystemPrompt({
155155
config,
156-
connectedServers,
156+
inferredServers,
157157
})
158158

159-
expect(prompt).toContain("Connected MCP Servers")
159+
expect(prompt).toContain("Server Selection Context")
160160
expect(prompt).toContain("github, gitlab")
161161
})
162162

163-
test("should handle empty connected servers set", async () => {
163+
test("should include connected and failed MCP servers sections", async () => {
164164
const config: AgentChatConfig = {
165165
mcpServers: {},
166166
}
167-
const connectedServers = new Set<string>()
167+
const mcpServers = [
168+
{ name: "github", status: "connected" },
169+
{ name: "gitlab", status: "failed" },
170+
]
168171

169172
const prompt = await buildSystemPrompt({
170173
config,
171-
connectedServers,
174+
mcpServers,
172175
})
173176

174-
expect(prompt).toContain("Connected MCP Servers")
175-
expect(prompt).toContain(
176-
"The following MCP servers are currently connected and available:"
177-
)
177+
expect(prompt).toContain("Available MCP Servers")
178+
expect(prompt).toContain("- github")
179+
expect(prompt).toContain("ONLY servers with available tools")
180+
expect(prompt).toContain("Unavailable MCP Servers")
181+
expect(prompt).toContain("- gitlab")
182+
expect(prompt).toContain("These servers have NO tools available")
178183
})
179184

180-
test("should work without connectedServers parameter", async () => {
185+
test("should not include connection status sections when no mcpServers provided", async () => {
186+
const config: AgentChatConfig = {
187+
mcpServers: {},
188+
}
189+
190+
const prompt = await buildSystemPrompt({
191+
config,
192+
})
193+
194+
expect(prompt).not.toContain("Available MCP Servers")
195+
expect(prompt).not.toContain("Unavailable MCP Servers")
196+
})
197+
198+
test("should handle empty inferred servers set", async () => {
199+
const config: AgentChatConfig = {
200+
mcpServers: {},
201+
}
202+
const inferredServers = new Set<string>()
203+
204+
const prompt = await buildSystemPrompt({
205+
config,
206+
inferredServers,
207+
})
208+
209+
expect(prompt).toContain("Current date:")
210+
expect(prompt).not.toContain("Server Selection Context")
211+
})
212+
213+
test("should work without inferredServers parameter", async () => {
181214
const config: AgentChatConfig = {
182215
mcpServers: {},
183216
}
184217

185218
const prompt = await buildSystemPrompt({ config })
186219

187-
expect(prompt).toContain("Connected MCP Servers")
188220
expect(prompt).toContain("Current date:")
221+
expect(prompt).not.toContain("Server Selection Context")
189222
})
190223

191224
test("should skip disabled MCP servers", async () => {

0 commit comments

Comments
 (0)