refactor(core): reuse MCP prompt helpers (#45660)

This commit is contained in:
Kit Langton 2026-08-28 13:26:51 -04:00 committed by GitHub
parent d28b6e9ac2
commit e4bc8b765b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 64 additions and 18 deletions

View file

@ -10,17 +10,14 @@ import {
CallToolResultSchema,
ElicitationCompleteNotificationSchema,
ElicitRequestSchema,
GetPromptResultSchema,
type Implementation,
type ElicitRequestFormParams,
type ElicitRequestParams,
type ElicitRequestURLParams,
type ElicitResult,
ListPromptsResultSchema,
ListRootsRequestSchema,
ListToolsResultSchema,
PromptListChangedNotificationSchema,
PromptSchema,
ResourceListChangedNotificationSchema,
type LoggingMessageNotification,
LoggingMessageNotificationSchema,
@ -41,10 +38,6 @@ const toError = (error: unknown) => (error instanceof Error ? error : new Error(
const TolerantListToolsResult = ListToolsResultSchema.extend({
tools: ToolSchema.omit({ outputSchema: true }).array(),
})
const TolerantListPromptsResult = ListPromptsResultSchema.extend({
prompts: PromptSchema.array(),
})
export class NeedsAuthError extends Schema.TaggedError<NeedsAuthError>()("MCP.NeedsAuthError", {
server: Schema.String,
}) {
@ -301,12 +294,8 @@ export const connect = Effect.fnUntraced(function* (
const prompts = yield* Effect.tryPromise({
try: () =>
paginate(
async (cursor) => {
const params = cursor === undefined ? undefined : { cursor }
return client.request({ method: "prompts/list", params }, TolerantListPromptsResult, {
timeout: catalogTimeout,
})
},
(cursor) =>
client.listPrompts(cursor === undefined ? undefined : { cursor }, { timeout: catalogTimeout }),
(result) => result.prompts,
),
catch: toError,
@ -396,11 +385,7 @@ export const connect = Effect.fnUntraced(function* (
prompt: (input) =>
Effect.tryPromise({
try: (signal) =>
client.request(
{ method: "prompts/get", params: { name: input.name, arguments: input.args ?? {} } },
GetPromptResultSchema,
{ signal, timeout: executionTimeout },
),
client.getPrompt({ name: input.name, arguments: input.args ?? {} }, { signal, timeout: executionTimeout }),
catch: toError,
}).pipe(
Effect.map((result) => ({

View file

@ -0,0 +1,30 @@
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import { GetPromptRequestSchema, ListPromptsRequestSchema } from "@modelcontextprotocol/sdk/types.js"
const server = new Server({ name: "prompts", version: "1.0.0" }, { capabilities: { prompts: {} } })
server.setRequestHandler(ListPromptsRequestSchema, ({ params }) =>
Promise.resolve(
params?.cursor === "page-2"
? { prompts: [{ name: "second", description: "Second prompt" }] }
: {
prompts: [
{
name: "first",
description: "First prompt",
arguments: [{ name: "topic", description: "Topic to explain", required: true }],
},
],
nextCursor: "page-2",
},
),
)
server.setRequestHandler(GetPromptRequestSchema, ({ params }) =>
Promise.resolve({
messages: [{ role: "user", content: { type: "text", text: params.arguments?.topic ?? "missing" } }],
}),
)
await server.connect(new StdioServerTransport())

View file

@ -474,6 +474,37 @@ test("retains output schemas across paginated MCP discovery", async () => {
])
})
test("lists paginated prompts and invokes them through the MCP client", async () => {
const result = await Effect.runPromise(
Effect.scoped(
Effect.gen(function* () {
const connection = yield* connect(
"prompts",
new ConfigMCP.Local({
type: "local",
command: [process.execPath, path.join(import.meta.dir, "fixture/mcp-prompts.ts")],
}),
import.meta.dir,
)
return {
prompts: yield* connection.prompts(),
result: yield* connection.prompt({ name: "first", args: { topic: "Effect" } }),
}
}),
),
)
expect(result.prompts).toEqual([
{
name: "first",
description: "First prompt",
arguments: [{ name: "topic", description: "Topic to explain", required: true }],
},
{ name: "second", description: "Second prompt", arguments: undefined },
])
expect(result.result).toEqual({ messages: [{ role: "user", content: { type: "text", text: "Effect" } }] })
})
test("spawns local MCP servers through the location environment", async () => {
const spawns: Array<ChildProcess.Command> = []
const cwd = path.join(import.meta.dir, "fixture")