Skip to content

feat(server): signed webhook management tools — create/list/get/delete (62 → 66) - #70

Open
johnxie wants to merge 4 commits into
mainfrom
feat/v2-webhook-crud-tools
Open

feat(server): signed webhook management tools — create/list/get/delete (62 → 66)#70
johnxie wants to merge 4 commits into
mainfrom
feat/v2-webhook-crud-tools

Conversation

@johnxie

@johnxie johnxie commented Jul 28, 2026

Copy link
Copy Markdown
Member

Upstream context

Taskade shipped signed-webhook CRUD in API v2 on Jul 20–21 2026 (changelog v6.213.0 — "MCP Connections Do More & Signed Webhooks"). Refreshing taskade-public.v2.json from the live spec (yarn fetch:openapi:v2) brings the path count from 45 → 47:

  • POST /webhooks — register a signed webhook (body: targetUrl, events[], optional spaceIds[])
  • GET /webhooks — list registered webhooks
  • GET /webhooks/{id} — get one (id = the URL-encoded target URL)
  • DELETE /webhooks/{id} — delete one

This PR exposes them as four new v2 tools — 62 → 66 tools: createWebhook, listWebhooks, getWebhook, deleteWebhook.

Other live-spec drift picked up by the refresh (kept as-is — it is the live spec): expanded agent-persona enums on /createAgent, includeTranscript on /getConversation, launcherIcon/color on the public-agent ops, expanded Convo model list, Field schema additions, and deprecation notes on the subscribeWebhook/unsubscribeWebhook summaries ([Deprecated: use POST /webhooks] / [Deprecated: use DELETE /webhooks/:id]) — those flow into the generated descriptions of the two legacy tools.

The naming-collision problem → nameOverrides

These four ops have no operationId, and they are the first REST-shaped (non-flat-RPC) routes in v2. The codegen's fallback deriveToolName drops {param} segments and ignores the HTTP method, so all four would collide as a single name, webhooks.

Design: the codegen/parser now accepts an optional nameOverrides map keyed by "<lowercase-method> <path>":

const NAME_OVERRIDES = {
  'post /webhooks': 'createWebhook',
  'get /webhooks': 'listWebhooks',
  'get /webhooks/{id}': 'getWebhook',
  'delete /webhooks/{id}': 'deleteWebhook',
};
  • Resolution order: override → operationId → derived. An override is explicit intent, so it beats operationId too.
  • The v2 gen script's prune/allow-list matcher (previously first-path-segment only) now mirrors that exact resolution order, so pruning matches the overridden names and the existing fail-loud check (unknown enabled action → build error) keeps working.
  • The parser is now exported from the package root so the gen script reuses the same deriveToolName instead of a drift-prone local copy.
  • New parser tests pin both sides: overrides applied per method+path, and the without-override collision (all four /webhooks* ops → webhooks) so the hazard stays documented by a failing-if-changed test.

Hard constraint held: the 5 existing v2 names (promptAgent, listConversations, getConversation, subscribeWebhook, unsubscribeWebhook) and all 57 v1 names are byte-identical (see A1 below).

HMAC secret shown once

The POST /webhooks response includes the HMAC signing secret with spec description "shown once. Store it now." — and it is not retrievable afterwards (not even via getWebhook). createWebhook's tool description (via the per-action description mechanism, TASKADE_V2_ACTION_DESCRIPTIONS) tells the model explicitly:

The response includes the HMAC signing secret exactly ONCE — it cannot be retrieved again later, so store it securely (e.g. a secret manager) before doing anything else.

The README's v2 section carries the same warning, and marks subscribeWebhook/unsubscribeWebhook as legacy/unsigned per upstream deprecation.

Stacked PR

Based on feat/derived-idempotent-openworld-hints (derived idempotent/openWorld hints); retargets to main automatically once that merges. The generated diff here includes the correct derived annotations for the new tools (list/get read-only + idempotent, delete destructive + idempotent).

Manual live-QA checklist (requires a real TASKADE_API_KEY; not run in CI)

  • createWebhook with a test targetUrl + events: ['task.due'] → response contains secret (returned once); note it down
  • listWebhooks → shows the new endpoint (no secret in the payload)
  • getWebhook with the webhook's raw target URL as id (do NOT pre-URL-encode — the runtime encodes path params automatically) → returns the endpoint
  • deleteWebhook with the same id → cleans up; listWebhooks no longer shows it
  • createWebhook again → a fresh, different secret (confirms shown-once semantics)

Assertions (scripted, outputs verbatim)

A1 — tool-name set diff vs baseline 24f491b:

v2 base count: 5  v2 new count: 9
v2 added:
createWebhook
deleteWebhook
getWebhook
listWebhooks
v2 removed:
v1 base count: 57  v1 new count: 57
v1 set diff (must be empty):
V1-IDENTICAL
total tools now: 66

