feat(opencode): support cwd on local MCP servers (#30676)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
This commit is contained in:
parent
ca8db315a9
commit
7e7ad37736
@ -6,6 +6,9 @@ import { PositiveInt } from "../schema"
|
||||
export class Local extends Schema.Class<Local>("ConfigV2.MCP.Local")({
|
||||
type: Schema.Literal("local"),
|
||||
command: Schema.String.pipe(Schema.Array),
|
||||
cwd: Schema.String.pipe(Schema.optional).annotate({
|
||||
description: "Working directory for the MCP server process. Relative paths resolve from the workspace directory.",
|
||||
}),
|
||||
environment: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
|
||||
disabled: Schema.Boolean.pipe(Schema.optional),
|
||||
timeout: PositiveInt.pipe(Schema.optional),
|
||||
|
||||
@ -8,6 +8,9 @@ export const Local = Schema.Struct({
|
||||
command: Schema.mutable(Schema.Array(Schema.String)).annotate({
|
||||
description: "Command and arguments to run the MCP server",
|
||||
}),
|
||||
cwd: Schema.optional(Schema.String).annotate({
|
||||
description: "Working directory for the MCP server process. Relative paths resolve from the workspace directory.",
|
||||
}),
|
||||
environment: Schema.optional(Schema.Record(Schema.String, Schema.String)).annotate({
|
||||
description: "Environment variables to set when running the MCP server",
|
||||
}),
|
||||
|
||||
@ -139,7 +139,14 @@ function mcp(info: typeof ConfigV1.Info.Type) {
|
||||
function migrateMcp(info: ConfigMCPV1.Info) {
|
||||
const disabled = info.enabled === undefined ? undefined : !info.enabled
|
||||
if (info.type === "local")
|
||||
return { type: info.type, command: info.command, environment: info.environment, disabled, timeout: info.timeout }
|
||||
return {
|
||||
type: info.type,
|
||||
command: info.command,
|
||||
cwd: info.cwd,
|
||||
environment: info.environment,
|
||||
disabled,
|
||||
timeout: info.timeout,
|
||||
}
|
||||
return {
|
||||
type: info.type,
|
||||
url: info.url,
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import path from "node:path"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { type Tool } from "ai"
|
||||
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
|
||||
@ -304,7 +305,8 @@ export const layer = Layer.effect(
|
||||
mcp: ConfigMCPV1.Info & { type: "local" },
|
||||
) {
|
||||
const [cmd, ...args] = mcp.command
|
||||
const cwd = yield* InstanceState.directory
|
||||
const baseDir = yield* InstanceState.directory
|
||||
const cwd = mcp.cwd ? path.resolve(baseDir, mcp.cwd) : baseDir
|
||||
const transport = new StdioClientTransport({
|
||||
stderr: "pipe",
|
||||
command: cmd,
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import path from "node:path"
|
||||
import { expect, mock, beforeEach } from "bun:test"
|
||||
import { ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js"
|
||||
import { Cause, Effect, Exit } from "effect"
|
||||
import type { MCP as MCPNS } from "../../src/mcp/index"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { TestInstance } from "../fixture/fixture"
|
||||
|
||||
// --- Mock infrastructure ---
|
||||
|
||||
@ -48,6 +50,8 @@ let connectError = "Mock transport cannot connect"
|
||||
let clientCreateCount = 0
|
||||
// Tracks how many times transport.close() is called across all mock transports
|
||||
let transportCloseCount = 0
|
||||
// Captures the opts passed to each MockStdioTransport, keyed by lastCreatedClientName
|
||||
const stdioOptsByName = new Map<string, any>()
|
||||
|
||||
function getOrCreateClientState(name?: string): MockClientState {
|
||||
const key = name ?? "default"
|
||||
@ -82,8 +86,9 @@ function getOrCreateClientState(name?: string): MockClientState {
|
||||
class MockStdioTransport {
|
||||
stderr: null = null
|
||||
pid = 12345
|
||||
// oxlint-disable-next-line no-useless-constructor
|
||||
constructor(_opts: any) {}
|
||||
constructor(opts: any) {
|
||||
if (lastCreatedClientName) stdioOptsByName.set(lastCreatedClientName, opts)
|
||||
}
|
||||
async start() {
|
||||
if (connectShouldHang) return new Promise<void>(() => {}) // never resolves
|
||||
if (connectShouldFail) throw new Error(connectError)
|
||||
@ -246,6 +251,20 @@ function statusName(status: Record<string, MCPNS.Status> | MCPNS.Status, server:
|
||||
return status[server]?.status
|
||||
}
|
||||
|
||||
it.instance(
|
||||
"local mcp cwd resolves relative paths against instance directory",
|
||||
() =>
|
||||
MCP.Service.use((mcp: MCPNS.Interface) =>
|
||||
Effect.gen(function* () {
|
||||
const { directory } = yield* TestInstance
|
||||
lastCreatedClientName = "rel-cwd"
|
||||
yield* mcp.add("rel-cwd", { type: "local", command: ["echo", "test"], cwd: "plugins/sub" })
|
||||
expect(stdioOptsByName.get("rel-cwd")?.cwd).toBe(path.resolve(directory, "plugins/sub"))
|
||||
}),
|
||||
),
|
||||
{ config: { mcp: {} } },
|
||||
)
|
||||
|
||||
// ========================================================================
|
||||
// Test: tools() are cached after connect
|
||||
// ========================================================================
|
||||
|
||||
@ -116,13 +116,14 @@ use the mcp_everything tool to add the number 3 and 4
|
||||
|
||||
Here are all the options for configuring a local MCP server.
|
||||
|
||||
| Option | Type | Required | Description |
|
||||
| ------------- | ------- | -------- | ----------------------------------------------------------------------------------- |
|
||||
| `type` | String | Y | Type of MCP server connection, must be `"local"`. |
|
||||
| `command` | Array | Y | Command and arguments to run the MCP server. |
|
||||
| `environment` | Object | | Environment variables to set when running the server. |
|
||||
| `enabled` | Boolean | | Enable or disable the MCP server on startup. |
|
||||
| `timeout` | Number | | Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds). |
|
||||
| Option | Type | Required | Description |
|
||||
| ------------- | ------- | -------- | ---------------------------------------------------------------------------------------- |
|
||||
| `type` | String | Y | Type of MCP server connection, must be `"local"`. |
|
||||
| `command` | Array | Y | Command and arguments to run the MCP server. |
|
||||
| `cwd` | String | | Working directory for the MCP server process. Relative paths resolve from the workspace. |
|
||||
| `environment` | Object | | Environment variables to set when running the server. |
|
||||
| `enabled` | Boolean | | Enable or disable the MCP server on startup. |
|
||||
| `timeout` | Number | | Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds). |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user