Skip to content

Python: [AG-UI] MCP tools not executing when using human-in-the-loop approval #3297

@TsukasaIshimura

Description

@TsukasaIshimura

I am building a web app using Microsoft Agent Framework AG-UI and CopilotKit.
I want Human‑in‑the‑Loop approvals for MCP tool calls. However, after approval, the MCP tools are not called.
In contrast, standard tools (python function) execute correctly.

Could you please look into this? Thank you.

Source code (Backend)

from __future__ import annotations

import logging
import os
from typing import Any

import uvicorn
from agent_framework import ChatAgent, MCPStreamableHTTPTool, ai_function
from agent_framework.azure import AzureOpenAIChatClient
from agent_framework_ag_ui import (AgentFrameworkAgent,
                                   add_agent_framework_fastapi_endpoint)
from app.config import settings
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware


chat_client = AzureOpenAIChatClient(credential=AzureCliCredential())

app = FastAPI(title="CopilotKit + Microsoft Agent Framework (Python)")
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


@ai_function(
    name="get_weather",
    description="Get the current weather",
    approval_mode="always_require",
)
def get_weather() -> str:
    """Get the current weather."""

    print(">>> get_weather called")
    return "Today is sunny."


get_datetime_mcp = MCPStreamableHTTPTool(
    name="get-datetime-mcp",
    url="http://localhost:8001/mcp",
    approval_mode="always_require",
)

base_agent = ChatAgent(
    name="MyAgent",
    instructions="You are a helpful AI Agent.",
    chat_client=chat_client,
    tools=[
        get_weather,
        get_datetime_mcp,
    ],
)

add_agent_framework_fastapi_endpoint(
    app=app,
    agent=AgentFrameworkAgent(
        agent=base_agent,
        name="MyAgent",
        description="Helpful AI Agent",
        require_confirmation=True,
    ),
    path="/api/v1/agents/my-agent",
)

if __name__ == "__main__":
    host = os.getenv("AGENT_HOST", "0.0.0.0")
    port = int(os.getenv("AGENT_PORT", "8000"))
    uvicorn.run("sample3:app", host=host, port=port, reload=True)

Source code (Frontend)

'use client';

import { useState } from 'react';

import { CopilotKit, useHumanInTheLoop } from '@copilotkit/react-core';
import { CopilotChat } from '@copilotkit/react-ui';

const HumanInTheLoop = () => {
  return (
    <CopilotKit runtimeUrl="/master-agent/api/copilotkit" agent="my_agent">
      <CopilotKitPage />
    </CopilotKit>
  );
};

const CopilotKitPage = () => {
  useHumanInTheLoop({
    name: 'get_datetime',
    description: 'Get the current date and time',
    parameters: [],
    render: ({ status, respond }) => {
      return <ApprovalCard title="Would you like to allow the date and time retrieval tool to run?" status={status} respond={respond} />;
    },
  });

  useHumanInTheLoop({
    name: 'get_weather',
    description: 'Get the current weather',
    parameters: [],
    render: ({ status, respond }) => {
      return <ApprovalCard title="Would you like to allow the weather retrieval tool to run?" status={status} respond={respond} />;
    },
  });

  return (
    <div className="flex h-full w-full flex-1 items-center justify-center" data-testid="background-container">
      <div className="h-full w-full rounded-lg">
        <CopilotChat
          className="mx-auto h-full max-w-6xl rounded-2xl"
          labels={{ initial: "Hi, I'm an agent specialized in helping you with your tasks. How can I help you?" }}
        ></CopilotChat>
      </div>
    </div>
  );
};

interface ApprovalCardProps {
  title: string;
  status: 'inProgress' | 'executing' | 'complete';
  respond?: (response: { accepted: boolean }) => void;
}

const ApprovalCard = ({ title, status, respond }: ApprovalCardProps) => {
  const [decision, setDecision] = useState<'allowed' | 'aborted' | null>(null);

  const handleAllow = () => {
    setDecision('allowed');
    respond?.({ accepted: true });
  };

  const handleAbort = () => {
    setDecision('aborted');
    respond?.({ accepted: false });
  };

  return (
    <div className="mt-6 w-full max-w-md rounded-2xl bg-cyan-500 shadow-xl">
      <div className="w-full rounded-2xl bg-white/20 p-8 backdrop-blur-md">
        {decision === 'allowed' ? (
          <div className="text-center">
            <h2 className="mb-2 text-2xl font-bold text-white">🚀 Allowed</h2>
          </div>
        ) : decision === 'aborted' ? (
          <div className="text-center">
            <h2 className="mb-2 text-2xl font-bold text-white">✋ Aborted</h2>
          </div>
        ) : (
          <>
            <div className="mb-6 text-center">
              <h2 className="mb-2 text-2xl font-bold text-white">{title}</h2>
            </div>

            {status === 'executing' && (
              <div className="flex gap-3">
                <button
                  onClick={handleAllow}
                  className="flex-1 rounded-xl bg-white px-6 py-4 font-bold text-black shadow-lg transition-all hover:scale-105 hover:shadow-xl active:scale-95"
                >
                  🚀 Allow!
                </button>
                <button
                  onClick={handleAbort}
                  className="flex-1 rounded-xl border-2 border-white/30 bg-black/20 px-6 py-4 font-bold text-white shadow-lg transition-all hover:scale-105 hover:bg-black/30 active:scale-95"
                >
                  ✋ Abort
                </button>
              </div>
            )}
          </>
        )}
      </div>
    </div>
  );
};