A2 — regenerated schemas: createWebhook input has targetUrl (uri), events (enum array, min 1), spaceIds (string array, max 100 — present in spec); getWebhook/deleteWebhook both take z.object({ id: z.string().min(1) }) with pathParamKeys: ['id'].

A3 — count consistency: git grep -n '66' README.md packages/server/server.json → README lines 16/29/172/302/329 + server.json line 4; git grep -nE '\b62 tools\b' → no matches.

Gates: yarn build green; working tree clean after post-commit rebuild; yarn lint green; yarn test green (4 files, 23 tests — includes the new parser tests).

Related

Relates to #11 — the webhook layer is now first-class (signed create/list/get/delete via API v2); MCP resource subscriptions themselves remain blocked upstream.

Changesets

  • @taskade/mcp-openapi-codegen: minornameOverrides option + parser exported from package root
  • @taskade/mcp-server: minor — 4 new signed-webhook tools (62 → 66)

johnxie added 2 commits July 28, 2026 03:19
…e (62 → 66)

Refresh taskade-public.v2.json from the live spec (45 → 47 paths; adds the
signed-webhook CRUD from upstream v6.213.0) and expose it as four new v2
tools: createWebhook, listWebhooks, getWebhook, deleteWebhook.

These REST-shaped ops omit operationId and their path-derived names would
all collide as 'webhooks' (deriveToolName drops {param} segments and
ignores the method), so the codegen/parser gains an explicit nameOverrides
map keyed by '<lowercase-method> <path>'; the v2 gen script supplies the
four overrides and its prune matcher now mirrors the codegen's
override -> operationId -> derived resolution order.

createWebhook's description surfaces the HMAC reality: the signing secret
is returned exactly once and must be stored securely.
@changeset-bot

changeset-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1b25f60

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@taskade/mcp-openapi-codegen Minor
@taskade/mcp-server Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@johnxie

johnxie commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

Code review

Found 2 issues:

  1. Double-encoding contract bug: the generated getWebhook/deleteWebhook descriptions instruct callers to pass the id as "its URL-encoded target URL", but the shared runtime already applies encodeURIComponent to every path parameter — a caller following the tool's own description sends a double-encoded id that will never match a real webhook (404). The descriptions must say to pass the raw target URL. (bug due to resolvedPath.replace(..., encodeURIComponent(...)) in the embedded runtime)

'getWebhook',
'Get one registered outbound webhook by id (its URL-encoded target URL)',
z.object({ id: z.string().min(1) }).shape,

  1. server.json description is 101 characters — one over the MCP Registry's maxLength: 100 (verified against the schema this file's $schema points to). This re-introduces the exact publish blocker PR fix: shorten server.json description for MCP Registry #22 was merged to fix; mcp-publisher will reject the next registry publish.

"name": "io.github.taskade/mcp-server",
"description": "Connect Taskade to any AI assistant — 66 tools: projects, tasks, agents, agent chat, signed webhooks.",
"repository": {

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@johnxie
johnxie deleted the branch main July 28, 2026 10:58
@johnxie johnxie closed this Jul 28, 2026
@johnxie johnxie reopened this Jul 28, 2026
@johnxie
johnxie changed the base branch from feat/derived-idempotent-openworld-hints to main July 28, 2026 10:59
… auto-encodes); registry-safe server.json description
@johnxie

johnxie commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

Both code-review findings fixed in b64aa52.

1. Double-encoding trap (getWebhook/deleteWebhook descriptions). The upstream spec text ("…by id (its URL-encoded target URL)") told the model to pre-encode the id, but the generated runtime already applies encodeURIComponent to every path param — following the description double-encoded the id and broke the lookup. Fixed via the existing per-action description mechanism (TASKADE_V2_ACTION_DESCRIPTIONS, the same one createWebhook uses for its secret-shown-once warning). The generated descriptions now read:

Get a webhook subscription by id. Pass the webhook’s raw target URL as id — do NOT URL-encode it; encoding is applied automatically.

(and the deleteWebhook analogue). No parser/codegen changes needed — a new codegen test pins that ActionConfig.description overrides the spec text in the generated output. The PR body's manual-QA checklist line has been corrected to match (raw URL, no pre-encoding).

2. MCP Registry description limit. server.json description was 101 chars, one over the registry schema's 100-char cap (the exact failure mode from #22). Now: Connect Taskade to any AI assistant — 66 tools: projects, tasks, agents, agent chat, webhooks.94 chars (96 UTF-8 bytes), verified with python3 -c 'print(len(...))'.

Gates re-run on the branch: yarn build green (regen committed, tree clean), yarn lint green, yarn test green (4 files, 24 tests — includes the new description-override test). Tool-name sets unchanged: 9 v2, 57 v1, 66 total.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant