feat(mcp): register ast-grep as built-in MCP
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { afterEach, describe, expect, it } from "bun:test"
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"
|
||||
import { tmpdir } from "node:os"
|
||||
import { join } from "node:path"
|
||||
import { pathToFileURL } from "node:url"
|
||||
import { createAstGrepMcpConfig } from "./ast-grep"
|
||||
|
||||
const temporaryDirectories: string[] = []
|
||||
|
||||
function createTemporaryDirectory(prefix: string): string {
|
||||
const directory = mkdtempSync(join(tmpdir(), prefix))
|
||||
temporaryDirectories.push(directory)
|
||||
return directory
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const directory of temporaryDirectories.splice(0)) {
|
||||
rmSync(directory, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
describe("createAstGrepMcpConfig", () => {
|
||||
it("resolves bundled dist cli from module root when cwd is unrelated", () => {
|
||||
// given
|
||||
const packageRoot = createTemporaryDirectory("omo-ast-grep-package-root-")
|
||||
const unrelatedCwd = createTemporaryDirectory("omo-ast-grep-unrelated-cwd-")
|
||||
const moduleFilePath = join(packageRoot, "dist", "index.js")
|
||||
const cliPath = join(packageRoot, "packages", "ast-grep-mcp", "dist", "cli.js")
|
||||
mkdirSync(join(packageRoot, "dist"), { recursive: true })
|
||||
mkdirSync(join(packageRoot, "packages", "ast-grep-mcp", "dist"), { recursive: true })
|
||||
writeFileSync(cliPath, "#!/usr/bin/env node\n", "utf-8")
|
||||
|
||||
// when
|
||||
const config = createAstGrepMcpConfig({
|
||||
cwd: unrelatedCwd,
|
||||
moduleUrl: pathToFileURL(moduleFilePath).href,
|
||||
})
|
||||
|
||||
// then
|
||||
expect(config.command).toEqual(["node", cliPath, "mcp"])
|
||||
expect(config.environment?.OMO_AST_GREP_WORKSPACE).toBe(unrelatedCwd)
|
||||
})
|
||||
|
||||
it("falls back to bun source cli for source checkouts before build", () => {
|
||||
// given
|
||||
const packageRoot = createTemporaryDirectory("omo-ast-grep-source-root-")
|
||||
const moduleFilePath = join(packageRoot, "src", "mcp", "ast-grep.ts")
|
||||
const sourceCliPath = join(packageRoot, "packages", "ast-grep-mcp", "src", "cli.ts")
|
||||
mkdirSync(join(packageRoot, "src", "mcp"), { recursive: true })
|
||||
mkdirSync(join(packageRoot, "packages", "ast-grep-mcp", "src"), { recursive: true })
|
||||
writeFileSync(sourceCliPath, "console.log('mcp')\n", "utf-8")
|
||||
|
||||
// when
|
||||
const config = createAstGrepMcpConfig({
|
||||
cwd: createTemporaryDirectory("omo-ast-grep-source-cwd-"),
|
||||
moduleUrl: pathToFileURL(moduleFilePath).href,
|
||||
})
|
||||
|
||||
// then
|
||||
expect(config.command).toEqual(["bun", sourceCliPath, "mcp"])
|
||||
})
|
||||
|
||||
it("still returns a built-in MCP config when the cli has not been built yet", () => {
|
||||
// given
|
||||
const packageRoot = createTemporaryDirectory("omo-ast-grep-missing-root-")
|
||||
const moduleFilePath = join(packageRoot, "dist", "index.js")
|
||||
mkdirSync(join(packageRoot, "dist"), { recursive: true })
|
||||
|
||||
// when
|
||||
const config = createAstGrepMcpConfig({
|
||||
cwd: createTemporaryDirectory("omo-ast-grep-missing-cwd-"),
|
||||
moduleUrl: pathToFileURL(moduleFilePath).href,
|
||||
})
|
||||
|
||||
// then
|
||||
expect(config.enabled).toBe(true)
|
||||
expect(config.command[0]).toBe("node")
|
||||
expect(config.command[1]).toContain(join("packages", "ast-grep-mcp", "dist", "cli.js"))
|
||||
expect(config.command[2]).toBe("mcp")
|
||||
})
|
||||
|
||||
it("does not resolve the MCP command from the opened workspace", () => {
|
||||
// given
|
||||
const packageRoot = createTemporaryDirectory("omo-ast-grep-safe-package-root-")
|
||||
const workspaceRoot = createTemporaryDirectory("omo-ast-grep-malicious-workspace-")
|
||||
const moduleFilePath = join(packageRoot, "dist", "index.js")
|
||||
const workspaceCliPath = join(workspaceRoot, "packages", "ast-grep-mcp", "dist", "cli.js")
|
||||
mkdirSync(join(packageRoot, "dist"), { recursive: true })
|
||||
mkdirSync(join(workspaceRoot, "packages", "ast-grep-mcp", "dist"), { recursive: true })
|
||||
writeFileSync(workspaceCliPath, "console.log('malicious')\n", "utf-8")
|
||||
|
||||
// when
|
||||
const config = createAstGrepMcpConfig({
|
||||
cwd: workspaceRoot,
|
||||
moduleUrl: pathToFileURL(moduleFilePath).href,
|
||||
})
|
||||
|
||||
// then
|
||||
expect(config.command[1]).not.toBe(workspaceCliPath)
|
||||
expect(config.command[1]).toContain(packageRoot)
|
||||
})
|
||||
|
||||
it("maps disabled ast-grep tool names to MCP subtools", () => {
|
||||
// given
|
||||
const packageRoot = createTemporaryDirectory("omo-ast-grep-disabled-root-")
|
||||
const moduleFilePath = join(packageRoot, "dist", "index.js")
|
||||
mkdirSync(join(packageRoot, "dist"), { recursive: true })
|
||||
|
||||
// when
|
||||
const config = createAstGrepMcpConfig({
|
||||
cwd: createTemporaryDirectory("omo-ast-grep-disabled-cwd-"),
|
||||
disabledTools: ["ast_grep_replace", "glob"],
|
||||
moduleUrl: pathToFileURL(moduleFilePath).href,
|
||||
})
|
||||
|
||||
// then
|
||||
expect(config.environment?.OMO_AST_GREP_DISABLED_TOOLS).toBe("replace")
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,97 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { LocalMcpConfig } from "./lsp";
|
||||
|
||||
const PACKAGE_REL = "packages/ast-grep-mcp";
|
||||
const DIST_CLI_REL = "dist/cli.js";
|
||||
const SOURCE_CLI_REL = "src/cli.ts";
|
||||
const WORKSPACE_ENV = "OMO_AST_GREP_WORKSPACE";
|
||||
const DISABLED_TOOLS_ENV = "OMO_AST_GREP_DISABLED_TOOLS";
|
||||
|
||||
const MCP_TOOL_BY_OPENCODE_TOOL: Readonly<Record<string, string>> = {
|
||||
ast_grep_search: "search",
|
||||
ast_grep_replace: "replace",
|
||||
};
|
||||
|
||||
type AstGrepMcpConfigOptions = {
|
||||
readonly cwd?: string;
|
||||
readonly disabledTools?: readonly string[];
|
||||
readonly moduleUrl?: string;
|
||||
readonly exists?: (path: string) => boolean;
|
||||
};
|
||||
|
||||
type CommandCandidate = {
|
||||
readonly command: string[];
|
||||
readonly path: string;
|
||||
readonly exists: boolean;
|
||||
};
|
||||
|
||||
function addAncestorCommandCandidates(
|
||||
startDirectory: string,
|
||||
target: CommandCandidate[],
|
||||
seenPaths: Set<string>,
|
||||
pathExists: (path: string) => boolean,
|
||||
): void {
|
||||
let currentDirectory = resolve(startDirectory);
|
||||
while (true) {
|
||||
const distCliPath = resolve(currentDirectory, PACKAGE_REL, DIST_CLI_REL);
|
||||
if (!seenPaths.has(distCliPath)) {
|
||||
seenPaths.add(distCliPath);
|
||||
target.push({ command: ["node", distCliPath, "mcp"], path: distCliPath, exists: pathExists(distCliPath) });
|
||||
}
|
||||
|
||||
const sourceCliPath = resolve(currentDirectory, PACKAGE_REL, SOURCE_CLI_REL);
|
||||
if (!seenPaths.has(sourceCliPath)) {
|
||||
seenPaths.add(sourceCliPath);
|
||||
target.push({ command: ["bun", sourceCliPath, "mcp"], path: sourceCliPath, exists: pathExists(sourceCliPath) });
|
||||
}
|
||||
|
||||
const parentDirectory = resolve(currentDirectory, "..");
|
||||
if (parentDirectory === currentDirectory) return;
|
||||
currentDirectory = parentDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
function getModuleDirectory(moduleUrl: string): string | null {
|
||||
try {
|
||||
return dirname(fileURLToPath(moduleUrl));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveAstGrepCommand(options: AstGrepMcpConfigOptions = {}): string[] {
|
||||
const pathExists = options.exists ?? existsSync;
|
||||
const candidates: CommandCandidate[] = [];
|
||||
const seenPaths = new Set<string>();
|
||||
const moduleDirectory = getModuleDirectory(options.moduleUrl ?? import.meta.url);
|
||||
if (moduleDirectory) addAncestorCommandCandidates(moduleDirectory, candidates, seenPaths, pathExists);
|
||||
|
||||
const distCandidate = candidates.find((candidate) => candidate.path.endsWith(DIST_CLI_REL) && candidate.exists);
|
||||
if (distCandidate) return distCandidate.command;
|
||||
const sourceCandidate = candidates.find((candidate) => candidate.path.endsWith(SOURCE_CLI_REL) && candidate.exists);
|
||||
if (sourceCandidate) return sourceCandidate.command;
|
||||
return candidates[0]?.command ?? ["node", resolve(PACKAGE_REL, DIST_CLI_REL), "mcp"];
|
||||
}
|
||||
|
||||
function astGrepDisabledTools(disabledTools: readonly string[] | undefined): string {
|
||||
if (!disabledTools) return "";
|
||||
return disabledTools
|
||||
.map((toolName) => MCP_TOOL_BY_OPENCODE_TOOL[toolName])
|
||||
.filter((toolName): toolName is string => typeof toolName === "string")
|
||||
.join(",");
|
||||
}
|
||||
|
||||
export function createAstGrepMcpConfig(options: AstGrepMcpConfigOptions = {}): LocalMcpConfig {
|
||||
const workspaceDirectory = options.cwd ?? process.cwd();
|
||||
return {
|
||||
type: "local",
|
||||
command: resolveAstGrepCommand(options),
|
||||
enabled: true,
|
||||
environment: {
|
||||
[WORKSPACE_ENV]: workspaceDirectory,
|
||||
[DISABLED_TOOLS_ENV]: astGrepDisabledTools(options.disabledTools),
|
||||
},
|
||||
};
|
||||
}
|
||||
+10
-1
@@ -1,6 +1,7 @@
|
||||
import { createWebsearchConfig } from "./websearch"
|
||||
import { context7 } from "./context7"
|
||||
import { grep_app } from "./grep-app"
|
||||
import { createAstGrepMcpConfig } from "./ast-grep"
|
||||
import { createLspMcpConfig, type LocalMcpConfig } from "./lsp"
|
||||
import type { OhMyOpenCodeConfig } from "../config/schema"
|
||||
|
||||
@@ -16,7 +17,11 @@ type RemoteMcpConfig = {
|
||||
|
||||
type BuiltinMcpConfig = RemoteMcpConfig | LocalMcpConfig
|
||||
|
||||
export function createBuiltinMcps(disabledMcps: string[] = [], config?: OhMyOpenCodeConfig) {
|
||||
type BuiltinMcpOptions = {
|
||||
readonly cwd?: string
|
||||
}
|
||||
|
||||
export function createBuiltinMcps(disabledMcps: string[] = [], config?: OhMyOpenCodeConfig, options: BuiltinMcpOptions = {}) {
|
||||
const mcps: Record<string, BuiltinMcpConfig> = {}
|
||||
|
||||
if (!disabledMcps.includes("websearch")) {
|
||||
@@ -38,5 +43,9 @@ export function createBuiltinMcps(disabledMcps: string[] = [], config?: OhMyOpen
|
||||
mcps.lsp = createLspMcpConfig()
|
||||
}
|
||||
|
||||
if (!disabledMcps.includes("ast_grep")) {
|
||||
mcps.ast_grep = createAstGrepMcpConfig({ cwd: options.cwd, disabledTools: config?.disabled_tools })
|
||||
}
|
||||
|
||||
return mcps
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export const McpNameSchema = z.enum(["websearch", "context7", "grep_app", "lsp"])
|
||||
export const McpNameSchema = z.enum(["websearch", "context7", "grep_app", "lsp", "ast_grep"])
|
||||
|
||||
export type McpName = z.infer<typeof McpNameSchema>
|
||||
|
||||
|
||||
@@ -4,12 +4,19 @@ afterEach(() => {
|
||||
mock.restore()
|
||||
})
|
||||
|
||||
function mockLocalMcps(): void {
|
||||
mock.module("../lsp", () => ({
|
||||
createLspMcpConfig: () => ({ type: "local", command: ["node", "dist/cli.js", "mcp"], enabled: true }),
|
||||
}))
|
||||
mock.module("../ast-grep", () => ({
|
||||
createAstGrepMcpConfig: () => ({ type: "local", command: ["node", "ast-grep-mcp", "mcp"], enabled: true }),
|
||||
}))
|
||||
}
|
||||
|
||||
describe("createBuiltinMcps", () => {
|
||||
test("should return all MCPs when disabled_mcps is empty", () => {
|
||||
// given
|
||||
mock.module("../lsp", () => ({
|
||||
createLspMcpConfig: () => ({ type: "local", command: ["node", "dist/cli.js", "mcp"], enabled: true }),
|
||||
}))
|
||||
mockLocalMcps()
|
||||
const { createBuiltinMcps } = require("../index") as typeof import("../index")
|
||||
const disabledMcps: string[] = []
|
||||
|
||||
@@ -22,13 +29,12 @@ describe("createBuiltinMcps", () => {
|
||||
expect(result.context7).toBeDefined()
|
||||
expect(result.grep_app).toBeDefined()
|
||||
expect(result.lsp).toBeDefined()
|
||||
expect(result.ast_grep).toBeDefined()
|
||||
})
|
||||
|
||||
test("should filter out disabled MCPs", () => {
|
||||
// given
|
||||
mock.module("../lsp", () => ({
|
||||
createLspMcpConfig: () => ({ type: "local", command: ["node", "dist/cli.js", "mcp"], enabled: true }),
|
||||
}))
|
||||
mockLocalMcps()
|
||||
const { createBuiltinMcps } = require("../index") as typeof import("../index")
|
||||
const disabledMcps = ["websearch"]
|
||||
|
||||
@@ -40,6 +46,7 @@ describe("createBuiltinMcps", () => {
|
||||
expect(result.context7).toBeDefined()
|
||||
expect(result.grep_app).toBeDefined()
|
||||
expect(result.lsp).toBeDefined()
|
||||
expect(result.ast_grep).toBeDefined()
|
||||
})
|
||||
|
||||
test("should keep lsp when it uses a bootstrap command", () => {
|
||||
@@ -57,22 +64,21 @@ describe("createBuiltinMcps", () => {
|
||||
})
|
||||
|
||||
test("should return empty array when all MCPs are disabled", () => {
|
||||
// given - disable all known MCPs
|
||||
mock.module("../lsp", () => ({
|
||||
createLspMcpConfig: () => ({ type: "local", command: ["node", "dist/cli.js", "mcp"], enabled: true }),
|
||||
}))
|
||||
// given
|
||||
mockLocalMcps()
|
||||
const { createBuiltinMcps } = require("../index") as typeof import("../index")
|
||||
const disabledMcps = ["websearch", "context7", "grep_app", "lsp"]
|
||||
const disabledMcps = ["websearch", "context7", "grep_app", "lsp", "ast_grep"]
|
||||
|
||||
// when
|
||||
const result = createBuiltinMcps(disabledMcps)
|
||||
|
||||
// then - may still have MCPs we didn't list
|
||||
// then
|
||||
const remainingMcpNames = Object.keys(result)
|
||||
expect(remainingMcpNames).not.toContain("websearch")
|
||||
expect(remainingMcpNames).not.toContain("context7")
|
||||
expect(remainingMcpNames).not.toContain("grep_app")
|
||||
expect(remainingMcpNames).not.toContain("lsp")
|
||||
expect(remainingMcpNames).not.toContain("ast_grep")
|
||||
expect(remainingMcpNames).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -38,7 +38,7 @@ export function createConfigHandler(deps: ConfigHandlerDeps) {
|
||||
});
|
||||
|
||||
applyToolConfig({ config, pluginConfig, agentResult });
|
||||
await applyMcpConfig({ config, pluginConfig, pluginComponents });
|
||||
await applyMcpConfig({ config, pluginConfig, ctx, pluginComponents });
|
||||
await applyCommandConfig({ config, pluginConfig, ctx, pluginComponents });
|
||||
|
||||
config.formatter = formatterConfig;
|
||||
|
||||
@@ -46,6 +46,8 @@ const EMPTY_PLUGIN_COMPONENTS = {
|
||||
errors: [],
|
||||
}
|
||||
|
||||
const TEST_CTX = { directory: "/workspace/project" }
|
||||
|
||||
async function importFreshMcpConfigHandlerModule(): Promise<typeof import("./mcp-config-handler")> {
|
||||
return import(`./mcp-config-handler?test=${Date.now()}-${Math.random()}`)
|
||||
}
|
||||
@@ -69,7 +71,7 @@ describe("applyMcpConfig collision handling", () => {
|
||||
|
||||
//#when
|
||||
const { applyMcpConfig } = await importFreshMcpConfigHandlerModule()
|
||||
await applyMcpConfig({ config, pluginConfig, pluginComponents: EMPTY_PLUGIN_COMPONENTS })
|
||||
await applyMcpConfig({ config, ctx: TEST_CTX, pluginConfig, pluginComponents: EMPTY_PLUGIN_COMPONENTS })
|
||||
|
||||
//#then
|
||||
const mergedMcp = config.mcp as Record<string, Record<string, unknown>>
|
||||
@@ -98,7 +100,7 @@ describe("applyMcpConfig collision handling", () => {
|
||||
|
||||
//#when
|
||||
const { applyMcpConfig } = await importFreshMcpConfigHandlerModule()
|
||||
await applyMcpConfig({ config, pluginConfig, pluginComponents: EMPTY_PLUGIN_COMPONENTS })
|
||||
await applyMcpConfig({ config, ctx: TEST_CTX, pluginConfig, pluginComponents: EMPTY_PLUGIN_COMPONENTS })
|
||||
|
||||
//#then
|
||||
const mergedMcp = config.mcp as Record<string, Record<string, unknown>>
|
||||
@@ -126,7 +128,7 @@ describe("applyMcpConfig collision handling", () => {
|
||||
|
||||
//#when
|
||||
const { applyMcpConfig } = await importFreshMcpConfigHandlerModule()
|
||||
await applyMcpConfig({ config, pluginConfig, pluginComponents: EMPTY_PLUGIN_COMPONENTS })
|
||||
await applyMcpConfig({ config, ctx: TEST_CTX, pluginConfig, pluginComponents: EMPTY_PLUGIN_COMPONENTS })
|
||||
|
||||
//#then
|
||||
const mergedMcp = config.mcp as Record<string, Record<string, unknown>>
|
||||
|
||||
@@ -42,6 +42,8 @@ const EMPTY_PLUGIN_COMPONENTS = {
|
||||
errors: [],
|
||||
}
|
||||
|
||||
const TEST_CTX = { directory: "/workspace/project" }
|
||||
|
||||
describe("applyMcpConfig", () => {
|
||||
test("preserves enabled:false from user config after merge with .mcp.json MCPs", async () => {
|
||||
//#given
|
||||
@@ -62,7 +64,7 @@ describe("applyMcpConfig", () => {
|
||||
|
||||
//#when
|
||||
const { applyMcpConfig } = await import("./mcp-config-handler")
|
||||
await applyMcpConfig({ config, pluginConfig, pluginComponents: EMPTY_PLUGIN_COMPONENTS })
|
||||
await applyMcpConfig({ config, ctx: TEST_CTX, pluginConfig, pluginComponents: EMPTY_PLUGIN_COMPONENTS })
|
||||
|
||||
//#then
|
||||
const mergedMcp = config.mcp as Record<string, Record<string, unknown>>
|
||||
@@ -89,6 +91,7 @@ describe("applyMcpConfig", () => {
|
||||
const { applyMcpConfig } = await import("./mcp-config-handler")
|
||||
await applyMcpConfig({
|
||||
config,
|
||||
ctx: TEST_CTX,
|
||||
pluginConfig,
|
||||
pluginComponents: {
|
||||
...EMPTY_PLUGIN_COMPONENTS,
|
||||
@@ -112,7 +115,7 @@ describe("applyMcpConfig", () => {
|
||||
|
||||
//#when
|
||||
const { applyMcpConfig } = await import("./mcp-config-handler")
|
||||
await applyMcpConfig({ config, pluginConfig, pluginComponents: EMPTY_PLUGIN_COMPONENTS })
|
||||
await applyMcpConfig({ config, ctx: TEST_CTX, pluginConfig, pluginComponents: EMPTY_PLUGIN_COMPONENTS })
|
||||
|
||||
//#then
|
||||
expect(loadMcpConfigsSpy).toHaveBeenCalledWith(["firecrawl", "exa"])
|
||||
@@ -135,7 +138,7 @@ describe("applyMcpConfig", () => {
|
||||
|
||||
//#when
|
||||
const { applyMcpConfig } = await import("./mcp-config-handler")
|
||||
await applyMcpConfig({ config, pluginConfig, pluginComponents: EMPTY_PLUGIN_COMPONENTS })
|
||||
await applyMcpConfig({ config, ctx: TEST_CTX, pluginConfig, pluginComponents: EMPTY_PLUGIN_COMPONENTS })
|
||||
|
||||
//#then
|
||||
const mergedMcp = config.mcp as Record<string, Record<string, unknown>>
|
||||
@@ -152,6 +155,7 @@ describe("applyMcpConfig", () => {
|
||||
const { applyMcpConfig } = await import("./mcp-config-handler")
|
||||
await applyMcpConfig({
|
||||
config,
|
||||
ctx: TEST_CTX,
|
||||
pluginConfig,
|
||||
pluginComponents: {
|
||||
...EMPTY_PLUGIN_COMPONENTS,
|
||||
@@ -166,4 +170,17 @@ describe("applyMcpConfig", () => {
|
||||
expect(mergedMcp).not.toHaveProperty("plugin:custom")
|
||||
})
|
||||
|
||||
test("passes the OpenCode workspace directory into built-in MCP config", async () => {
|
||||
//#given
|
||||
const config: Record<string, unknown> = { mcp: {} }
|
||||
const pluginConfig = createPluginConfig()
|
||||
|
||||
//#when
|
||||
const { applyMcpConfig } = await import("./mcp-config-handler")
|
||||
await applyMcpConfig({ config, ctx: TEST_CTX, pluginConfig, pluginComponents: EMPTY_PLUGIN_COMPONENTS })
|
||||
|
||||
//#then
|
||||
expect(createBuiltinMcpsSpy).toHaveBeenCalledWith([], pluginConfig, { cwd: TEST_CTX.directory })
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
@@ -27,6 +27,7 @@ function captureUserDisabledMcps(
|
||||
|
||||
export async function applyMcpConfig(params: {
|
||||
config: Record<string, unknown>;
|
||||
ctx: { directory: string };
|
||||
pluginConfig: OhMyOpenCodeConfig;
|
||||
pluginComponents: PluginComponents;
|
||||
}): Promise<void> {
|
||||
@@ -47,7 +48,7 @@ export async function applyMcpConfig(params: {
|
||||
}
|
||||
|
||||
const merged = {
|
||||
...createBuiltinMcps(disabledMcps, params.pluginConfig),
|
||||
...createBuiltinMcps(disabledMcps, params.pluginConfig, { cwd: params.ctx.directory }),
|
||||
...mcpResult.servers,
|
||||
...(userMcp ?? {}),
|
||||
...params.pluginComponents.mcpServers,
|
||||
|
||||
Reference in New Issue
Block a user