export default HumanInTheLoop;

Log

INFO:httpx:HTTP Request: POST http://localhost:8001/mcp "HTTP/1.1 200 OK"
DEBUG:httpcore.http11:receive_response_body.started request=<Request [b'POST']>
DEBUG:mcp.client.streamable_http:SSE message: root=JSONRPCResponse(jsonrpc='2.0', id=8, result={'content': [{'type': 'text', 'text': "1 validation error for call[get_datetime]\noptions\n  Unexpected keyword argument [type=unexpected_keyword_argument, input_value={'metadata': {'ag_ui_thre...8d13c1'}, 'store': True}, input_type=dict]\n    For further information visit https://errors.pydantic.dev/2.12/v/unexpected_keyword_argument"}], 'isError': True})
DEBUG:httpcore.http11:response_closed.started
DEBUG:httpcore.http11:response_closed.complete
INFO:agent_framework:Function get_datetime succeeded.
DEBUG:agent_framework:Function result: [<agent_framework._types.TextContent object at 0x769b3c3ed160>]
DEBUG:openai._base_client:Request options: {'method': 'post', 'url': '/chat/completions', 'headers': {'api-key': '<redacted>'}, 'files': None, 'idempotency_key': 'stainless-python-retry-99582d9d-5a66-4a77-b91e-60060fac54aa', 'json_data': {'messages': [{'role': 'system', 'content': [{'type': 'text', 'text': 'You are a helpful AI Agent.'}]}, {'role': 'user', 'content': [{'type': 'text', 'text': 'Please tell me the weather.'}]}, {'role': 'assistant', 'tool_calls': [{'id': 'call_7f61P2Z8N49FgToGE7zVNEYj', 'type': 'function', 'function': {'name': 'get_weather', 'arguments': '{}'}}, {'id': '80058605-e3d5-49d0-9124-35cd9d472e0c', 'type': 'function', 'function': {'name': 'confirm_changes', 'arguments': '{"function_name": "get_weather", "function_call_id": "call_7f61P2Z8N49FgToGE7zVNEYj", "function_arguments": {}, "steps": [{"description": "Execute get_weather", "status": "enabled"}]}'}}], 'content': ''}, {'role': 'tool', 'tool_call_id': '80058605-e3d5-49d0-9124-35cd9d472e0c', 'content': 'Confirmed'}, {'role': 'tool', 'tool_call_id': 'call_7f61P2Z8N49FgToGE7zVNEYj', 'content': 'Today is sunny.'}, {'role': 'assistant', 'content': [{'type': 'text', 'text': 'I can look up the current weather for any location — what city or ZIP code should I check? \n\nIf you meant a general update: today is sunny.'}]}, {'role': 'user', 'content': [{'type': 'text', 'text': 'Please tell me the current time.'}]}, {'role': 'assistant', 'tool_calls': [{'id': 'call_NEZhIt4eCJeqRZkjGMdFxUSo', 'type': 'function', 'function': {'name': 'get_datetime', 'arguments': '{}'}}, {'id': '06b716ed-510f-4517-92e8-6397ab1688ea', 'type': 'function', 'function': {'name': 'confirm_changes', 'arguments': '{"function_name": "get_datetime", "function_call_id": "call_NEZhIt4eCJeqRZkjGMdFxUSo", "function_arguments": {}, "steps": [{"description": "Execute get_datetime", "status": "enabled"}]}'}}], 'content': ''}, {'role': 'tool', 'tool_call_id': '06b716ed-510f-4517-92e8-6397ab1688ea', 'content': 'Confirmed'}, {'role': 'tool', 'tool_call_id': 'call_NEZhIt4eCJeqRZkjGMdFxUSo', 'content': '[{"type": "text", "text": "1 validation error for call[get_datetime]\\noptions\\n  Unexpected keyword argument [type=unexpected_keyword_argument, input_value={\'metadata\': {\'ag_ui_thre...8d13c1\'}, \'store\': True}, input_type=dict]\\n    For further information visit https://errors.pydantic.dev/2.12/v/unexpected_keyword_argument"}]'}], 'model': 'openai5-mini', 'metadata': {'ag_ui_thread_id': '7bf6120e-70e2-4991-9ed4-57524365c3c4', 'ag_ui_run_id': '66cfad04-c957-4e4d-8c0b-beff288d13c1'}, 'store': True, 'stream': True, 'stream_options': {'include_usage': True}, 'tool_choice': 'auto', 'tools': [{'type': 'function', 'function': {'name': 'get_weather', 'description': 'Get the current weather', 'parameters': {'properties': {}, 'title': 'get_weather_input', 'type': 'object'}}}, {'type': 'function', 'function': {'name': 'get_datetime', 'description': 'Get the current date and time.', 'parameters': {'properties': {}, 'title': 'get_datetime_input', 'type': 'object'}}}]}}

Screenshots

Image

Version information
Python 3.13.10
agent-framework-ag-ui==1.0.0b260116
agent-framework-core==1.0.0b260116

Thank you in advance.

Metadata

Metadata

Assignees

Type

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions