Javascript
Prerequisites
This tutorial assumes that you have set up MCP Toolbox with a basic agent as described in the local quickstart.
This guide demonstrates how to implement these patterns in your Toolbox applications.
Implementation
The following example demonstrates how to use the beforeToolCallback and afterToolCallback hooks in the ADK LlmAgent to implement pre and post processing logic.
import { InMemoryRunner, LlmAgent, LogLevel } from '@google/adk';
import { ToolboxClient } from '@toolbox-sdk/adk';
process.env.GOOGLE_GENAI_API_KEY = process.env.GOOGLE_API_KEY || 'your-api-key'; // Replace it with your API key
const systemPrompt = `
You're a helpful hotel assistant. You handle hotel searching, booking and
cancellations. When the user searches for a hotel, mention it's name, id,
location and price tier. Always mention hotel ids while performing any
searches. This is very important for any operations. For any bookings or
cancellations, please provide the appropriate confirmation. Be sure to
update checkin or checkout dates if mentioned by the user.
Don't ask for confirmations from the user.
`;
// Pre-Processing
function enforeBusinessRules({tool, args}) {
const name = tool.name;
console.log(`POLICY CHECK: Intercepting '${name}'`);
if (name === "update-hotel" && args.checkin_date && args.checkout_date) {
try {
const start = new Date(args.checkin_date);
const end = new Date(args.checkout_date);
const duration = (end - start) / (1000 * 60 * 60 * 24); // days
if (duration > 14) {
console.log("BLOCKED: Stay too long");
return "Error: Maximum stay duration is 14 days.";
}
} catch (e) {
// Ignore invalid dates
}
}
return undefined;
}
// Post-Processing
function enrichResponse({tool, response}) {
const name = tool.name;
console.log(`ENRICHING RESPONSE: Intercepting '${name}'`);
if (name === "book-hotel") {
let content = response;
if (response && typeof response === "object") {
content = response.content;
}
if (typeof content === "string" && !content.includes("Error")) {
const loyaltyBonus = 500;
const enrichedContent = `Booking Confirmed!\n You earned ${loyaltyBonus} Loyalty Points with this stay.\n\nSystem Details: ${content}`;
if (response && typeof response === "object") {
return { ...response, content: enrichedContent };
}
return enrichedContent;
}
}
return response;
}
async function runTurn(runner, userId, sessionId, prompt) {
console.log(`\nUSER: '${prompt}'`);
const content = { role: 'user', parts: [{ text: prompt }] };
const stream = runner.runAsync({ userId, sessionId, newMessage: content });
let fullText = "";
for await (const chunk of stream) {
if (chunk.content && chunk.content.parts) {
fullText += chunk.content.parts.map(p => p.text || "").join("");
}
}
console.log("-".repeat(50));
console.log(`AI: ${fullText}`);
}
export async function main() {
const userId = 'test_user';
const client = new ToolboxClient('http://127.0.0.1:5000');
const tools = await client.loadToolset("my-toolset");
const rootAgent = new LlmAgent({
name: 'hotel_agent',
model: 'gemini-2.5-flash',
description: 'Agent for hotel bookings and administration.',
instruction: systemPrompt,
tools: tools,
// Add any pre- and post- processing callbacks
beforeToolCallback: enforeBusinessRules,
afterToolCallback: enrichResponse
});
const appName = rootAgent.name;
const runner = new InMemoryRunner({ agent: rootAgent, appName, logLevel: LogLevel.ERROR });
const session = await runner.sessionService.createSession({ appName, userId });
// Turn 1: Booking
await runTurn(runner, userId, session.id, "Book hotel with id 3.");
// Turn 2: Policy Violation
await runTurn(runner, userId, session.id, "Update my hotel with id 3 with checkin date 2025-01-18 and checkout date 2025-02-10");
}
main();
You can also add model-level (beforeModelCallback, afterModelCallback) and agent-level (beforeAgentCallback, afterAgentCallback) hooks to intercept messages at different stages of the execution loop.
For more information, see the ADK Callbacks documentation.
The following example demonstrates how to use ToolboxClient with LangChain’s middleware to implement pre and post processing for tool calls.
import { ToolboxClient } from "@toolbox-sdk/core";
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
import { createAgent, createMiddleware, ToolMessage } from "langchain";
import { tool } from "@langchain/core/tools";
import { fileURLToPath } from "url";
import process from "process";
const systemPrompt = `
You're a helpful hotel assistant. You handle hotel searching, booking and
cancellations. When the user searches for a hotel, mention it's name, id,
location and price tier. Always mention hotel ids while performing any
searches. This is very important for any operations. For any bookings or
cancellations, please provide the appropriate confirmation. Be sure to
update checkin or checkout dates if mentioned by the user.
Don't ask for confirmations from the user.
`;
const GOOGLE_API_KEY = process.env.GOOGLE_API_KEY || 'your-api-key'; // Replace it with your API key
const businessRulesMiddleware = createMiddleware({
name: "BusinessRules",
wrapToolCall: async (request, handler) => {
const toolName = request.toolCall.name;
const toolArgs = request.toolCall.args;
console.log(`POLICY CHECK: Intercepting '${toolName}' running with args ${JSON.stringify(toolArgs)}`);
if (toolName === "update-hotel" && toolArgs.checkin_date && toolArgs.checkout_date) {
try {
const start = new Date(toolArgs.checkin_date);
const end = new Date(toolArgs.checkout_date);
const duration = (end - start) / (1000 * 60 * 60 * 24); // days
if (duration > 14) {
console.log("BLOCKED: Stay too long");
return ToolMessage({content:'Error: Maximum stay duration is 14 days.', status:"error"})
}
} catch (e) {
// Ignore invalid dates
}
}
return handler(request);
}
});
const enrichmentMiddleware = createMiddleware({
name: "Enrichment",
wrapToolCall: async (request, handler) => {
const result = await handler(request);
const toolName = request.toolCall.name;
let content = result;
if (typeof result === 'object' && result !== null && result.content) {
content = result.content;
}
if (toolName === "book-hotel" && typeof content === 'string' && !content.includes("Error")) {
const loyaltyBonus = 500;
const enrichedContent = `Booking Confirmed!\n You earned ${loyaltyBonus} Loyalty Points with this stay.\n\nSystem Details: ${content}`;
if (typeof result === 'object' && result !== null) {
result.content = enrichedContent;
return result;
}
return enrichedContent;
}
return result;
}
});
const queries = [
"Book hotel with id 3.",
"Update my hotel with id 3 with checkin date 2025-01-18 and checkout date 2025-02-10"
];
async function main() {
const client = new ToolboxClient("http://127.0.0.1:5000");
const rawTools = await client.loadToolset("my-toolset");
const tools = rawTools
.map(t => tool(t, {
name: t.getName(),
description: t.getDescription(),
schema: t.getParamSchema()
}));
const model = new ChatGoogleGenerativeAI({
model: "gemini-2.5-flash",
});
const agent = createAgent({
model: model,
tools: tools,
systemPrompt: systemPrompt,
middleware: [businessRulesMiddleware, enrichmentMiddleware]
});
for (const query of queries) {
console.log(`\nUSER: '${query}'`);
const result = await agent.invoke({
messages: [
{ role: "user", content: query},
],
});
console.log("-".repeat(50));
console.log(`AI: ${result.messages[result.messages.length-1].content}`);
}
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
main();
}
export { main };
You can also use the wrapModelCall hook to intercept messages before and after model calls.
You can also use node-style hooks to intercept messages at the agent and model level.
See the LangChain Middleware documentation for details on these additional hook types.
Results
The output should look similar to the following.
Note
The exact responses may vary due to the non-deterministic nature of LLMs and differences between orchestration frameworks.
AI: Booking Confirmed! You earned 500 Loyalty Points with this stay.
AI: Error: Maximum stay duration is 14 days.