src / toolsProvider.ts
import { tool, Tool, ToolsProviderController } from "@lmstudio/sdk";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { z } from "zod";
import path from "path";
import { configSchematics } from "./config";
const pluginRoot = path.resolve(__dirname, "..");
let client: Client | null = null;
let currentUserAgent: string = "";
export async function toolsProvider(ctl: ToolsProviderController) {
const userAgent = ctl.getPluginConfig(configSchematics).get("secEdgarUserAgent") as string;
if (!client || currentUserAgent !== userAgent) {
if (client) {
await client.close();
client = null;
}
const isWindows = process.platform === "win32";
const venvScript = isWindows
? path.join(pluginRoot, "venv", "Scripts", "sec-edgar-mcp.exe")
: path.join(pluginRoot, "venv", "bin", "sec-edgar-mcp");
const transport = new StdioClientTransport({
command: venvScript,
args: [],
env: {
...process.env,
SEC_EDGAR_USER_AGENT: userAgent,
},
});
client = new Client({
name: "sec-edgar-lmstudio-client",
version: "1.0.0"
});
try {
await client.connect(transport);
currentUserAgent = userAgent;
} catch (e) {
console.error("Failed to connect to MCP server:", e);
return [];
}
}
try {
const { tools: mcpTools } = await client.listTools();
return mcpTools.map(mcpTool => {
// 將原始 JSON Schema 注入描述中,並明確指示 LLM 將參數放入 'params' 物件中
const schemaString = mcpTool.inputSchema
? `\n\nIMPORTANT: Pass all arguments inside the 'params' object.\nExpected Input Structure:\n${JSON.stringify(mcpTool.inputSchema, null, 2)}`
: "";
return tool({
name: mcpTool.name,
description: `${mcpTool.description || ""}${schemaString}`,
// 符合 LM Studio SDK 型別要求:使用物件結構,並用 z.any() 確保絕對不崩潰
parameters: { params: z.any() },
implementation: async ({ params }: any) => {
// 將 LLM 傳入的 params 物件解包,直接作為 MCP Server 的 arguments
const result = await client!.callTool({
name: mcpTool.name,
arguments: params,
}) as any;
return result.content.map((c: any) => c.text).join("\n");
}
});
});
} catch (e) {
console.error("Failed to list/call tools:", e);
return [];
}
}src / toolsProvider.ts
import { tool, Tool, ToolsProviderController } from "@lmstudio/sdk";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { z } from "zod";
import path from "path";
import { configSchematics } from "./config";
const pluginRoot = path.resolve(__dirname, "..");
let client: Client | null = null;
let currentUserAgent: string = "";
export async function toolsProvider(ctl: ToolsProviderController) {
const userAgent = ctl.getPluginConfig(configSchematics).get("secEdgarUserAgent") as string;
if (!client || currentUserAgent !== userAgent) {
if (client) {
await client.close();
client = null;
}
const isWindows = process.platform === "win32";
const venvScript = isWindows
? path.join(pluginRoot, "venv", "Scripts", "sec-edgar-mcp.exe")
: path.join(pluginRoot, "venv", "bin", "sec-edgar-mcp");
const transport = new StdioClientTransport({
command: venvScript,
args: [],
env: {
...process.env,
SEC_EDGAR_USER_AGENT: userAgent,
},
});
client = new Client({
name: "sec-edgar-lmstudio-client",
version: "1.0.0"
});
try {
await client.connect(transport);
currentUserAgent = userAgent;
} catch (e) {
console.error("Failed to connect to MCP server:", e);
return [];
}
}
try {
const { tools: mcpTools } = await client.listTools();
return mcpTools.map(mcpTool => {
// 將原始 JSON Schema 注入描述中,並明確指示 LLM 將參數放入 'params' 物件中
const schemaString = mcpTool.inputSchema
? `\n\nIMPORTANT: Pass all arguments inside the 'params' object.\nExpected Input Structure:\n${JSON.stringify(mcpTool.inputSchema, null, 2)}`
: "";
return tool({
name: mcpTool.name,
description: `${mcpTool.description || ""}${schemaString}`,
// 符合 LM Studio SDK 型別要求:使用物件結構,並用 z.any() 確保絕對不崩潰
parameters: { params: z.any() },
implementation: async ({ params }: any) => {
// 將 LLM 傳入的 params 物件解包,直接作為 MCP Server 的 arguments
const result = await client!.callTool({
name: mcpTool.name,
arguments: params,
}) as any;
return result.content.map((c: any) => c.text).join("\n");
}
});
});
} catch (e) {
console.error("Failed to list/call tools:", e);
return [];
}
}