Skip to content

Mark page pulls destructive in MCP - #216

Open
keithah wants to merge 2 commits into
antiwork:mainfrom
keithah:feat/compact-codemode-mcp
Open

Mark page pulls destructive in MCP#216
keithah wants to merge 2 commits into
antiwork:mainfrom
keithah:feat/compact-codemode-mcp

Conversation

@keithah

@keithah keithah commented Sep 10, 2026

Copy link
Copy Markdown

What

Marks pages_pull as destructive in the existing MCP tool catalog.

Why

pages pull uses a GET request, but it also creates or replaces a local HTML file. The MCP annotation must describe all side effects, not only the Gumroad API method, so clients can request confirmation before the tool writes to disk.

Before/After

Before: pages_pull was marked read-only even though it writes <slug>.html or the requested output path.

After: pages_pull carries destructiveHint; it is no longer advertised as read-only.

Test Results

At 0efee25f4f1344e650758f4aede878669ce5dcc2:

  • go test ./internal/cmd/mcp -run TestEnumerationAndMetadata -count=1 -v — passed.
  • make test-cover — passed; MCP coverage: 99.3%.
  • PATH=/Users/hermes/work/bin:$PATH make lint — passed.
  • PATH=/Users/hermes/work/bin:$PATH make test-race — passed.

Scope

This replaces the earlier oversized Code Mode proposal with one independently useful safety correction: 10 additions and 3 deletions across two MCP files. The broader Code Mode work is deliberately excluded for later small PRs.


AI disclosure

This PR was implemented with GPT-5.6-terra via OpenAI Codex.

Prompts that directed the implementation:

  • “focus on getting this PR down to 100 lines”
  • “how did steward miss these? write a prompt for me to share”
  • “yeah lets do codemode”

The author inspected and verified the final diff and local checks.

@keithah

keithah commented Sep 10, 2026

Copy link
Copy Markdown
Author

Self-review completed for da641e94d098883d691ceee87b4cef8cf6f33f31.

  • Reviewed command registration, catalog generation, the sandbox boundary, confirmation handling, result truncation, tests, module dependencies, and agent documentation.
  • Confirmed that the JavaScript runtime has no shell, direct-network, process, or credential APIs.
  • Confirmed existing CLI commands may still consume explicit file paths, which is necessary for existing upload/download behavior and is documented as distinct from a general filesystem API.
  • Confirmed the legacy gumroad mcp command remains registered.
  • Local coverage, lint, race, build, diff-check, and MCP initialization/tool-list checks passed as recorded in the PR description.

@keithah

keithah commented Sep 10, 2026

Copy link
Copy Markdown
Author

@claude review once

@greptile-apps

greptile-apps Bot commented Sep 10, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

The runtime change appears correct, but the explicit repository requirement to keep the agent skill synchronized must be satisfied before merging.

Findings

  1. P1 Deadline bypasses CLI operations
  2. P1 File writes bypass confirmation
  3. P1 JavaScript memory is unbounded
  4. P2 Agent guidance remains stale
  5. P2 Help returns uncallable paths
Fix with agent prompt
### Issue 1
internal/cmd/codemode/runtime.go:undefined-156
The five-second execution context only triggers a JavaScript interrupt, while this call receives the original request context. If a Gumroad API request is slow, the native operation can continue until the request or HTTP client's longer timeout expires. Code Mode therefore does not enforce its advertised per-call deadline. Pass the derived execution context through the installed operations and into `Catalog.Execute`.

### Issue 2
internal/cmd/codemode/runtime.go:147-151
The confirmation boundary trusts the legacy MCP read-only annotation, but `pages.pull` is classified as read-only even though it creates and replaces a local file. Calling `gumroad.pages.pull(...)` therefore writes to the server filesystem without `{confirm: true}`, contrary to Code Mode's rule that mutations first produce a no-write plan. Classify operations using both remote and local side effects before using this annotation as an execution gate.

### Issue 3
internal/cmd/codemode/runtime.go:undefined-29
The 64 KiB limits bound source text and serialized output, but not the JavaScript heap or the exported result before serialization. A short call such as `return "x".repeat(1_000_000_000)` can allocate a very large value before the result-size check runs, potentially exhausting memory in the shared stdio server. Agent-supplied code needs an enforceable memory boundary rather than only input, time, and post-execution output checks.

### Issue 4
internal/cmd/mcp/mcp.go:157-163
This change marks `pages_pull` as destructive, but the canonical agent guidance in `skills/gumroad/SKILL.md` still says pull-style MCP tools are read-only. The repository requires command behavior changes to be reflected in the skill documentation, so this requirement must be satisfied before merging to prevent agents from relying on incorrect annotation guidance.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

### Issue 5
internal/cmd/codemode/runtime.go:undefined-41
`gumroad.help()` reports raw command paths such as `offer-codes.list`, while the installed JavaScript namespace is `gumroad.offer_codes.list`. The discovery API therefore returns names that cannot be used as shown, and `help("offer_codes.list")` rejects the actual callable name. Returning the normalized JavaScript path, or both the CLI and JavaScript names, would keep discovery consistent with execution.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

  • Classifies pages_pull as destructive because it writes to the local filesystem.
  • Keeps hypothetical non-pages pull commands read-only.
  • Updates MCP metadata coverage for the new classification.
  • The canonical agent skill still needs to reflect the exception.

Reviews (2) · Last reviewed commit: "Mark page pulls destructive in MCP"

Comment thread internal/cmd/codemode/runtime.go Outdated
_ = json.Unmarshal(raw, &arguments)
return runtime.ToValue(map[string]any{"executed": false, "requires_confirmation": true, "operation": strings.Join(operation.Path, "."), "arguments": arguments})
}
output, err := r.catalog.Execute(ctx, operation, raw)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Deadline bypasses CLI operations

The five-second execution context only triggers a JavaScript interrupt, while this call receives the original request context. If a Gumroad API request is slow, the native operation can continue until the request or HTTP client's longer timeout expires. Code Mode therefore does not enforce its advertised per-call deadline. Pass the derived execution context through the installed operations and into Catalog.Execute.

Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/cmd/codemode/runtime.go
Line: 156

Comment:
**Deadline bypasses CLI operations**

The five-second execution context only triggers a JavaScript interrupt, while this call receives the original request context. If a Gumroad API request is slow, the native operation can continue until the request or HTTP client's longer timeout expires. Code Mode therefore does not enforce its advertised per-call deadline. Pass the derived execution context through the installed operations and into `Catalog.Execute`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread internal/cmd/codemode/runtime.go Outdated
Comment on lines +147 to +151
if operation.ReadOnly {
if len(call.Arguments) == 2 {
panic(runtime.NewTypeError("read-only operations do not accept options"))
}
} else if !confirmed {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 File writes bypass confirmation

The confirmation boundary trusts the legacy MCP read-only annotation, but pages.pull is classified as read-only even though it creates and replaces a local file. Calling gumroad.pages.pull(...) therefore writes to the server filesystem without {confirm: true}, contrary to Code Mode's rule that mutations first produce a no-write plan. Classify operations using both remote and local side effects before using this annotation as an execution gate.

Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/cmd/codemode/runtime.go
Line: 147-151

Comment:
**File writes bypass confirmation**

The confirmation boundary trusts the legacy MCP read-only annotation, but `pages.pull` is classified as read-only even though it creates and replaces a local file. Calling `gumroad.pages.pull(...)` therefore writes to the server filesystem without `{confirm: true}`, contrary to Code Mode's rule that mutations first produce a no-write plan. Classify operations using both remote and local side effects before using this annotation as an execution gate.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread internal/cmd/codemode/runtime.go Outdated
}

func (r Runtime) Execute(ctx context.Context, source string) (any, error) {
runtime := goja.New()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 JavaScript memory is unbounded

The 64 KiB limits bound source text and serialized output, but not the JavaScript heap or the exported result before serialization. A short call such as return "x".repeat(1_000_000_000) can allocate a very large value before the result-size check runs, potentially exhausting memory in the shared stdio server. Agent-supplied code needs an enforceable memory boundary rather than only input, time, and post-execution output checks.

Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/cmd/codemode/runtime.go
Line: 29

Comment:
**JavaScript memory is unbounded**

The 64 KiB limits bound source text and serialized output, but not the JavaScript heap or the exported result before serialization. A short call such as `return "x".repeat(1_000_000_000)` can allocate a very large value before the result-size check runs, potentially exhausting memory in the shared stdio server. Agent-supplied code needs an enforceable memory boundary rather than only input, time, and post-execution output checks.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread internal/cmd/codemode/runtime.go Outdated
if len(call.Arguments) == 0 || goja.IsUndefined(call.Argument(0)) {
operations := make([]string, 0)
for _, operation := range r.catalog.Operations() {
operations = append(operations, strings.Join(operation.Path, "."))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Help returns uncallable paths

gumroad.help() reports raw command paths such as offer-codes.list, while the installed JavaScript namespace is gumroad.offer_codes.list. The discovery API therefore returns names that cannot be used as shown, and help("offer_codes.list") rejects the actual callable name. Returning the normalized JavaScript path, or both the CLI and JavaScript names, would keep discovery consistent with execution.

Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/cmd/codemode/runtime.go
Line: 41

Comment:
**Help returns uncallable paths**

`gumroad.help()` reports raw command paths such as `offer-codes.list`, while the installed JavaScript namespace is `gumroad.offer_codes.list`. The discovery API therefore returns names that cannot be used as shown, and `help("offer_codes.list")` rejects the actual callable name. Returning the normalized JavaScript path, or both the CLI and JavaScript names, would keep discovery consistent with execution.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@keithah
keithah force-pushed the feat/compact-codemode-mcp branch from da641e9 to 0efee25 Compare September 10, 2026 02:02
@keithah keithah changed the title Add a compact Code Mode MCP server Mark page pulls destructive in MCP Sep 10, 2026
@keithah
keithah marked this pull request as ready for review September 10, 2026 05:18
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-10T05:20:32.856530Z 0efee25 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Comment thread internal/cmd/mcp/mcp.go
Comment on lines +157 to +163
case "pull":
if len(path) != 2 || path[0] != "pages" {
annotations.ReadOnlyHint = true
} else {
value := true
annotations.DestructiveHint = &value
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Agent guidance remains stale

This change marks pages_pull as destructive, but the canonical agent guidance in skills/gumroad/SKILL.md still says pull-style MCP tools are read-only. The repository requires command behavior changes to be reflected in the skill documentation, so this requirement must be satisfied before merging to prevent agents from relying on incorrect annotation guidance.

Context Used: CLAUDE.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/cmd/mcp/mcp.go
Line: 157-163

Comment:
**Agent guidance remains stale**

This change marks `pages_pull` as destructive, but the canonical agent guidance in `skills/gumroad/SKILL.md` still says pull-style MCP tools are read-only. The repository requires command behavior changes to be reflected in the skill documentation, so this requirement must be satisfied before merging to prevent agents from relying on incorrect annotation guidance.

**Context Used:** CLAUDE.md ([source](https://github.com/antiwork/gumroad-cli/blob/main/CLAUDE.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in d5dee02. The canonical MCP guidance now distinguishes non-page pull commands from pages_pull, which carries destructiveHint because it writes a local HTML file. Focused MCP and embedded-skill tests passed.

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