Skip to main content
Glama
plunger

plunger

Official

save_workflow

Idempotent

Save an ordered sequence of HTTP requests as a reusable workflow, letting later steps reuse values extracted from earlier responses for repeatable login or create-then-fetch flows.

Instructions

Save a workflow: an ordered list of requests where a later one can use values from an earlier response. Each step is described like send_request, plus extract (for example {name: "token", from: "json:$.access_token"}, which keeps the value as {{token}} for the steps after it, hidden if it looks like a credential) and expect_status. Use it for logins, create-then-fetch flows and any sequence you will repeat.

Tool call template

Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "arguments": {
      "name": "<name>",
      "steps": "<steps>"
    },
    "name": "save_workflow"
  }
}

Input Schema

NameRequiredDescriptionDefault
nameYesThe workflow's name.
groupNoFile it under a group, such as "Jira". Left out, a replaced workflow keeps its group; an empty string takes it out of its group.
stepsYesThe requests, in order. Each is like send_request, plus `extract` (values to keep for later steps) and `expect_status` (default: any 2xx).
overwriteNoReplace a workflow that already has this name.

Output Schema

NameRequiredDescriptionDefault
nameYes
groupNoThe group it is filed under (`Jira`, `Jira/Issues`), when it has one.
stepsNoThe steps. `list_workflows` leaves them out unless asked (`include_steps`); `step_count` and `step_labels` say what is in there.
step_countNoHow many steps there are (always present).
step_labelsNoEach step's label, or its URL or saved request when it has none.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed7 schema fields changedv0.8.1
    • changedInput schema / $defs / Step / properties / select / description
      Previous value: -"Return only these values instead of the whole body, e.g. [\"$.data[0].id\", \"header:Location\", \"status\"].\nSaves context on a big response; `json`, `body` and the headers are then left out. `[?(@.name=='Done')]`\nkeeps the array items whose field equals the value. If every path fails the whole response is returned with the reasons; if only some fail, the values that could be read come back and `problems` names the rest."New value: +"Return only these values instead of the whole body, e.g. [\"$.data[0].id\", \"header:Location\", \"status\"].\nSaves context on a big response; `json`, `body` and the headers are then left out. `[?(@.name=='Done')]`\nkeeps the array items whose field equals the value. `jq:<program>` reshapes the whole body with a jq program\n(e.g. \"jq:[.items[] | {id, name}]\"; pure, no files or network; several outputs come back as an array). On a 4xx or\n5xx the start of the body is returned as `error_body`, so the reason is not hidden. If every path fails the whole response is returned with the reasons; if only some fail, the values that could be read come back and `problems` names the rest."
    • changedOutput schema / $defs / Step / properties / select / description
      Previous value: -"Return only these values instead of the whole body, e.g. [\"$.data[0].id\", \"header:Location\", \"status\"].\nSaves context on a big response; `json`, `body` and the headers are then left out. `[?(@.name=='Done')]`\nkeeps the array items whose field equals the value. If every path fails the whole response is returned with the reasons; if only some fail, the values that could be read come back and `problems` names the rest."New value: +"Return only these values instead of the whole body, e.g. [\"$.data[0].id\", \"header:Location\", \"status\"].\nSaves context on a big response; `json`, `body` and the headers are then left out. `[?(@.name=='Done')]`\nkeeps the array items whose field equals the value. `jq:<program>` reshapes the whole body with a jq program\n(e.g. \"jq:[.items[] | {id, name}]\"; pure, no files or network; several outputs come back as an array). On a 4xx or\n5xx the start of the body is returned as `error_body`, so the reason is not hidden. If every path fails the whole response is returned with the reasons; if only some fail, the values that could be read come back and `problems` names the rest."
    • addedOutput schema / properties / step_count
      Added value: +{
      +  "default": 0,
      +  "description": "How many steps there are (always present).",
      +  "format": "uint",
      +  "minimum": 0,
      +  "type": "integer"
      +}
    • addedOutput schema / properties / step_labels
      Added value: +{
      +  "description": "Each step's label, or its URL or saved request when it has none.",
      +  "items": {
      +    "type": "string"
      +  },
      +  "type": "array"
      +}
    • addedOutput schema / properties / steps / default
      Added value: +[]
    • addedOutput schema / properties / steps / description
      Added value: +"The steps. `list_workflows` leaves them out unless asked (`include_steps`); `step_count` and\n`step_labels` say what is in there."
    • changedOutput schema / required
      Previous value: -[
      -  "name",
      -  "steps"
      -]New value: +[
      +  "name"
      +]
  2. Changed28 schema fields changedv0.8.0
    • addedInput schema / $defs / Assert
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "One check on a response. Give `from` and at least one operator; every operator given must hold.",
      +  "properties": {
      +    "contains": {
      +      "description": "A text value must contain this text; an array must have this item."
      +    },
      +    "equals": {
      +      "description": "The value must equal this (numbers compare as numbers, so 5 equals 5.0)."
      +    },
      +    "exists": {
      +      "description": "`true`: the value must exist (a null counts as existing); `false`: it must be absent.",
      +      "type": [
      +        "boolean",
      +        "null"
      +      ]
      +    },
      +    "from": {
      +      "description": "What to check: `json:$.data.id` (or just `$.data.id`), `header:Name`, `status`, `time` (milliseconds\nthe response took) or `body` (the whole body as text).",
      +      "type": "string"
      +    },
      +    "gt": {
      +      "description": "The value must be a number greater than this.",
      +      "format": "double",
      +      "type": [
      +        "number",
      +        "null"
      +      ]
      +    },
      +    "gte": {
      +      "description": "...at least this.",
      +      "format": "double",
      +      "type": [
      +        "number",
      +        "null"
      +      ]
      +    },
      +    "length": {
      +      "description": "The number of characters (text), items (array) or members (object) must be this.",
      +      "format": "uint",
      +      "minimum": 0,
      +      "type": [
      +        "integer",
      +        "null"
      +      ]
      +    },
      +    "lt": {
      +      "description": "...less than this.",
      +      "format": "double",
      +      "type": [
      +        "number",
      +        "null"
      +      ]
      +    },
      +    "lte": {
      +      "description": "...at most this.",
      +      "format": "double",
      +      "type": [
      +        "number",
      +        "null"
      +      ]
      +    },
      +    "name": {
      +      "description": "A label for the report. Without it the check is described from its parts.",
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "not_equals": {
      +      "description": "The value must differ from this."
      +    },
      +    "type": {
      +      "description": "The JSON type: `string`, `number`, `boolean`, `array`, `object` or `null`.",
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    }
      +  },
      +  "required": [
      +    "from"
      +  ],
      +  "type": "object"
      +}
    • addedInput schema / $defs / Paginate
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "Send the step's request again for the next page, until there are no more. Each page must return the\nexpected status. The items of every page are gathered into one list (see `collect`); `asserts` and\n`extract` see the last page.\n\nWhat the request can use: `{{page.number}}` (1, 2, ...), `{{page.offset}}` (items fetched so far) and\n`{{page.cursor}}` (the cursor from the page before; write `{{page.cursor?}}` so the first page leaves it out).",
      +  "properties": {
      +    "items": {
      +      "description": "Where the list is in each response: `json:$.issues`. Without it the whole body must be an array.",
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "max_pages": {
      +      "description": "Stop after this many pages (default 20, at most 500). The step says so when it stopped here with more to fetch.",
      +      "format": "uint",
      +      "minimum": 0,
      +      "type": [
      +        "integer",
      +        "null"
      +      ]
      +    },
      +    "next": {
      +      "description": "For `cursor`: where the next cursor is (`json:$.next_cursor`). A missing, null or empty value ends the paging.",
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "page_size": {
      +      "description": "The page size the request asks for. A page with fewer items than this is the last one.",
      +      "format": "uint",
      +      "minimum": 0,
      +      "type": [
      +        "integer",
      +        "null"
      +      ]
      +    },
      +    "style": {
      +      "description": "How the API pages: `offset` (default: `startAt=0`, `startAt=100`, ...), `page` (`page=1`, `page=2`, ...),\n`cursor` (each response says where the next page starts; needs `next`) or `link` (each response has a\n`Link: <url>; rel=\"next\"` header, as GitHub does).",
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "total": {
      +      "description": "For `offset`: where the total count is (`json:$.total`), so paging stops exactly at the end.",
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    }
      +  },
      +  "type": "object"
      +}
    • addedInput schema / $defs / Poll
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "Send the step's request again and again until a condition holds, for something that finishes later (an\nexport, a deployment, an async job). Use `extract` with `\"scope\": \"run\"` to read the status from each response,\nthen name it in `until`. The step fails if the condition never holds.",
      +  "properties": {
      +    "every_ms": {
      +      "description": "Wait this long between requests (default 1000, at most 60000).",
      +      "format": "uint64",
      +      "minimum": 0,
      +      "type": [
      +        "integer",
      +        "null"
      +      ]
      +    },
      +    "max_attempts": {
      +      "description": "Give up after this many requests (default 30, at most 1000).",
      +      "format": "uint",
      +      "minimum": 0,
      +      "type": [
      +        "integer",
      +        "null"
      +      ]
      +    },
      +    "until": {
      +      "description": "A condition on the values extracted so far, like `skip_if`: `{{state}} == DONE`.",
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "until"
      +  ],
      +  "type": "object"
      +}
    • addedInput schema / $defs / Retry
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "Send the request again when it fails for a reason that passes: a rate limit, a busy server, a dropped\nconnection. It waits the `Retry-After` the server asks for, or `backoff_ms` doubled each time.",
      +  "properties": {
      +    "attempts": {
      +      "description": "How many requests in all, the first included (default 3, at most 10).",
      +      "format": "uint",
      +      "minimum": 0,
      +      "type": [
      +        "integer",
      +        "null"
      +      ]
      +    },
      +    "backoff_ms": {
      +      "description": "The first wait in milliseconds, doubled for each try (default 500, at most 60000).",
      +      "format": "uint64",
      +      "minimum": 0,
      +      "type": [
      +        "integer",
      +        "null"
      +      ]
      +    },
      +    "on_status": {
      +      "description": "The statuses to try again on (default 429, 500, 502, 503, 504). A connection that failed is always tried again.",
      +      "items": {
      +        "format": "uint16",
      +        "maximum": 65535,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "type": [
      +        "array",
      +        "null"
      +      ]
      +    }
      +  },
      +  "type": "object"
      +}
    • addedInput schema / $defs / Step / properties / as
      Added value: +{
      +  "description": "The name of the element in a `for_each` (default `item`).",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedInput schema / $defs / Step / properties / asserts
      Added value: +{
      +  "description": "Checks on the response (`{\"from\": \"json:$.id\", \"equals\": 7}`, `{\"from\": \"time\", \"lt\": 500}`). Every one is\nrun and reported; the step fails if any does. They run after the status check and before `extract`.",
      +  "items": {
      +    "$ref": "#/$defs/Assert"
      +  },
      +  "type": "array"
      +}
    • addedInput schema / $defs / Step / properties / collect
      Added value: +{
      +  "description": "Keep what the step fetched as the run's value `{{name}}` for the later steps (and for `return`): the items\nof every page for a `paginate`, the response body otherwise; with `for_each`, a list with one entry per\nelement. It lives for this run only and is never stored. Known secrets in it are masked.",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedInput schema / $defs / Step / properties / continue_on_error
      Added value: +{
      +  "description": "With `for_each`: an element that fails is recorded (as null in the collected list) and the step goes on,\ninstead of ending the run. The step fails only if every element fails.",
      +  "type": "boolean"
      +}
    • addedInput schema / $defs / Step / properties / delay_ms
      Added value: +{
      +  "description": "Wait this long between the requests of one step (pages, elements), to stay under a rate limit.",
      +  "format": "uint64",
      +  "minimum": 0,
      +  "type": [
      +    "integer",
      +    "null"
      +  ]
      +}
    • addedInput schema / $defs / Step / properties / for_each
      Added value: +{
      +  "description": "Send this step once for every element of a list: the name of a variable that holds a JSON array (one a\n`collect` filled), or an array written out. The element is `{{item}}` (or the name in `as`), and for an\nobject its fields are `{{item.key}}`, `{{item.fields.name}}`. `{{loop.index}}` counts from 0.",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedInput schema / $defs / Step / properties / paginate
      Added value: +{
      +  "anyOf": [
      +    {
      +      "$ref": "#/$defs/Paginate"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "Page through a list: send the request again for the next page and gather the items. See `Paginate`."
      +}
    • addedInput schema / $defs / Step / properties / poll
      Added value: +{
      +  "anyOf": [
      +    {
      +      "$ref": "#/$defs/Poll"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "Send the request again until a condition holds, for a job that finishes later. See `Poll`."
      +}
    • addedInput schema / $defs / Step / properties / retry
      Added value: +{
      +  "anyOf": [
      +    {
      +      "$ref": "#/$defs/Retry"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "Try again after a 429, a 5xx or a dropped connection. See `Retry`."
      +}
    • addedInput schema / $defs / Step / properties / return
      Added value: +{
      +  "description": "A jq program that turns the run's values into the answer the run returns, e.g.\n`{count: (.issues|length), points: ([.issues[].fields.points]|add)}`. The input is an object of the run's\nvalues (`collect`ed lists, `scope: run` extracts, the variables passed in), without secrets. A step may have\nonly this and no request. The last `return` of the run is its `output`. jq here has no file, network or\nenvironment access.",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / $defs / Assert
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "One check on a response. Give `from` and at least one operator; every operator given must hold.",
      +  "properties": {
      +    "contains": {
      +      "description": "A text value must contain this text; an array must have this item."
      +    },
      +    "equals": {
      +      "description": "The value must equal this (numbers compare as numbers, so 5 equals 5.0)."
      +    },
      +    "exists": {
      +      "description": "`true`: the value must exist (a null counts as existing); `false`: it must be absent.",
      +      "type": [
      +        "boolean",
      +        "null"
      +      ]
      +    },
      +    "from": {
      +      "description": "What to check: `json:$.data.id` (or just `$.data.id`), `header:Name`, `status`, `time` (milliseconds\nthe response took) or `body` (the whole body as text).",
      +      "type": "string"
      +    },
      +    "gt": {
      +      "description": "The value must be a number greater than this.",
      +      "format": "double",
      +      "type": [
      +        "number",
      +        "null"
      +      ]
      +    },
      +    "gte": {
      +      "description": "...at least this.",
      +      "format": "double",
      +      "type": [
      +        "number",
      +        "null"
      +      ]
      +    },
      +    "length": {
      +      "description": "The number of characters (text), items (array) or members (object) must be this.",
      +      "format": "uint",
      +      "minimum": 0,
      +      "type": [
      +        "integer",
      +        "null"
      +      ]
      +    },
      +    "lt": {
      +      "description": "...less than this.",
      +      "format": "double",
      +      "type": [
      +        "number",
      +        "null"
      +      ]
      +    },
      +    "lte": {
      +      "description": "...at most this.",
      +      "format": "double",
      +      "type": [
      +        "number",
      +        "null"
      +      ]
      +    },
      +    "name": {
      +      "description": "A label for the report. Without it the check is described from its parts.",
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "not_equals": {
      +      "description": "The value must differ from this."
      +    },
      +    "type": {
      +      "description": "The JSON type: `string`, `number`, `boolean`, `array`, `object` or `null`.",
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    }
      +  },
      +  "required": [
      +    "from"
      +  ],
      +  "type": "object"
      +}
    • addedOutput schema / $defs / Paginate
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "Send the step's request again for the next page, until there are no more. Each page must return the\nexpected status. The items of every page are gathered into one list (see `collect`); `asserts` and\n`extract` see the last page.\n\nWhat the request can use: `{{page.number}}` (1, 2, ...), `{{page.offset}}` (items fetched so far) and\n`{{page.cursor}}` (the cursor from the page before; write `{{page.cursor?}}` so the first page leaves it out).",
      +  "properties": {
      +    "items": {
      +      "description": "Where the list is in each response: `json:$.issues`. Without it the whole body must be an array.",
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "max_pages": {
      +      "description": "Stop after this many pages (default 20, at most 500). The step says so when it stopped here with more to fetch.",
      +      "format": "uint",
      +      "minimum": 0,
      +      "type": [
      +        "integer",
      +        "null"
      +      ]
      +    },
      +    "next": {
      +      "description": "For `cursor`: where the next cursor is (`json:$.next_cursor`). A missing, null or empty value ends the paging.",
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "page_size": {
      +      "description": "The page size the request asks for. A page with fewer items than this is the last one.",
      +      "format": "uint",
      +      "minimum": 0,
      +      "type": [
      +        "integer",
      +        "null"
      +      ]
      +    },
      +    "style": {
      +      "description": "How the API pages: `offset` (default: `startAt=0`, `startAt=100`, ...), `page` (`page=1`, `page=2`, ...),\n`cursor` (each response says where the next page starts; needs `next`) or `link` (each response has a\n`Link: <url>; rel=\"next\"` header, as GitHub does).",
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "total": {
      +      "description": "For `offset`: where the total count is (`json:$.total`), so paging stops exactly at the end.",
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    }
      +  },
      +  "type": "object"
      +}
    • addedOutput schema / $defs / Poll
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "Send the step's request again and again until a condition holds, for something that finishes later (an\nexport, a deployment, an async job). Use `extract` with `\"scope\": \"run\"` to read the status from each response,\nthen name it in `until`. The step fails if the condition never holds.",
      +  "properties": {
      +    "every_ms": {
      +      "description": "Wait this long between requests (default 1000, at most 60000).",
      +      "format": "uint64",
      +      "minimum": 0,
      +      "type": [
      +        "integer",
      +        "null"
      +      ]
      +    },
      +    "max_attempts": {
      +      "description": "Give up after this many requests (default 30, at most 1000).",
      +      "format": "uint",
      +      "minimum": 0,
      +      "type": [
      +        "integer",
      +        "null"
      +      ]
      +    },
      +    "until": {
      +      "description": "A condition on the values extracted so far, like `skip_if`: `{{state}} == DONE`.",
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "until"
      +  ],
      +  "type": "object"
      +}
    • addedOutput schema / $defs / Retry
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "Send the request again when it fails for a reason that passes: a rate limit, a busy server, a dropped\nconnection. It waits the `Retry-After` the server asks for, or `backoff_ms` doubled each time.",
      +  "properties": {
      +    "attempts": {
      +      "description": "How many requests in all, the first included (default 3, at most 10).",
      +      "format": "uint",
      +      "minimum": 0,
      +      "type": [
      +        "integer",
      +        "null"
      +      ]
      +    },
      +    "backoff_ms": {
      +      "description": "The first wait in milliseconds, doubled for each try (default 500, at most 60000).",
      +      "format": "uint64",
      +      "minimum": 0,
      +      "type": [
      +        "integer",
      +        "null"
      +      ]
      +    },
      +    "on_status": {
      +      "description": "The statuses to try again on (default 429, 500, 502, 503, 504). A connection that failed is always tried again.",
      +      "items": {
      +        "format": "uint16",
      +        "maximum": 65535,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "type": [
      +        "array",
      +        "null"
      +      ]
      +    }
      +  },
      +  "type": "object"
      +}
    • addedOutput schema / $defs / Step / properties / as
      Added value: +{
      +  "description": "The name of the element in a `for_each` (default `item`).",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / $defs / Step / properties / asserts
      Added value: +{
      +  "description": "Checks on the response (`{\"from\": \"json:$.id\", \"equals\": 7}`, `{\"from\": \"time\", \"lt\": 500}`). Every one is\nrun and reported; the step fails if any does. They run after the status check and before `extract`.",
      +  "items": {
      +    "$ref": "#/$defs/Assert"
      +  },
      +  "type": "array"
      +}
    • addedOutput schema / $defs / Step / properties / collect
      Added value: +{
      +  "description": "Keep what the step fetched as the run's value `{{name}}` for the later steps (and for `return`): the items\nof every page for a `paginate`, the response body otherwise; with `for_each`, a list with one entry per\nelement. It lives for this run only and is never stored. Known secrets in it are masked.",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / $defs / Step / properties / continue_on_error
      Added value: +{
      +  "description": "With `for_each`: an element that fails is recorded (as null in the collected list) and the step goes on,\ninstead of ending the run. The step fails only if every element fails.",
      +  "type": "boolean"
      +}
    • addedOutput schema / $defs / Step / properties / delay_ms
      Added value: +{
      +  "description": "Wait this long between the requests of one step (pages, elements), to stay under a rate limit.",
      +  "format": "uint64",
      +  "minimum": 0,
      +  "type": [
      +    "integer",
      +    "null"
      +  ]
      +}
    • addedOutput schema / $defs / Step / properties / for_each
      Added value: +{
      +  "description": "Send this step once for every element of a list: the name of a variable that holds a JSON array (one a\n`collect` filled), or an array written out. The element is `{{item}}` (or the name in `as`), and for an\nobject its fields are `{{item.key}}`, `{{item.fields.name}}`. `{{loop.index}}` counts from 0.",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / $defs / Step / properties / paginate
      Added value: +{
      +  "anyOf": [
      +    {
      +      "$ref": "#/$defs/Paginate"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "Page through a list: send the request again for the next page and gather the items. See `Paginate`."
      +}
    • addedOutput schema / $defs / Step / properties / poll
      Added value: +{
      +  "anyOf": [
      +    {
      +      "$ref": "#/$defs/Poll"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "Send the request again until a condition holds, for a job that finishes later. See `Poll`."
      +}
    • addedOutput schema / $defs / Step / properties / retry
      Added value: +{
      +  "anyOf": [
      +    {
      +      "$ref": "#/$defs/Retry"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "Try again after a 429, a 5xx or a dropped connection. See `Retry`."
      +}
    • addedOutput schema / $defs / Step / properties / return
      Added value: +{
      +  "description": "A jq program that turns the run's values into the answer the run returns, e.g.\n`{count: (.issues|length), points: ([.issues[].fields.points]|add)}`. The input is an object of the run's\nvalues (`collect`ed lists, `scope: run` extracts, the variables passed in), without secrets. A step may have\nonly this and no request. The last `return` of the run is its `output`. jq here has no file, network or\nenvironment access.",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
  3. First observedv0.1.0

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations are thin (idempotentHint=true, destructiveHint=false) but the description adds real behavior: extracted values persist for later steps and credential-looking values are stored hidden. However, it says nothing about what happens when a workflow with that name already exists, even though the schema exposes an `overwrite` flag and the tool is annotated non-destructive — a material omission for a write tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with the core concept before the step-level details. The extract illustration earns its space, though the parenthetical example is slightly long relative to the rest.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a highly complex tool (deeply nested step schema), the description need not re-document fields given 100% schema coverage and an output schema. It supplies the two things the schema can't: the mental model of a workflow and the cross-reference to send_request for step shape.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description does add a useful conceptual anchor by equating each step with send_request and singling out `extract` and `expect_status`, but the remaining step fields (poll, paginate, retry, collect, for_each, return) get no orientation at this level.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Save a workflow') and then defines what a workflow is — an ordered list of requests where later steps consume earlier responses — which is genuinely informative. It does not name a sibling (save_request, run_workflow) to contrast against, so the agent must infer the boundary itself.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'Use it for logins, create-then-fetch flows and any sequence you will repeat' gives concrete positive triggers, which is better than nothing. There is no when-not guidance and no mention of the obvious alternatives — save_request for a single request, run_workflow to execute — leaving the save-vs-run and one-vs-many decisions to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.