Automation-Backed Action Tool
Description
This sample gives an AI agent the ability to do something rather than just read: it exposes a webhook-triggered automation as a Model Context Protocol (MCP) tool. The agent calls the tool, Stack9 runs the automation with the tool arguments as the request body, and the automation's last action output is returned to the agent.
Unlike query tools, automation tools have no schema auto-derivation — an automation tool without an inputSchema exposes zero arguments to the agent, so the inline schema below is mandatory rather than optional. Authorization comes from the same automation-permission model the automation's HTTP webhook route uses, and it fails closed: if no permission record exists for the automation, the call is refused.
Use Case
A customer-support assistant has triaged an inbound email, identified the customer, and needs to raise a ticket. Instead of the agent calling a bespoke HTTP endpoint with hand-rolled auth, it calls the create_support_ticket MCP tool on the instance and receives the created ticket's id and reference back.
Key Features
- Write/side-effecting action exposed safely to an agent
- Mandatory inline
inputSchemawith per-argument descriptions - Arguments passed through as the automation body (
trigger.body) - Gated by
app_automation_permissions— fail-closed, no record means no access - Runs as the calling user, so downstream audit fields attribute correctly
- Returns the automation's last action output to the agent
The automation it points at
src/automations/create_support_ticket.json
{
"key": "create_support_ticket",
"name": "Create support ticket",
"entityKey": "support_ticket",
"app": "support",
"triggerType": "webhook",
"triggerParams": {
"method": "post",
"path": "/create-support-ticket"
},
"actions": [
{
"name": "Validate customer exists",
"key": "validate_customer",
"actionTypeKey": "entity_find",
"params": {
"entityKey": "customer",
"query": {
"$where": { "id": "{{trigger.body.customer_id}}" }
}
}
},
{
"name": "Create the ticket",
"key": "create_ticket",
"actionTypeKey": "entity_create",
"params": {
"entityKey": "support_ticket",
"body": {
"customer_id": "{{trigger.body.customer_id}}",
"subject": "{{trigger.body.subject}}",
"body": "{{trigger.body.body}}",
"priority": "{{trigger.body.priority}}",
"channel": "ai_assistant",
"status": "open"
}
}
}
]
}
Two things to notice:
- The automation must have
"triggerType": "webhook"— the Console's tool picker only lists webhook automations, and no other trigger type can be invoked through MCP. - The
triggerParams.pathis irrelevant to MCP. MCP invokes the automation by key, not over its HTTP route. The path only matters if you also want the automation reachable as a normal webhook endpoint.
JSON Definition
src/mcps/support_actions.json
{
"key": "support_actions",
"name": "Support Actions",
"description": "Write actions the support assistant may perform on behalf of an agent.",
"tools": [
{
"key": "create_support_ticket",
"name": "create_support_ticket",
"description": "Create a support ticket against an existing customer. Requires the numeric Stack9 customer id — look it up first if you only have a name or email. Returns the created ticket id and reference.",
"sourceType": "automation",
"inputSchema": {
"customer_id": {
"type": "number",
"description": "Numeric Stack9 customer id the ticket belongs to"
},
"subject": {
"type": "string",
"description": "One-line summary of the issue, max ~120 characters"
},
"body": {
"type": "string",
"description": "Full description of the issue, including any steps to reproduce"
},
"priority": {
"type": "string",
"description": "One of: low, medium, high. Defaults to medium when omitted.",
"optional": true
},
"metadata": {
"type": "object",
"description": "Optional free-form context, e.g. source email id or conversation id",
"optional": true
}
}
}
]
}
Permission setup
The automation belongs to the support app, so access is decided by that app's automation permission record:
- If no permission record exists for
create_support_ticket, every MCP call is refused with403. This is deliberate — an automation is not exposed by accident. - If a record exists, the caller's role for the
supportapp must meet or exceed the record's required role. - If the record's required role is
Public, any authenticated caller passes.
An automation tool is a write path. Grant the agent's principal the narrowest app role that satisfies the permission record, and prefer a dedicated Stack9 user for the agent so its actions are attributable and revocable independently of a human account.
Result
The tool returns the last action's output. With the automation above, that is the create_ticket action:
{
"content": [
{
"type": "text",
"text": "{\"id\":9114,\"reference\":\"TKT-9114\",\"status\":\"open\",\"priority\":\"medium\"}"
}
]
}
If the last action produces no output, the tool returns null. Order your actions so the one whose result the agent needs is last.
Notes
- Always declare
inputSchemafor automation tools. There is no generated-model fallback forsourceType: automation; without a schema the agent can only call the tool with an empty body. - The whole arguments object becomes the body. Every argument is available in the automation as
trigger.body.{argument}. There is no filter/variable partitioning as there is for query tools. objectandarrayare untyped. Themetadataargument accepts any object shape; validate it inside the automation if the shape matters.- Errors are sanitized. A Stack9 validation or permission failure is returned to the agent with its message. Any other failure — a broken action, a database error — is returned as the generic
Tool 'create_support_ticket' failed due to an internal errorand logged server-side. Debug from the Core logs, not from the agent transcript. - Idempotency is your responsibility. An agent that retries a failed call may create a duplicate ticket. If that matters, add a dedupe key argument and handle it in the automation.
- Endpoint for this config:
{coreBaseUrl}/api/mcp/support_actions.