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

@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""
Deletes MCP (Model Control Protocol) tools from the TrustGraph system.
Removes MCP tool configurations by name from the 'mcp' configuration group.
"""
import argparse
import os
from trustgraph.api import Api, ConfigKey
import textwrap
default_url = os.getenv("TRUSTGRAPH_URL", 'http://localhost:8088/')
def delete_mcp_tool(
url : str,
name : str,
):
api = Api(url).config()
# Check if the tool exists first
try:
values = api.get([
ConfigKey(type="mcp", key=name)
])
if not values or not values[0].value:
print(f"MCP tool '{name}' not found.")
return False
except Exception as e:
print(f"MCP tool '{name}' not found.")
return False
# Delete the MCP tool configuration from the 'mcp' group
try:
api.delete([
ConfigKey(type="mcp", key=name)
])
print(f"MCP tool '{name}' deleted successfully.")
return True
except Exception as e:
print(f"Error deleting MCP tool '{name}': {e}")
return False
def main():
parser = argparse.ArgumentParser(
prog='tg-delete-mcp-tool',
description=__doc__,
epilog=textwrap.dedent('''
This utility removes MCP tool configurations from the TrustGraph system.
Once deleted, the tool will no longer be available for use.
Examples:
%(prog)s --name weather
%(prog)s --name calculator
%(prog)s --api-url http://localhost:9000/ --name file-reader
''').strip(),
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
'-u', '--api-url',
default=default_url,
help=f'API URL (default: {default_url})',
)
parser.add_argument(
'--name',
required=True,
help='MCP tool name to delete',
)
args = parser.parse_args()
try:
if not args.name:
raise RuntimeError("Must specify --name for MCP tool to delete")
delete_mcp_tool(
url=args.api_url,
name=args.name
)
except Exception as e:
print("Exception:", e, flush=True)
main()

View file

@ -0,0 +1,127 @@
#!/usr/bin/env python3
"""
Deletes tools from the TrustGraph system.
Removes tool configurations by ID from the agent configuration
and updates the tool index accordingly.
"""
import argparse
import os
from trustgraph.api import Api, ConfigKey, ConfigValue
import json
import textwrap
default_url = os.getenv("TRUSTGRAPH_URL", 'http://localhost:8088/')
def delete_tool(
url : str,
id : str,
):
api = Api(url).config()
# Get the current tool index
try:
values = api.get([
ConfigKey(type="agent", key="tool-index")
])
ix = json.loads(values[0].value)
except Exception as e:
print(f"Error reading tool index: {e}")
return False
# Check if the tool exists in the index
if id not in ix:
print(f"Tool '{id}' not found in tool index.")
return False
# Check if the tool configuration exists
try:
tool_values = api.get([
ConfigKey(type="agent", key=f"tool.{id}")
])
if not tool_values or not tool_values[0].value:
print(f"Tool configuration for '{id}' not found.")
return False
except Exception as e:
print(f"Tool configuration for '{id}' not found.")
return False
# Remove the tool ID from the index
ix.remove(id)
# Delete the tool configuration and update the index
try:
# Update the tool index
api.put([
ConfigValue(
type="agent", key="tool-index", value=json.dumps(ix)
)
])
# Delete the tool configuration
api.delete([
ConfigKey(type="agent", key=f"tool.{id}")
])
print(f"Tool '{id}' deleted successfully.")
return True
except Exception as e:
print(f"Error deleting tool '{id}': {e}")
return False
def main():
parser = argparse.ArgumentParser(
prog='tg-delete-tool',
description=__doc__,
epilog=textwrap.dedent('''
This utility removes tool configurations from the TrustGraph system.
It removes the tool from both the tool index and deletes the tool
configuration. Once deleted, the tool will no longer be available for use.
Examples:
%(prog)s --id weather
%(prog)s --id calculator
%(prog)s --api-url http://localhost:9000/ --id file-reader
''').strip(),
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
'-u', '--api-url',
default=default_url,
help=f'API URL (default: {default_url})',
)
parser.add_argument(
'--id',
required=True,
help='Tool ID to delete',
)
args = parser.parse_args()
try:
if not args.id:
raise RuntimeError("Must specify --id for tool to delete")
delete_tool(
url=args.api_url,
id=args.id
)
except Exception as e:
print("Exception:", e, flush=True)
main()

View file

@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""
Invokes MCP (Model Control Protocol) tools through the TrustGraph API.
Allows calling MCP tools by specifying the tool name and providing
parameters as a JSON-encoded dictionary. The tool is executed within
the context of a specified flow.
"""
import argparse
import os
import json
from trustgraph.api import Api
default_url = os.getenv("TRUSTGRAPH_URL", 'http://localhost:8088/')
def query(url, flow_id, name, parameters):
api = Api(url).flow().id(flow_id)
resp = api.mcp_tool(name=name, parameters=parameters)
if isinstance(resp, str):
print(resp)
else:
print(json.dumps(resp, indent=4))
def main():
parser = argparse.ArgumentParser(
prog='tg-invoke-mcp-tool',
description=__doc__,
)
parser.add_argument(
'-u', '--url',
default=default_url,
help=f'API URL (default: {default_url})',
)
parser.add_argument(
'-f', '--flow-id',
default="default",
help=f'Flow ID (default: default)'
)
parser.add_argument(
'-n', '--name',
metavar='tool-name',
help=f'MCP tool name',
)
parser.add_argument(
'-P', '--parameters',
help='''Tool parameters, should be JSON-encoded dict.''',
)
args = parser.parse_args()
if args.parameters:
parameters = json.loads(args.parameters)
else:
parameters = {}
try:
query(
url = args.url,
flow_id = args.flow_id,
name = args.name,
parameters = parameters,
)
except Exception as e:
print("Exception:", e, flush=True)
main()

