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