This commit is contained in:
Deleted user 2026-05-27 11:29:01 +08:00 committed by GitHub
commit 21cf6a7e50
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 179 additions and 1 deletions

View file

@ -70,6 +70,7 @@ const BaseConfigSchema = z.object({
alwaysAllow: z.array(z.string()).default([]),
watchPaths: z.array(z.string()).optional(), // paths to watch for changes and restart server
disabledTools: z.array(z.string()).default([]),
serverInstructions: z.string().optional(), // user-defined instructions that supplement or override protocol-provided instructions
})
// Custom error messages for better user feedback
@ -632,6 +633,7 @@ export class McpHub {
disabled: reason === DisableReason.SERVER_DISABLED ? true : config.disabled,
source,
projectPath: source === "project" ? vscode.workspace.workspaceFolders?.[0]?.uri.fsPath : undefined,
instructions: config.serverInstructions,
errorHistory: [],
},
client: null,
@ -878,7 +880,16 @@ export class McpHub {
await client.connect(transport)
connection.server.status = "connected"
connection.server.error = ""
connection.server.instructions = client.getInstructions()
// Merge user-defined serverInstructions from config with protocol-provided instructions.
// Config-defined serverInstructions take precedence; if both exist, they are combined.
const protocolInstructions = client.getInstructions()
const configInstructions = configInjected.serverInstructions
if (configInstructions && protocolInstructions) {
connection.server.instructions = `${configInstructions}\n\n${protocolInstructions}`
} else {
connection.server.instructions = configInstructions || protocolInstructions
}
// Initial fetch of tools and resources
connection.server.tools = await this.fetchToolsList(name, source)

View file

@ -2019,6 +2019,173 @@ describe("McpHub", () => {
})
})
describe("serverInstructions config support", () => {
it("should accept serverInstructions in config schema", () => {
const result = ServerConfigSchema.safeParse({
command: "node",
args: ["test.js"],
serverInstructions: "Custom instructions for this server",
})
expect(result.success).toBe(true)
if (result.success) {
expect(result.data.serverInstructions).toBe("Custom instructions for this server")
}
})
it("should accept config without serverInstructions (optional field)", () => {
const result = ServerConfigSchema.safeParse({
command: "node",
args: ["test.js"],
})
expect(result.success).toBe(true)
if (result.success) {
expect(result.data.serverInstructions).toBeUndefined()
}
})
it("should accept serverInstructions for SSE config", () => {
const result = ServerConfigSchema.safeParse({
type: "sse",
url: "http://localhost:3000/sse",
serverInstructions: "SSE server custom instructions",
})
expect(result.success).toBe(true)
if (result.success) {
expect(result.data.serverInstructions).toBe("SSE server custom instructions")
}
})
it("should accept serverInstructions for streamable-http config", () => {
const result = ServerConfigSchema.safeParse({
type: "streamable-http",
url: "http://localhost:3000/mcp",
serverInstructions: "Streamable HTTP server custom instructions",
})
expect(result.success).toBe(true)
if (result.success) {
expect(result.data.serverInstructions).toBe("Streamable HTTP server custom instructions")
}
})
it("should use config serverInstructions when protocol provides none", async () => {
// Mock StdioClientTransport
const stdioModule = await import("@modelcontextprotocol/sdk/client/stdio.js")
const StdioClientTransport = stdioModule.StdioClientTransport as ReturnType<typeof vi.fn>
const mockTransport = {
start: vi.fn().mockResolvedValue(undefined),
close: vi.fn().mockResolvedValue(undefined),
stderr: { on: vi.fn() },
onerror: null,
onclose: null,
}
StdioClientTransport.mockImplementation(() => mockTransport)
// Mock Client - returns undefined for getInstructions (no protocol instructions)
const clientModule = await import("@modelcontextprotocol/sdk/client/index.js")
const Client = clientModule.Client as ReturnType<typeof vi.fn>
Client.mockImplementation(() => ({
connect: vi.fn().mockResolvedValue(undefined),
close: vi.fn().mockResolvedValue(undefined),
getInstructions: vi.fn().mockReturnValue(undefined),
request: vi.fn().mockResolvedValue({ tools: [], resources: [], resourceTemplates: [] }),
}))
vi.mocked(fs.readFile).mockResolvedValue(
JSON.stringify({
mcpServers: {
"instructions-test-server": {
command: "node",
args: ["test.js"],
serverInstructions: "Config-defined instructions",
},
},
}),
)
const mcpHub = new McpHub(mockProvider as ClineProvider)
await new Promise((resolve) => setTimeout(resolve, 100))
const connection = mcpHub.connections.find((conn) => conn.server.name === "instructions-test-server")
expect(connection).toBeDefined()
expect(connection?.server.instructions).toBe("Config-defined instructions")
})
it("should combine config serverInstructions with protocol instructions", async () => {
// Mock StdioClientTransport
const stdioModule = await import("@modelcontextprotocol/sdk/client/stdio.js")
const StdioClientTransport = stdioModule.StdioClientTransport as ReturnType<typeof vi.fn>
const mockTransport = {
start: vi.fn().mockResolvedValue(undefined),
close: vi.fn().mockResolvedValue(undefined),
stderr: { on: vi.fn() },
onerror: null,
onclose: null,
}
StdioClientTransport.mockImplementation(() => mockTransport)
// Mock Client - returns protocol instructions
const clientModule = await import("@modelcontextprotocol/sdk/client/index.js")
const Client = clientModule.Client as ReturnType<typeof vi.fn>
Client.mockImplementation(() => ({
connect: vi.fn().mockResolvedValue(undefined),
close: vi.fn().mockResolvedValue(undefined),
getInstructions: vi.fn().mockReturnValue("Protocol instructions from server"),
request: vi.fn().mockResolvedValue({ tools: [], resources: [], resourceTemplates: [] }),
}))
vi.mocked(fs.readFile).mockResolvedValue(
JSON.stringify({
mcpServers: {
"combined-instructions-server": {
command: "node",
args: ["test.js"],
serverInstructions: "Config-defined instructions",
},
},
}),
)
const mcpHub = new McpHub(mockProvider as ClineProvider)
await new Promise((resolve) => setTimeout(resolve, 100))
const connection = mcpHub.connections.find((conn) => conn.server.name === "combined-instructions-server")
expect(connection).toBeDefined()
// Config instructions should come first, followed by protocol instructions
expect(connection?.server.instructions).toBe(
"Config-defined instructions\n\nProtocol instructions from server",
)
})
it("should set serverInstructions on placeholder connections for disabled servers", async () => {
vi.mocked(fs.readFile).mockResolvedValue(
JSON.stringify({
mcpServers: {
"disabled-instructions-server": {
command: "node",
args: ["test.js"],
disabled: true,
serverInstructions: "Instructions for disabled server",
},
},
}),
)
const mcpHub = new McpHub(mockProvider as ClineProvider)
await new Promise((resolve) => setTimeout(resolve, 100))
const connection = mcpHub.connections.find((conn) => conn.server.name === "disabled-instructions-server")
expect(connection).toBeDefined()
expect(connection?.type).toBe("disconnected")
expect(connection?.server.instructions).toBe("Instructions for disabled server")
})
})
describe("Windows command wrapping", () => {
let StdioClientTransport: ReturnType<typeof vi.fn>
let Client: ReturnType<typeof vi.fn>