Tool Type Generator

using raw tool definition to build your own platform on top of Composio

This is a bit of a checky tutorial as it is dogfooding the docs tool generation process.

To motivate this example clearly, in our tools section — we have details about let’s say Github tool, that shows its auth scheme, actions and their params.

Now why would anyone outside of Composio want to do this? Well if you are building a platform on top of Composio, perchance a Workflow builder like langflow. You would want to show some or all of this information to your users.

This is a non standard use case, that we support and love users building on top of us but if this is uninteresting to you, you can skip this tutorial.

How does one build a tool type generator?

In composio, we have two internal states for tools

  1. Raw tool definition
  2. Provider tool definition

The raw tool definition is an generic input output schema definition that we internally for tools, we expose it for customers if they want to build on top of it but it is not the primary way tools are normally used.

The provider tool definition, translates this raw tool definition to the specific schema of a provider (by default openai).

For building something like this, we need to use the raw tool definition.

Getting the raw tool definition

Of course, you need to initiate the Composio sdk first and use a COMPOSIO_API_KEY environment variable.

tool_doc_generator/main.py
1 toolkit_model = self.composio.toolkits.get(slug=toolkit.slug)
2 tools = self.composio.tools.get_raw_composio_tools(toolkits=[toolkit.slug])
3 logger.info(f"Toolkit tools: {toolkit}")

Let us see an example output for a raw GMAIL toolkit, with all of its tools.

this is just a taste but you can see the full output here.

