# Files changed · PR \#10556 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/10556/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[feat\(mcp\): expose pending user questions](https://git.cafe/pingdotgg/t3code/pull/10556.md)

Version: 4

Head commit: 51483abe7fe7d45839507cd982e24545658b95da

Base commit: 0a74c9341cb5342e0e5f1d5697eebc5a7dc95a7e

Comparison base: 0a74c9341cb5342e0e5f1d5697eebc5a7dc95a7e

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/server/src/mcp/toolkits/thread/handlers.ts

Status: modified; +53; −0

```diff
@@ -1,5 +1,6 @@
 import {
   type CommandId,
+  type RuntimeRequestId,
   type ThreadId,
   type OrchestrationV2ThreadProjection,
   type RunId,
@@ -34,7 +35,59 @@
   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 };
+    }),
   t3_queue_list: (input) =>
     Effect.gen(function* () {
       const { projection } = yield* readThread(input.threadId);
```

## apps/server/src/mcp/toolkits/thread/tools.ts

Status: modified; +52; −0

```diff
@@ -1,4 +1,6 @@
 import {
+  RuntimeRequestId,
+  ProviderUserInputAnswers,
   IsoDateTime,
   OrchestratorMcpFailure,
   OrchestrationV2DispatchCommandResult,
@@ -101,7 +103,57 @@
   parameters: Schema.Struct({ ...queueTarget, targetRunId: RunId }),
 }).annotate(Tool.Destructive, true);
 
+const requestTarget = { threadId: Schema.optional(ThreadId), requestId: RuntimeRequestId };
+const question = Schema.Struct({
+  id: Schema.String,
+  header: Schema.String,
+  question: Schema.String,
+  options: Schema.Array(
+    Schema.Struct({
+      label: Schema.String,
+      description: Schema.String,
+      value: Schema.optional(Schema.String),
+    }),
+  ),
+  multiSelect: Schema.optional(Schema.Boolean),
+  allowCustomAnswer: Schema.optional(Schema.Boolean),
+  required: Schema.optional(Schema.Boolean),
+});
+const pendingRequest = Schema.Struct({
+  requestId: RuntimeRequestId,
+  questions: Schema.Array(question),
+});
+export const PendingRequestListTool = Tool.make("t3_pending_request_list", {
+  ...commandTool,
+  description:
+    "List pending user questions in a thread in the calling project. Approval requests are not included.",
+  parameters: Schema.Struct({ threadId: Schema.optional(ThreadId) }),
+  success: Schema.Struct({ requestIds: Schema.Array(RuntimeRequestId) }),
+})
+  .annotate(Tool.Readonly, true)
+  .annotate(Tool.Destructive, false);
+export const PendingRequestReadTool = Tool.make("t3_pending_request_read", {
+  ...commandTool,
+  description:
+    "Read a pending user question. Answer with t3_pending_request_respond; existing live or message response handling is used.",
+  parameters: Schema.Struct(requestTarget),
+  success: pendingRequest,
+})
+  .annotate(Tool.Readonly, true)
+  .annotate(Tool.Destructive, false);
+export const PendingRequestRespondTool = Tool.make("t3_pending_request_respond", {
+  ...commandTool,
+  description:
+    "Answer a pending user-input request using the existing runtime response command. This cannot approve a permission request.",
+  parameters: Schema.Struct({ ...requestTarget, answers: ProviderUserInputAnswers }),
+})
+  .annotate(Tool.Destructive, true)
+  .annotate(Tool.OpenWorld, true);
+
 export const ThreadToolkit = Toolkit.make(
+  PendingRequestListTool,
+  PendingRequestReadTool,
+  PendingRequestRespondTool,
   ThreadOrganizeTool,
   QueueListTool,
   QueueReadTool,
```

## apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts

Status: modified; +2; −0

```diff
@@ -808,6 +808,8 @@
   "mcp__t3-code__list_scheduled_tasks",
   "mcp__t3-code__t3_thread_list",
   "mcp__t3-code__t3_thread_wait",
+  "mcp__t3-code__t3_pending_request_list",
+  "mcp__t3-code__t3_pending_request_read",
   "mcp__t3-code__t3_queue_list",
   "mcp__t3-code__t3_queue_read",
 ];
```

## packages/shared/src/t3McpToolPresentation.ts

Status: modified; +3; −0

```diff
@@ -54,6 +54,9 @@
   t3_queue_cancel: { displayName: "Cancel a queued run" },
   t3_queue_reorder: { displayName: "Reorder a queued run" },
   t3_queue_promote_to_steer: { displayName: "Steer with a queued message" },
+  t3_pending_request_list: { displayName: "List pending questions" },
+  t3_pending_request_read: { displayName: "Read pending questions" },
+  t3_pending_request_respond: { displayName: "Answer pending questions" },
   t3_thread_organize: { displayName: "Organize a thread" },
   t3_thread_update: { displayName: "Update T3 thread metadata" },
   t3_thread_send: { displayName: "Send to a T3 thread", summaryAction: "thread-send" },
```