View file

@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""
Configures and registers MCP (Model Control Protocol) tools in the
TrustGraph system. Allows defining MCP tool configurations with name and
URL. Tools are stored in the 'mcp' configuration group for discovery and
execution.
"""
import argparse
import os
from trustgraph.api import Api, ConfigValue
import textwrap
import json
default_url = os.getenv("TRUSTGRAPH_URL", 'http://localhost:8088/')
def set_mcp_tool(
url : str,
name : str,
tool_url : str,
):
api = Api(url).config()
# Store the MCP tool configuration in the 'mcp' group
values = api.put([
ConfigValue(
type="mcp", key=name, value=json.dumps({
"name": name,
"url": tool_url,
})
)
])
print(f"MCP tool '{name}' set with URL: {tool_url}")
def main():
parser = argparse.ArgumentParser(
prog='tg-set-mcp-tool',
description=__doc__,
epilog=textwrap.dedent('''
MCP tools are configured with just a name and URL. The URL should point
to the MCP server endpoint that provides the tool functionality.
Examples:
%(prog)s --name weather --tool-url "http://localhost:3000/weather"
%(prog)s --name calculator --tool-url "http://mcp-tools.example.com/calc"
''').strip(),
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
'-u', '--api-url',
default=default_url,
help=f'API URL (default: {default_url})',
)
parser.add_argument(
'--name',
required=True,
help='MCP tool name',
)
parser.add_argument(
'--tool-url',
required=True,
help='MCP tool URL endpoint',
)
args = parser.parse_args()
try:
if not args.name:
raise RuntimeError("Must specify --name for MCP tool")
if not args.tool_url:
raise RuntimeError("Must specify --url for MCP tool")
set_mcp_tool(
url=args.api_url,
name=args.name,
tool_url=args.tool_url
)
except Exception as e:
print("Exception:", e, flush=True)
main()

View file

@ -0,0 +1,195 @@
#!/usr/bin/env python3
"""
Configures and registers tools in the TrustGraph system.
Allows defining tool metadata including ID, name, description, type,
and argument specifications. Tools are stored in the agent configuration
and indexed for discovery and execution.
"""
from typing import List
import argparse
import os
from trustgraph.api import Api, ConfigKey, ConfigValue
import json
import tabulate
import textwrap
import dataclasses
default_url = os.getenv("TRUSTGRAPH_URL", 'http://localhost:8088/')
@dataclasses.dataclass
class Argument:
name : str
type : str
description : str
@staticmethod
def parse(s):
parts = s.split(":")
if len(parts) != 3:
raise RuntimeError(
"Arguments should be form name:type:description"
)
valid_types = [
"string", "number",
]
if parts[1] not in valid_types:
raise RuntimeError(
f"Type {parts[1]} invalid, use: " +
", ".join(valid_types)
)
return Argument(name=parts[0], type=parts[1], description=parts[2])
def set_tool(
url : str,
id : str,
name : str,
description : str,
type : str,
arguments : List[Argument],
):
api = Api(url).config()
values = api.get([
ConfigKey(type="agent", key="tool-index")
])
ix = json.loads(values[0].value)
object = {
"id": id,
"name": name,
"description": description,
"type": type,
"arguments": [
{
"name": a.name,
"type": a.type,
"description": a.description,
}
for a in arguments
]
}
if id not in ix:
ix.append(id)
values = api.put([
ConfigValue(
type="agent", key="tool-index", value=json.dumps(ix)
),
ConfigValue(
type="agent", key=f"tool.{id}", value=json.dumps(object)
)
])
print("Tool set.")
def main():
parser = argparse.ArgumentParser(
prog='tg-set-tool',
description=__doc__,
epilog=textwrap.dedent('''
Valid tool types:
knowledge-query - Query knowledge bases
text-completion - Text completion/generation
mcp-tool - Model Control Protocol tool
Valid argument types:
string - String/text parameter
number - Numeric parameter
Examples:
%(prog)s --id weather --name "Weather lookup" \\
--type knowledge-query \\
--description "Get weather information" \\
--argument location:string:"Location to query" \\
--argument units:string:"Temperature units (C/F)"
%(prog)s --id calculator --name "Calculator" --type mcp-tool \\
--description "Perform calculations" \\
--argument expression:string:"Mathematical expression"
''').strip(),
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
'-u', '--api-url',
default=default_url,
help=f'API URL (default: {default_url})',
)
parser.add_argument(
'--id',
help=f'Tool ID',
)
parser.add_argument(
'--name',
help=f'Tool name',
)
parser.add_argument(
'--description',
help=f'Tool description',
)
parser.add_argument(
'--type',
help=f'Tool type, one of: knowledge-query, text-completion, mcp-tool',
)
parser.add_argument(
'--argument',
nargs="*",
help=f'Arguments, form: name:type:description',
)
args = parser.parse_args()
try:
valid_types = [
"knowledge-query", "text-completion", "mcp-tool"
]
if args.id is None:
raise RuntimeError("Must specify --id for prompt")
if args.name is None:
raise RuntimeError("Must specify --name for prompt")
if args.type:
if args.type not in valid_types:
raise RuntimeError(
"Type must be one of: " + ", ".join(valid_types)
)
if args.argument:
arguments = [
Argument.parse(a)
for a in args.argument
]
else:
arguments = []
set_tool(
url=args.api_url, id=args.id, name=args.name,
description=args.description,
type=args.type,
arguments=arguments
)
except Exception as e:
print("Exception:", e, flush=True)
main()

View file

@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""
Dumps out the current agent tool configuration
"""
import argparse
import os
from trustgraph.api import Api, ConfigKey
import json
import tabulate
import textwrap
default_url = os.getenv("TRUSTGRAPH_URL", 'http://localhost:8088/')
def show_config(url):
api = Api(url).config()
values = api.get_values(type="mcp")
for n, value in enumerate(values):
data = json.loads(value.value)
table = []
table.append(("id", value.key))
table.append(("name", data["name"]))
table.append(("url", data["url"]))
print()
print(value.key + ":")
print(tabulate.tabulate(
table,
tablefmt="pretty",
maxcolwidths=[None, 70],
stralign="left"
))
print()
def main():
parser = argparse.ArgumentParser(
prog='tg-show-mcp-tools',
description=__doc__,
)
parser.add_argument(
'-u', '--api-url',
default=default_url,
help=f'API URL (default: {default_url})',
)
args = parser.parse_args()
try:
show_config(
url=args.api_url,
)
except Exception as e:
print("Exception:", e, flush=True)
main()

View file

@ -37,6 +37,7 @@ def show_config(url):
table.append(("id", data["id"]))
table.append(("name", data["name"]))
table.append(("description", data["description"]))
table.append(("type", data["type"]))
for n, arg in enumerate(data["arguments"]):
table.append((