This commit is contained in:
2025-12-07 20:25:53 -07:00
parent 96dd462043
commit fbb050056f
2 changed files with 76 additions and 42 deletions

View File

@@ -37,31 +37,41 @@ backend_tools = [
# your_tool_here # your_tool_here
] ]
# Initialize MCP client def get_mcp_client(access_token: str = None):
mcp_client = MultiServerMCPClient( """Create MCP client with optional authentication headers."""
headers = {}
if access_token:
headers["Authorization"] = f"Bearer {access_token}"
return MultiServerMCPClient(
{ {
"cavepedia": { "cavepedia": {
"transport": "streamable_http", "transport": "streamable_http",
"url": "https://mcp.caving.dev/mcp", "url": "https://mcp.caving.dev/mcp",
"timeout": 10.0, "timeout": 10.0,
"headers": headers,
} }
} }
) )
# Global variable to hold loaded MCP tools # Cache for MCP tools per access token
_mcp_tools = None _mcp_tools_cache = {}
async def get_mcp_tools(): async def get_mcp_tools(access_token: str = None):
"""Lazy load MCP tools on first access.""" """Lazy load MCP tools with authentication."""
global _mcp_tools cache_key = access_token or "default"
if _mcp_tools is None:
if cache_key not in _mcp_tools_cache:
try: try:
_mcp_tools = await mcp_client.get_tools() mcp_client = get_mcp_client(access_token)
print(f"Loaded {len(_mcp_tools)} tools from MCP server") tools = await mcp_client.get_tools()
_mcp_tools_cache[cache_key] = tools
print(f"Loaded {len(tools)} tools from MCP server with auth: {bool(access_token)}")
except Exception as e: except Exception as e:
print(f"Warning: Failed to load MCP tools: {e}") print(f"Warning: Failed to load MCP tools: {e}")
_mcp_tools = [] _mcp_tools_cache[cache_key] = []
return _mcp_tools
return _mcp_tools_cache[cache_key]
async def chat_node(state: AgentState, config: RunnableConfig) -> dict: async def chat_node(state: AgentState, config: RunnableConfig) -> dict:
@@ -76,11 +86,18 @@ async def chat_node(state: AgentState, config: RunnableConfig) -> dict:
https://www.perplexity.ai/search/react-agents-NcXLQhreS0WDzpVaS4m9Cg https://www.perplexity.ai/search/react-agents-NcXLQhreS0WDzpVaS4m9Cg
""" """
# 0. Extract Auth0 access token from config
configurable = config.get("configurable", {})
access_token = configurable.get("auth0_access_token")
user_roles = configurable.get("auth0_user_roles", [])
print(f"Chat node invoked with auth token: {bool(access_token)}, roles: {user_roles}")
# 1. Define the model # 1. Define the model
model = ChatAnthropic(model="claude-sonnet-4-5-20250929") model = ChatAnthropic(model="claude-sonnet-4-5-20250929")
# 1.5 Load MCP tools from the cavepedia server # 1.5 Load MCP tools from the cavepedia server with authentication
mcp_tools = await get_mcp_tools() mcp_tools = await get_mcp_tools(access_token)
# 2. Bind the tools to the model # 2. Bind the tools to the model
model_with_tools = model.bind_tools( model_with_tools = model.bind_tools(
@@ -98,7 +115,7 @@ async def chat_node(state: AgentState, config: RunnableConfig) -> dict:
# 3. Define the system message by which the chat model will be run # 3. Define the system message by which the chat model will be run
system_message = SystemMessage( system_message = SystemMessage(
content="You are a helpful assistant with access to cave-related information through the Cavepedia MCP server. You can help users find information about caves, caving techniques, and related topics." content=f"You are a helpful assistant with access to cave-related information through the Cavepedia MCP server. You can help users find information about caves, caving techniques, and related topics. User roles: {', '.join(user_roles) if user_roles else 'none'}"
) )
# 4. Run the model to generate a response # 4. Run the model to generate a response
@@ -114,17 +131,21 @@ async def chat_node(state: AgentState, config: RunnableConfig) -> dict:
return {"messages": [response]} return {"messages": [response]}
async def tool_node_wrapper(state: AgentState) -> dict: async def tool_node_wrapper(state: AgentState, config: RunnableConfig) -> dict:
""" """
Custom tool node that handles both backend tools and MCP tools. Custom tool node that handles both backend tools and MCP tools.
""" """
# Load MCP tools and combine with backend tools # Extract Auth0 access token from config
mcp_tools = await get_mcp_tools() configurable = config.get("configurable", {})
access_token = configurable.get("auth0_access_token")
# Load MCP tools with authentication and combine with backend tools
mcp_tools = await get_mcp_tools(access_token)
all_tools = [*backend_tools, *mcp_tools] all_tools = [*backend_tools, *mcp_tools]
# Use the standard ToolNode with all tools # Use the standard ToolNode with all tools
node = ToolNode(tools=all_tools) node = ToolNode(tools=all_tools)
result = await node.ainvoke(state) result = await node.ainvoke(state, config)
return result return result

View File

@@ -6,25 +6,38 @@ import {
import { LangGraphAgent } from "@ag-ui/langgraph" import { LangGraphAgent } from "@ag-ui/langgraph"
import { NextRequest } from "next/server"; import { NextRequest } from "next/server";
import { auth0 } from "@/lib/auth0";
// 1. You can use any service adapter here for multi-agent support. We use // 1. You can use any service adapter here for multi-agent support. We use
// the empty adapter since we're only using one agent. // the empty adapter since we're only using one agent.
const serviceAdapter = new ExperimentalEmptyAdapter(); const serviceAdapter = new ExperimentalEmptyAdapter();
// 2. Create the CopilotRuntime instance and utilize the LangGraph AG-UI // 3. Build a Next.js API route that handles the CopilotKit runtime requests.
// integration to setup the connection. export const POST = async (req: NextRequest) => {
// Get Auth0 session
const session = await auth0.getSession();
// Extract access token and roles from session
const accessToken = session?.accessToken;
const userRoles = session?.user?.roles || [];
// 2. Create the CopilotRuntime instance with Auth0 configuration
const runtime = new CopilotRuntime({ const runtime = new CopilotRuntime({
agents: { agents: {
"sample_agent": new LangGraphAgent({ "sample_agent": new LangGraphAgent({
deploymentUrl: process.env.LANGGRAPH_DEPLOYMENT_URL || "http://localhost:8123", deploymentUrl: process.env.LANGGRAPH_DEPLOYMENT_URL || "http://localhost:8123",
graphId: "sample_agent", graphId: "sample_agent",
langsmithApiKey: process.env.LANGSMITH_API_KEY || "", langsmithApiKey: process.env.LANGSMITH_API_KEY || "",
langgraphConfig: {
configurable: {
auth0_access_token: accessToken,
auth0_user_roles: userRoles,
}
}
}), }),
} }
}); });
// 3. Build a Next.js API route that handles the CopilotKit runtime requests.
export const POST = async (req: NextRequest) => {
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({ const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
runtime, runtime,
serviceAdapter, serviceAdapter,