Split call_canister into canister_query and canister_update_call - #70
Merged
Conversation
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
There was a problem hiding this comment.
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) andcanister_update_call(updates only), replacing the old dual-modecall_canister. - Folds OQL schema retrieval into
get_canister_candid(whenderivation_originis 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.
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
marked this pull request as ready for review
July 21, 2026 14:12
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
7 tasks
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Splits the dual-mode
call_canisterMCP tool into two focused tools —canister_update_call(writes) andcanister_query(reads) — wherecanister_queryaccepts either a Candidquerymethod or an OQL query. The standalonerun_canister_oql_querytool is removed (its OQL logic now backscanister_query);get_canister_oql_schemaremains its own tool.Changes
canister_update_call— state-changing update calls only. Drops theis_queryflag; no OQL redirect (updates always pass through). Left permissive by design: the IC accepts aquerymethod invoked via an update (a replicated/certified read, likedfx --update), so it is not blocked.canister_query— reads only, taking EITHER a Candidquerymethod(+args) OR anoqlquery (a JSON object string run againstexecute). Exactly one must be provided. Theoqlpath requiresderivation_origin(anonymous per-app reads stay disabled); the Candidmethodpath may be anonymous and is redirected tooqlon canisters that expose OQL. When the.didis readable andmethodis an update method, it fails fast with a pointer tocanister_update_call(the replica rejects a query call to a non-query method); fail-open when the interface can't be read. Output carries amodediscriminator ("candid"/"oql").run_canister_oql_query— its logic is nowcanister_query'soqlbranch (same query validation, empty/trap diagnosis, and paged table decoding).get_canister_oql_schemastays a standalone tool — the fixedicp_oql_guide→get_canister_oql_schema→canister_query(oql) flow is unchanged; its per-entity examples now referencecanister_query.get_canister_candidremains a plain anonymous interface read.calls::is_query_method(treatsqueryandcomposite_queryas callable-as-query) backs the read/write guard.Testing
cargo build --all-targetscargo test --all-targets(125 passed, 0 failed)Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_01Hm7kgJhGH8j5F27gL5jWtb