refactor(cli): convert mcp list, auth, auth list, logout to effectCmd (#25521)

This commit is contained in:
Kit Langton
2026-05-03 03:03:32 +00:00
committed by GitHub
parent 31cb0bfa4f
commit db24f89313
+51 -62
View File
@@ -1,4 +1,6 @@
import { cmd } from "./cmd" import { cmd } from "./cmd"
import { effectCmd } from "../effect-cmd"
import { Cause } from "effect"
import { Client } from "@modelcontextprotocol/sdk/client/index.js" import { Client } from "@modelcontextprotocol/sdk/client/index.js"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js" import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js" import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"
@@ -65,9 +67,8 @@ function oauthServers(config: Config.Info) {
) )
} }
async function listState() { function listState() {
return AppRuntime.runPromise( return Effect.gen(function* () {
Effect.gen(function* () {
const cfg = yield* Config.Service const cfg = yield* Config.Service
const mcp = yield* MCP.Service const mcp = yield* MCP.Service
const config = yield* cfg.get() const config = yield* cfg.get()
@@ -77,13 +78,11 @@ async function listState() {
{ concurrency: "unbounded" }, { concurrency: "unbounded" },
) )
return { config, statuses, stored } return { config, statuses, stored }
}), })
)
} }
async function authState() { function authState() {
return AppRuntime.runPromise( return Effect.gen(function* () {
Effect.gen(function* () {
const cfg = yield* Config.Service const cfg = yield* Config.Service
const mcp = yield* MCP.Service const mcp = yield* MCP.Service
const config = yield* cfg.get() const config = yield* cfg.get()
@@ -92,8 +91,7 @@ async function authState() {
{ concurrency: "unbounded" }, { concurrency: "unbounded" },
) )
return { config, auth } return { config, auth }
}), })
)
} }
export const McpCommand = cmd({ export const McpCommand = cmd({
@@ -110,18 +108,15 @@ export const McpCommand = cmd({
async handler() {}, async handler() {},
}) })
export const McpListCommand = cmd({ export const McpListCommand = effectCmd({
command: "list", command: "list",
aliases: ["ls"], aliases: ["ls"],
describe: "list MCP servers and their status", describe: "list MCP servers and their status",
async handler() { handler: Effect.fn("Cli.mcp.list")(function* () {
await WithInstance.provide({
directory: process.cwd(),
async fn() {
UI.empty() UI.empty()
prompts.intro("MCP Servers") prompts.intro("MCP Servers")
const { config, statuses, stored } = await listState() const { config, statuses, stored } = yield* listState()
const servers = configuredServers(config) const servers = configuredServers(config)
if (servers.length === 0) { if (servers.length === 0) {
@@ -171,12 +166,10 @@ export const McpListCommand = cmd({
} }
prompts.outro(`${servers.length} server(s)`) prompts.outro(`${servers.length} server(s)`)
}, }),
})
},
}) })
export const McpAuthCommand = cmd({ export const McpAuthCommand = effectCmd({
command: "auth [name]", command: "auth [name]",
describe: "authenticate with an OAuth-enabled MCP server", describe: "authenticate with an OAuth-enabled MCP server",
builder: (yargs) => builder: (yargs) =>
@@ -186,14 +179,11 @@ export const McpAuthCommand = cmd({
type: "string", type: "string",
}) })
.command(McpAuthListCommand), .command(McpAuthListCommand),
async handler(args) { handler: Effect.fn("Cli.mcp.auth")(function* (args) {
await WithInstance.provide({
directory: process.cwd(),
async fn() {
UI.empty() UI.empty()
prompts.intro("MCP OAuth Authentication") prompts.intro("MCP OAuth Authentication")
const { config, auth } = await authState() const { config, auth } = yield* authState()
const mcpServers = config.mcp ?? {} const mcpServers = config.mcp ?? {}
const servers = oauthServers(config) const servers = oauthServers(config)
@@ -226,10 +216,12 @@ export const McpAuthCommand = cmd({
} }
}) })
const selected = await prompts.select({ const selected = yield* Effect.promise(() =>
prompts.select({
message: "Select MCP server to authenticate", message: "Select MCP server to authenticate",
options, options,
}) }),
)
if (prompts.isCancel(selected)) throw new UI.CancelledError() if (prompts.isCancel(selected)) throw new UI.CancelledError()
serverName = selected serverName = selected
} }
@@ -248,12 +240,13 @@ export const McpAuthCommand = cmd({
} }
// Check if already authenticated // Check if already authenticated
const authStatus = const authStatus = auth[serverName] ?? (yield* MCP.Service.use((mcp) => mcp.getAuthStatus(serverName)))
auth[serverName] ?? (await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.getAuthStatus(serverName))))
if (authStatus === "authenticated") { if (authStatus === "authenticated") {
const confirm = await prompts.confirm({ const confirm = yield* Effect.promise(() =>
prompts.confirm({
message: `${serverName} already has valid credentials. Re-authenticate?`, message: `${serverName} already has valid credentials. Re-authenticate?`,
}) }),
)
if (prompts.isCancel(confirm) || !confirm) { if (prompts.isCancel(confirm) || !confirm) {
prompts.outro("Cancelled") prompts.outro("Cancelled")
return return
@@ -275,9 +268,10 @@ export const McpAuthCommand = cmd({
} }
}) })
try { yield* MCP.Service.use((mcp) => mcp.authenticate(serverName))
const status = await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.authenticate(serverName))) .pipe(
Effect.tap((status) =>
Effect.sync(() => {
if (status.status === "connected") { if (status.status === "connected") {
spinner.stop("Authentication successful!") spinner.stop("Authentication successful!")
} else if (status.status === "needs_client_registration") { } else if (status.status === "needs_client_registration") {
@@ -301,31 +295,31 @@ export const McpAuthCommand = cmd({
} else { } else {
spinner.stop("Unexpected status: " + status.status, 1) spinner.stop("Unexpected status: " + status.status, 1)
} }
} catch (error) { }),
),
Effect.catchCause((cause) =>
Effect.sync(() => {
spinner.stop("Authentication failed", 1) spinner.stop("Authentication failed", 1)
const error = Cause.squash(cause)
prompts.log.error(error instanceof Error ? error.message : String(error)) prompts.log.error(error instanceof Error ? error.message : String(error))
} finally { }),
unsubscribe() ),
} Effect.ensuring(Effect.sync(() => unsubscribe())),
)
prompts.outro("Done") prompts.outro("Done")
}, }),
})
},
}) })
export const McpAuthListCommand = cmd({ export const McpAuthListCommand = effectCmd({
command: "list", command: "list",
aliases: ["ls"], aliases: ["ls"],
describe: "list OAuth-capable MCP servers and their auth status", describe: "list OAuth-capable MCP servers and their auth status",
async handler() { handler: Effect.fn("Cli.mcp.auth.list")(function* () {
await WithInstance.provide({
directory: process.cwd(),
async fn() {
UI.empty() UI.empty()
prompts.intro("MCP OAuth Status") prompts.intro("MCP OAuth Status")
const { config, auth } = await authState() const { config, auth } = yield* authState()
const servers = oauthServers(config) const servers = oauthServers(config)
if (servers.length === 0) { if (servers.length === 0) {
@@ -344,12 +338,10 @@ export const McpAuthListCommand = cmd({
} }
prompts.outro(`${servers.length} OAuth-capable server(s)`) prompts.outro(`${servers.length} OAuth-capable server(s)`)
}, }),
})
},
}) })
export const McpLogoutCommand = cmd({ export const McpLogoutCommand = effectCmd({
command: "logout [name]", command: "logout [name]",
describe: "remove OAuth credentials for an MCP server", describe: "remove OAuth credentials for an MCP server",
builder: (yargs) => builder: (yargs) =>
@@ -357,14 +349,11 @@ export const McpLogoutCommand = cmd({
describe: "name of the MCP server", describe: "name of the MCP server",
type: "string", type: "string",
}), }),
async handler(args) { handler: Effect.fn("Cli.mcp.logout")(function* (args) {
await WithInstance.provide({
directory: process.cwd(),
async fn() {
UI.empty() UI.empty()
prompts.intro("MCP OAuth Logout") prompts.intro("MCP OAuth Logout")
const credentials = await AppRuntime.runPromise(McpAuth.Service.use((auth) => auth.all())) const credentials = yield* McpAuth.Service.use((auth) => auth.all())
const serverNames = Object.keys(credentials) const serverNames = Object.keys(credentials)
if (serverNames.length === 0) { if (serverNames.length === 0) {
@@ -375,7 +364,8 @@ export const McpLogoutCommand = cmd({
let serverName = args.name let serverName = args.name
if (!serverName) { if (!serverName) {
const selected = await prompts.select({ const selected = yield* Effect.promise(() =>
prompts.select({
message: "Select MCP server to logout", message: "Select MCP server to logout",
options: serverNames.map((name) => { options: serverNames.map((name) => {
const entry = credentials[name] const entry = credentials[name]
@@ -391,7 +381,8 @@ export const McpLogoutCommand = cmd({
hint, hint,
} }
}), }),
}) }),
)
if (prompts.isCancel(selected)) throw new UI.CancelledError() if (prompts.isCancel(selected)) throw new UI.CancelledError()
serverName = selected serverName = selected
} }
@@ -402,12 +393,10 @@ export const McpLogoutCommand = cmd({
return return
} }
await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.removeAuth(serverName))) yield* MCP.Service.use((mcp) => mcp.removeAuth(serverName))
prompts.log.success(`Removed OAuth credentials for ${serverName}`) prompts.log.success(`Removed OAuth credentials for ${serverName}`)
prompts.outro("Done") prompts.outro("Done")
}, }),
})
},
}) })
async function resolveConfigPath(baseDir: string, global = false) { async function resolveConfigPath(baseDir: string, global = false) {