output.json
1[
2 {
3 "deprecated": {
4 "available_versions": [
5 "0_1",
6 "latest",
7 "latest:base"
8 ],
9 "display_name": "Modify email labels",
10 "is_deprecated": false,
11 "toolkit": {
12 "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/gmail.svg"
13 },
14 "version": "0_1",
15 "displayName": "Modify email labels"
16 },
17 "description": "Adds and/or removes specified gmail labels for a message; ensure `message id` and all `label ids` are valid (use 'listlabels' for custom label ids).",
18 "input_parameters": {
19 "properties": {
20 "add_label_ids": {
21 "default": [],
22 "description": "Label IDs to add. For custom labels, obtain IDs via 'listLabels'. System labels (e.g., 'INBOX', 'SPAM') can also be used.",
23 "examples": [
24 "STARRED",
25 "IMPORTANT",
26 "Label_123"
27 ],
28 "items": {
29 "type": "string"
30 },
31 "title": "Add Label Ids",
32 "type": "array"
33 },
34 "message_id": {
35 "description": "Immutable ID of the message to modify (e.g., from 'fetchEmails' or 'fetchMessagesByThreadId').",
36 "examples": [
37 "17f1b2b9c1b2a3d4"
38 ],
39 "title": "Message Id",
40 "type": "string"
41 },
42 "remove_label_ids": {
43 "default": [],
44 "description": "Label IDs to remove. For custom labels, obtain IDs via 'listLabels'. System labels can also be used.",
45 "examples": [
46 "UNREAD",
47 "Label_456"
48 ],
49 "items": {
50 "type": "string"
51 },
52 "title": "Remove Label Ids",
53 "type": "array"
54 },
55 "user_id": {
56 "default": "me",
57 "description": "User's email address or 'me' for the authenticated user.",
58 "examples": [
59 "me",
60 "user@example.com"
61 ],
62 "title": "User Id",
63 "type": "string"
64 }
65 },
66 "required": [
67 "message_id"
68 ],
69 "title": "AddLabelToEmailRequest",
70 "type": "object"
71 },
72 "name": "Modify email labels",
73 "no_auth": false,
74 "output_parameters": {
75 "properties": {
76 "data": {
77 "description": "Data from the action execution",
78 "properties": {
79 "response_data": {
80 "description": "Full `Message` resource with updated labels.",
81 "title": "Response Data",
82 "type": "object"
83 }
84 },
85 "required": [
86 "response_data"
87 ],
88 "title": "Data",
89 "type": "object"
90 },
91 "error": {
92 "anyOf": [
93 {
94 "type": "string"
95 },
96 {
97 "type": "null"
98 }
99 ],
100 "default": null,
1jq '.[0] | keys' pages/src/examples/tool-generator/output.json
2[
3 "available_versions",
4 "deprecated",
5 "description",
6 "input_parameters",
7 "name",
8 "no_auth",
9 "output_parameters",
10 "scopes",
11 "slug",
12 "tags",
13 "toolkit",
14 "version"
15]

There is a bunch of useful information here, around the input_parameters and output_parameters for this example but scopes is very valuable to know what permissions are required for this tool.

Now from these input_parameters and output_parameters you can showcase the tool definitions.

tool_doc_generator/main.py
1 def _extract_tool_data(self, tool: Tool) -> t.Dict[str, t.Any]:
2 """Extract data from tool object or dict."""
3
4 return {
5 'name': tool.name or tool.slug,
6 'description': tool.description,
7 "slug": tool.slug,
8 'params': tool.input_parameters,
9 'response': tool.output_parameters

There is a bunch of other processing things happening here that are super generally relevant, so not going to call them out here that said there is another thing i want to showcase

Toolkit Information

Toolkis are what we call apps or integrations, for us they are a collection of tools. GMAIL has GMAIL_SEND_EMAIL as a tool.

Now for building something out like this, you might also want information about the toolkit itself.

A toolkit has information like categories or auth_schemes

tool_doc_generator/main.py
1 # Extract category from toolkit meta
2 category = None
3 if toolkit_model.meta.categories:

auth_schemes here are OAUTH2, API_KEY or BASIC_AUTH, etc — essentially the types of how one could authenticate with the toolkit.

tool_doc_generator/main.py
1 # Handle auth schemes safely
2 auth_schemes = None
3 try:
4 auth_schemes = toolkit_model.auth_config_details
5 except Exception as e:
6 logger.info(f"Could not process auth schemes for {toolkit.name}: {e}")
7 # Continue without auth schemes

Here is a way to parse the auth_scheme data

these are tuple objects as they have different schema for specific conditions like auth_config_creation or connected_account_initiation

they also have required and optional fields.

the context here is there are some fields you need while creating an auth config and some you need while connecting an account. this separation is done by the tuple here

tool_doc_generator/main.py
1 def _add_auth_schemes(self, auth_schemes: t.List[toolkit_retrieve_response.AuthConfigDetail]) -> None:
2 """Add authentication schemes section."""
3 self._blocks.append("### Authentication Details")
4
5 for scheme in auth_schemes:
6 fields = self._extract_auth_fields(scheme)
7 if fields:
8 auth_type = self._get_auth_type(scheme)
9 self._blocks.append(MDX.as_accordion(auth_type, "\n".join(fields)))
10
11 def _extract_auth_fields(self, scheme: toolkit_retrieve_response.AuthConfigDetail) -> t.List[str]:
12 """Extract authentication fields from scheme."""
13 fields = []
14
15 if hasattr(scheme, 'fields') and scheme.fields:
16 for field in scheme.fields:
17 fields.extend(self._process_tuple_field(field))
18
19
20 return fields
21
22 def _process_tuple_field(self, field: tuple) -> t.List[str]:
23 """Process tuple-style authentication field."""
24 fields = []
25 _, field_config = field
26
27 for field_list, required in [(getattr(field_config, 'required', []), True),
28 (getattr(field_config, 'optional', []), False)]:
29 for f in field_list:
30 if hasattr(f, 'name'):
31 fields.append(self._create_param_from_field(f, required))
32
33 return fields
34
35 def _create_param_from_field(self, field: toolkit_retrieve_response.AuthConfigDetail, required: t.Optional[bool] = None) -> str:
36 """Create parameter string from field object."""
37 return MDX.as_param(
38 name=str(field.name).replace('"', '\\"'),
39 required=required if required is not None else getattr(field, 'required', False),
40 typ=str(getattr(field, 'type', '')).replace('"', '\\"'),
41 default=str(getattr(field, 'default', '')).replace('"', '\\"') if getattr(field, 'default', None) else "",
42 doc=str(getattr(field, 'description', '')).replace('"', '\\"')
43 )
44
45 def _get_auth_type(self, scheme: toolkit_retrieve_response.AuthConfigDetail) -> str:

This is a fairly minimal explanation for the amount of code, as most of it is not super related to composio but it will be a good example on seeing behind the scenes of how composio is working and how to leverage the platform further.