feat: MCP tool client infrastructure for agent extensibility

Add the full MCP tool pipeline enabling agents to invoke external tools
(like Brave Search) via MCP servers:

- Add ToolRequest/ToolResponse types and mcp-tool topics to @trustgraph/base
- Create McpToolService (FlowProcessor) that connects to external MCP servers
  via @modelcontextprotocol/sdk StreamableHTTP transport
- Add createMcpTool() to wire MCP tools into the agent's ReAct loop
- Implement config-driven tool registration in AgentService with backward-
  compatible fallback to hardcoded tools
- Add tool filtering by group and state (port of Python tool_filter.py)
- Register mcp-tool in gateway dispatcher and export from @trustgraph/flow
- Fix flow restart race condition: skip restart when flow definitions unchanged
- Update seed config with MCP server config and tool definitions
- Add run scripts for MCP tool service and Brave Search MCP server

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
elpresidank 2026-04-10 05:45:46 -05:00
parent f2b376abef
commit b854b56558
17 changed files with 600 additions and 17 deletions

View file

@ -22,6 +22,7 @@ export abstract class FlowProcessor extends AsyncProcessor {
private specifications: Spec[] = [];
private flows = new Map<string, Flow>();
private configConsumer: BackendConsumer<ConfigPush> | null = null;
private lastFlowsJson = "";
protected constructor(config: ProcessorConfig) {
super(config);
@ -76,6 +77,16 @@ export abstract class FlowProcessor extends AsyncProcessor {
return;
}
// Skip flow restart if the flow definitions haven't changed.
// This prevents disrupting in-flight requests when non-flow config
// sections (prompts, tools, mcp) are updated.
const flowsJson = JSON.stringify(flowDefs);
if (this.lastFlowsJson && flowsJson === this.lastFlowsJson && this.flows.size > 0) {
console.log(`[${this.config.id}] Flow definitions unchanged, skipping restart`);
return;
}
this.lastFlowsJson = flowsJson;
// Stop removed flows
for (const [name, flow] of this.flows) {
if (!(name in flowDefs)) {

View file

@ -304,6 +304,19 @@ export interface CollectionManagementResponse {
collections?: { user: string; collection: string; name: string; description: string; tags: string[] }[];
}
// ---------- Tool invocation (MCP tools) ----------
export interface ToolRequest {
name: string;
parameters: string; // JSON-encoded
}
export interface ToolResponse {
error?: TgError;
text?: string; // Plain text response
object?: string; // JSON-encoded structured response
}
// ---------- Flow management ----------
// Flow request/response use kebab-case wire format to match the client.

View file

@ -60,6 +60,10 @@ export const topics = {
promptRequest: topic("prompt-request"),
promptResponse: topic("prompt-response"),
// MCP tool invocation
mcpToolRequest: topic("mcp-tool-request"),
mcpToolResponse: topic("mcp-tool-response"),
// Librarian (document management)
librarianRequest: topic("librarian-request"),
librarianResponse: topic("librarian-response"),