Repository: openai/openai-agents-js (2025-06-25T09:47:16Z)

Issue:
Human in the Loop MCP approval fails
### Please read this first

- **Have you read the docs?** [Agents SDK docs](https://openai.github.io/openai-agents-js/) ✅ 
- **Have you searched for related issues?** Others may have faced similar issues. ✅ 

### Describe the bug

There is a bug in the serialization logic for `RunState` that causes data loss for MCP approval items when an agent run is interrupted and resumed. Specifically, the Zod schema for `lastProcessedResponse.mcpApprovalRequests` is incomplete, causing essential properties like `id` and `providerData` to be stripped from the rawItem during serialization.
This leads to a corrupted state upon deserialization, where subsequent logic that relies on these properties fails, making it impossible to correctly handle MCP tool approvals across agent turns.

This results to an `400 The following MCP approval requests do not have an approval: mcpr_...` error.

[This](https://github.com/openai/openai-agents-js/blob/main/packages/agents-core/src/runImplementation.ts#L330) filter returns an empty array, because `providerData` is not there.

Solution:
Add `id` and `providerData` to [rawItem](https://github.com/openai/openai-agents-js/blob/main/packages/agents-core/src/runState.ts#L162)

### Debug information

- Agents SDK version: (e.g. `v0.0.8`)
- Runtime environment (e.g. `Node.js 22.16.0`)

### Repro steps

```ts
export const agent = new Agent({
  name: "Assistant",
  model: "gpt-4.1-2025-04-14",
  instructions: "You are a helpful assistant.",
  tools: [
    hostedMcpTool({
      serverLabel: "deepwiki",
      serverUrl: "https://mcp.deepwiki.com/mcp",
      requireApproval: "always",
    }),
  ],
});

// Serialization:
let result = await run(agent, [
    { role: "system", content: systemContent },
    { role: "user", content: prompt },
  ]);

  const hasInterruptions = result.interruptions?.length > 0;

  if (hasInterruptions) {
    const uuid = crypto.randomUUID();
    const statePath = path.join("./agent_states", `${uuid}.json`);
    await fs.mkdir(path.dirname(statePath), { recursive: true });
    await fs.writeFile(statePath, JSON.stringify(result.state, null, 2), "utf-8");
}

 // Deserealization:
    const stored = await fs.readFile(statePath, "utf-8");

    const state = await RunState.fromString(agent, stored); // rawItem providerData is no longer there
    const interruptions = state.getInterruptions();

    for (const interruption of interruptions) {
      if (approve) {
        console.log("Approving interruption:", interruption);
        state.approve(interruption, { alwaysApprove: true });
      } else {
        state.reject(interruption);
      }
    }

   let result = await run(agent, state); // error
```

### Expected behavior

No approval error.


I'm happy to open a PR, if it's verified by your side.



Discussion:
If you'd like to open a PR that would be great! @seratch can you review?
Submitted https://github.com/openai/openai-agents-js/pull/156 for this issue



Patch:
diff --git a/packages/agents-core/src/runState.ts b/packages/agents-core/src/runState.ts
index 2de2ff3a..449cd79b 100644
--- a/packages/agents-core/src/runState.ts
+++ b/packages/agents-core/src/runState.ts
@@ -165,6 +165,8 @@ const serializedProcessedResponseSchema = z.object({
             arguments: z.string().optional(),
             status: z.string().optional(),
             output: z.string().optional(),
+            // this always exists but marked as optional for early version compatibility; when releasing 1.0, we can remove the nullable and optional
+            providerData: z.record(z.string(), z.any()).nullable().optional(),
           }),
         }),
         // HostedMCPTool


Test patch:
diff --git a/packages/agents-core/test/runState.test.ts b/packages/agents-core/test/runState.test.ts
index 341d9caf..4d9a57ef 100644
--- a/packages/agents-core/test/runState.test.ts
+++ b/packages/agents-core/test/runState.test.ts
@@ -278,7 +278,50 @@ describe('deserialize helpers', () => {
       functions: [],
       handoffs: [],
       computerActions: [{ toolCall: call, computer: tool }],
-      mcpApprovalRequests: [],
+      mcpApprovalRequests: [
+        {
+          requestItem: {
+            rawItem: {
+              type: 'hosted_tool_call',
+              name: 'fetch_generic_url_content',
+              status: 'in_progress',
+              providerData: {
+                id: 'mcpr_685bc3c47ed88192977549b5206db77504d4306d5de6ab36',
+                type: 'mcp_approval_request',
+                arguments:
+                  '{"url":"https://raw.githubusercontent.com/openai/codex/main/README.md"}',
+                name: 'fetch_generic_url_content',
+                server_label: 'gitmcp',
+              },
+            },
+            type: 'tool_approval_item',
+            agent: new Agent({ name: 'foo ' }),
+            toJSON: function (): any {
+              throw new Error('Function not implemented.');
+            },
+          },
+          mcpTool: {
+            type: 'hosted_tool',
+            name: 'hosted_mcp',
+            providerData: {
+              type: 'mcp',
+              server_label: 'gitmcp',
+              server_url: 'https://gitmcp.io/openai/codex',
+              require_approval: {
+                always: {
+                  tool_names: ['fetch_generic_url_content'],
+                },
+                never: {
+                  tool_names: [
+                    'search_codex_code',
+                    'fetch_codex_documentation',
+                  ],
+                },
+              },
+            },
+          },
+        },
+      ],
       toolsUsed: [],
       hasToolsOrApprovalsToRun: () => true,
     };
@@ -292,5 +335,15 @@ describe('deserialize helpers', () => {
     if (restored._currentStep?.type === 'next_step_handoff') {
       expect(restored._currentStep.newAgent).toBe(agent);
     }
+    expect(
+      restored._lastProcessedResponse?.mcpApprovalRequests[0].mcpTool,
+    ).toEqual(state._lastProcessedResponse?.mcpApprovalRequests[0].mcpTool);
+    expect(
+      restored._lastProcessedResponse?.mcpApprovalRequests[0].requestItem
+        .rawItem.providerData,
+    ).toEqual(
+      state._lastProcessedResponse?.mcpApprovalRequests[0].requestItem.rawItem
+        .providerData,
+    );
   });
 });