refactor(mcp): tighten connection result types

This commit is contained in:
Aiden Cline 2026-06-09 13:46:50 -05:00
commit 91a696da0b

View file

@ -217,11 +217,13 @@ function fetchFromClient<T extends { name: string }>(
) )
} }
interface CreateResult { type ConnectedStatus = Extract<Status, { status: "connected" }>
mcpClient?: MCPClient type DisconnectedStatus = Exclude<Status, ConnectedStatus>
status: Status type ConnectionFailedStatus = Exclude<DisconnectedStatus, { status: "disabled" }>
defs?: MCPToolDef[] type ConnectResult = { client: MCPClient; status: ConnectedStatus } | { status: ConnectionFailedStatus }
} type CreateResult =
| { mcpClient: MCPClient; status: ConnectedStatus; defs: MCPToolDef[] }
| { status: DisconnectedStatus }
interface AuthResult { interface AuthResult {
authorizationUrl: string authorizationUrl: string
@ -302,15 +304,14 @@ export const layer = Layer.effect(
const connectRemote = Effect.fn("MCP.connectRemote")(function* ( const connectRemote = Effect.fn("MCP.connectRemote")(function* (
key: string, key: string,
mcp: ConfigMCPV1.Info & { type: "remote" }, mcp: ConfigMCPV1.Remote,
) { ): Effect.fn.Return<ConnectResult> {
const oauthDisabled = mcp.oauth === false const oauthDisabled = mcp.oauth === false
const oauthConfig = typeof mcp.oauth === "object" ? mcp.oauth : undefined const oauthConfig = typeof mcp.oauth === "object" ? mcp.oauth : undefined
const url = remoteURL(mcp.url) const url = remoteURL(mcp.url)
if (!url) { if (!url) {
return { return {
client: undefined as MCPClient | undefined, status: { status: "failed", error: `Invalid MCP URL for "${key}"` },
status: { status: "failed" as const, error: `Invalid MCP URL for "${key}"` },
} }
} }
let authProvider: McpOAuthProvider | undefined let authProvider: McpOAuthProvider | undefined
@ -351,7 +352,7 @@ export const layer = Layer.effect(
] ]
const connectTimeout = mcp.timeout ?? DEFAULT_TIMEOUT const connectTimeout = mcp.timeout ?? DEFAULT_TIMEOUT
let lastStatus: Status | undefined let lastStatus: ConnectionFailedStatus | undefined
for (const { name, transport } of transports) { for (const { name, transport } of transports) {
const result = yield* connectTransport(transport, connectTimeout).pipe( const result = yield* connectTransport(transport, connectTimeout).pipe(
@ -393,21 +394,19 @@ export const layer = Layer.effect(
return Effect.void return Effect.void
}), }),
) )
if (result) return { client: result.client, status: { status: "connected" } as Status } if (result) return { client: result.client, status: { status: "connected" } }
// If this was an auth error, stop trying other transports // If this was an auth error, stop trying other transports
if (lastStatus?.status === "needs_auth" || lastStatus?.status === "needs_client_registration") break if (lastStatus?.status === "needs_auth" || lastStatus?.status === "needs_client_registration") break
} }
return { return {
client: undefined as MCPClient | undefined, status: lastStatus ?? { status: "failed", error: "Unknown error" },
status: (lastStatus ?? { status: "failed", error: "Unknown error" }) as Status,
} }
}) })
const connectLocal = Effect.fn("MCP.connectLocal")(function* ( const connectLocal = Effect.fn("MCP.connectLocal")(function* (
key: string, mcp: ConfigMCPV1.Local,
mcp: ConfigMCPV1.Info & { type: "local" }, ): Effect.fn.Return<ConnectResult> {
) {
const [cmd, ...args] = mcp.command const [cmd, ...args] = mcp.command
const cwd = yield* InstanceState.directory const cwd = yield* InstanceState.directory
const transport = new StdioClientTransport({ const transport = new StdioClientTransport({
@ -424,13 +423,10 @@ export const layer = Layer.effect(
const connectTimeout = mcp.timeout ?? DEFAULT_TIMEOUT const connectTimeout = mcp.timeout ?? DEFAULT_TIMEOUT
return yield* connectTransport(transport, connectTimeout).pipe( return yield* connectTransport(transport, connectTimeout).pipe(
Effect.map((client): { client: MCPClient | undefined; status: Status } => ({ Effect.map((client) => ({ client, status: { status: "connected" } }) satisfies ConnectResult),
client, Effect.catch((error) => {
status: { status: "connected" },
})),
Effect.catch((error): Effect.Effect<{ client: MCPClient | undefined; status: Status }> => {
const msg = error instanceof Error ? error.message : String(error) const msg = error instanceof Error ? error.message : String(error)
return Effect.succeed({ client: undefined, status: { status: "failed", error: msg } }) return Effect.succeed({ status: { status: "failed", error: msg } } satisfies ConnectResult)
}), }),
) )
}) })
@ -440,25 +436,20 @@ export const layer = Layer.effect(
return DISABLED_RESULT return DISABLED_RESULT
} }
const { client: mcpClient, status } = const result = mcp.type === "remote" ? yield* connectRemote(key, mcp) : yield* connectLocal(mcp)
mcp.type === "remote"
? yield* connectRemote(key, mcp as ConfigMCPV1.Info & { type: "remote" })
: yield* connectLocal(key, mcp as ConfigMCPV1.Info & { type: "local" })
if (!mcpClient) { if (!("client" in result)) {
if (status.status !== "connected" && status.status !== "disabled") { yield* Effect.logWarning("server unavailable", { key, type: mcp.type, status: result.status.status })
yield* Effect.logWarning("server unavailable", { key, type: mcp.type, status: status.status }) return result
}
return { status } satisfies CreateResult
} }
const listed = mcpClient.getServerCapabilities()?.tools ? yield* defs(mcpClient, mcp.timeout) : [] const listed = result.client.getServerCapabilities()?.tools ? yield* defs(result.client, mcp.timeout) : []
if (!listed) { if (!listed) {
yield* Effect.tryPromise(() => mcpClient.close()).pipe(Effect.ignore) yield* Effect.tryPromise(() => result.client.close()).pipe(Effect.ignore)
return { status: { status: "failed", error: "Failed to get tools" } } satisfies CreateResult return { status: { status: "failed", error: "Failed to get tools" } } satisfies CreateResult
} }
return { mcpClient, status, defs: listed } satisfies CreateResult return { mcpClient: result.client, status: result.status, defs: listed } satisfies CreateResult
}) })
const cfgSvc = yield* Config.Service const cfgSvc = yield* Config.Service
@ -529,9 +520,9 @@ export const layer = Layer.effect(
if (!result) return if (!result) return
s.status[key] = result.status s.status[key] = result.status
if (result.mcpClient) { if ("mcpClient" in result) {
s.clients[key] = result.mcpClient s.clients[key] = result.mcpClient
s.defs[key] = result.defs! s.defs[key] = result.defs
watch(s, key, result.mcpClient, bridge, mcp.timeout) watch(s, key, result.mcpClient, bridge, mcp.timeout)
} }
}), }),
@ -617,13 +608,13 @@ export const layer = Layer.effect(
const result = yield* create(name, mcp) const result = yield* create(name, mcp)
s.status[name] = result.status s.status[name] = result.status
if (!result.mcpClient) { if (!("mcpClient" in result)) {
yield* closeClient(s, name) yield* closeClient(s, name)
delete s.clients[name] delete s.clients[name]
return result.status return result.status
} }
return yield* storeClient(s, name, result.mcpClient, result.defs!, mcp.timeout) return yield* storeClient(s, name, result.mcpClient, result.defs, mcp.timeout)
}) })
const add = Effect.fn("MCP.add")(function* (name: string, mcp: ConfigMCPV1.Info) { const add = Effect.fn("MCP.add")(function* (name: string, mcp: ConfigMCPV1.Info) {