Skip to content

Split call_canister into canister_query and canister_update_call - #70

Merged
aterga merged 3 commits into
mainfrom
claude/split-call-canister-mcp-i71jq8
Jul 21, 2026
Merged

Split call_canister into canister_query and canister_update_call#70
aterga merged 3 commits into
mainfrom
claude/split-call-canister-mcp-i71jq8

Conversation

@aterga

@aterga aterga commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

Splits the dual-mode call_canister MCP tool into two focused tools — canister_update_call (writes) and canister_query (reads) — where canister_query accepts either a Candid query method or an OQL query. The standalone run_canister_oql_query tool is removed (its OQL logic now backs canister_query); get_canister_oql_schema remains its own tool.

Changes

  • canister_update_call — state-changing update calls only. Drops the is_query flag; no OQL redirect (updates always pass through). Left permissive by design: the IC accepts a query method invoked via an update (a replicated/certified read, like dfx --update), so it is not blocked.
  • canister_query — reads only, taking EITHER a Candid query method (+ args) OR an oql query (a JSON object string run against execute). Exactly one must be provided. The oql path requires derivation_origin (anonymous per-app reads stay disabled); the Candid method path may be anonymous and is redirected to oql on canisters that expose OQL. When the .did is readable and method is an update method, it fails fast with a pointer to canister_update_call (the replica rejects a query call to a non-query method); fail-open when the interface can't be read. Output carries a mode discriminator ("candid" / "oql").
  • Removed run_canister_oql_query — its logic is now canister_query's oql branch (same query validation, empty/trap diagnosis, and paged table decoding).
  • get_canister_oql_schema stays a standalone tool — the fixed icp_oql_guideget_canister_oql_schemacanister_query (oql) flow is unchanged; its per-entity examples now reference canister_query. get_canister_candid remains a plain anonymous interface read.
  • Updated the server instructions, tool descriptions, the OQL-redirect message, landing page, README, and OQL primer to the new names and flow.
  • Tool count stays 26; the annotation and count tests are updated. New helper calls::is_query_method (treats query and composite_query as callable-as-query) backs the read/write guard.

Testing

  • cargo build --all-targets
  • cargo test --all-targets (125 passed, 0 failed)
  • clippy: the new code introduces no new warnings (the pre-existing tree has drift in files not touched here).

Checklist

  • I have read the Contributing guidelines.
  • Docs (README / comments / landing page / primer) updated for the user-visible tool changes.
  • No secrets, credentials, or internal-only information are included.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Hm7kgJhGH8j5F27gL5jWtb

Replace the dual-mode `call_canister` tool with two focused tools:

- `canister_update_call` — state-changing update calls only (drops the
  `is_query` flag; no OQL redirect, since updates always pass through).
- `canister_query` — reads only, taking EITHER a Candid `query` method
  (`method` + `args`) OR an OQL query (`oql`, a JSON object string).
  Exactly one must be given. The OQL path requires `derivation_origin`
  (anonymous per-app reads stay disabled); the Candid `method` path may be
  anonymous and is redirected to `oql` on canisters that expose OQL.

Remove the standalone `run_canister_oql_query` tool — its logic now backs
`canister_query`'s `oql` argument.

Fold the former `get_canister_oql_schema` tool into `get_canister_candid`:
it now accepts an optional `derivation_origin`/`account` and, for an OQL
canister read with an origin, returns the `oql_schema` (entities/fields/
edges plus ready-to-run examples). The schema read degrades to a note on
failure so the interface is always returned.

Update the server instructions, tool descriptions, landing page, README,
OQL primer, and Candid syntax doc to the new tool names and flow
(guide -> get_canister_candid with origin -> canister_query oql). Tool
count drops 26 -> 25; tests updated accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hm7kgJhGH8j5F27gL5jWtb

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the IC connector’s canister-call surface to cleanly separate reads from writes by splitting the former dual-mode call_canister tool into canister_query (read-only) and canister_update_call (state-changing), while folding OQL schema discovery into get_canister_candid and removing the standalone OQL query tool.

