feat(mcp): expose pending user questions#10556
1 unresolved thread · Jump to file
apps/server/src/mcp/toolkits/thread/handlers.ts · Earlier revision 61205e4f · right 65–92
Original diff
@@ -36,7 +37,59 @@ const dispatch = Effect.fn("mcp.dispatchThreadCommand")(function* (
return { sequence: result.sequence };
});
+const readQuestion = Effect.fn("mcp.readQuestion")(function* (
+ input: {
+ threadId?: ThreadId | undefined;
+ requestId: RuntimeRequestId;
+ },
+ writable = false,
+) {
+ const context = yield* writable ? readWritableThread(input.threadId) : readThread(input.threadId);
+ const request = context.projection.runtimeRequests.find(
+ (request) =>
+ request.id === input.requestId &&
+ request.kind === "user_input" &&
+ request.status === "pending",
+ );
+ const item = context.projection.turnItems.find(
+ (item) => item.type === "user_input_request" && item.requestId === input.requestId,
+ );
+ if (request === undefined || item?.type !== "user_input_request")
+ return yield* new OrchestratorMcpFailure({
+ code: "invalid_request",
+ message: "The pending user-input request was not found.",
+ });
+ return { ...context, request, item };
+});
export const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
+ t3_pending_request_list: (input) =>
+ Effect.gen(function* () {
+ const { projection } = yield* readThread(input.threadId);
+ return {
+ requestIds: projection.runtimeRequests
+ .filter((request) => request.kind === "user_input" && request.status === "pending")
+ .map((request) => request.id),
+ };
+ }),
+ t3_pending_request_read: (input) =>
+ Effect.gen(function* () {
+ const { item } = yield* readQuestion(input);
+ return { requestId: input.requestId, questions: item.questions };
+ }),
+ t3_pending_request_respond: (input) =>
+ Effect.gen(function* () {
+ const { threads, projection } = yield* readQuestion(input, true);
+ const result = yield* threads
+ .dispatch({
+ type: "runtime-request.respond",
+ threadId: projection.thread.id,
+ commandId: yield* newCommandId(),
+ requestId: input.requestId,
+ answers: input.answers,
+ })
+ .pipe(Effect.mapError(unavailable));
+ return { sequence: result.sequence };
+ }),apps/server/src/mcp/toolkits/thread/handlers.ts · right 65–92
These three handlers add new backend behavior (pending-request filtering, question lookup failure, and a runtime-request.respond dispatch), but no test exercises them — the thread toolkit currently has no test module and the integration test only invokes t3_thread_organize. Consider adding a focused test alongside the existing toolkit tests (apps/server/src/mcp/toolkits/core.test.ts or OrchestratorMcpToolkit.integration.test.ts) covering at least the respond path and the invalid_request case for a non-pending/unknown requestId, using test layers for ThreadManagementService rather than mocking the handler logic itself.
Posted via Macroscope — Effect Service Conventions
The maintainer explicitly requested shared MCP coverage rather than per-tool service-test duplication for this rebuild. These handlers reuse readThread/readWritableThread for scope and ceilings, the existing ProviderUserInputAnswers contract, and the existing runtime-request.respond command. The filter/lookup only selects pending user_input records; no response execution or provider state machine is introduced. Shared production registration/schema/capability/error tests and the existing real MCP/V2 integration remain in the stack; runtime response behavior remains tested with the service. No concrete faulty behavior was identified here, so no wrapper-only dispatch mock suite is being added.
The service-layer coverage does not exercise these MCP handlers. Current shared MCP tests cover infrastructure only, and the integration suite does not invoke the pending-request tools. The requested focused MCP test—especially invalid_request plus the respond dispatch path—remains warranted.