Skip to content

feat(mcp): add appduct_list_events MCP tool (#125) - #144

Merged
V3RON merged 21 commits into
mainfrom
issue-125-add-appduct-list-events-mcp-tool
Oct 2, 2026
Merged

V3RON merged 21 commits into
mainfrom
issue-125-add-appduct-list-events-mcp-tool

Conversation

@V3RON

@V3RON V3RON commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Closes #125

Stacked on #143 (issue #124): base is that branch, not main. Part of #95.

What changed

Agents get an appduct_list_events MCP tool that lists the events an app declared, with descriptions, and the payload schema when name is an exact name. Docs: shipped skill, both READMEs, website, docs/ARCHITECTURE.md.

Acceptance criteria

# Criterion Test Tier
1 Signature lines with descriptions and total mcp-server.test.ts "returns one signature line with its description per declared event..." unit
2 name: "cart.*" returns only matching events "a glob name returns only the matching events..." unit
3 Exact name also returns payload_schema "an exact name that matches one declared event also returns its payload schema" unit
4 No declared events: empty list, total: 0 "a session with no declared events returns an empty list..." unit
5 Skill says to list events before waiting and names appduct events ls skills/appduct/SKILL.md, references/cli.md (already listed events ls from #124) docs

E2E evidence

Target: iOS simulator (iPhone 16 Pro, iOS 26.4), Expo playground, commit 53d4a1d. Branch daemon with isolated APPDUCT_STATE_DIR and OS-assigned wssPort (wssPort: 0).
Smoke: SMOKE_OK (throwing_tool: exit 72, type tool_execution_error)
Feature: appduct_list_events through appduct mcp (stdio MCP client) against the live session; no SDK declares events yet.

listed: true
{}                    -> {"session":"iphone","total":0,"limit":50,"events":[]}
{"name":"cart.*"}     -> {"session":"iphone","total":0,"limit":50,"events":[]}
{"name":"cart.added"} -> {"session":"iphone","total":0,"limit":50,"events":[]}
{"limit":5,"offset":0}-> {"session":"iphone","total":0,"limit":5,"events":[]}
{"name":"x","bogus":1}-> isError: invalid_request: appduct_list_events does not take "bogus". It takes: selector, name, limit, offset.

Checklist

  • CHANGELOG.md has an entry under Unreleased (writing-changelog skill), or the change is not user-visible
  • User-facing docs updated for every surface the change touches (writing-user-docs skill), or the change is not user-visible
  • No new import past a module's index.ts; no new direct node:* I/O outside an adapter
  • Simplification checklist from the architecture skill applied, exceptions explained above
  • docs/ARCHITECTURE.md updated if a surface it describes changed

Out of scope

policy-and-audit.integration.test.ts "a prompt tool call from the CLI (no consent channel)..." failed once in the full run and passed on rerun, untouched by this change; no issue filed yet.

Status

Implement: done (5/5 green) Review: round 1, approve (2 nits fixed) E2E: pass (iOS) Ready: yes

@V3RON V3RON left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve (posted as a comment; GitHub blocks approving your own PR): 0 blocker, 0 should-fix, 2 nits (both docs).
Spec: issue #125 and the #95 design comment (slice 2).
Fix first: the extra appduct events ls cell in the website's two-column table, which GFM drops.

| --- | --- |
| `appduct_connect` | Creates a connection link and opens it on a booted iOS Simulator or Android emulator it finds. If there's none, it returns a QR code for you to scan. |
| `appduct_wait_for_session` | Waits until the device connects. |
| `appduct_list_events` | Lists the events your app declared, as one-line signatures with descriptions. `name` filters to a glob, e.g. `"cart.*"`, and an exact name also returns the payload schema. Returns 50 at a time unless given `limit`; takes `offset`. Call it before waiting on an event. | `appduct events ls` |

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this table has two columns (Tool | What it does), so the third cell appduct events ls is dropped when the page renders and readers never see the CLI equivalent. Fold it into the description, e.g. "... Call it before waiting on an event. CLI: appduct events ls."

Four more built-in tools cover what the app's own tools can't. `appduct_connect` mints a link and, by default, delivers it to whichever `android`/`ios-sim` device it detects — pass `target`/`device` to choose, or `target: "none"` to force the human flow — falling back to a QR code, plus instructions to show it, only when there's nothing to deliver to. Delivering to `android` (chosen or detected) needs `appId`, resolved the same way as `--app-id` (see [Delivering the link to a device](#delivering-the-link-to-a-device)); passing it with `target: "ios-sim"` or `"none"` is an error. `appduct_wait_for_session` then waits for that session to be claimed. `target: "ios-device"` reaches a paired physical iPhone or iPad, with `appId` and the [prerequisites above](#--open-ios-device-experimental) — it's experimental and never auto-detected, so an agent has to ask for it by name.

The other two give an agent a pull surface over `postEvent()`-pushed app events: `appduct_events` drains everything retained since a cursor, and `appduct_wait_for_event` blocks for the next event whose name matches `name`, a whole-name glob (`*` waits for any name) — checking what's already retained before waiting live — rejecting with `tool_timeout` if none arrives in time.
The other three give an agent a pull surface over `postEvent()`-pushed app events. `appduct_list_events` lists the events the app declared (`appduct events ls`) as signatures with descriptions, plus the payload schema when `name` is an exact name, so an agent learns the names before it waits on one. `appduct_events` drains everything retained since a cursor, and `appduct_wait_for_event` blocks for the next event whose name matches `name`, a whole-name glob (`*` waits for any name) — checking what's already retained before waiting live — rejecting with `tool_timeout` if none arrives in time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the paragraph above still opens with "Four more built-in tools", but it now covers two connection tools plus "the other three" here, five in all. Change "Four" to "Five" on line 226.

@V3RON
V3RON added this pull request to stack #148 October 2, 2026 09:41
@V3RON
V3RON marked this pull request as ready for review October 2, 2026 09:41
Base automatically changed from issue-124-declare-events-over-the-wire-and-list-th to main October 2, 2026 11:16
…-list-events-mcp-tool

# Conflicts:
#	CHANGELOG.md
@V3RON
V3RON merged commit 20b5418 into main Oct 2, 2026
10 checks passed
@V3RON
V3RON deleted the issue-125-add-appduct-list-events-mcp-tool branch October 2, 2026 12:03
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.

Add appduct_list_events MCP tool

1 participant