Changes:

  • Introduces canister_query (Candid query method or OQL query) and canister_update_call (updates only), replacing the old dual-mode call_canister.
  • Folds OQL schema retrieval into get_canister_candid (when derivation_origin is provided) and removes the separate OQL schema/query tools.
  • Updates docs/UI copy (README, landing page, primers) and adjusts tool-count/annotation tests accordingly.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
static/oql-primer.md Updates OQL workflow to use get_canister_candid (with origin) + canister_query (oql).
static/candid-textual-syntax.md Updates references to new tool names for typed Candid encoding/decoding.
src/main.rs Wires new tools, folds schema read into get_canister_candid, and implements canister_query/canister_update_call.
src/discover.rs Updates open_app output docs to reference the new call tools.
src/calls.rs Renames/reshapes argument/output types and updates OQL redirect/examples to the new flow.
src/assets/index.html Updates landing-page tool list to the new tool names and usage.
README.md Updates tool table, usage narrative, and curl examples to the new read/write split and OQL flow.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/main.rs
Comment thread src/main.rs
claude added 2 commits July 21, 2026 10:40
Undo folding get_canister_oql_schema into get_canister_candid: restore it
as its own tool (taking `derivation_origin`/`account`, requiring an origin
since the schema is caller-gated) and return get_canister_candid to a plain
anonymous interface read. Its per-entity examples now reference
`canister_query` (its `oql` argument), since run_canister_oql_query is gone.

The canister_query / canister_update_call split and the removal of
run_canister_oql_query are unchanged. Docs, server instructions, landing
page, and the OQL primer point back at get_canister_oql_schema; tool count
returns to 26.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hm7kgJhGH8j5F27gL5jWtb
Add a fail-fast check to canister_query's Candid `method` path: when the
`.did` is readable and `method` is declared as an update (not a query),
return a clear error pointing to canister_update_call, instead of the
replica's opaque "not a query method" runtime rejection. Fail open when the
interface can't be read or the method isn't declared — the IC then decides.

The reverse is deliberately NOT blocked: the IC lets a query method be
invoked via an update call (a replicated/certified read), so
canister_update_call stays permissive. New helper calls::is_query_method
treats both `query` and `composite_query` as callable-as-query.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hm7kgJhGH8j5F27gL5jWtb
@aterga
aterga marked this pull request as ready for review July 21, 2026 14:12
Copilot AI review requested due to automatic review settings July 21, 2026 14:12
@aterga
aterga merged commit c6af692 into main Jul 21, 2026
2 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

aterga pushed a commit that referenced this pull request Jul 21, 2026
A client that reads per-tool descriptions but not the server instructions
block (e.g. surfacing tools via lazy search) could see the low-level
primitives, find no literal "balance"/"get_X" tool, and conclude the
capability is missing, giving up instead of composing it.

Move the routing know-how into the descriptions themselves:

- open_app: lead with a generic decision chain (is the request about a
  specific app or the user's data in it? is it an ICP app? then open_app
  to resolve + discover the service), and state that an app feature is
  composed from what open_app returns (canister_query incl. OQL for
  reads, canister_update_call for acts), not a single named tool.
- canister_query: a one-line nudge to start from open_app when the
  request is app- or user-data-scoped, rather than reaching for a raw
  read.

Rebased onto main after #70 split call_canister into canister_query /
canister_update_call; references updated to the new tool names.
Descriptions only; no behavior change. Not app-specific.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014T9N8USDfNK5yzznPGg7Ym
aterga pushed a commit that referenced this pull request Jul 21, 2026
A client that reads per-tool descriptions but not the server instructions
block (e.g. surfacing tools via lazy search) could see the low-level
primitives, find no literal "balance"/"get_X" tool, and conclude the
capability is missing, giving up instead of composing it.

Move the routing know-how into the descriptions themselves:

- open_app: lead with a generic decision chain (is the request about a
  specific app or the user's data in it? is it an ICP app? then open_app
  to resolve + discover the service), and state that an app feature is
  composed from what open_app returns (canister_query incl. OQL for
  reads, canister_update_call for acts), not a single named tool.
- canister_query: a one-line nudge to start from open_app when the
  request is app- or user-data-scoped, rather than reaching for a raw
  read.

Rebased onto main after #70 split call_canister into canister_query /
canister_update_call; references updated to the new tool names.
Descriptions only; no behavior change. Not app-specific.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014T9N8USDfNK5yzznPGg7Ym
aterga pushed a commit that referenced this pull request Jul 21, 2026
A client that reads per-tool descriptions but not the server instructions
block (e.g. surfacing tools via lazy search) could see the low-level
primitives, find no literal "balance"/"get_X" tool, and conclude the
capability is missing, giving up instead of composing it.

Move the routing know-how into the descriptions themselves:

- open_app: lead with a generic decision chain (is the request about a
  specific app or the user's data in it? is it an ICP app? then open_app
  to resolve + discover the service), and state that an app feature is
  composed from what open_app returns (canister_query incl. OQL for
  reads, canister_update_call for acts), not a single named tool.
- canister_query: a one-line nudge to start from open_app when the
  request is app- or user-data-scoped, rather than reaching for a raw
  read.

Rebased onto main after #70 split call_canister into canister_query /
canister_update_call; references updated to the new tool names.
Descriptions only; no behavior change. Not app-specific.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014T9N8USDfNK5yzznPGg7Ym
aterga added a commit that referenced this pull request Jul 21, 2026
A client that reads per-tool descriptions but not the server instructions
block (e.g. surfacing tools via lazy search) could see the low-level
primitives, find no literal "balance"/"get_X" tool, and conclude the
capability is missing, giving up instead of composing it.

Move the routing know-how into the descriptions themselves:

- open_app: lead with a generic decision chain (is the request about a
  specific app or the user's data in it? is it an ICP app? then open_app
  to resolve + discover the service), and state that an app feature is
  composed from what open_app returns (canister_query incl. OQL for
  reads, canister_update_call for acts), not a single named tool.
- canister_query: a one-line nudge to start from open_app when the
  request is app- or user-data-scoped, rather than reaching for a raw
  read.

Rebased onto main after #70 split call_canister into canister_query /
canister_update_call; references updated to the new tool names.
Descriptions only; no behavior change. Not app-specific.


Claude-Session: https://claude.ai/code/session_014T9N8USDfNK5yzznPGg7Ym

Co-authored-by: Claude <noreply@anthropic.com>
aterga pushed a commit that referenced this pull request Jul 23, 2026
Follow-up reconciliation after #26 (library crate), #70, #73, #74 landed on
main. Verified against the current source; three concrete fixes:

- Deploy: the connect/landing HTML/CSS/SVG live in `src/assets/` (compiled in
  via include_str! from src/main.rs and src/auth.rs), not `static/`; `static/`
  holds only the reference docs. The old text conflated the two and contradicted
  the "Browser-facing error screens" section. Reframed as the deployment binary
  being self-contained, with both dirs as build-time inputs.
- Intro: ic-agent pin is 0.49 (Cargo.toml), not 0.48.
- Tools table: `canister_query` and `canister_update_call` take an optional
  `candid` arg (the .did fallback when a canister publishes no candid:service
  metadata); added it to both rows.

A wider audit found the rest of the doc already matches main (tool set,
endpoints, OAuth path-pinned allow-list, env vars, II methods, library API).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014T9N8USDfNK5yzznPGg7Ym
aterga pushed a commit that referenced this pull request Jul 28, 2026
Follow-up reconciliation after #26 (library crate), #70, #73, #74 landed on
main. Verified against the current source; three concrete fixes:

- Deploy: the connect/landing HTML/CSS/SVG live in `src/assets/` (compiled in
  via include_str! from src/main.rs and src/auth.rs), not `static/`; `static/`
  holds only the reference docs. The old text conflated the two and contradicted
  the "Browser-facing error screens" section. Reframed as the deployment binary
  being self-contained, with both dirs as build-time inputs.
- Intro: ic-agent pin is 0.49 (Cargo.toml), not 0.48.
- Tools table: `canister_query` and `canister_update_call` take an optional
  `candid` arg (the .did fallback when a canister publishes no candid:service
  metadata); added it to both rows.

A wider audit found the rest of the doc already matches main (tool set,
endpoints, OAuth path-pinned allow-list, env vars, II methods, library API).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014T9N8USDfNK5yzznPGg7Ym
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.

3 participants