feat(mcp): support client roots (#32230)
This commit is contained in:
parent
42f339c681
commit
f55a931f59
4 changed files with 56 additions and 7 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import path from "node:path"
|
||||
import { pathToFileURL } from "node:url"
|
||||
import { expect, mock, beforeEach } from "bun:test"
|
||||
import { ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js"
|
||||
import { ListRootsRequestSchema, 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"
|
||||
|
|
@ -38,6 +39,8 @@ interface MockClientState {
|
|||
{ resources: Array<{ name: string; uri: string; description?: string }>; nextCursor?: string }
|
||||
>
|
||||
closed: boolean
|
||||
clientOptions?: { capabilities?: { roots?: { listChanged?: boolean } } }
|
||||
requestHandlers: Map<unknown, (...args: any[]) => Promise<any>>
|
||||
notificationHandlers: Map<unknown, (...args: any[]) => any>
|
||||
}
|
||||
|
||||
|
|
@ -75,6 +78,7 @@ function getOrCreateClientState(name?: string): MockClientState {
|
|||
promptPages: {},
|
||||
resourcePages: {},
|
||||
closed: false,
|
||||
requestHandlers: new Map(),
|
||||
notificationHandlers: new Map(),
|
||||
}
|
||||
clientStates.set(key, state)
|
||||
|
|
@ -149,8 +153,10 @@ void mock.module("@modelcontextprotocol/sdk/client/index.js", () => ({
|
|||
_state!: MockClientState
|
||||
transport: any
|
||||
|
||||
constructor(_opts: any) {
|
||||
constructor(_info: any, options?: MockClientState["clientOptions"]) {
|
||||
clientCreateCount++
|
||||
this._state = getOrCreateClientState(lastCreatedClientName)
|
||||
this._state.clientOptions = options
|
||||
}
|
||||
|
||||
async connect(transport: { start: () => Promise<void> }) {
|
||||
|
|
@ -160,6 +166,10 @@ void mock.module("@modelcontextprotocol/sdk/client/index.js", () => ({
|
|||
this._state = getOrCreateClientState(lastCreatedClientName)
|
||||
}
|
||||
|
||||
setRequestHandler(schema: unknown, handler: (...args: any[]) => Promise<any>) {
|
||||
this._state.requestHandlers.set(schema, handler)
|
||||
}
|
||||
|
||||
setNotificationHandler(schema: unknown, handler: (...args: any[]) => any) {
|
||||
this._state?.notificationHandlers.set(schema, handler)
|
||||
}
|
||||
|
|
@ -251,6 +261,28 @@ function statusName(status: Record<string, MCPNS.Status> | MCPNS.Status, server:
|
|||
return status[server]?.status
|
||||
}
|
||||
|
||||
it.instance(
|
||||
"advertises and lists the instance directory as its root",
|
||||
() =>
|
||||
MCP.Service.use((mcp: MCPNS.Interface) =>
|
||||
Effect.gen(function* () {
|
||||
const { directory } = yield* TestInstance
|
||||
lastCreatedClientName = "roots"
|
||||
yield* mcp.add("roots", { type: "local", command: ["echo", "test"] })
|
||||
|
||||
const state = getOrCreateClientState("roots")
|
||||
expect(state.clientOptions?.capabilities?.roots).toEqual({})
|
||||
expect(state.clientOptions?.capabilities?.roots?.listChanged).toBeUndefined()
|
||||
|
||||
const handler = state.requestHandlers.get(ListRootsRequestSchema)
|
||||
expect(handler).toBeDefined()
|
||||
const result = yield* Effect.promise(() => handler?.() ?? Promise.reject(new Error("roots handler missing")))
|
||||
expect(result).toEqual({ roots: [{ uri: pathToFileURL(directory).href }] })
|
||||
}),
|
||||
),
|
||||
{ config: { mcp: {} } },
|
||||
)
|
||||
|
||||
it.instance(
|
||||
"local mcp cwd resolves relative paths against instance directory",
|
||||
() =>
|
||||
|
|
|
|||
|
|
@ -87,6 +87,8 @@ void mock.module("@modelcontextprotocol/sdk/client/sse.js", () => ({
|
|||
// Mock the MCP SDK Client
|
||||
void mock.module("@modelcontextprotocol/sdk/client/index.js", () => ({
|
||||
Client: class MockClient {
|
||||
setRequestHandler() {}
|
||||
|
||||
async connect(transport: { start: () => Promise<void> }) {
|
||||
await transport.start()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,6 +89,8 @@ void mock.module("@modelcontextprotocol/sdk/client/sse.js", () => ({
|
|||
// Mock the MCP SDK Client to trigger OAuth flow
|
||||
void mock.module("@modelcontextprotocol/sdk/client/index.js", () => ({
|
||||
Client: class MockClient {
|
||||
setRequestHandler() {}
|
||||
|
||||
async connect(transport: { start: () => Promise<void> }) {
|
||||
await transport.start()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue