Skip to content

Commit 0686c07

Browse files
leggettercursoragentgithub-actions[bot]dependabot[bot]claude
authored
chore: v2.3.0 release prep — dependency bumps + MCP list filter parity (#314)
* feat(mcp): add full list filter parity for events and requests Expose payload search (body, headers, parsed_query, path), date range after/before params, and remaining CLI list filters on hookdeck_events and hookdeck_requests. Document ISO 8601 date windows and gte/lte mapping for agents; add unit and MCP acceptance smoke tests. Co-authored-by: Cursor <cursoragent@cursor.com> * Update package.json version to 2.3.0-beta.1 * chore(deps): bump golang.org/x/sys from 0.45.0 to 0.47.0 Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.45.0 to 0.47.0. - [Commits](golang/sys@v0.45.0...v0.47.0) --- updated-dependencies: - dependency-name: golang.org/x/sys dependency-version: 0.47.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump golang.org/x/term from 0.43.0 to 0.45.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.43.0 to 0.45.0. - [Commits](golang/term@v0.43.0...v0.45.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-version: 0.45.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump github.com/modelcontextprotocol/go-sdk Bumps [github.com/modelcontextprotocol/go-sdk](https://github.com/modelcontextprotocol/go-sdk) from 1.6.1 to 1.7.0. - [Release notes](https://github.com/modelcontextprotocol/go-sdk/releases) - [Commits](modelcontextprotocol/go-sdk@v1.6.1...v1.7.0) --- updated-dependencies: - dependency-name: github.com/modelcontextprotocol/go-sdk dependency-version: 1.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * docs(listen): surface --cli-key/--api-key on listen for ad-hoc auth What: document that `listen` accepts a key directly — add an authentication example to `hookdeck listen --help` and a section in the README listen docs covering `--cli-key` (user-scoped CLI key) and `--api-key` (project-scoped key). Why: passing a key to `listen` already works (both are global persistent flags bound to the profile key), but the flags are hidden, so the capability was undiscoverable. Users authenticating in CI or switching accounts had no signal that `listen --cli-key <key>` is supported. A CLI key is user-scoped and can navigate across projects; a project API/CI key is scoped to one project — the docs now make that distinction explicit. No behavior change: this is help-text and README only. Docs are hand-maintained (no cobra doc-gen in the repo), so the README is updated directly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016QCuRsTPjpi6hbu9WSaE5J * docs: name the Event Gateway product correctly in the listen intro The listen overview said "Hookdeck works by routing events…", attributing a product capability to the company/brand. Hookdeck is the company; the Event Gateway is the product that routes events. Reword so the product is named correctly, and note that `hookdeck listen` is a standalone command that works with whichever product you're authenticated with (Hookdeck Console or the Event Gateway). Also tidy the duplicated `destination`, `i.e.`→`e.g.`, and `Github`→`GitHub`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016QCuRsTPjpi6hbu9WSaE5J --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 57eaa04 commit 0686c07

14 files changed

Lines changed: 581 additions & 57 deletions

File tree

‎README.md‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,23 @@ Flags:
197197
--filter-headers string Filter events by request headers using Hookdeck filter syntax (JSON)
198198
--filter-query string Filter events by query parameters using Hookdeck filter syntax (JSON)
199199
--filter-path string Filter events by request path using Hookdeck filter syntax (JSON)
200+
--cli-key string Authenticate with a user-scoped CLI key instead of the stored login
201+
--api-key string Authenticate with a project-scoped key instead of the stored login
200202
```
201203

202-
Hookdeck works by routing events received for a given `source` (i.e., Shopify, Github, etc.) to its defined `destination` by connecting them with a `connection` to a `destination`. The CLI allows you to receive events for any given connection and forward them to your localhost at the specified port or any valid URL.
204+
By default `listen` uses the credentials saved by `hookdeck login`. To authenticate a single invocation without logging in first — for example in CI or when switching accounts — pass a key directly:
205+
206+
```sh
207+
# User-scoped CLI key (created by `hookdeck login`; can access all your projects)
208+
$ hookdeck listen 3000 stripe --cli-key <your-cli-key>
209+
210+
# Project-scoped key (e.g. a Project API key or a key from `hookdeck ci`)
211+
$ hookdeck listen 3000 stripe --api-key <your-project-api-key>
212+
```
213+
214+
Both flags are global, so they work with any command. A **CLI key** is tied to your user account and can navigate across projects; a **project API key** is scoped to a single project. Within the CLI both are stored and used the same way (see [Credential Types](#security-config-files-and-source-control)).
215+
216+
The Event Gateway routes events received for a given `source` (e.g. Shopify, GitHub) to a `destination` via a `connection`. `hookdeck listen` is a standalone command that works with whichever product you're authenticated with — Hookdeck Console or the Event Gateway — receiving events for a given connection and forwarding them to your localhost at the specified port or any valid URL.
203217

204218
Each `source` is assigned an Event URL, which you can use to receive events. When starting with a fresh account, the CLI will prompt you to create your first source. Each CLI process can listen to one source at a time.
205219

@@ -599,6 +613,8 @@ The client starts `hookdeck gateway mcp` as a stdio subprocess. If you haven't a
599613
| `hookdeck_metrics` | Query aggregate metrics — counts, failure rates, queue depth over time |
600614
| `hookdeck_help` | Discover available tools and their actions |
601615

616+
`hookdeck_events` and `hookdeck_requests` **list** actions support the same filters as `hookdeck gateway event list` and `hookdeck gateway request list` — including payload search (`body`, `headers`, `parsed_query`, `path`) and date windows via `*_after` / `*_before` (ISO 8601; maps to API `field[gte]` / `field[lte]`). See `hookdeck_help` with topic `hookdeck_events` or `hookdeck_requests` for the full parameter list.
617+
602618
#### Example prompts
603619

604620
Once the MCP server is configured, you can ask your agent questions like:
@@ -624,6 +640,12 @@ Once the MCP server is configured, you can ask your agent questions like:
624640
625641
"Compare failure rates across all my destinations this week."
626642
→ Agent uses hookdeck_metrics with dimensions set to destination_id and measures like error_rate.
643+
644+
"Find Stripe charge.succeeded events from the last week."
645+
→ Agent uses hookdeck_events list with body filter {"type":"charge.succeeded"} and created_after / created_before ISO datetimes.
646+
647+
"Show failed events that had delivery attempts in the last 24 hours."
648+
→ Agent uses hookdeck_events list with status FAILED and last_attempt_after set to yesterday's ISO datetime.
627649
```
628650

629651
### Manage connections

‎go.mod‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@ require (
1515
github.com/gosimple/slug v1.15.0
1616
github.com/logrusorgru/aurora v2.0.3+incompatible
1717
github.com/mitchellh/go-homedir v1.1.0
18-
github.com/modelcontextprotocol/go-sdk v1.6.1
18+
github.com/modelcontextprotocol/go-sdk v1.7.0
1919
github.com/sirupsen/logrus v1.9.4
2020
github.com/spf13/cobra v1.10.2
2121
github.com/spf13/pflag v1.0.10
2222
github.com/spf13/viper v1.21.0
2323
github.com/stretchr/testify v1.11.1
2424
github.com/tidwall/pretty v1.2.1
2525
github.com/x-cray/logrus-prefixed-formatter v0.5.2
26-
golang.org/x/sys v0.45.0
27-
golang.org/x/term v0.43.0
26+
golang.org/x/sys v0.47.0
27+
golang.org/x/term v0.45.0
2828
)
2929

3030
require (
@@ -71,7 +71,9 @@ require (
7171
go.yaml.in/yaml/v3 v3.0.4 // indirect
7272
golang.org/x/crypto v0.45.0 // indirect
7373
golang.org/x/oauth2 v0.35.0 // indirect
74+
golang.org/x/sync v0.20.0 // indirect
7475
golang.org/x/text v0.31.0 // indirect
76+
golang.org/x/time v0.15.0 // indirect
7577
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
7678
gopkg.in/yaml.v2 v2.4.0 // indirect
7779
gopkg.in/yaml.v3 v3.0.1 // indirect

‎go.sum‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQ
109109
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
110110
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
111111
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
112-
github.com/modelcontextprotocol/go-sdk v1.6.1 h1:0zOSupjKUxPKSocPT1Wtago+mUHU2/uZ4xSOY0FGReU=
113-
github.com/modelcontextprotocol/go-sdk v1.6.1/go.mod h1:kzm3kzFL1/+AziGOE0nUs3gvPoNxMCvkxokMkuFapXQ=
112+
github.com/modelcontextprotocol/go-sdk v1.7.0 h1:yqjY2dsbKAC0LSuWZVBMrHgiG8ukXv6NRo0JiALay44=
113+
github.com/modelcontextprotocol/go-sdk v1.7.0/go.mod h1:dL7u98E/zjJTGzEq+j30jQ8K2k1mb6LeAH4inEcSGts=
114114
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
115115
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
116116
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
@@ -193,6 +193,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
193193
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
194194
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
195195
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
196+
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
197+
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
196198
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
197199
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
198200
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -207,19 +209,21 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc
207209
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
208210
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
209211
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
210-
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
211-
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
212+
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
213+
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
212214
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
213215
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
214-
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
215-
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
216+
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
217+
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
216218
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
217219
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
218220
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
219221
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
220222
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
221223
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
222224
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
225+
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
226+
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
223227
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
224228
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
225229
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hookdeck-cli",
3-
"version": "2.2.0",
3+
"version": "2.3.0-beta.1",
44
"description": "Hookdeck CLI",
55
"repository": {
66
"type": "git",

‎pkg/cmd/listen.go‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,12 @@ Examples:
226226
Filter using operators (see https://hookdeck.com/docs/filters for syntax):
227227
228228
hookdeck listen %[1]d api --filter-body '{"amount": {"$gte": 100}}'
229+
230+
Authenticate with a specific key instead of the stored login, e.g. in CI or
231+
when switching accounts (--cli-key takes a user-scoped CLI key; --api-key a
232+
project-scoped key):
233+
234+
hookdeck listen %[1]d stripe --cli-key <your-cli-key>
229235
`, 3000)
230236

231237
lc.cmd.SetUsageTemplate(usage)

‎pkg/gateway/mcp/input.go‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,44 @@ func setInt(params map[string]string, key string, value int) {
113113
params[key] = strconv.Itoa(value)
114114
}
115115
}
116+
117+
// JSONFilterParam returns a JSON filter value for API query params (body, headers, etc.).
118+
// Accepts a JSON string or object from MCP tool arguments.
119+
func (in input) JSONFilterParam(key string) (string, error) {
120+
v, ok := in[key]
121+
if !ok {
122+
return "", nil
123+
}
124+
switch val := v.(type) {
125+
case string:
126+
return val, nil
127+
case map[string]interface{}:
128+
b, err := json.Marshal(val)
129+
if err != nil {
130+
return "", fmt.Errorf("%s: invalid JSON object: %w", key, err)
131+
}
132+
return string(b), nil
133+
default:
134+
return "", fmt.Errorf("%s must be a JSON string or object", key)
135+
}
136+
}
137+
138+
// setJSONFilter adds a JSON filter param when present and valid.
139+
func setJSONFilter(params map[string]string, key string, in input) error {
140+
value, err := in.JSONFilterParam(key)
141+
if err != nil {
142+
return err
143+
}
144+
setIfNonEmpty(params, key, value)
145+
return nil
146+
}
147+
148+
// setPayloadSearchFilters forwards body, headers, parsed_query, and path list filters.
149+
func setPayloadSearchFilters(params map[string]string, in input) error {
150+
for _, key := range []string{"body", "headers", "parsed_query", "path"} {
151+
if err := setJSONFilter(params, key, in); err != nil {
152+
return err
153+
}
154+
}
155+
return nil
156+
}

‎pkg/gateway/mcp/input_test.go‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package mcp
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestInput_JSONFilterParam_Missing(t *testing.T) {
11+
in := input{}
12+
value, err := in.JSONFilterParam("body")
13+
require.NoError(t, err)
14+
assert.Empty(t, value)
15+
}
16+
17+
func TestInput_JSONFilterParam_String(t *testing.T) {
18+
in := input{"body": `{"type":"payment"}`}
19+
value, err := in.JSONFilterParam("body")
20+
require.NoError(t, err)
21+
assert.Equal(t, `{"type":"payment"}`, value)
22+
}
23+
24+
func TestInput_JSONFilterParam_Object(t *testing.T) {
25+
in := input{"body": map[string]interface{}{"type": "payment", "amount": float64(100)}}
26+
value, err := in.JSONFilterParam("body")
27+
require.NoError(t, err)
28+
assert.JSONEq(t, `{"type":"payment","amount":100}`, value)
29+
}
30+
31+
func TestInput_JSONFilterParam_InvalidType(t *testing.T) {
32+
in := input{"body": 42}
33+
_, err := in.JSONFilterParam("body")
34+
require.Error(t, err)
35+
assert.Contains(t, err.Error(), "body must be a JSON string or object")
36+
}
37+
38+
func TestSetPayloadSearchFilters(t *testing.T) {
39+
params := make(map[string]string)
40+
in := input{
41+
"body": map[string]interface{}{"a": "b"},
42+
"headers": `{"x-test":"1"}`,
43+
"parsed_query": map[string]interface{}{"q": "x"},
44+
"path": "/webhooks",
45+
}
46+
require.NoError(t, setPayloadSearchFilters(params, in))
47+
assert.JSONEq(t, `{"a":"b"}`, params["body"])
48+
assert.Equal(t, `{"x-test":"1"}`, params["headers"])
49+
assert.JSONEq(t, `{"q":"x"}`, params["parsed_query"])
50+
assert.Equal(t, "/webhooks", params["path"])
51+
}

0 commit comments

Comments
 (0)