Feature/react call mcp (#428)

Key Features

  - MCP Tool Integration: Added core MCP tool support with ToolClientSpec and ToolClient classes
  - API Enhancement: New mcp_tool method for flow-specific tool invocation
  - CLI Tooling: New tg-invoke-mcp-tool command for testing MCP integration
  - React Agent Enhancement: Fixed and improved multi-tool invocation capabilities
  - Tool Management: Enhanced CLI for tool configuration and management

Changes

  - Added MCP tool invocation to API with flow-specific integration
  - Implemented ToolClientSpec and ToolClient for tool call handling
  - Updated agent-manager-react to invoke MCP tools with configurable types
  - Enhanced CLI with new commands and improved help text
  - Added comprehensive documentation for new CLI commands
  - Improved tool configuration management

Testing

  - Added tg-invoke-mcp-tool CLI command for isolated MCP integration testing
  - Enhanced agent capability to invoke multiple tools simultaneously
This commit is contained in:
cybermaggedon 2025-07-08 16:19:19 +01:00 committed by GitHub
parent e56186054a
commit 9c7a070681
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 2718 additions and 9 deletions

View file

@ -14,12 +14,19 @@ class AgentManager:
async def reason(self, question, history, context):
print(f"calling reason: {question}", flush=True)
tools = self.tools
print(f"in reason", flush=True)
print(tools, flush=True)
tool_names = ",".join([
t for t in self.tools.keys()
])
print("Tool names:", tool_names, flush=True)
variables = {
"question": question,
"tools": [
@ -83,6 +90,9 @@ class AgentManager:
async def react(self, question, history, think, observe, context):
logger.info(f"question: {question}")
print(f"question: {question}", flush=True)
act = await self.reason(
question = question,
history = history,
@ -104,13 +114,12 @@ class AgentManager:
else:
raise RuntimeError(f"No action for {act.name}!")
print("TOOL>>>", act)
print("TOOL>>>", act, flush=True)
resp = await action.implementation(context).invoke(
**act.arguments
)
print("RSETUL", resp)
resp = resp.strip()
logger.info(f"resp: {resp}")

View file

@ -5,13 +5,14 @@ Simple agent infrastructure broadly implements the ReAct flow.
import json
import re
import sys
import functools
from ... base import AgentService, TextCompletionClientSpec, PromptClientSpec
from ... base import GraphRagClientSpec
from ... base import GraphRagClientSpec, ToolClientSpec
from ... schema import AgentRequest, AgentResponse, AgentStep, Error
from . tools import KnowledgeQueryImpl, TextCompletionImpl
from . tools import KnowledgeQueryImpl, TextCompletionImpl, McpToolImpl
from . agent_manager import AgentManager
from . types import Final, Action, Tool, Argument
@ -67,6 +68,13 @@ class Processor(AgentService):
)
)
self.register_specification(
ToolClientSpec(
request_name = "mcp-tool-request",
response_name = "mcp-tool-response",
)
)
async def on_tools_config(self, config, version):
print("Loading configuration version", version)
@ -102,17 +110,21 @@ class Processor(AgentService):
impl_id = data.get("type")
name = data.get("name")
if impl_id == "knowledge-query":
impl = KnowledgeQueryImpl
elif impl_id == "text-completion":
impl = TextCompletionImpl
elif impl_id == "mcp-tool":
impl = functools.partial(McpToolImpl, name=k)
else:
raise RuntimeError(
f"Tool-kind {impl_id} not known"
)
tools[data.get("name")] = Tool(
name = data.get("name"),
name = name,
description = data.get("description"),
implementation = impl,
config=data.get("config", {}),
@ -181,6 +193,8 @@ class Processor(AgentService):
await respond(r)
print("Call React", flush=True)
act = await self.agent.react(
question = request.question,
history = history,

View file

@ -1,4 +1,6 @@
import json
# This tool implementation knows how to put a question to the graph RAG
# service
class KnowledgeQueryImpl:
@ -23,3 +25,29 @@ class TextCompletionImpl:
arguments.get("question")
)
# This tool implementation knows how to do MCP tool invocation. This uses
# the mcp-tool service.
class McpToolImpl:
def __init__(self, context, name):
self.context = context
self.name = name
async def invoke(self, **arguments):
client = self.context("mcp-tool-request")
print(f"MCP tool invocation: {self.name}...", flush=True)
output = await client.invoke(
name = self.name,
parameters = {},
)
print(output)
if isinstance(output, str):
return output
else:
return json.dumps(output)