Skip to content
6 changes: 6 additions & 0 deletions packages/cli/src/commands/catalog.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,9 @@ import memoryDelete from "./memory/delete.ts";
import memoryProfileCreate from "./memory/profile-create.ts";
import memoryProfileGet from "./memory/profile-get.ts";
import knowledgeRetrieve from "./knowledge/retrieve.ts";
import mcpCall from "./mcp/call.ts";
import mcpList from "./mcp/list.ts";
import mcpTools from "./mcp/tools.ts";
import searchWeb from "./search/web.ts";
import speechSynthesize from "./speech/synthesize.ts";
import speechRecognize from "./speech/recognize.ts";
Expand DownExpand Up@@ -61,6 +64,9 @@ export const commands: Record<string, Command> = {
"memory profile create": memoryProfileCreate,
"memory profile get": memoryProfileGet,
"knowledge retrieve": knowledgeRetrieve,
"mcp list": mcpList,
"mcp tools": mcpTools,
"mcp call": mcpCall,
"search web": searchWeb,
"speech synthesize": speechSynthesize,
"speech recognize": speechRecognize,
Expand Down
123 changes: 123 additions & 0 deletions packages/cli/src/commands/mcp/call.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
import {
defineCommand,
McpClient,
bailianMcpUrl,
detectOutputFormat,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { failIfMissing } from "../../output/prompt.ts";
import { emitResult } from "../../output/output.ts";
import { ensureApiKey } from "../../utils/ensure-key.ts";

function parseArgFlags(raw: string[]): Record<string, unknown> {
const out: Record<string, unknown> = {};
for (const item of raw) {
const idx = item.indexOf("=");
if (idx <= 0) {
process.stderr.write(`Error: --arg must be in K=V form, got: ${item}\n`);
process.exit(1);
}
const key = item.slice(0, idx).trim();
const rawVal = item.slice(idx + 1);
try {
out[key] = JSON.parse(rawVal);
} catch {
out[key] = rawVal;
}
}
return out;
}

export default defineCommand({
name: "mcp call",
description: "Call a tool on an MCP server (tools/call)",
usage: "bl mcp call <server-code>.<tool> [--arg k=v ...] [--json '{...}'] [--url <url>]",
options: [
{
flag: "<server-code>.<tool>",
description:
"Server code and tool name joined by a dot, e.g. market-cmapi00073529.SmartStockSelection",
required: true,
},
{
flag: "--arg <kv>",
description: "Tool argument (repeatable). Values parsed as JSON if possible, else string.",
type: "array",
},
{
flag: "--json <obj>",
description: "Full arguments object as JSON; merged with --arg (arg wins).",
},
{
flag: "--query <text>",
description: "Shortcut for --arg query=<text> (mirrors many DashScope MCP tools).",
},
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
],
examples: [
'bl mcp call market-cmapi00073529.SmartStockSelection --query "筛选ROE>15%的消费股"',
'bl mcp call market-cmapi00073529.FinQuery --json \'{"q":"贵州茅台","limit":5}\'',
"bl mcp call market-cmapi00073529.SmartFundSelection --arg riskLevel=R3 --arg minScale=10",
],
async run(config: Config, flags: GlobalFlags) {
const positional =
((flags as Record<string, unknown>)._positional as string[] | undefined) ?? [];
const target = positional[0];
if (!target) failIfMissing("<server-code>.<tool>", "bl mcp call <server-code>.<tool>");

const dot = target!.indexOf(".");
if (dot <= 0 || dot === target!.length - 1) {
process.stderr.write(`Error: target must be <server-code>.<tool>, got "${target}".\n`);
process.exit(1);
}
const serverCode = target!.slice(0, dot);
const toolName = target!.slice(dot + 1);

let toolArgs: Record<string, unknown> = {};
if (flags.json) {
try {
const parsed = JSON.parse(flags.json as string);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
process.stderr.write("Error: --json must decode to an object.\n");
process.exit(1);
}
toolArgs = parsed as Record<string, unknown>;
} catch (err) {
process.stderr.write(`Error: --json is not valid JSON — ${(err as Error).message}\n`);
process.exit(1);
}
}
Object.assign(toolArgs, parseArgFlags((flags.arg as string[] | undefined) ?? []));
if (flags.query !== undefined) toolArgs.query = flags.query;

const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, serverCode);
const format = detectOutputFormat(config.output);

if (config.dryRun) {
emitResult(
{
server: serverCode,
url,
tool: toolName,
arguments: toolArgs,
},
format,
);
return;
}

await ensureApiKey(config);
const client = new McpClient(config, url);
await client.initialize();
const result = await client.callTool(toolName, toolArgs);

if (result.isError) {
const errText = result.content.map((c) => c.text || "").join("\n");
process.stderr.write(`Tool error: ${errText}\n`);
process.exit(1);
}

emitResult(result, format);
},
});
103 changes: 103 additions & 0 deletions packages/cli/src/commands/mcp/list.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
import {
defineCommand,
callConsoleGateway,
resolveConsoleGatewayCredential,
detectOutputFormat,
BailianError,
ExitCode,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { emitResult } from "../../output/output.ts";

const MCP_LIST_API = "zeldaEasy.broadscope-bailian.mcp-server.PageList";

interface ServerSummary {
code: string;
name: string;
description?: string;
type: string;
source?: string;
bizType?: string;
installType?: string;
streamable: boolean;
}

export default defineCommand({
name: "mcp list",
description: "List MCP servers activated under your Bailian account",
usage: "bl mcp list [flags]",
options: [
{ flag: "--name <text>", description: "Filter by server name (substring match)" },
{
flag: "--type <type>",
description: "Server type: OFFICIAL | PRIVATE (default: OFFICIAL)",
},
{ flag: "--page <n>", description: "Page number (default: 1)", type: "number" },
{ flag: "--page-size <n>", description: "Results per page (default: 30)", type: "number" },
{ flag: "--region <region>", description: "API region (default: cn-beijing)" },
],
examples: ["bl mcp list", "bl mcp list --name 金融", "bl mcp list --output json"],
async run(config: Config, flags: GlobalFlags) {
const serverName = (flags.name as string) || "";
const type = (flags.type as string) || "OFFICIAL";
const pageNo = (flags.page as number) || 1;
const pageSize = (flags.pageSize as number) || 30;
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);

const data = {
reqDTO: {
type,
displayTools: false,
activated: 1,
pageNo,
pageSize,
serverName,
},
};

if (config.dryRun) {
emitResult({ api: MCP_LIST_API, data, region }, format);
return;
}

const credential = await resolveConsoleGatewayCredential(config);

const result = (await callConsoleGateway(config, credential.token, {
api: MCP_LIST_API,
data,
region,
})) as Record<string, unknown>;

const dataField = (result?.data as Record<string, unknown> | undefined) ?? {};
if (dataField.success === false) {
const code = (dataField.errorCode as string | undefined) ?? "UnknownError";
const msg = (dataField.errorMsg as string | undefined) ?? code;
const hint =
code === "BailianGateway.Login.NotLogined"
? "Run `bl auth login --console` to refresh your console session."
: undefined;
throw new BailianError(`Console gateway: ${msg}`, ExitCode.AUTH, hint);
}
const dataV2 = (dataField.DataV2 as Record<string, unknown> | undefined) ?? {};
const inner =
(dataV2.data as { data?: { mcpServerDetailList?: unknown[]; total?: number } } | undefined)
?.data ?? {};
const list = (inner.mcpServerDetailList ?? []) as Array<Record<string, unknown>>;
const total = (inner.total as number) ?? 0;

const servers: ServerSummary[] = list.map((item) => ({
code: (item.serverCode as string | undefined) ?? "",
name: (item.serverName as string | undefined) ?? "",
description: item.description as string | undefined,
type: (item.type as string | undefined) ?? "",
source: item.source as string | undefined,
bizType: item.bizType as string | undefined,
installType: item.installType as string | undefined,
streamable: item.streamable === true,
}));

emitResult({ total, servers }, format);
},
});
50 changes: 50 additions & 0 deletions packages/cli/src/commands/mcp/tools.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
import {
defineCommand,
McpClient,
bailianMcpUrl,
detectOutputFormat,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { failIfMissing } from "../../output/prompt.ts";
import { emitResult } from "../../output/output.ts";
import { ensureApiKey } from "../../utils/ensure-key.ts";

export default defineCommand({
name: "mcp tools",
description: "List tools exposed by an MCP server (tools/list)",
usage: "bl mcp tools <server-code> [--url <url>]",
options: [
{
flag: "<server-code>",
description: "Server code from `bl mcp list` (e.g. market-cmapi00073529)",
required: true,
},
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
],
examples: [
"bl mcp tools market-cmapi00073529",
"bl mcp tools market-cmapi00073529 --output json",
"bl mcp tools my-server --url https://example.com/mcp",
],
async run(config: Config, flags: GlobalFlags) {
const positional =
((flags as Record<string, unknown>)._positional as string[] | undefined) ?? [];
const code = positional[0];
if (!code) failIfMissing("server-code", "bl mcp tools <server-code>");

const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, code!);
const format = detectOutputFormat(config.output);

if (config.dryRun) {
emitResult({ server: code, url, action: "tools/list" }, format);
return;
}

await ensureApiKey(config);
const client = new McpClient(config, url);
await client.initialize();
const tools = await client.listTools();
emitResult({ server: code, url, tools }, format);
},
});
2 changes: 1 addition & 1 deletion packages/cli/src/commands/search/web.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import {
defineCommand,
mcpWebSearchEndpoint,
detectOutputFormat,
mcpWebSearchEndpoint,
type Config,
type GlobalFlags,
isInteractive,
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,9 @@ const NO_AUTH_SETUP = [
["app", "list"],
["console", "call"],
["usage", "free"],
["mcp", "list"],
["mcp", "tools"],
["mcp", "call"],
];

async function main() {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Feat/mcp command by XXPermanentXX · Pull Request #27 · modelstudioai/cli · GitHub
Skip to content
6 changes: 6 additions & 0 deletions packages/cli/src/commands/catalog.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,9 @@ import memoryDelete from "./memory/delete.ts";
import memoryProfileCreate from "./memory/profile-create.ts";
import memoryProfileGet from "./memory/profile-get.ts";
import knowledgeRetrieve from "./knowledge/retrieve.ts";
import mcpCall from "./mcp/call.ts";
import mcpList from "./mcp/list.ts";
import mcpTools from "./mcp/tools.ts";
import searchWeb from "./search/web.ts";
import speechSynthesize from "./speech/synthesize.ts";
import speechRecognize from "./speech/recognize.ts";
Expand DownExpand Up@@ -61,6 +64,9 @@ export const commands: Record<string, Command> = {
"memory profile create": memoryProfileCreate,
"memory profile get": memoryProfileGet,
"knowledge retrieve": knowledgeRetrieve,
"mcp list": mcpList,
"mcp tools": mcpTools,
"mcp call": mcpCall,
"search web": searchWeb,
"speech synthesize": speechSynthesize,
"speech recognize": speechRecognize,
Expand Down
123 changes: 123 additions & 0 deletions packages/cli/src/commands/mcp/call.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
import {
defineCommand,
McpClient,
bailianMcpUrl,
detectOutputFormat,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { failIfMissing } from "../../output/prompt.ts";
import { emitResult } from "../../output/output.ts";
import { ensureApiKey } from "../../utils/ensure-key.ts";

function parseArgFlags(raw: string[]): Record<string, unknown> {
const out: Record<string, unknown> = {};
for (const item of raw) {
const idx = item.indexOf("=");
if (idx <= 0) {
process.stderr.write(`Error: --arg must be in K=V form, got: ${item}\n`);
process.exit(1);
}
const key = item.slice(0, idx).trim();
const rawVal = item.slice(idx + 1);
try {
out[key] = JSON.parse(rawVal);
} catch {
out[key] = rawVal;
}
}
return out;
}

export default defineCommand({
name: "mcp call",
description: "Call a tool on an MCP server (tools/call)",
usage: "bl mcp call <server-code>.<tool> [--arg k=v ...] [--json '{...}'] [--url <url>]",
options: [
{
flag: "<server-code>.<tool>",
description:
"Server code and tool name joined by a dot, e.g. market-cmapi00073529.SmartStockSelection",
required: true,
},
{
flag: "--arg <kv>",
description: "Tool argument (repeatable). Values parsed as JSON if possible, else string.",
type: "array",
},
{
flag: "--json <obj>",
description: "Full arguments object as JSON; merged with --arg (arg wins).",
},
{
flag: "--query <text>",
description: "Shortcut for --arg query=<text> (mirrors many DashScope MCP tools).",
},
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
],
examples: [
'bl mcp call market-cmapi00073529.SmartStockSelection --query "筛选ROE>15%的消费股"',
'bl mcp call market-cmapi00073529.FinQuery --json \'{"q":"贵州茅台","limit":5}\'',
"bl mcp call market-cmapi00073529.SmartFundSelection --arg riskLevel=R3 --arg minScale=10",
],
async run(config: Config, flags: GlobalFlags) {
const positional =
((flags as Record<string, unknown>)._positional as string[] | undefined) ?? [];
const target = positional[0];
if (!target) failIfMissing("<server-code>.<tool>", "bl mcp call <server-code>.<tool>");

const dot = target!.indexOf(".");
if (dot <= 0 || dot === target!.length - 1) {
process.stderr.write(`Error: target must be <server-code>.<tool>, got "${target}".\n`);
process.exit(1);
}
const serverCode = target!.slice(0, dot);
const toolName = target!.slice(dot + 1);

let toolArgs: Record<string, unknown> = {};
if (flags.json) {
try {
const parsed = JSON.parse(flags.json as string);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
process.stderr.write("Error: --json must decode to an object.\n");
process.exit(1);
}
toolArgs = parsed as Record<string, unknown>;
} catch (err) {
process.stderr.write(`Error: --json is not valid JSON — ${(err as Error).message}\n`);
process.exit(1);
}
}
Object.assign(toolArgs, parseArgFlags((flags.arg as string[] | undefined) ?? []));
if (flags.query !== undefined) toolArgs.query = flags.query;

const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, serverCode);
const format = detectOutputFormat(config.output);

if (config.dryRun) {
emitResult(
{
server: serverCode,
url,
tool: toolName,
arguments: toolArgs,
},
format,
);
return;
}

await ensureApiKey(config);
const client = new McpClient(config, url);
await client.initialize();
const result = await client.callTool(toolName, toolArgs);

if (result.isError) {
const errText = result.content.map((c) => c.text || "").join("\n");
process.stderr.write(`Tool error: ${errText}\n`);
process.exit(1);
}

emitResult(result, format);
},
});
103 changes: 103 additions & 0 deletions packages/cli/src/commands/mcp/list.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
import {
defineCommand,
callConsoleGateway,
resolveConsoleGatewayCredential,
detectOutputFormat,
BailianError,
ExitCode,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { emitResult } from "../../output/output.ts";

const MCP_LIST_API = "zeldaEasy.broadscope-bailian.mcp-server.PageList";

interface ServerSummary {
code: string;
name: string;
description?: string;
type: string;
source?: string;
bizType?: string;
installType?: string;
streamable: boolean;
}

export default defineCommand({
name: "mcp list",
description: "List MCP servers activated under your Bailian account",
usage: "bl mcp list [flags]",
options: [
{ flag: "--name <text>", description: "Filter by server name (substring match)" },
{
flag: "--type <type>",
description: "Server type: OFFICIAL | PRIVATE (default: OFFICIAL)",
},
{ flag: "--page <n>", description: "Page number (default: 1)", type: "number" },
{ flag: "--page-size <n>", description: "Results per page (default: 30)", type: "number" },
{ flag: "--region <region>", description: "API region (default: cn-beijing)" },
],
examples: ["bl mcp list", "bl mcp list --name 金融", "bl mcp list --output json"],
async run(config: Config, flags: GlobalFlags) {
const serverName = (flags.name as string) || "";
const type = (flags.type as string) || "OFFICIAL";
const pageNo = (flags.page as number) || 1;
const pageSize = (flags.pageSize as number) || 30;
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);

const data = {
reqDTO: {
type,
displayTools: false,
activated: 1,
pageNo,
pageSize,
serverName,
},
};

if (config.dryRun) {
emitResult({ api: MCP_LIST_API, data, region }, format);
return;
}

const credential = await resolveConsoleGatewayCredential(config);

const result = (await callConsoleGateway(config, credential.token, {
api: MCP_LIST_API,
data,
region,
})) as Record<string, unknown>;

const dataField = (result?.data as Record<string, unknown> | undefined) ?? {};
if (dataField.success === false) {
const code = (dataField.errorCode as string | undefined) ?? "UnknownError";
const msg = (dataField.errorMsg as string | undefined) ?? code;
const hint =
code === "BailianGateway.Login.NotLogined"
? "Run `bl auth login --console` to refresh your console session."
: undefined;
throw new BailianError(`Console gateway: ${msg}`, ExitCode.AUTH, hint);
}
const dataV2 = (dataField.DataV2 as Record<string, unknown> | undefined) ?? {};
const inner =
(dataV2.data as { data?: { mcpServerDetailList?: unknown[]; total?: number } } | undefined)
?.data ?? {};
const list = (inner.mcpServerDetailList ?? []) as Array<Record<string, unknown>>;
const total = (inner.total as number) ?? 0;

const servers: ServerSummary[] = list.map((item) => ({
code: (item.serverCode as string | undefined) ?? "",
name: (item.serverName as string | undefined) ?? "",
description: item.description as string | undefined,
type: (item.type as string | undefined) ?? "",
source: item.source as string | undefined,
bizType: item.bizType as string | undefined,
installType: item.installType as string | undefined,
streamable: item.streamable === true,
}));

emitResult({ total, servers }, format);
},
});
50 changes: 50 additions & 0 deletions packages/cli/src/commands/mcp/tools.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
import {
defineCommand,
McpClient,
bailianMcpUrl,
detectOutputFormat,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { failIfMissing } from "../../output/prompt.ts";
import { emitResult } from "../../output/output.ts";
import { ensureApiKey } from "../../utils/ensure-key.ts";

export default defineCommand({
name: "mcp tools",
description: "List tools exposed by an MCP server (tools/list)",
usage: "bl mcp tools <server-code> [--url <url>]",
options: [
{
flag: "<server-code>",
description: "Server code from `bl mcp list` (e.g. market-cmapi00073529)",
required: true,
},
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
],
examples: [
"bl mcp tools market-cmapi00073529",
"bl mcp tools market-cmapi00073529 --output json",
"bl mcp tools my-server --url https://example.com/mcp",
],
async run(config: Config, flags: GlobalFlags) {
const positional =
((flags as Record<string, unknown>)._positional as string[] | undefined) ?? [];
const code = positional[0];
if (!code) failIfMissing("server-code", "bl mcp tools <server-code>");

const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, code!);
const format = detectOutputFormat(config.output);

if (config.dryRun) {
emitResult({ server: code, url, action: "tools/list" }, format);
return;
}

await ensureApiKey(config);
const client = new McpClient(config, url);
await client.initialize();
const tools = await client.listTools();
emitResult({ server: code, url, tools }, format);
},
});
2 changes: 1 addition & 1 deletion packages/cli/src/commands/search/web.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import {
defineCommand,
mcpWebSearchEndpoint,
detectOutputFormat,
mcpWebSearchEndpoint,
type Config,
type GlobalFlags,
isInteractive,
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,9 @@ const NO_AUTH_SETUP = [
["app", "list"],
["console", "call"],
["usage", "free"],
["mcp", "list"],
["mcp", "tools"],
["mcp", "call"],
];

async function main() {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Feat/mcp command by XXPermanentXX · Pull Request #27 · modelstudioai/cli · GitHub
Skip to content
6 changes: 6 additions & 0 deletions packages/cli/src/commands/catalog.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,9 @@ import memoryDelete from "./memory/delete.ts";
import memoryProfileCreate from "./memory/profile-create.ts";
import memoryProfileGet from "./memory/profile-get.ts";
import knowledgeRetrieve from "./knowledge/retrieve.ts";
import mcpCall from "./mcp/call.ts";
import mcpList from "./mcp/list.ts";
import mcpTools from "./mcp/tools.ts";
import searchWeb from "./search/web.ts";
import speechSynthesize from "./speech/synthesize.ts";
import speechRecognize from "./speech/recognize.ts";
Expand DownExpand Up@@ -61,6 +64,9 @@ export const commands: Record<string, Command> = {
"memory profile create": memoryProfileCreate,
"memory profile get": memoryProfileGet,
"knowledge retrieve": knowledgeRetrieve,
"mcp list": mcpList,
"mcp tools": mcpTools,
"mcp call": mcpCall,
"search web": searchWeb,
"speech synthesize": speechSynthesize,
"speech recognize": speechRecognize,
Expand Down
123 changes: 123 additions & 0 deletions packages/cli/src/commands/mcp/call.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
import {
defineCommand,
McpClient,
bailianMcpUrl,
detectOutputFormat,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { failIfMissing } from "../../output/prompt.ts";
import { emitResult } from "../../output/output.ts";
import { ensureApiKey } from "../../utils/ensure-key.ts";

function parseArgFlags(raw: string[]): Record<string, unknown> {
const out: Record<string, unknown> = {};
for (const item of raw) {
const idx = item.indexOf("=");
if (idx <= 0) {
process.stderr.write(`Error: --arg must be in K=V form, got: ${item}\n`);
process.exit(1);
}
const key = item.slice(0, idx).trim();
const rawVal = item.slice(idx + 1);
try {
out[key] = JSON.parse(rawVal);
} catch {
out[key] = rawVal;
}
}
return out;
}

export default defineCommand({
name: "mcp call",
description: "Call a tool on an MCP server (tools/call)",
usage: "bl mcp call <server-code>.<tool> [--arg k=v ...] [--json '{...}'] [--url <url>]",
options: [
{
flag: "<server-code>.<tool>",
description:
"Server code and tool name joined by a dot, e.g. market-cmapi00073529.SmartStockSelection",
required: true,
},
{
flag: "--arg <kv>",
description: "Tool argument (repeatable). Values parsed as JSON if possible, else string.",
type: "array",
},
{
flag: "--json <obj>",
description: "Full arguments object as JSON; merged with --arg (arg wins).",
},
{
flag: "--query <text>",
description: "Shortcut for --arg query=<text> (mirrors many DashScope MCP tools).",
},
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
],
examples: [
'bl mcp call market-cmapi00073529.SmartStockSelection --query "筛选ROE>15%的消费股"',
'bl mcp call market-cmapi00073529.FinQuery --json \'{"q":"贵州茅台","limit":5}\'',
"bl mcp call market-cmapi00073529.SmartFundSelection --arg riskLevel=R3 --arg minScale=10",
],
async run(config: Config, flags: GlobalFlags) {
const positional =
((flags as Record<string, unknown>)._positional as string[] | undefined) ?? [];
const target = positional[0];
if (!target) failIfMissing("<server-code>.<tool>", "bl mcp call <server-code>.<tool>");

const dot = target!.indexOf(".");
if (dot <= 0 || dot === target!.length - 1) {
process.stderr.write(`Error: target must be <server-code>.<tool>, got "${target}".\n`);
process.exit(1);
}
const serverCode = target!.slice(0, dot);
const toolName = target!.slice(dot + 1);

let toolArgs: Record<string, unknown> = {};
if (flags.json) {
try {
const parsed = JSON.parse(flags.json as string);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
process.stderr.write("Error: --json must decode to an object.\n");
process.exit(1);
}
toolArgs = parsed as Record<string, unknown>;
} catch (err) {
process.stderr.write(`Error: --json is not valid JSON — ${(err as Error).message}\n`);
process.exit(1);
}
}
Object.assign(toolArgs, parseArgFlags((flags.arg as string[] | undefined) ?? []));
if (flags.query !== undefined) toolArgs.query = flags.query;

const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, serverCode);
const format = detectOutputFormat(config.output);

if (config.dryRun) {
emitResult(
{
server: serverCode,
url,
tool: toolName,
arguments: toolArgs,
},
format,
);
return;
}

await ensureApiKey(config);
const client = new McpClient(config, url);
await client.initialize();
const result = await client.callTool(toolName, toolArgs);

if (result.isError) {
const errText = result.content.map((c) => c.text || "").join("\n");
process.stderr.write(`Tool error: ${errText}\n`);
process.exit(1);
}

emitResult(result, format);
},
});
103 changes: 103 additions & 0 deletions packages/cli/src/commands/mcp/list.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
import {
defineCommand,
callConsoleGateway,
resolveConsoleGatewayCredential,
detectOutputFormat,
BailianError,
ExitCode,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { emitResult } from "../../output/output.ts";

const MCP_LIST_API = "zeldaEasy.broadscope-bailian.mcp-server.PageList";

interface ServerSummary {
code: string;
name: string;
description?: string;
type: string;
source?: string;
bizType?: string;
installType?: string;
streamable: boolean;
}

export default defineCommand({
name: "mcp list",
description: "List MCP servers activated under your Bailian account",
usage: "bl mcp list [flags]",
options: [
{ flag: "--name <text>", description: "Filter by server name (substring match)" },
{
flag: "--type <type>",
description: "Server type: OFFICIAL | PRIVATE (default: OFFICIAL)",
},
{ flag: "--page <n>", description: "Page number (default: 1)", type: "number" },
{ flag: "--page-size <n>", description: "Results per page (default: 30)", type: "number" },
{ flag: "--region <region>", description: "API region (default: cn-beijing)" },
],
examples: ["bl mcp list", "bl mcp list --name 金融", "bl mcp list --output json"],
async run(config: Config, flags: GlobalFlags) {
const serverName = (flags.name as string) || "";
const type = (flags.type as string) || "OFFICIAL";
const pageNo = (flags.page as number) || 1;
const pageSize = (flags.pageSize as number) || 30;
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);

const data = {
reqDTO: {
type,
displayTools: false,
activated: 1,
pageNo,
pageSize,
serverName,
},
};

if (config.dryRun) {
emitResult({ api: MCP_LIST_API, data, region }, format);
return;
}

const credential = await resolveConsoleGatewayCredential(config);

const result = (await callConsoleGateway(config, credential.token, {
api: MCP_LIST_API,
data,
region,
})) as Record<string, unknown>;

const dataField = (result?.data as Record<string, unknown> | undefined) ?? {};
if (dataField.success === false) {
const code = (dataField.errorCode as string | undefined) ?? "UnknownError";
const msg = (dataField.errorMsg as string | undefined) ?? code;
const hint =
code === "BailianGateway.Login.NotLogined"
? "Run `bl auth login --console` to refresh your console session."
: undefined;
throw new BailianError(`Console gateway: ${msg}`, ExitCode.AUTH, hint);
}
const dataV2 = (dataField.DataV2 as Record<string, unknown> | undefined) ?? {};
const inner =
(dataV2.data as { data?: { mcpServerDetailList?: unknown[]; total?: number } } | undefined)
?.data ?? {};
const list = (inner.mcpServerDetailList ?? []) as Array<Record<string, unknown>>;
const total = (inner.total as number) ?? 0;

const servers: ServerSummary[] = list.map((item) => ({
code: (item.serverCode as string | undefined) ?? "",
name: (item.serverName as string | undefined) ?? "",
description: item.description as string | undefined,
type: (item.type as string | undefined) ?? "",
source: item.source as string | undefined,
bizType: item.bizType as string | undefined,
installType: item.installType as string | undefined,
streamable: item.streamable === true,
}));

emitResult({ total, servers }, format);
},
});
50 changes: 50 additions & 0 deletions packages/cli/src/commands/mcp/tools.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
import {
defineCommand,
McpClient,
bailianMcpUrl,
detectOutputFormat,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { failIfMissing } from "../../output/prompt.ts";
import { emitResult } from "../../output/output.ts";
import { ensureApiKey } from "../../utils/ensure-key.ts";

export default defineCommand({
name: "mcp tools",
description: "List tools exposed by an MCP server (tools/list)",
usage: "bl mcp tools <server-code> [--url <url>]",
options: [
{
flag: "<server-code>",
description: "Server code from `bl mcp list` (e.g. market-cmapi00073529)",
required: true,
},
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
],
examples: [
"bl mcp tools market-cmapi00073529",
"bl mcp tools market-cmapi00073529 --output json",
"bl mcp tools my-server --url https://example.com/mcp",
],
async run(config: Config, flags: GlobalFlags) {
const positional =
((flags as Record<string, unknown>)._positional as string[] | undefined) ?? [];
const code = positional[0];
if (!code) failIfMissing("server-code", "bl mcp tools <server-code>");

const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, code!);
const format = detectOutputFormat(config.output);

if (config.dryRun) {
emitResult({ server: code, url, action: "tools/list" }, format);
return;
}

await ensureApiKey(config);
const client = new McpClient(config, url);
await client.initialize();
const tools = await client.listTools();
emitResult({ server: code, url, tools }, format);
},
});
2 changes: 1 addition & 1 deletion packages/cli/src/commands/search/web.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import {
defineCommand,
mcpWebSearchEndpoint,
detectOutputFormat,
mcpWebSearchEndpoint,
type Config,
type GlobalFlags,
isInteractive,
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,9 @@ const NO_AUTH_SETUP = [
["app", "list"],
["console", "call"],
["usage", "free"],
["mcp", "list"],
["mcp", "tools"],
["mcp", "call"],
];

async function main() {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Feat/mcp command by XXPermanentXX · Pull Request #27 · modelstudioai/cli · GitHub
Skip to content
6 changes: 6 additions & 0 deletions packages/cli/src/commands/catalog.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,9 @@ import memoryDelete from "./memory/delete.ts";
import memoryProfileCreate from "./memory/profile-create.ts";
import memoryProfileGet from "./memory/profile-get.ts";
import knowledgeRetrieve from "./knowledge/retrieve.ts";
import mcpCall from "./mcp/call.ts";
import mcpList from "./mcp/list.ts";
import mcpTools from "./mcp/tools.ts";
import searchWeb from "./search/web.ts";
import speechSynthesize from "./speech/synthesize.ts";
import speechRecognize from "./speech/recognize.ts";
Expand DownExpand Up@@ -61,6 +64,9 @@ export const commands: Record<string, Command> = {
"memory profile create": memoryProfileCreate,
"memory profile get": memoryProfileGet,
"knowledge retrieve": knowledgeRetrieve,
"mcp list": mcpList,
"mcp tools": mcpTools,
"mcp call": mcpCall,
"search web": searchWeb,
"speech synthesize": speechSynthesize,
"speech recognize": speechRecognize,
Expand Down
123 changes: 123 additions & 0 deletions packages/cli/src/commands/mcp/call.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
import {
defineCommand,
McpClient,
bailianMcpUrl,
detectOutputFormat,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { failIfMissing } from "../../output/prompt.ts";
import { emitResult } from "../../output/output.ts";
import { ensureApiKey } from "../../utils/ensure-key.ts";

function parseArgFlags(raw: string[]): Record<string, unknown> {
const out: Record<string, unknown> = {};
for (const item of raw) {
const idx = item.indexOf("=");
if (idx <= 0) {
process.stderr.write(`Error: --arg must be in K=V form, got: ${item}\n`);
process.exit(1);
}
const key = item.slice(0, idx).trim();
const rawVal = item.slice(idx + 1);
try {
out[key] = JSON.parse(rawVal);
} catch {
out[key] = rawVal;
}
}
return out;
}

export default defineCommand({
name: "mcp call",
description: "Call a tool on an MCP server (tools/call)",
usage: "bl mcp call <server-code>.<tool> [--arg k=v ...] [--json '{...}'] [--url <url>]",
options: [
{
flag: "<server-code>.<tool>",
description:
"Server code and tool name joined by a dot, e.g. market-cmapi00073529.SmartStockSelection",
required: true,
},
{
flag: "--arg <kv>",
description: "Tool argument (repeatable). Values parsed as JSON if possible, else string.",
type: "array",
},
{
flag: "--json <obj>",
description: "Full arguments object as JSON; merged with --arg (arg wins).",
},
{
flag: "--query <text>",
description: "Shortcut for --arg query=<text> (mirrors many DashScope MCP tools).",
},
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
],
examples: [
'bl mcp call market-cmapi00073529.SmartStockSelection --query "筛选ROE>15%的消费股"',
'bl mcp call market-cmapi00073529.FinQuery --json \'{"q":"贵州茅台","limit":5}\'',
"bl mcp call market-cmapi00073529.SmartFundSelection --arg riskLevel=R3 --arg minScale=10",
],
async run(config: Config, flags: GlobalFlags) {
const positional =
((flags as Record<string, unknown>)._positional as string[] | undefined) ?? [];
const target = positional[0];
if (!target) failIfMissing("<server-code>.<tool>", "bl mcp call <server-code>.<tool>");

const dot = target!.indexOf(".");
if (dot <= 0 || dot === target!.length - 1) {
process.stderr.write(`Error: target must be <server-code>.<tool>, got "${target}".\n`);
process.exit(1);
}
const serverCode = target!.slice(0, dot);
const toolName = target!.slice(dot + 1);

let toolArgs: Record<string, unknown> = {};
if (flags.json) {
try {
const parsed = JSON.parse(flags.json as string);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
process.stderr.write("Error: --json must decode to an object.\n");
process.exit(1);
}
toolArgs = parsed as Record<string, unknown>;
} catch (err) {
process.stderr.write(`Error: --json is not valid JSON — ${(err as Error).message}\n`);
process.exit(1);
}
}
Object.assign(toolArgs, parseArgFlags((flags.arg as string[] | undefined) ?? []));
if (flags.query !== undefined) toolArgs.query = flags.query;

const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, serverCode);
const format = detectOutputFormat(config.output);

if (config.dryRun) {
emitResult(
{
server: serverCode,
url,
tool: toolName,
arguments: toolArgs,
},
format,
);
return;
}

await ensureApiKey(config);
const client = new McpClient(config, url);
await client.initialize();
const result = await client.callTool(toolName, toolArgs);

if (result.isError) {
const errText = result.content.map((c) => c.text || "").join("\n");
process.stderr.write(`Tool error: ${errText}\n`);
process.exit(1);
}

emitResult(result, format);
},
});
103 changes: 103 additions & 0 deletions packages/cli/src/commands/mcp/list.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
import {
defineCommand,
callConsoleGateway,
resolveConsoleGatewayCredential,
detectOutputFormat,
BailianError,
ExitCode,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { emitResult } from "../../output/output.ts";

const MCP_LIST_API = "zeldaEasy.broadscope-bailian.mcp-server.PageList";

interface ServerSummary {
code: string;
name: string;
description?: string;
type: string;
source?: string;
bizType?: string;
installType?: string;
streamable: boolean;
}

export default defineCommand({
name: "mcp list",
description: "List MCP servers activated under your Bailian account",
usage: "bl mcp list [flags]",
options: [
{ flag: "--name <text>", description: "Filter by server name (substring match)" },
{
flag: "--type <type>",
description: "Server type: OFFICIAL | PRIVATE (default: OFFICIAL)",
},
{ flag: "--page <n>", description: "Page number (default: 1)", type: "number" },
{ flag: "--page-size <n>", description: "Results per page (default: 30)", type: "number" },
{ flag: "--region <region>", description: "API region (default: cn-beijing)" },
],
examples: ["bl mcp list", "bl mcp list --name 金融", "bl mcp list --output json"],
async run(config: Config, flags: GlobalFlags) {
const serverName = (flags.name as string) || "";
const type = (flags.type as string) || "OFFICIAL";
const pageNo = (flags.page as number) || 1;
const pageSize = (flags.pageSize as number) || 30;
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);

const data = {
reqDTO: {
type,
displayTools: false,
activated: 1,
pageNo,
pageSize,
serverName,
},
};

if (config.dryRun) {
emitResult({ api: MCP_LIST_API, data, region }, format);
return;
}

const credential = await resolveConsoleGatewayCredential(config);

const result = (await callConsoleGateway(config, credential.token, {
api: MCP_LIST_API,
data,
region,
})) as Record<string, unknown>;

const dataField = (result?.data as Record<string, unknown> | undefined) ?? {};
if (dataField.success === false) {
const code = (dataField.errorCode as string | undefined) ?? "UnknownError";
const msg = (dataField.errorMsg as string | undefined) ?? code;
const hint =
code === "BailianGateway.Login.NotLogined"
? "Run `bl auth login --console` to refresh your console session."
: undefined;
throw new BailianError(`Console gateway: ${msg}`, ExitCode.AUTH, hint);
}
const dataV2 = (dataField.DataV2 as Record<string, unknown> | undefined) ?? {};
const inner =
(dataV2.data as { data?: { mcpServerDetailList?: unknown[]; total?: number } } | undefined)
?.data ?? {};
const list = (inner.mcpServerDetailList ?? []) as Array<Record<string, unknown>>;
const total = (inner.total as number) ?? 0;

const servers: ServerSummary[] = list.map((item) => ({
code: (item.serverCode as string | undefined) ?? "",
name: (item.serverName as string | undefined) ?? "",
description: item.description as string | undefined,
type: (item.type as string | undefined) ?? "",
source: item.source as string | undefined,
bizType: item.bizType as string | undefined,
installType: item.installType as string | undefined,
streamable: item.streamable === true,
}));

emitResult({ total, servers }, format);
},
});
50 changes: 50 additions & 0 deletions packages/cli/src/commands/mcp/tools.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
import {
defineCommand,
McpClient,
bailianMcpUrl,
detectOutputFormat,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { failIfMissing } from "../../output/prompt.ts";
import { emitResult } from "../../output/output.ts";
import { ensureApiKey } from "../../utils/ensure-key.ts";

export default defineCommand({
name: "mcp tools",
description: "List tools exposed by an MCP server (tools/list)",
usage: "bl mcp tools <server-code> [--url <url>]",
options: [
{
flag: "<server-code>",
description: "Server code from `bl mcp list` (e.g. market-cmapi00073529)",
required: true,
},
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
],
examples: [
"bl mcp tools market-cmapi00073529",
"bl mcp tools market-cmapi00073529 --output json",
"bl mcp tools my-server --url https://example.com/mcp",
],
async run(config: Config, flags: GlobalFlags) {
const positional =
((flags as Record<string, unknown>)._positional as string[] | undefined) ?? [];
const code = positional[0];
if (!code) failIfMissing("server-code", "bl mcp tools <server-code>");

const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, code!);
const format = detectOutputFormat(config.output);

if (config.dryRun) {
emitResult({ server: code, url, action: "tools/list" }, format);
return;
}

await ensureApiKey(config);
const client = new McpClient(config, url);
await client.initialize();
const tools = await client.listTools();
emitResult({ server: code, url, tools }, format);
},
});
2 changes: 1 addition & 1 deletion packages/cli/src/commands/search/web.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import {
defineCommand,
mcpWebSearchEndpoint,
detectOutputFormat,
mcpWebSearchEndpoint,
type Config,
type GlobalFlags,
isInteractive,
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,9 @@ const NO_AUTH_SETUP = [
["app", "list"],
["console", "call"],
["usage", "free"],
["mcp", "list"],
["mcp", "tools"],
["mcp", "call"],
];

async function main() {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Feat/mcp command by XXPermanentXX · Pull Request #27 · modelstudioai/cli · GitHub
Skip to content
6 changes: 6 additions & 0 deletions packages/cli/src/commands/catalog.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,9 @@ import memoryDelete from "./memory/delete.ts";
import memoryProfileCreate from "./memory/profile-create.ts";
import memoryProfileGet from "./memory/profile-get.ts";
import knowledgeRetrieve from "./knowledge/retrieve.ts";
import mcpCall from "./mcp/call.ts";
import mcpList from "./mcp/list.ts";
import mcpTools from "./mcp/tools.ts";
import searchWeb from "./search/web.ts";
import speechSynthesize from "./speech/synthesize.ts";
import speechRecognize from "./speech/recognize.ts";
Expand DownExpand Up@@ -61,6 +64,9 @@ export const commands: Record<string, Command> = {
"memory profile create": memoryProfileCreate,
"memory profile get": memoryProfileGet,
"knowledge retrieve": knowledgeRetrieve,
"mcp list": mcpList,
"mcp tools": mcpTools,
"mcp call": mcpCall,
"search web": searchWeb,
"speech synthesize": speechSynthesize,
"speech recognize": speechRecognize,
Expand Down
123 changes: 123 additions & 0 deletions packages/cli/src/commands/mcp/call.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
import {
defineCommand,
McpClient,
bailianMcpUrl,
detectOutputFormat,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { failIfMissing } from "../../output/prompt.ts";
import { emitResult } from "../../output/output.ts";
import { ensureApiKey } from "../../utils/ensure-key.ts";

function parseArgFlags(raw: string[]): Record<string, unknown> {
const out: Record<string, unknown> = {};
for (const item of raw) {
const idx = item.indexOf("=");
if (idx <= 0) {
process.stderr.write(`Error: --arg must be in K=V form, got: ${item}\n`);
process.exit(1);
}
const key = item.slice(0, idx).trim();
const rawVal = item.slice(idx + 1);
try {
out[key] = JSON.parse(rawVal);
} catch {
out[key] = rawVal;
}
}
return out;
}

export default defineCommand({
name: "mcp call",
description: "Call a tool on an MCP server (tools/call)",
usage: "bl mcp call <server-code>.<tool> [--arg k=v ...] [--json '{...}'] [--url <url>]",
options: [
{
flag: "<server-code>.<tool>",
description:
"Server code and tool name joined by a dot, e.g. market-cmapi00073529.SmartStockSelection",
required: true,
},
{
flag: "--arg <kv>",
description: "Tool argument (repeatable). Values parsed as JSON if possible, else string.",
type: "array",
},
{
flag: "--json <obj>",
description: "Full arguments object as JSON; merged with --arg (arg wins).",
},
{
flag: "--query <text>",
description: "Shortcut for --arg query=<text> (mirrors many DashScope MCP tools).",
},
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
],
examples: [
'bl mcp call market-cmapi00073529.SmartStockSelection --query "筛选ROE>15%的消费股"',
'bl mcp call market-cmapi00073529.FinQuery --json \'{"q":"贵州茅台","limit":5}\'',
"bl mcp call market-cmapi00073529.SmartFundSelection --arg riskLevel=R3 --arg minScale=10",
],
async run(config: Config, flags: GlobalFlags) {
const positional =
((flags as Record<string, unknown>)._positional as string[] | undefined) ?? [];
const target = positional[0];
if (!target) failIfMissing("<server-code>.<tool>", "bl mcp call <server-code>.<tool>");

const dot = target!.indexOf(".");
if (dot <= 0 || dot === target!.length - 1) {
process.stderr.write(`Error: target must be <server-code>.<tool>, got "${target}".\n`);
process.exit(1);
}
const serverCode = target!.slice(0, dot);
const toolName = target!.slice(dot + 1);

let toolArgs: Record<string, unknown> = {};
if (flags.json) {
try {
const parsed = JSON.parse(flags.json as string);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
process.stderr.write("Error: --json must decode to an object.\n");
process.exit(1);
}
toolArgs = parsed as Record<string, unknown>;
} catch (err) {
process.stderr.write(`Error: --json is not valid JSON — ${(err as Error).message}\n`);
process.exit(1);
}
}
Object.assign(toolArgs, parseArgFlags((flags.arg as string[] | undefined) ?? []));
if (flags.query !== undefined) toolArgs.query = flags.query;

const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, serverCode);
const format = detectOutputFormat(config.output);

if (config.dryRun) {
emitResult(
{
server: serverCode,
url,
tool: toolName,
arguments: toolArgs,
},
format,
);
return;
}

await ensureApiKey(config);
const client = new McpClient(config, url);
await client.initialize();
const result = await client.callTool(toolName, toolArgs);

if (result.isError) {
const errText = result.content.map((c) => c.text || "").join("\n");
process.stderr.write(`Tool error: ${errText}\n`);
process.exit(1);
}

emitResult(result, format);
},
});
103 changes: 103 additions & 0 deletions packages/cli/src/commands/mcp/list.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
import {
defineCommand,
callConsoleGateway,
resolveConsoleGatewayCredential,
detectOutputFormat,
BailianError,
ExitCode,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { emitResult } from "../../output/output.ts";

const MCP_LIST_API = "zeldaEasy.broadscope-bailian.mcp-server.PageList";

interface ServerSummary {
code: string;
name: string;
description?: string;
type: string;
source?: string;
bizType?: string;
installType?: string;
streamable: boolean;
}

export default defineCommand({
name: "mcp list",
description: "List MCP servers activated under your Bailian account",
usage: "bl mcp list [flags]",
options: [
{ flag: "--name <text>", description: "Filter by server name (substring match)" },
{
flag: "--type <type>",
description: "Server type: OFFICIAL | PRIVATE (default: OFFICIAL)",
},
{ flag: "--page <n>", description: "Page number (default: 1)", type: "number" },
{ flag: "--page-size <n>", description: "Results per page (default: 30)", type: "number" },
{ flag: "--region <region>", description: "API region (default: cn-beijing)" },
],
examples: ["bl mcp list", "bl mcp list --name 金融", "bl mcp list --output json"],
async run(config: Config, flags: GlobalFlags) {
const serverName = (flags.name as string) || "";
const type = (flags.type as string) || "OFFICIAL";
const pageNo = (flags.page as number) || 1;
const pageSize = (flags.pageSize as number) || 30;
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);

const data = {
reqDTO: {
type,
displayTools: false,
activated: 1,
pageNo,
pageSize,
serverName,
},
};

if (config.dryRun) {
emitResult({ api: MCP_LIST_API, data, region }, format);
return;
}

const credential = await resolveConsoleGatewayCredential(config);

const result = (await callConsoleGateway(config, credential.token, {
api: MCP_LIST_API,
data,
region,
})) as Record<string, unknown>;

const dataField = (result?.data as Record<string, unknown> | undefined) ?? {};
if (dataField.success === false) {
const code = (dataField.errorCode as string | undefined) ?? "UnknownError";
const msg = (dataField.errorMsg as string | undefined) ?? code;
const hint =
code === "BailianGateway.Login.NotLogined"
? "Run `bl auth login --console` to refresh your console session."
: undefined;
throw new BailianError(`Console gateway: ${msg}`, ExitCode.AUTH, hint);
}
const dataV2 = (dataField.DataV2 as Record<string, unknown> | undefined) ?? {};
const inner =
(dataV2.data as { data?: { mcpServerDetailList?: unknown[]; total?: number } } | undefined)
?.data ?? {};
const list = (inner.mcpServerDetailList ?? []) as Array<Record<string, unknown>>;
const total = (inner.total as number) ?? 0;

const servers: ServerSummary[] = list.map((item) => ({
code: (item.serverCode as string | undefined) ?? "",
name: (item.serverName as string | undefined) ?? "",
description: item.description as string | undefined,
type: (item.type as string | undefined) ?? "",
source: item.source as string | undefined,
bizType: item.bizType as string | undefined,
installType: item.installType as string | undefined,
streamable: item.streamable === true,
}));

emitResult({ total, servers }, format);
},
});
50 changes: 50 additions & 0 deletions packages/cli/src/commands/mcp/tools.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
import {
defineCommand,
McpClient,
bailianMcpUrl,
detectOutputFormat,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { failIfMissing } from "../../output/prompt.ts";
import { emitResult } from "../../output/output.ts";
import { ensureApiKey } from "../../utils/ensure-key.ts";

export default defineCommand({
name: "mcp tools",
description: "List tools exposed by an MCP server (tools/list)",
usage: "bl mcp tools <server-code> [--url <url>]",
options: [
{
flag: "<server-code>",
description: "Server code from `bl mcp list` (e.g. market-cmapi00073529)",
required: true,
},
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
],
examples: [
"bl mcp tools market-cmapi00073529",
"bl mcp tools market-cmapi00073529 --output json",
"bl mcp tools my-server --url https://example.com/mcp",
],
async run(config: Config, flags: GlobalFlags) {
const positional =
((flags as Record<string, unknown>)._positional as string[] | undefined) ?? [];
const code = positional[0];
if (!code) failIfMissing("server-code", "bl mcp tools <server-code>");

const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, code!);
const format = detectOutputFormat(config.output);

if (config.dryRun) {
emitResult({ server: code, url, action: "tools/list" }, format);
return;
}

await ensureApiKey(config);
const client = new McpClient(config, url);
await client.initialize();
const tools = await client.listTools();
emitResult({ server: code, url, tools }, format);
},
});
2 changes: 1 addition & 1 deletion packages/cli/src/commands/search/web.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import {
defineCommand,
mcpWebSearchEndpoint,
detectOutputFormat,
mcpWebSearchEndpoint,
type Config,
type GlobalFlags,
isInteractive,
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,9 @@ const NO_AUTH_SETUP = [
["app", "list"],
["console", "call"],
["usage", "free"],
["mcp", "list"],
["mcp", "tools"],
["mcp", "call"],
];

async function main() {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Feat/mcp command by XXPermanentXX · Pull Request #27 · modelstudioai/cli · GitHub
Skip to content
6 changes: 6 additions & 0 deletions packages/cli/src/commands/catalog.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,9 @@ import memoryDelete from "./memory/delete.ts";
import memoryProfileCreate from "./memory/profile-create.ts";
import memoryProfileGet from "./memory/profile-get.ts";
import knowledgeRetrieve from "./knowledge/retrieve.ts";
import mcpCall from "./mcp/call.ts";
import mcpList from "./mcp/list.ts";
import mcpTools from "./mcp/tools.ts";
import searchWeb from "./search/web.ts";
import speechSynthesize from "./speech/synthesize.ts";
import speechRecognize from "./speech/recognize.ts";
Expand DownExpand Up@@ -61,6 +64,9 @@ export const commands: Record<string, Command> = {
"memory profile create": memoryProfileCreate,
"memory profile get": memoryProfileGet,
"knowledge retrieve": knowledgeRetrieve,
"mcp list": mcpList,
"mcp tools": mcpTools,
"mcp call": mcpCall,
"search web": searchWeb,
"speech synthesize": speechSynthesize,
"speech recognize": speechRecognize,
Expand Down
123 changes: 123 additions & 0 deletions packages/cli/src/commands/mcp/call.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
import {
defineCommand,
McpClient,
bailianMcpUrl,
detectOutputFormat,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { failIfMissing } from "../../output/prompt.ts";
import { emitResult } from "../../output/output.ts";
import { ensureApiKey } from "../../utils/ensure-key.ts";

function parseArgFlags(raw: string[]): Record<string, unknown> {
const out: Record<string, unknown> = {};
for (const item of raw) {
const idx = item.indexOf("=");
if (idx <= 0) {
process.stderr.write(`Error: --arg must be in K=V form, got: ${item}\n`);
process.exit(1);
}
const key = item.slice(0, idx).trim();
const rawVal = item.slice(idx + 1);
try {
out[key] = JSON.parse(rawVal);
} catch {
out[key] = rawVal;
}
}
return out;
}

export default defineCommand({
name: "mcp call",
description: "Call a tool on an MCP server (tools/call)",
usage: "bl mcp call <server-code>.<tool> [--arg k=v ...] [--json '{...}'] [--url <url>]",
options: [
{
flag: "<server-code>.<tool>",
description:
"Server code and tool name joined by a dot, e.g. market-cmapi00073529.SmartStockSelection",
required: true,
},
{
flag: "--arg <kv>",
description: "Tool argument (repeatable). Values parsed as JSON if possible, else string.",
type: "array",
},
{
flag: "--json <obj>",
description: "Full arguments object as JSON; merged with --arg (arg wins).",
},
{
flag: "--query <text>",
description: "Shortcut for --arg query=<text> (mirrors many DashScope MCP tools).",
},
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
],
examples: [
'bl mcp call market-cmapi00073529.SmartStockSelection --query "筛选ROE>15%的消费股"',
'bl mcp call market-cmapi00073529.FinQuery --json \'{"q":"贵州茅台","limit":5}\'',
"bl mcp call market-cmapi00073529.SmartFundSelection --arg riskLevel=R3 --arg minScale=10",
],
async run(config: Config, flags: GlobalFlags) {
const positional =
((flags as Record<string, unknown>)._positional as string[] | undefined) ?? [];
const target = positional[0];
if (!target) failIfMissing("<server-code>.<tool>", "bl mcp call <server-code>.<tool>");

const dot = target!.indexOf(".");
if (dot <= 0 || dot === target!.length - 1) {
process.stderr.write(`Error: target must be <server-code>.<tool>, got "${target}".\n`);
process.exit(1);
}
const serverCode = target!.slice(0, dot);
const toolName = target!.slice(dot + 1);

let toolArgs: Record<string, unknown> = {};
if (flags.json) {
try {
const parsed = JSON.parse(flags.json as string);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
process.stderr.write("Error: --json must decode to an object.\n");
process.exit(1);
}
toolArgs = parsed as Record<string, unknown>;
} catch (err) {
process.stderr.write(`Error: --json is not valid JSON — ${(err as Error).message}\n`);
process.exit(1);
}
}
Object.assign(toolArgs, parseArgFlags((flags.arg as string[] | undefined) ?? []));
if (flags.query !== undefined) toolArgs.query = flags.query;

const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, serverCode);
const format = detectOutputFormat(config.output);

if (config.dryRun) {
emitResult(
{
server: serverCode,
url,
tool: toolName,
arguments: toolArgs,
},
format,
);
return;
}

await ensureApiKey(config);
const client = new McpClient(config, url);
await client.initialize();
const result = await client.callTool(toolName, toolArgs);

if (result.isError) {
const errText = result.content.map((c) => c.text || "").join("\n");
process.stderr.write(`Tool error: ${errText}\n`);
process.exit(1);
}

emitResult(result, format);
},
});
103 changes: 103 additions & 0 deletions packages/cli/src/commands/mcp/list.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
import {
defineCommand,
callConsoleGateway,
resolveConsoleGatewayCredential,
detectOutputFormat,
BailianError,
ExitCode,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { emitResult } from "../../output/output.ts";

const MCP_LIST_API = "zeldaEasy.broadscope-bailian.mcp-server.PageList";

interface ServerSummary {
code: string;
name: string;
description?: string;
type: string;
source?: string;
bizType?: string;
installType?: string;
streamable: boolean;
}

export default defineCommand({
name: "mcp list",
description: "List MCP servers activated under your Bailian account",
usage: "bl mcp list [flags]",
options: [
{ flag: "--name <text>", description: "Filter by server name (substring match)" },
{
flag: "--type <type>",
description: "Server type: OFFICIAL | PRIVATE (default: OFFICIAL)",
},
{ flag: "--page <n>", description: "Page number (default: 1)", type: "number" },
{ flag: "--page-size <n>", description: "Results per page (default: 30)", type: "number" },
{ flag: "--region <region>", description: "API region (default: cn-beijing)" },
],
examples: ["bl mcp list", "bl mcp list --name 金融", "bl mcp list --output json"],
async run(config: Config, flags: GlobalFlags) {
const serverName = (flags.name as string) || "";
const type = (flags.type as string) || "OFFICIAL";
const pageNo = (flags.page as number) || 1;
const pageSize = (flags.pageSize as number) || 30;
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);

const data = {
reqDTO: {
type,
displayTools: false,
activated: 1,
pageNo,
pageSize,
serverName,
},
};

if (config.dryRun) {
emitResult({ api: MCP_LIST_API, data, region }, format);
return;
}

const credential = await resolveConsoleGatewayCredential(config);

const result = (await callConsoleGateway(config, credential.token, {
api: MCP_LIST_API,
data,
region,
})) as Record<string, unknown>;

const dataField = (result?.data as Record<string, unknown> | undefined) ?? {};
if (dataField.success === false) {
const code = (dataField.errorCode as string | undefined) ?? "UnknownError";
const msg = (dataField.errorMsg as string | undefined) ?? code;
const hint =
code === "BailianGateway.Login.NotLogined"
? "Run `bl auth login --console` to refresh your console session."
: undefined;
throw new BailianError(`Console gateway: ${msg}`, ExitCode.AUTH, hint);
}
const dataV2 = (dataField.DataV2 as Record<string, unknown> | undefined) ?? {};
const inner =
(dataV2.data as { data?: { mcpServerDetailList?: unknown[]; total?: number } } | undefined)
?.data ?? {};
const list = (inner.mcpServerDetailList ?? []) as Array<Record<string, unknown>>;
const total = (inner.total as number) ?? 0;

const servers: ServerSummary[] = list.map((item) => ({
code: (item.serverCode as string | undefined) ?? "",
name: (item.serverName as string | undefined) ?? "",
description: item.description as string | undefined,
type: (item.type as string | undefined) ?? "",
source: item.source as string | undefined,
bizType: item.bizType as string | undefined,
installType: item.installType as string | undefined,
streamable: item.streamable === true,
}));

emitResult({ total, servers }, format);
},
});
50 changes: 50 additions & 0 deletions packages/cli/src/commands/mcp/tools.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
import {
defineCommand,
McpClient,
bailianMcpUrl,
detectOutputFormat,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { failIfMissing } from "../../output/prompt.ts";
import { emitResult } from "../../output/output.ts";
import { ensureApiKey } from "../../utils/ensure-key.ts";

export default defineCommand({
name: "mcp tools",
description: "List tools exposed by an MCP server (tools/list)",
usage: "bl mcp tools <server-code> [--url <url>]",
options: [
{
flag: "<server-code>",
description: "Server code from `bl mcp list` (e.g. market-cmapi00073529)",
required: true,
},
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
],
examples: [
"bl mcp tools market-cmapi00073529",
"bl mcp tools market-cmapi00073529 --output json",
"bl mcp tools my-server --url https://example.com/mcp",
],
async run(config: Config, flags: GlobalFlags) {
const positional =
((flags as Record<string, unknown>)._positional as string[] | undefined) ?? [];
const code = positional[0];
if (!code) failIfMissing("server-code", "bl mcp tools <server-code>");

const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, code!);
const format = detectOutputFormat(config.output);

if (config.dryRun) {
emitResult({ server: code, url, action: "tools/list" }, format);
return;
}

await ensureApiKey(config);
const client = new McpClient(config, url);
await client.initialize();
const tools = await client.listTools();
emitResult({ server: code, url, tools }, format);
},
});
2 changes: 1 addition & 1 deletion packages/cli/src/commands/search/web.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import {
defineCommand,
mcpWebSearchEndpoint,
detectOutputFormat,
mcpWebSearchEndpoint,
type Config,
type GlobalFlags,
isInteractive,
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,9 @@ const NO_AUTH_SETUP = [
["app", "list"],
["console", "call"],
["usage", "free"],
["mcp", "list"],
["mcp", "tools"],
["mcp", "call"],
];

async function main() {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Feat/mcp command by XXPermanentXX · Pull Request #27 · modelstudioai/cli · GitHub
Skip to content
6 changes: 6 additions & 0 deletions packages/cli/src/commands/catalog.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,9 @@ import memoryDelete from "./memory/delete.ts";
import memoryProfileCreate from "./memory/profile-create.ts";
import memoryProfileGet from "./memory/profile-get.ts";
import knowledgeRetrieve from "./knowledge/retrieve.ts";
import mcpCall from "./mcp/call.ts";
import mcpList from "./mcp/list.ts";
import mcpTools from "./mcp/tools.ts";
import searchWeb from "./search/web.ts";
import speechSynthesize from "./speech/synthesize.ts";
import speechRecognize from "./speech/recognize.ts";
Expand DownExpand Up@@ -61,6 +64,9 @@ export const commands: Record<string, Command> = {
"memory profile create": memoryProfileCreate,
"memory profile get": memoryProfileGet,
"knowledge retrieve": knowledgeRetrieve,
"mcp list": mcpList,
"mcp tools": mcpTools,
"mcp call": mcpCall,
"search web": searchWeb,
"speech synthesize": speechSynthesize,
"speech recognize": speechRecognize,
Expand Down
123 changes: 123 additions & 0 deletions packages/cli/src/commands/mcp/call.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
import {
defineCommand,
McpClient,
bailianMcpUrl,
detectOutputFormat,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { failIfMissing } from "../../output/prompt.ts";
import { emitResult } from "../../output/output.ts";
import { ensureApiKey } from "../../utils/ensure-key.ts";

function parseArgFlags(raw: string[]): Record<string, unknown> {
const out: Record<string, unknown> = {};
for (const item of raw) {
const idx = item.indexOf("=");
if (idx <= 0) {
process.stderr.write(`Error: --arg must be in K=V form, got: ${item}\n`);
process.exit(1);
}
const key = item.slice(0, idx).trim();
const rawVal = item.slice(idx + 1);
try {
out[key] = JSON.parse(rawVal);
} catch {
out[key] = rawVal;
}
}
return out;
}

export default defineCommand({
name: "mcp call",
description: "Call a tool on an MCP server (tools/call)",
usage: "bl mcp call <server-code>.<tool> [--arg k=v ...] [--json '{...}'] [--url <url>]",
options: [
{
flag: "<server-code>.<tool>",
description:
"Server code and tool name joined by a dot, e.g. market-cmapi00073529.SmartStockSelection",
required: true,
},
{
flag: "--arg <kv>",
description: "Tool argument (repeatable). Values parsed as JSON if possible, else string.",
type: "array",
},
{
flag: "--json <obj>",
description: "Full arguments object as JSON; merged with --arg (arg wins).",
},
{
flag: "--query <text>",
description: "Shortcut for --arg query=<text> (mirrors many DashScope MCP tools).",
},
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
],
examples: [
'bl mcp call market-cmapi00073529.SmartStockSelection --query "筛选ROE>15%的消费股"',
'bl mcp call market-cmapi00073529.FinQuery --json \'{"q":"贵州茅台","limit":5}\'',
"bl mcp call market-cmapi00073529.SmartFundSelection --arg riskLevel=R3 --arg minScale=10",
],
async run(config: Config, flags: GlobalFlags) {
const positional =
((flags as Record<string, unknown>)._positional as string[] | undefined) ?? [];
const target = positional[0];
if (!target) failIfMissing("<server-code>.<tool>", "bl mcp call <server-code>.<tool>");

const dot = target!.indexOf(".");
if (dot <= 0 || dot === target!.length - 1) {
process.stderr.write(`Error: target must be <server-code>.<tool>, got "${target}".\n`);
process.exit(1);
}
const serverCode = target!.slice(0, dot);
const toolName = target!.slice(dot + 1);

let toolArgs: Record<string, unknown> = {};
if (flags.json) {
try {
const parsed = JSON.parse(flags.json as string);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
process.stderr.write("Error: --json must decode to an object.\n");
process.exit(1);
}
toolArgs = parsed as Record<string, unknown>;
} catch (err) {
process.stderr.write(`Error: --json is not valid JSON — ${(err as Error).message}\n`);
process.exit(1);
}
}
Object.assign(toolArgs, parseArgFlags((flags.arg as string[] | undefined) ?? []));
if (flags.query !== undefined) toolArgs.query = flags.query;

const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, serverCode);
const format = detectOutputFormat(config.output);

if (config.dryRun) {
emitResult(
{
server: serverCode,
url,
tool: toolName,
arguments: toolArgs,
},
format,
);
return;
}

await ensureApiKey(config);
const client = new McpClient(config, url);
await client.initialize();
const result = await client.callTool(toolName, toolArgs);

if (result.isError) {
const errText = result.content.map((c) => c.text || "").join("\n");
process.stderr.write(`Tool error: ${errText}\n`);
process.exit(1);
}

emitResult(result, format);
},
});
103 changes: 103 additions & 0 deletions packages/cli/src/commands/mcp/list.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
import {
defineCommand,
callConsoleGateway,
resolveConsoleGatewayCredential,
detectOutputFormat,
BailianError,
ExitCode,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { emitResult } from "../../output/output.ts";

const MCP_LIST_API = "zeldaEasy.broadscope-bailian.mcp-server.PageList";

interface ServerSummary {
code: string;
name: string;
description?: string;
type: string;
source?: string;
bizType?: string;
installType?: string;
streamable: boolean;
}

export default defineCommand({
name: "mcp list",
description: "List MCP servers activated under your Bailian account",
usage: "bl mcp list [flags]",
options: [
{ flag: "--name <text>", description: "Filter by server name (substring match)" },
{
flag: "--type <type>",
description: "Server type: OFFICIAL | PRIVATE (default: OFFICIAL)",
},
{ flag: "--page <n>", description: "Page number (default: 1)", type: "number" },
{ flag: "--page-size <n>", description: "Results per page (default: 30)", type: "number" },
{ flag: "--region <region>", description: "API region (default: cn-beijing)" },
],
examples: ["bl mcp list", "bl mcp list --name 金融", "bl mcp list --output json"],
async run(config: Config, flags: GlobalFlags) {
const serverName = (flags.name as string) || "";
const type = (flags.type as string) || "OFFICIAL";
const pageNo = (flags.page as number) || 1;
const pageSize = (flags.pageSize as number) || 30;
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);

const data = {
reqDTO: {
type,
displayTools: false,
activated: 1,
pageNo,
pageSize,
serverName,
},
};

if (config.dryRun) {
emitResult({ api: MCP_LIST_API, data, region }, format);
return;
}

const credential = await resolveConsoleGatewayCredential(config);

const result = (await callConsoleGateway(config, credential.token, {
api: MCP_LIST_API,
data,
region,
})) as Record<string, unknown>;

const dataField = (result?.data as Record<string, unknown> | undefined) ?? {};
if (dataField.success === false) {
const code = (dataField.errorCode as string | undefined) ?? "UnknownError";
const msg = (dataField.errorMsg as string | undefined) ?? code;
const hint =
code === "BailianGateway.Login.NotLogined"
? "Run `bl auth login --console` to refresh your console session."
: undefined;
throw new BailianError(`Console gateway: ${msg}`, ExitCode.AUTH, hint);
}
const dataV2 = (dataField.DataV2 as Record<string, unknown> | undefined) ?? {};
const inner =
(dataV2.data as { data?: { mcpServerDetailList?: unknown[]; total?: number } } | undefined)
?.data ?? {};
const list = (inner.mcpServerDetailList ?? []) as Array<Record<string, unknown>>;
const total = (inner.total as number) ?? 0;

const servers: ServerSummary[] = list.map((item) => ({
code: (item.serverCode as string | undefined) ?? "",
name: (item.serverName as string | undefined) ?? "",
description: item.description as string | undefined,
type: (item.type as string | undefined) ?? "",
source: item.source as string | undefined,
bizType: item.bizType as string | undefined,
installType: item.installType as string | undefined,
streamable: item.streamable === true,
}));

emitResult({ total, servers }, format);
},
});
50 changes: 50 additions & 0 deletions packages/cli/src/commands/mcp/tools.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
import {
defineCommand,
McpClient,
bailianMcpUrl,
detectOutputFormat,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { failIfMissing } from "../../output/prompt.ts";
import { emitResult } from "../../output/output.ts";
import { ensureApiKey } from "../../utils/ensure-key.ts";

export default defineCommand({
name: "mcp tools",
description: "List tools exposed by an MCP server (tools/list)",
usage: "bl mcp tools <server-code> [--url <url>]",
options: [
{
flag: "<server-code>",
description: "Server code from `bl mcp list` (e.g. market-cmapi00073529)",
required: true,
},
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
],
examples: [
"bl mcp tools market-cmapi00073529",
"bl mcp tools market-cmapi00073529 --output json",
"bl mcp tools my-server --url https://example.com/mcp",
],
async run(config: Config, flags: GlobalFlags) {
const positional =
((flags as Record<string, unknown>)._positional as string[] | undefined) ?? [];
const code = positional[0];
if (!code) failIfMissing("server-code", "bl mcp tools <server-code>");

const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, code!);
const format = detectOutputFormat(config.output);

if (config.dryRun) {
emitResult({ server: code, url, action: "tools/list" }, format);
return;
}

await ensureApiKey(config);
const client = new McpClient(config, url);
await client.initialize();
const tools = await client.listTools();
emitResult({ server: code, url, tools }, format);
},
});
2 changes: 1 addition & 1 deletion packages/cli/src/commands/search/web.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import {
defineCommand,
mcpWebSearchEndpoint,
detectOutputFormat,
mcpWebSearchEndpoint,
type Config,
type GlobalFlags,
isInteractive,
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,9 @@ const NO_AUTH_SETUP = [
["app", "list"],
["console", "call"],
["usage", "free"],
["mcp", "list"],
["mcp", "tools"],
["mcp", "call"],
];

async function main() {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Feat/mcp command by XXPermanentXX · Pull Request #27 · modelstudioai/cli · GitHub
Skip to content
6 changes: 6 additions & 0 deletions packages/cli/src/commands/catalog.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,9 @@ import memoryDelete from "./memory/delete.ts";
import memoryProfileCreate from "./memory/profile-create.ts";
import memoryProfileGet from "./memory/profile-get.ts";
import knowledgeRetrieve from "./knowledge/retrieve.ts";
import mcpCall from "./mcp/call.ts";
import mcpList from "./mcp/list.ts";
import mcpTools from "./mcp/tools.ts";
import searchWeb from "./search/web.ts";
import speechSynthesize from "./speech/synthesize.ts";
import speechRecognize from "./speech/recognize.ts";
Expand DownExpand Up@@ -61,6 +64,9 @@ export const commands: Record<string, Command> = {
"memory profile create": memoryProfileCreate,
"memory profile get": memoryProfileGet,
"knowledge retrieve": knowledgeRetrieve,
"mcp list": mcpList,
"mcp tools": mcpTools,
"mcp call": mcpCall,
"search web": searchWeb,
"speech synthesize": speechSynthesize,
"speech recognize": speechRecognize,
Expand Down
123 changes: 123 additions & 0 deletions packages/cli/src/commands/mcp/call.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
import {
defineCommand,
McpClient,
bailianMcpUrl,
detectOutputFormat,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { failIfMissing } from "../../output/prompt.ts";
import { emitResult } from "../../output/output.ts";
import { ensureApiKey } from "../../utils/ensure-key.ts";

function parseArgFlags(raw: string[]): Record<string, unknown> {
const out: Record<string, unknown> = {};
for (const item of raw) {
const idx = item.indexOf("=");
if (idx <= 0) {
process.stderr.write(`Error: --arg must be in K=V form, got: ${item}\n`);
process.exit(1);
}
const key = item.slice(0, idx).trim();
const rawVal = item.slice(idx + 1);
try {
out[key] = JSON.parse(rawVal);
} catch {
out[key] = rawVal;
}
}
return out;
}

export default defineCommand({
name: "mcp call",
description: "Call a tool on an MCP server (tools/call)",
usage: "bl mcp call <server-code>.<tool> [--arg k=v ...] [--json '{...}'] [--url <url>]",
options: [
{
flag: "<server-code>.<tool>",
description:
"Server code and tool name joined by a dot, e.g. market-cmapi00073529.SmartStockSelection",
required: true,
},
{
flag: "--arg <kv>",
description: "Tool argument (repeatable). Values parsed as JSON if possible, else string.",
type: "array",
},
{
flag: "--json <obj>",
description: "Full arguments object as JSON; merged with --arg (arg wins).",
},
{
flag: "--query <text>",
description: "Shortcut for --arg query=<text> (mirrors many DashScope MCP tools).",
},
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
],
examples: [
'bl mcp call market-cmapi00073529.SmartStockSelection --query "筛选ROE>15%的消费股"',
'bl mcp call market-cmapi00073529.FinQuery --json \'{"q":"贵州茅台","limit":5}\'',
"bl mcp call market-cmapi00073529.SmartFundSelection --arg riskLevel=R3 --arg minScale=10",
],
async run(config: Config, flags: GlobalFlags) {
const positional =
((flags as Record<string, unknown>)._positional as string[] | undefined) ?? [];
const target = positional[0];
if (!target) failIfMissing("<server-code>.<tool>", "bl mcp call <server-code>.<tool>");

const dot = target!.indexOf(".");
if (dot <= 0 || dot === target!.length - 1) {
process.stderr.write(`Error: target must be <server-code>.<tool>, got "${target}".\n`);
process.exit(1);
}
const serverCode = target!.slice(0, dot);
const toolName = target!.slice(dot + 1);

let toolArgs: Record<string, unknown> = {};
if (flags.json) {
try {
const parsed = JSON.parse(flags.json as string);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
process.stderr.write("Error: --json must decode to an object.\n");
process.exit(1);
}
toolArgs = parsed as Record<string, unknown>;
} catch (err) {
process.stderr.write(`Error: --json is not valid JSON — ${(err as Error).message}\n`);
process.exit(1);
}
}
Object.assign(toolArgs, parseArgFlags((flags.arg as string[] | undefined) ?? []));
if (flags.query !== undefined) toolArgs.query = flags.query;

const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, serverCode);
const format = detectOutputFormat(config.output);

if (config.dryRun) {
emitResult(
{
server: serverCode,
url,
tool: toolName,
arguments: toolArgs,
},
format,
);
return;
}

await ensureApiKey(config);
const client = new McpClient(config, url);
await client.initialize();
const result = await client.callTool(toolName, toolArgs);

if (result.isError) {
const errText = result.content.map((c) => c.text || "").join("\n");
process.stderr.write(`Tool error: ${errText}\n`);
process.exit(1);
}

emitResult(result, format);
},
});
103 changes: 103 additions & 0 deletions packages/cli/src/commands/mcp/list.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
import {
defineCommand,
callConsoleGateway,
resolveConsoleGatewayCredential,
detectOutputFormat,
BailianError,
ExitCode,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { emitResult } from "../../output/output.ts";

const MCP_LIST_API = "zeldaEasy.broadscope-bailian.mcp-server.PageList";

interface ServerSummary {
code: string;
name: string;
description?: string;
type: string;
source?: string;
bizType?: string;
installType?: string;
streamable: boolean;
}

export default defineCommand({
name: "mcp list",
description: "List MCP servers activated under your Bailian account",
usage: "bl mcp list [flags]",
options: [
{ flag: "--name <text>", description: "Filter by server name (substring match)" },
{
flag: "--type <type>",
description: "Server type: OFFICIAL | PRIVATE (default: OFFICIAL)",
},
{ flag: "--page <n>", description: "Page number (default: 1)", type: "number" },
{ flag: "--page-size <n>", description: "Results per page (default: 30)", type: "number" },
{ flag: "--region <region>", description: "API region (default: cn-beijing)" },
],
examples: ["bl mcp list", "bl mcp list --name 金融", "bl mcp list --output json"],
async run(config: Config, flags: GlobalFlags) {
const serverName = (flags.name as string) || "";
const type = (flags.type as string) || "OFFICIAL";
const pageNo = (flags.page as number) || 1;
const pageSize = (flags.pageSize as number) || 30;
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);

const data = {
reqDTO: {
type,
displayTools: false,
activated: 1,
pageNo,
pageSize,
serverName,
},
};

if (config.dryRun) {
emitResult({ api: MCP_LIST_API, data, region }, format);
return;
}

const credential = await resolveConsoleGatewayCredential(config);

const result = (await callConsoleGateway(config, credential.token, {
api: MCP_LIST_API,
data,
region,
})) as Record<string, unknown>;

const dataField = (result?.data as Record<string, unknown> | undefined) ?? {};
if (dataField.success === false) {
const code = (dataField.errorCode as string | undefined) ?? "UnknownError";
const msg = (dataField.errorMsg as string | undefined) ?? code;
const hint =
code === "BailianGateway.Login.NotLogined"
? "Run `bl auth login --console` to refresh your console session."
: undefined;
throw new BailianError(`Console gateway: ${msg}`, ExitCode.AUTH, hint);
}
const dataV2 = (dataField.DataV2 as Record<string, unknown> | undefined) ?? {};
const inner =
(dataV2.data as { data?: { mcpServerDetailList?: unknown[]; total?: number } } | undefined)
?.data ?? {};
const list = (inner.mcpServerDetailList ?? []) as Array<Record<string, unknown>>;
const total = (inner.total as number) ?? 0;

const servers: ServerSummary[] = list.map((item) => ({
code: (item.serverCode as string | undefined) ?? "",
name: (item.serverName as string | undefined) ?? "",
description: item.description as string | undefined,
type: (item.type as string | undefined) ?? "",
source: item.source as string | undefined,
bizType: item.bizType as string | undefined,
installType: item.installType as string | undefined,
streamable: item.streamable === true,
}));

emitResult({ total, servers }, format);
},
});
50 changes: 50 additions & 0 deletions packages/cli/src/commands/mcp/tools.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
import {
defineCommand,
McpClient,
bailianMcpUrl,
detectOutputFormat,
type Config,
type GlobalFlags,
} from "bailian-cli-core";
import { failIfMissing } from "../../output/prompt.ts";
import { emitResult } from "../../output/output.ts";
import { ensureApiKey } from "../../utils/ensure-key.ts";

export default defineCommand({
name: "mcp tools",
description: "List tools exposed by an MCP server (tools/list)",
usage: "bl mcp tools <server-code> [--url <url>]",
options: [
{
flag: "<server-code>",
description: "Server code from `bl mcp list` (e.g. market-cmapi00073529)",
required: true,
},
{ flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" },
],
examples: [
"bl mcp tools market-cmapi00073529",
"bl mcp tools market-cmapi00073529 --output json",
"bl mcp tools my-server --url https://example.com/mcp",
],
async run(config: Config, flags: GlobalFlags) {
const positional =
((flags as Record<string, unknown>)._positional as string[] | undefined) ?? [];
const code = positional[0];
if (!code) failIfMissing("server-code", "bl mcp tools <server-code>");

const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, code!);
const format = detectOutputFormat(config.output);

if (config.dryRun) {
emitResult({ server: code, url, action: "tools/list" }, format);
return;
}

await ensureApiKey(config);
const client = new McpClient(config, url);
await client.initialize();
const tools = await client.listTools();
emitResult({ server: code, url, tools }, format);
},
});
2 changes: 1 addition & 1 deletion packages/cli/src/commands/search/web.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import {
defineCommand,
mcpWebSearchEndpoint,
detectOutputFormat,
mcpWebSearchEndpoint,
type Config,
type GlobalFlags,
isInteractive,
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,9 @@ const NO_AUTH_SETUP = [
["app", "list"],
["console", "call"],
["usage", "free"],
["mcp", "list"],
["mcp", "tools"],
["mcp", "call"],
];

async function main() {
Expand Down
Loading