MCP Tool Hook inserts a proxy layer between the MCP client and server to run custom logic before and after tool calls, such as masking sensitive data or adding audit tags, without modifying the original MCP service code.
How it works
MCP Tool Hook runs on the AgentRun MCP proxy layer, which sits transparently between the MCP client (agent) and MCP server. When enabled, tool call requests pass through the proxy instead of going directly to the MCP service. The proxy triggers configured Hook events before and after tools/list and tools/call operations, forwarding the request or response to an external Hook service (HTTP webhook) for processing.
The MCP protocol standard itself does not define a Hook or middleware mechanism. MCP Tool Hook is an extension capability provided by AgentRun above the protocol layer and is fully transparent to the MCP server.
Take the POST_CALL_TOOL event as an example. The data flow is as follows:
-
The agent sends a tool call request.
-
The AgentRun MCP proxy receives the request and calls the MCP service to execute the tool operation.
-
The MCP service returns the raw result.
-
The proxy forwards the result to the Hook service via an HTTP POST request based on the Hook configuration.
-
The Hook service processes the result (for example, by masking sensitive data or adding audit tags) and returns the modified result.
-
The proxy returns the final result to the agent.
Other events follow a similar flow. PRE_CALL_TOOL triggers before the tool call and can modify request parameters or block the call. PRE_LIST_TOOLS and POST_LIST_TOOLS trigger before and after fetching the tool list, allowing you to preprocess or filter the tool list.
Hook event types
The following Hook events are supported:
|
Event type |
Trigger timing |
Description |
|
PRE_CALL_TOOL |
Before tool call |
Triggers before the MCP service executes the tool call. The Hook service receives the request parameters and can modify them or block the call. |
|
POST_CALL_TOOL |
After tool call completes |
Triggers after the MCP service returns the tool call result. The Hook service receives the raw result and can return a modified version. |
|
PRE_LIST_TOOLS |
Before fetching tool list |
Triggers before the MCP service executes |
|
POST_LIST_TOOLS |
After fetching tool list |
Triggers after the MCP service returns the tool list. The Hook service can filter or modify the list. |
Scenarios
-
Data masking: Mask sensitive information in tool results, such as phone numbers, email addresses, and physical addresses.
-
Audit tagging: Inject audit identifiers like request IDs and timestamps into tool results for traceability and auditing.
-
Result enhancement: Format, enrich, or filter raw data returned by tools.
-
Access control: Add permission verification logic before or after tool calls to restrict usage of specific tools.
Deployment modes
AgentRun supports two MCP Tool Hook deployment modes:
|
Deployment mode |
Architecture |
Use case |
|
mcp_remote |
Remote MCP + proxy + Hook. The MCP service and Hook service are deployed separately as HTTP services. |
The MCP service is already deployed independently. |
|
mcp_code |
Code-packaged MCP + proxy + Hook. The MCP service code is bundled directly into the AgentRun runtime. |
You want to simplify deployment by bundling the MCP service code into the runtime. |
Both modes use the same Hook configuration method. The difference lies in how the MCP service is deployed and referenced.
Preparations
-
You have activated the AgentRun service. Confirm this in the AgentRun console.
-
You have activated Function Compute (FC) to deploy the MCP service and Hook service.
-
You have installed a Golang 1.23 or later development environment.
-
You have obtained your Alibaba Cloud account UID, AccessKey ID, and AccessKey secret. For instructions, see Obtain an AccessKey.
-
You have prepared the MCP service code and Hook service code. Obtain a complete example from the start-agentrun repository:
git clone https://github.com/devsapp/start-agentrun.git cd start-agentrunThe project includes two example directories:
mcp_remote(remote MCP mode) andmcp_code(code-packaged mode), each providing a complete example for its respective deployment mode. All relative paths in the following steps are based on the project root directorystart-agentrun/.
When deploying Function Compute services, the default region is on the Alibaba Cloud International Website (for example, ap-southeast-1). Choose a region that fits your business needs.
Configure MCP Tool Hook (mcp_remote mode)
The following example uses an order query service (orderdesk) and a data masking Hook service (userhook) to demonstrate how to configure mcp_remote mode.
Step 1: Configure environment variables
Go to the mcp_remote example directory and configure your Alibaba Cloud credentials.
-
Enter the example directory:
cd mcp_remote -
Copy the environment variable template and fill in your credentials:
cp .env.example .envEdit the
.envfile and enter your Alibaba Cloud account information. Replace<ALIBABA_CLOUD_UID>inAGENTRUN_DATA_ENDPOINTwith your actual UID:ALIBABA_CLOUD_UID=<Your Alibaba Cloud account UID> ALIBABA_CLOUD_ACCESS_KEY_ID=<Your AccessKey ID> ALIBABA_CLOUD_ACCESS_KEY_SECRET=<Your AccessKey secret>
Step 2: One-click deployment
Run the example program to deploy the MCP service (orderdesk) and Hook service (userhook), create the tool in AgentRun, enable the proxy, and configure the Hook.
go run .
The program automatically performs the following actions:
-
Deploys the MCP service and Hook service to Function Compute.
-
Creates an MCP tool in AgentRun and enables the proxy.
-
Configures the Hook (
POST_CALL_TOOLevent) and associates the Hook service endpoint with the tool configuration. -
Makes a test call and outputs the validation result.
The example program does not automatically clean up deployed resources after validation. The retained Function Compute functions incur no extra cost. To clean up, manually delete the corresponding functions in Function Compute and the MCP tool in AgentRun.
Step 3: Verify results
After the program finishes, check the output to confirm the Hook is active.
Raw order data returned without the Hook (querying ORDER-1001 as an example):
{
"order_id": "ORDER-1001",
"customer_name": "Zhang San",
"phone": "138****5678",
"email": "zhangsan@example.com",
"shipping_address": "No. 100 Wensan Road, Xihu District, Hangzhou City, Zhejiang Province",
"status": "PAID",
"amount": 259.8,
"items": [
{"sku": "SKU-AX100", "name": "Ergonomic Mouse", "quantity": 1, "price": 129.9},
{"sku": "SKU-KB200", "name": "Mechanical Keyboard Keycaps", "quantity": 1, "price": 129.9}
]
}
Data returned with the Hook enabled (masked):
{
"order_id": "ORDER-1001",
"customer_name": "Zhang*",
"phone": "138****5678",
"email": "zh***@example.com",
"shipping_address": "Zhejiang Province, Hangzhou City, ****",
"status": "PAID",
"amount": 259.8,
"items": [
{"sku": "SKU-AX100", "name": "Ergonomic Mouse", "quantity": 1, "price": 129.9},
{"sku": "SKU-KB200", "name": "Mechanical Keyboard Keycaps", "quantity": 1, "price": 129.9}
],
"audit_id": "audit_xxxxxxxxxxxxxxxx"
}
Configuration reference
The example program auto-generates the following mcpProxyConfiguration:
{
"proxyEnabled": true,
"hooks": [
{
"event": "POST_CALL_TOOL",
"endpoint": "<Endpoint address of the Hook service>"
}
]
}
Parameter descriptions:
|
Parameter |
Type |
Required |
Description |
|
proxyEnabled |
Boolean |
Yes |
Whether to enable the MCP proxy. Set to |
|
hooks |
Array |
No |
List of Hook configurations. Each item includes an event type and Hook service endpoint. |
|
hooks[].event |
String |
Yes |
Hook event type. Supported values: |
|
hooks[].endpoint |
String |
Yes |
HTTP endpoint of the Hook service. The proxy sends an HTTP POST request to this address when the event triggers. |
|
hooks[].enabled |
Boolean |
No |
Whether to enable this Hook. Default is |
|
hooks[].timeout |
Integer |
No |
Hook request timeout in seconds. |
|
hooks[].description |
String |
No |
Description of the Hook. |
|
hooks[].headers |
Object |
No |
Custom request headers in key-value format. |
Configure MCP Tool Hook (mcp_code mode)
In mcp_code mode, the MCP service code is bundled into the AgentRun runtime, so you do not need to deploy a standalone MCP service. The following example also uses orderdesk (MCP service) and userhook (Hook service).
Step 1: Configure environment variables
Go to the mcp_code example directory and configure your Alibaba Cloud credentials.
-
Enter the example directory:
cd mcp_code -
Copy the environment variable template and fill in your credentials:
cp .env.example .envEdit the
.envfile and enter your Alibaba Cloud account information (same format as in mcp_remote mode).
Step 2: One-click deployment
Run the example program to bundle the MCP service code, deploy the Hook service, create the tool in AgentRun, enable the proxy, and configure the Hook.
go run .
Unlike mcp_remote mode, in mcp_code mode the MCP service code is bundled directly into the AgentRun runtime, so no separate MCP service deployment is needed.
Step 3: Verify results
Verification is the same as in mcp_remote mode. See Step 3: Verify results.
Example: Order data masking Hook service
The Hook service in the example (userhook) is implemented in Go and handles the POST_CALL_TOOL event. Below are the core masking functions. For the complete code, see the start-agentrun repository.
// maskPhone masks a phone number, keeping the first 3 and last 4 digits.
func maskPhone(phone string) string {
runes := []rune(strings.TrimSpace(phone))
if len(runes) <= 7 {
return "***"
}
return string(runes[:3]) + "****" + string(runes[len(runes)-4:])
}
// maskEmail masks an email address, keeping the first 1–2 characters of the username.
func maskEmail(email string) string {
parts := strings.SplitN(email, "@", 2)
if len(parts) != 2 {
return "***"
}
name := []rune(parts[0])
keep := 1
if len(name) > 2 {
keep = 2
}
return string(name[:keep]) + "***@" + parts[1]
}
// maskAddress masks an address, keeping the first 6 characters.
func maskAddress(address string) string {
runes := []rune(strings.TrimSpace(address))
if len(runes) <= 6 {
return "***"
}
return string(runes[:6]) + "****"
}
// maskName masks a name, keeping only the surname (first character).
func maskName(name string) string {
runes := []rune(strings.TrimSpace(name))
if len(runes) == 0 {
return "***"
}
return string(runes[:1]) + "*"
}
// auditIDFromBytes generates an audit ID from the raw response (SHA256 hash prefix).
func auditIDFromBytes(body []byte) string {
sum := sha256.Sum256(body)
return "audit_" + hex.EncodeToString(sum[:])[:16]
}
The Hook service receives the event payload from the proxy via HTTP POST. The request and response bodies in the payload are Base64-encoded. The Hook service decodes them, masks sensitive fields, injects an audit identifier, and returns the modified result as a Base64-encoded string.
Error handling
If the Hook service fails, the proxy interrupts the current MCP request:
|
Error scenario |
Proxy behavior |
|
Hook service timeout |
The MCP request fails and returns an error to the caller. |
|
Hook service unavailable (connection failure) |
The MCP request fails and returns an error to the caller. |
|
Hook service returns a non-200 status code |
The MCP request fails and returns an error to the caller. |
If the Hook service fails due to timeout, unavailability, or a non-200 status code, the proxy does not fall back. It marks the current MCP request as failed. If PRE_CALL_TOOL fails, the tool is not executed. If POST_CALL_TOOL fails, the raw result is not returned to the caller. Ensure your Hook service has high availability.
FAQ
Hook service does not take effect after configuration
Follow these steps to troubleshoot:
-
Confirm
proxyEnabledis set totrue. -
Confirm the
hooks[].endpointaddress is correct and the Hook service is running. -
Confirm the
hooks[].eventvalue is a supported event type (PRE_CALL_TOOL,POST_CALL_TOOL,PRE_LIST_TOOLS, orPOST_LIST_TOOLS; note case sensitivity). -
Check the Hook service logs to confirm it received the HTTP POST request from the proxy.
How to choose a deployment mode
|
Condition |
Recommended mode |
|
MCP service is already deployed independently |
mcp_remote |
|
You want to simplify deployment by bundling the MCP service code into the runtime |
mcp_code |