Tool Modifiers in Composio: How to Transform Tool Inputs and Outputs Before Execution
Tool modifiers are middleware-style functions in the Composio SDK that intercept tool calls at three specific points—before execution, after execution, and during schema generation—to transform inputs, reshape outputs, and customize metadata without modifying underlying tool implementations.
The Composio SDK provides a pluggable architecture for LLM-invoked tools, enabling developers to inject custom logic at critical execution stages. According to the ComposioHQ/composio source code, these modifiers allow you to adjust authentication headers, truncate large payloads, or reshape tool schemas before they reach the LLM, all through a consistent type-safe interface defined in ts/packages/core/src/types/modifiers.types.ts.
Types of Tool Modifiers
The SDK defines three distinct modifier types, each triggering at a different lifecycle phase. All three are defined in the core TypeScript types file and receive the toolSlug and toolkitSlug parameters, enabling conditional logic that targets specific tools while leaving others untouched.
Before-Execution Modifier
The before-execution modifier (beforeExecuteModifier) runs immediately before a tool is sent to the Composio API. This hook receives the ToolExecuteParams object and allows you to adjust or enrich arguments, inject authentication headers, or add contextual data.
Use this modifier when you need to override LLM-generated parameters, enforce business rules, or inject runtime secrets that should not be exposed in the tool schema.
After-Execution Modifier
The after-execution modifier (afterExecuteModifier) executes right after the API returns a ToolExecuteResponse. This transformation layer enables you to truncate large payloads, map fields to a different structure, inject derived information, or filter sensitive data before returning results to the LLM.
Schema Modifier
The schema modifier (TransformToolSchemaModifier) fires when a tool's schema is first fetched via tools.get(). This modifier changes the tool's description, input parameters, or metadata before exposure to the LLM. Common use cases include versioning tools, redacting internal fields, or appending organization-specific context to tool descriptions.
Implementation by Provider Type
The wiring of modifiers differs based on whether you use non-agentic or agentic providers.
For non-agentic providers (plain chat completions), pass modifiers directly to composio.tools.execute() as an options object. For agentic providers (such as Vercel, Mastra, or CrewAI), pass modifiers to composio.tools.get(), where they are applied internally during the provider's execution step.
Code Examples
TypeScript: Transforming Inputs and Outputs with execute()
When working with chat-completion providers, supply beforeExecute and afterExecute callbacks to the execute() method. The following example limits HackerNews results and logs the transformation:
import { Composio } from "@composio/core";
const composio = new Composio({ apiKey: process.env.COMPOSIO_API_KEY });
const userId = "default";
const result = await composio.tools.execute(
"HACKERNEWS_GET_LATEST_POSTS",
{
userId,
arguments: { size: 10 }
},
{
beforeExecute: ({ toolSlug, toolkitSlug, params }) => {
if (toolSlug === "HACKERNEWS_GET_LATEST_POSTS") {
params.arguments.size = 1;
}
console.log(`[before] ${toolSlug} args:`, params.arguments);
return params;
},
afterExecute: ({ toolSlug, toolkitSlug, result }) => {
if (toolSlug === "HACKERNEWS_GET_LATEST_POSTS") {
const first = result.data.items?.[0];
return { ...result, data: { items: first ? [first] : [] } };
}
return result;
},
},
);
Both modifiers must return the modified object (params for before-execution, result for after-execution) to propagate changes through the execution chain.
Python: Decorator-Based Modifiers
The Python SDK offers decorator-based registration via @before_execute and @after_execute. These decorators accept a tools list to filter which tool slugs trigger the modifier:
from composio import Composio, before_execute, after_execute
from composio.types import ToolExecuteParams, ToolExecutionResponse
composio = Composio()
user_id = "default"
@before_execute(tools=["HACKERNEWS_GET_LATEST_POSTS"])
def limit_posts(tool: str, toolkit: str, params: ToolExecuteParams) -> ToolExecuteParams:
params["arguments"]["size"] = 1
return params
@after_execute(tools=["HACKERNEWS_GET_USER"])
def extract_karma(
tool: str, toolkit: str, response: ToolExecutionResponse
) -> ToolExecutionResponse:
return {
**response,
"data": {"karma": response["data"]["karma"]},
}
tools = composio.tools.get(user_id=user_id, slug="HACKERNEWS_GET_LATEST_POSTS")
result = composio.provider.handle_tool_calls(
response=llm_response,
user_id=user_id,
modifiers=[limit_posts, extract_karma],
)
Pass the registered modifier functions to handle_tool_calls() or similar provider-specific execution methods to apply them.
Schema Modification: Customizing Tool Definitions
Use the modifySchema option in tools.get() to transform how a tool appears to the LLM before schema generation:
import { Composio } from "@composio/core";
const composio = new Composio({ apiKey: process.env.COMPOSIO_API_KEY });
const tools = await composio.tools.get(
"default",
{ tools: ["GITHUB_GET_REPOS"] },
{
modifySchema: ({ schema, toolSlug, toolkitSlug }) => {
if (toolSlug === "GITHUB_GET_REPOS") {
schema.name = `Acme Corp – ${schema.name}`;
schema.metadata = { ...schema.metadata, internal: true };
}
return schema;
},
},
);
This approach is particularly useful for multi-tenant applications where the same underlying tool requires different descriptions or metadata for different customers.
Key Source Files
The implementation and type definitions for tool modifiers reside in the following locations within the ComposioHQ/composio repository:
ts/packages/core/src/types/modifiers.types.ts– Core TypeScript definitions forbeforeExecuteModifier,afterExecuteModifier, andTransformToolSchemaModifierfern/pages/src/tools-and-triggers/before-execution-modifiers.mdx– Documentation for pre-execution transformationsfern/pages/src/tools-and-triggers/after-execution-modifiers.mdx– Documentation for post-execution transformationsdocs/content/docs/tools-direct/modify-tool-behavior/before-execution-modifiers.mdx– Updated documentation location for before-execution modifiersdocs/content/docs/tools-direct/modify-tool-behavior/after-execution-modifiers.mdx– Updated documentation location for after-execution modifiers
Summary
- Tool modifiers provide middleware-style hooks at three lifecycle stages: before execution, after execution, and during schema generation.
- The
beforeExecuteModifiertransformsToolExecuteParamsto adjust arguments or inject authentication before API calls. - The
afterExecuteModifierreshapesToolExecuteResponseobjects to filter, truncate, or enrich returned data. - The
TransformToolSchemaModifiercustomizes tool metadata and descriptions before exposure to LLMs. - TypeScript implementations use callback options in
composio.tools.execute()orcomposio.tools.get(). - Python implementations use
@before_executeand@after_executedecorators registered with specific tool slugs. - All modifier types receive
toolSlugandtoolkitSlugparameters, enabling selective application to specific tools while bypassing others.
Frequently Asked Questions
What is the difference between before-execution and after-execution modifiers?
Before-execution modifiers run immediately before the tool call is sent to the Composio API, allowing you to modify input arguments, inject headers, or enforce validation rules. After-execution modifiers run immediately after receiving the API response, enabling you to transform, filter, or truncate the output data before it returns to the LLM. Both receive the tool and toolkit slugs as parameters for conditional logic.
Can I apply modifiers to only specific tools in my toolkit?
Yes. All modifier functions receive toolSlug and toolkitSlug parameters. You can write conditional logic inside the modifier body to return unmodified data when these identifiers do not match your target tools. In Python, the @before_execute and @after_execute decorators accept a tools list that automatically filters which slugs trigger the modifier.
How do schema modifiers affect tool calling behavior?
Schema modifiers alter the tool's definition—such as its name, description, and input parameters—before the LLM receives the schema. This affects how the LLM understands and invokes the tool, allowing you to hide internal fields, add organizational context, or version tool descriptions without changing the underlying API implementation. The transformation occurs during the tools.get() call.
Do modifiers work with all LLM providers in the Composio SDK?
Modifiers work across both non-agentic providers (standard chat completions) and agentic providers (Vercel, Mastra, CrewAI). For non-agentic workflows, pass modifiers directly to composio.tools.execute(). For agentic frameworks, pass them to composio.tools.get(), where the SDK internally applies them during the provider's execution step.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →