Skip to content

Add vault item WebMCP invoke command - #279

Open
rgarcia wants to merge 3 commits into
mainfrom
hypeship/vault-webmcp-invoke
Open

rgarcia wants to merge 3 commits into
mainfrom
hypeship/vault-webmcp-invoke

Conversation

@rgarcia

@rgarcia rgarcia commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds CLI support for the vault webmcp_invoke operation, which invokes a live WebMCP tool with values from a credential item or ready Link card substituted into null input slots.

  • Bumps github.com/kernel/kernel-go-sdk from v0.116.0 to v0.117.0, which adds WebmcpInvokeVaultItemOperationRequestParam and the webmcp_invoke result.
  • New flag-based command:
    kernel vaults items webmcp invoke <vault> <key> \
      --browser-id <session-id> --tool-ref <tool-ref> --page-url <source.page_url> \
      --input '{"email":null,"password":null}' \
      --bind email=/email --bind password=/password [--timeout-sec 1..120] [-o json]
    --input-file <path|-> replaces --input. Card expiration uses --bind expiration:MM/YY=/pointer.
  • kernel vaults items invoke <vault> <key> webmcp_invoke --params/--spec-file accepts the same request as JSON (browser_id, tool_ref, page_url, input, bindings[{field,input_path,format?}], timeout_sec). Both forms go through the same Invoke path: the item is fetched first, and the operation must appear in available_operations. Before this change, an advertised webmcp_invoke fell through to the parameterless-operation branch.
  • Local validation runs before any request and never echoes payloads. It mirrors the API's binding rules: 1-32 bindings; unique fields and paths; each input_path is an RFC 6901 pointer to an existing null (no root, no - append, canonical in-range indices, valid ~0/~1 escapes). It also checks: input is a JSON object of at most 64 KiB; tool_ref is non-empty and at most 128 bytes; page_url is absolute with no fragment; timeout_sec is 1-120. Top-level input members are sent as raw JSON so numbers are not re-encoded. The SDK encodes json.Number as a string, which would change them.
  • Output: -o json prints type, status, invocation_id, output (raw JSON preserved) and error_text as returned. Human output labels output/error_text as untrusted page data that may contain supplied values. Both are JSON-encoded so page-supplied terminal control characters are escaped.
  • Outcomes follow existing fill/1pw_fill conventions:
    • completed and awaiting_submission exit 0. awaiting_submission directs the user to submit the populated form instead of re-invoking, matching browsers webmcp invoke.
    • canceled, error and unknown exit nonzero with no extra diagnostic; the JSON result stays on stdout.
    • Requests use WithMaxRetries(0).
    • 400/403/404/409 errors keep the recognized code plus guidance, and state that the tool was not invoked.
    • 5xx errors, transport loss, and malformed results report that the tool may have run, and say not to retry.
  • Help text (vaults, items, items invoke, items webmcp invoke) and README updated. Existing fill semantics are unchanged.

Tests

make test (go vet + go test ./...) passes. New cmd/vaults_webmcp_test.go covers:

  • synthetic Resy login (email/password null slots) through both the flag command and items invoke ... webmcp_invoke --spec-file, asserting the exact request body and the JSON result, including a large integer preserved in output
  • card expiration format, nested/array/escaped pointers, and large-number preservation in input
  • 35 validation cases that make no HTTP request and don't leak payloads
  • each result status in JSON and human modes, including exit codes and escaping of control characters in error_text
  • 400/403/409/500/502 and malformed responses, each sent exactly once (no retry), with correct invoked-vs-uncertain guidance
  • an unadvertised operation and item lookup failure, neither of which POSTs

Not run against the live API.

@socket-security

socket-security Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedgolang/​github.com/​kernel/​kernel-go-sdk@​v0.116.0 ⏵ v0.117.073 +1100100100100

View full report

@rgarcia
rgarcia requested a review from masnwilliams October 2, 2026 16:20

@masnwilliams masnwilliams left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approve with comments.

The no-retry handling, the "not invoked" vs "may have run" errors, and the escaping of untrusted output all match how fill and 1pw_fill already behave, and the tests are thorough. Three things I'd like fixed before merge (inline):

  1. input_path: "/" is rejected locally but valid against the API.
  2. Parse straight into kernel.WebmcpInvokeVaultItemOperationRequestParam instead of a parallel struct.
  3. Reuse the spec-file reader for --input-file.

Optional:

  • vaultWebMCPRequestError is nearly identical to vaultFillRequestError (and to the inline block in onePasswordFill). One vaultOperationRequestError(err, op, messages, rejected, uncertain) would cover all three.
  • vaultFillOutcomeError.Error() returns "fill " + status, so a failed WebMCP invoke reports itself as fill error. It's silent, so nobody sees it today, but a generic name with the operation in the message would be clearer.
  • The status values appear in three switches (validation, human-mode hint, exit code). One map[status]{ok, hint} would combine them. The awaiting-submission text could also be shared with browsers_webmcp.go.

Comment thread cmd/vaults_webmcp.go Outdated
// vaultWebMCPNullSlot mirrors the API: a binding replaces an existing null and never
// creates a property or array entry.
func vaultWebMCPNullSlot(input any, path string) bool {
if len(path) < 2 || len(path) > 2048 || path[0] != '/' {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"/" is the RFC 6901 pointer to the empty-string key, and the API accepts it (it only rejects len(path) < 1). Changing this to len(path) < 1 fixes it. Since this copies the server's rules on purpose, a comment pointing to the API as the source of truth would help keep the two from drifting.

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.

Fixed in ebbfb39. The check is now len(path) < 1, so "/" addresses the empty-string key and only the root pointer "" is rejected. I added a doc comment on validateVaultWebMCPParams and vaultWebMCPNullSlot saying they mirror the API's rules and the API is the source of truth. New test: TestVaultWebMCPInvokeEmptyKeyPointerAndInputFile binds email=/ against {"":null,...} through --input, --input-file, stdin, and --params.

Comment thread cmd/vaults_webmcp.go Outdated
"execution_failed": "WebMCP invocation failed",
}

type vaultWebMCPParams struct {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

vaultWebMCPParams and vaultWebMCPBinding restate kernel.WebmcpInvokeVaultItemOperationRequestParam / kernel.VaultWebmcpBindingParam field for field, and webMCPInvoke spends about 20 lines (229-249) converting one into the other. 1pw_* already stores the full SDK request in vaultOperationParams. If decodeVaultWebMCPInput returns map[string]any with json.RawMessage values (as browsers_webmcp.go does), the conversion goes away and large numbers are still preserved.

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.

Fixed in ebbfb39. I removed vaultWebMCPParams and vaultWebMCPBinding. Both parseVaultWebMCPParams and vaultWebMCPParamsFromFlags now build kernel.WebmcpInvokeVaultItemOperationRequestParam directly, and vaultOperationParams.WebMCP holds that type. decodeVaultWebMCPInput returns map[string]any with json.RawMessage values, the same as browsers_webmcp.go, so the conversion block is gone and large numbers still pass through unchanged. The existing large-integer assertions still pass.

Comment thread cmd/vaults_webmcp.go
params.PageURL, _ = cmd.Flags().GetString("page-url")
input, _ := cmd.Flags().GetString("input")
data := []byte(input)
if cmd.Flags().Changed("input-file") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This repeats readVaultSpecFile: open the path or -, apply the 128 KiB limit, return the same errors. Changing it to readVaultJSONFile(cmd, flag) and calling it from both places removes the copy.

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.

Fixed in ebbfb39. I renamed readVaultSpecFile to readVaultJSONFile(cmd, flag), and --spec-file and --input-file now both use it. The spec-file errors are unchanged. --input-file gets the same open/128 KiB/JSON-object errors with its own flag name, covered by TestVaultWebMCPInvokeInputFileErrors.

@rgarcia

rgarcia commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @masnwilliams. All three required fixes are in ebbfb39 (replies inline). I took the optional ones too:

  • One request-error builder: vaultOperationRequestError(err, op, messages, rejected, uncertain) replaces vaultFillRequestError, the inline block in onePasswordFill, and vaultWebMCPRequestError. rejected maps a pre-invocation HTTP status to its guidance, which keeps 1pw_fill's separate 409 text. 1pw_fill still handles 503 before calling it. Fill and 1pw_fill error messages are unchanged, and their existing tests pass without edits.
  • Outcome error naming: vaultFillOutcomeError is now vaultOperationOutcomeError{operation, status}, so a failed invoke reports webmcp_invoke error (covered by TestVaultOperationOutcomeErrorNamesOperation).
  • One status table: vaultWebMCPStatuses maps each status to whether it exits 0 and its hint. It replaces the three switches (validation, hint, exit code). The awaiting-submission text is now webMCPAwaitingSubmissionHint, shared with browsers_webmcp.go.

make test passes. I'll wait for BugBot on the new commit.

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.

2 participants