From b619a849c5591293f8cde28b5d62fc5e8b23f553 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 09:23:38 +0000 Subject: [PATCH 1/4] Remove financial-operation references from the Terms of Service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Terms still described the Service in financial vocabulary: "never holds your funds" / "non-custodial" (section 1), "moving tokens, spending cycles, installing code, and deleting canisters" (section 3), and "loss of tokens, cycles, or other assets" (section 8). That language no longer matches the served surface — the funding and canister-management tools are not served (#153, #154, #158) and canister_update_call refuses the ledger methods — and, like the tool descriptions (per review in #154), the page should stay free of financial language rather than mention-and-deny. Rephrase those passages without financial vocabulary while keeping their substance and the user-responsibility posture: - Section 1 describes the Service as reading data applications hold for the user and submitting actions to them, and states it never holds long-term credentials — only the delegated, time-limited session authorization. (This also stops claiming canister management, which the served tool set no longer offers.) - Section 2 now states explicitly that requests made under the user's authorization are treated as theirs and are their sole responsibility. - Section 3 keeps irreversibility front and center: an action can permanently change or delete data an application holds for the user, what an action does is defined by the receiving application, and nothing can be undone once the network accepts it. - Section 8's liability examples drop the asset enumeration; the general damages language still covers everything it covered. Bump the effective date for the revision (adjust to the release date at cut time, as was done for August 3). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Xpg83AWtkQo34jbMxpX8WB --- src/assets/terms.html | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/assets/terms.html b/src/assets/terms.html index e94182c..5513426 100644 --- a/src/assets/terms.html +++ b/src/assets/terms.html @@ -115,7 +115,7 @@

ICP MCP

ICP MCP Terms of Service

-

Effective date: August 3, 2026

+

Effective date: August 28, 2026

These Terms of Service ("Terms") govern your use of the ICP MCP server (the "Service"), operated by DFINITY Stiftung, Genferstrasse 11, 8002 @@ -132,19 +132,20 @@

1. The Service

The Service is a Model Context Protocol server that lets an AI assistant read public information from the Internet Computer and, once you authorize a connection through Internet Identity, act on your behalf: read data -applications hold for you, submit actions, and manage canisters you control. +applications hold for you and submit actions to them. The Service is provided free of charge.

-

The Service is non-custodial: it never holds your funds -or your long-term credentials. What it holds during a session is a delegated, -time-limited authorization, as described in the +

The Service never holds your long-term credentials. What +it holds during a session is a delegated, time-limited authorization, as +described in the Privacy Policy.

2. Your Responsibilities

The Service acts on instructions your AI assistant sends under your authorization, and it cannot tell a wise instruction from an unwise one. -Requests made under your authorization are treated as yours. +Requests made under your authorization are treated as yours and are +your sole responsibility. You are responsible for: what you ask your assistant to do; the access level ("Questions only" or "Actions & questions") and duration you grant on the Internet Identity consent screen; the security of your own devices and @@ -160,12 +161,13 @@

2. Your Responsibilities

3. Actions Can Be Irreversible

Actions execute on the Internet Computer, a public network that neither we -nor anyone else can roll back. Depending on what you authorize, that can -include moving tokens, spending cycles, installing code, and deleting -canisters together with their data. We cannot undo an action after -the network accepts it. Prefer "Questions only" sessions when you do -not need actions, keep authorization durations short, and review what your -assistant intends before approving state-changing calls.

+nor anyone else can roll back. Depending on what you authorize, an action can +change or permanently delete data an application holds for you, and what an +action does is defined by the application that receives it, not by us. +We cannot undo an action after the network accepts it. +Prefer "Questions only" sessions when you do not need actions, keep +authorization durations short, and review what your assistant intends before +approving state-changing calls.

4. Third Parties

@@ -203,13 +205,13 @@

8. Limitation of Liability

To the maximum extent permitted by law, DFINITY Foundation is not liable for damages arising from your use of the Service, including indirect or -consequential damages, loss of tokens, cycles, or other assets, and loss of -data, whether resulting from actions taken under your authorization, from -third-party applications, from the behaviour of the Internet Computer or -Internet Identity, or from the Service being unavailable, changed, or -discontinued. Nothing in these Terms excludes or limits liability that -cannot be excluded under applicable law, including liability under Swiss law -for damage caused by unlawful intent or gross negligence.

+consequential damages and loss of data, whether resulting from actions taken +under your authorization, from third-party applications, from the behaviour +of the Internet Computer or Internet Identity, or from the Service being +unavailable, changed, or discontinued. Nothing in these Terms excludes or +limits liability that cannot be excluded under applicable law, including +liability under Swiss law for damage caused by unlawful intent or gross +negligence.

9. Changes to These Terms

From 18a9755c24c7d801fd3f8efc5544d7486707b6e9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 10:29:44 +0000 Subject: [PATCH 2/4] State the discoverability-based obligation of app developers in the Terms and Privacy Policy Application developers who want their applications' operations supported through the Service must comply with the ICP MCP Privacy Policy and accept the Terms of Service. They assert both by implementing the service discoverability instructions at https://docs.internetcomputer.org/guides/frontends/service-discoverability/ - Terms, section 4 (Third Parties): new paragraph stating the obligation, linking the Privacy Policy and the discoverability guide. - Privacy Policy, section 2 (Who Receives Data), "The applications you choose" recipient: matching sentence referring the developer to the Terms and the discoverability guide; effective date moves to August 28, 2026 per section 8 (align with the shipping release at cut time, as for August 3). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Xpg83AWtkQo34jbMxpX8WB --- src/assets/privacy-policy.html | 10 ++++++++-- src/assets/terms.html | 8 ++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/assets/privacy-policy.html b/src/assets/privacy-policy.html index 91ae59f..7ab3583 100644 --- a/src/assets/privacy-policy.html +++ b/src/assets/privacy-policy.html @@ -139,7 +139,7 @@

ICP MCP

ICP MCP Privacy Policy

-

Effective date: August 3, 2026

+

Effective date: August 28, 2026

We, DFINITY Stiftung, Genferstrasse 11, 8002 Zürich, Switzerland ("DFINITY Foundation") disclose in this ICP MCP Privacy Policy ("Privacy @@ -322,7 +322,13 @@

2. Who Receives Data

identity to that application and its operator, who may be anyone. What an application records, retains, or publishes is governed by that application, not by this Privacy Policy, and may be publicly accessible. Actions that -change state become part of that application's state on a public network.

+change state become part of that application's state on a public network. +Developers who want their applications' operations supported through the +Service must comply with this Privacy Policy and accept the +ICP MCP Terms of Service, which they assert by +implementing the +service +discoverability instructions.

Websites you ask the Service to look up. When you ask it to find the application behind a web address, the Service fetches metadata diff --git a/src/assets/terms.html b/src/assets/terms.html index 5513426..adcc61d 100644 --- a/src/assets/terms.html +++ b/src/assets/terms.html @@ -178,6 +178,14 @@

4. Third Parties

relationship with any of them, and data returned by the Service originates from them, not from us.

+

Application developers who want their applications' operations supported +through the Service must comply with the +ICP MCP Privacy Policy and accept +these Terms. They assert both by implementing the +service +discoverability instructions in the Internet Computer developer +documentation.

+

5. Availability and Changes to the Service

We aim to keep the Service available but do not promise it: we may change, From ed448dd6c7c185e447526378e1060afea26d462e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 10:48:02 +0000 Subject: [PATCH 3/4] Fix the developer-obligation wording: acceptance and duty, not compliance with a notice Review on the mirrored copy (dfinity/internetcomputer-org#76) caught a real contradiction in the wording added by the previous commit. The Privacy Policy states twice that application processing is governed by each application's own policy and not by this Privacy Policy (the scope carve-out, and the same "applications you choose" paragraph the sentence was added to), so requiring developers to "comply with" it both contradicted that scope and named no conduct: the Policy is a notice about our processing and carries no developer-facing duties. Reframe so the obligation is coherent and actionable, keeping the acceptance-by-implementation mechanism intact: - The developer accepts the Terms by implementing the discoverability instructions. Acceptance of a contract is something a third party can do; "compliance" with our privacy notice is not. - The duty now lives in the Terms, where duties belong: handle personal data reaching them through the Service lawfully and under their own privacy policy, and keep that policy accurate and available. - Both pages now say what the Privacy Policy actually is for the developer: it describes what the Service discloses to an application, and does not govern what the application does next. That agrees with the scope carve-out instead of fighting it. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Xpg83AWtkQo34jbMxpX8WB --- src/assets/privacy-policy.html | 7 ++++--- src/assets/terms.html | 12 ++++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/assets/privacy-policy.html b/src/assets/privacy-policy.html index 7ab3583..5bec6b4 100644 --- a/src/assets/privacy-policy.html +++ b/src/assets/privacy-policy.html @@ -324,11 +324,12 @@

2. Who Receives Data

not by this Privacy Policy, and may be publicly accessible. Actions that change state become part of that application's state on a public network. Developers who want their applications' operations supported through the -Service must comply with this Privacy Policy and accept the -ICP MCP Terms of Service, which they assert by +Service accept the ICP MCP Terms of Service by implementing the service -discoverability instructions.

+discoverability instructions, and under those Terms are responsible for +handling the data described in this paragraph lawfully and under their own +privacy policy.

Websites you ask the Service to look up. When you ask it to find the application behind a web address, the Service fetches metadata diff --git a/src/assets/terms.html b/src/assets/terms.html index adcc61d..6e452cf 100644 --- a/src/assets/terms.html +++ b/src/assets/terms.html @@ -179,12 +179,16 @@

4. Third Parties

from them, not from us.

Application developers who want their applications' operations supported -through the Service must comply with the -ICP MCP Privacy Policy and accept -these Terms. They assert both by implementing the +through the Service accept these Terms, and assert that acceptance by +implementing the service discoverability instructions in the Internet Computer developer -documentation.

+documentation. Under these Terms they are responsible for handling any +personal data that reaches them through the Service lawfully and under their +own privacy policy, and for keeping that policy accurate and available to +their users. The ICP MCP Privacy Policy +describes what the Service discloses to an application; it does not govern +what the application then does with it.

5. Availability and Changes to the Service

From dae5a2c7969aad1c19d54f37e01a81d07bcbe07e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 13:39:35 +0000 Subject: [PATCH 4/4] Drop the landing site from the server; redirect to its one home on internetcomputer.org MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The landing page and its /privacy-policy, /support and /terms subpages existed twice: served by this binary from src/assets/, and mirrored in dfinity/internetcomputer-org (public/icp-mcp/, served at internetcomputer.org/icp-mcp/). Two copies of legal pages drift — the mirror carried this branch's revisions before any imcp2 release could — so the internetcomputer-org copy becomes the only one, and this origin stops serving pages of its own: - The four asset pages are deleted. connect-error.html and favicon.svg stay: they are the OAuth connect flow's, not the landing site's, and the connect/callback screens link /favicon.svg for their tab icon. - /, /privacy-policy, /support and /terms answer with permanent redirects (308) to the same paths under https://internetcomputer.org/icp-mcp/ — trailing-slash targets, so a client lands in one hop. Every published link keeps working: the directory listings' policy URLs, bookmarks, search results. - /sitemap.xml is gone (a sitemap must list same-origin content, and this origin has none); robots.txt stays to keep crawlers off the machine surface, minus the sitemap reference, and site_metadata_router no longer needs PUBLIC_URL. /status/ is untouched — the live dashboard is this deployment's own monitoring surface, published by Caddy. - Tests follow: the sitemap and page-content tests are replaced by a redirect test pinning all four absolute targets and a robots test asserting no sitemap is advertised. - README and the directory-submission docs now name internetcomputer.org/icp-mcp/ as the pages' one home, with the old URLs redirecting from the release that ships this change. The earlier commits on this branch revised the pages' text in place; that text now lives in dfinity/internetcomputer-org#76 / #77, which is what makes this deletion safe: nothing is lost, one copy remains. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Xpg83AWtkQo34jbMxpX8WB --- README.md | 21 +- docs/anthropic-directory-submission.md | 33 +- docs/icp-mcp-privacy-policy-draft.md | 6 +- docs/openai-directory-submission.md | 8 +- src/assets/index.html | 411 ------------------- src/assets/privacy-policy.html | 521 ------------------------- src/assets/support.html | 170 -------- src/assets/terms.html | 249 ------------ src/main.rs | 323 +++++---------- 9 files changed, 138 insertions(+), 1604 deletions(-) delete mode 100644 src/assets/index.html delete mode 100644 src/assets/privacy-policy.html delete mode 100644 src/assets/support.html delete mode 100644 src/assets/terms.html diff --git a/README.md b/README.md index 612f8fd..53bebe7 100644 --- a/README.md +++ b/README.md @@ -396,27 +396,30 @@ flow. ```bash cargo run -# serves http://0.0.0.0:8000 (MCP at /mcp against production II, OAuth under it, info page at /) +# serves http://0.0.0.0:8000 (MCP at /mcp against production II, OAuth under it; / redirects to the landing site) # honours $PORT (default 8000), $PUBLIC_URL (default http://localhost:8000), # $MCP_SERVE_BETA (set it to also serve the beta II instance at /mcp-beta, for staging), # and $MCP_SERVE_METRICS (set it to serve the Prometheus exposition at /metrics) ``` -`GET /` serves a self-contained, ICP-styled landing page that names the -production `/mcp` endpoint (staging also serves beta II at `/mcp-beta`) and lists -the tools grouped by purpose. `GET /version` is the operations probe (see [Auth](#auth-oauth-21-login-via-internet-identity)). +The human-facing pages — the landing page and the `/privacy-policy`, +`/support`, and `/terms` documents the connector directories require — are +maintained in [dfinity/internetcomputer-org] (`public/icp-mcp/`) and served at +, so the content exists exactly once. +This origin answers their old paths (`/`, `/privacy-policy`, `/support`, +`/terms`) with permanent redirects there, keeping every published link +working. `GET /version` is the operations probe (see [Auth](#auth-oauth-21-login-via-internet-identity)). -The binary also serves the official pages the connector directories require: -`/privacy-policy` (linked from the landing page's footer), `/support`, and -`/terms` — self-contained documents compiled in like every other asset. For the -OpenAI directory's domain-verification check, `GET +[dfinity/internetcomputer-org]: https://github.com/dfinity/internetcomputer-org + +For the OpenAI directory's domain-verification check, `GET /.well-known/openai-apps-challenge` returns `$OPENAI_APPS_CHALLENGE_TOKEN` verbatim as `text/plain` (trimmed), and 404s while the variable is unset or blank — so the endpoint is inert except during a submission window. ## Deploy -The deployment binary is **self-contained**: the connect/landing HTML, CSS, and SVG +The deployment binary is **self-contained**: the connect HTML, CSS, and SVG (`src/assets/` and `crates/imcp2-core/src/assets/`) and the reference docs (`crates/imcp2-core/static/`) are compiled in with `include_str!`, so nothing has to ship next to it (both are build-time inputs only). The diff --git a/docs/anthropic-directory-submission.md b/docs/anthropic-directory-submission.md index 6411f34..9f58f9e 100644 --- a/docs/anthropic-directory-submission.md +++ b/docs/anthropic-directory-submission.md @@ -59,7 +59,7 @@ under submission; the live pre-release build still serves the old surface | OAuth endpoint latency ≤ 10 s (discovery/registration/token) | ✅ all sub-second in probes | | Support channel | ✅ (shown on every error screen) | | Security-vulnerability reporting mechanism (a Software Directory Terms obligation) | ✅ [`SECURITY.md`](../SECURITY.md) → Hackenproof bug bounty | -| Public documentation by publish date | ✅ this repo's README + the landing page at | +| Public documentation by publish date | ✅ this repo's README + the landing page at (its one home, maintained in dfinity/internetcomputer-org; `https://mcp.internetcomputer.org` permanently redirects there from the release that ships #165) | | Status/health visibility | ✅ | Notes on auth mode: pure M2M `client_credentials` is unsupported by Claude @@ -128,14 +128,16 @@ server actually does, it should cover at least: explicitly, or disclose them if added). - **Controller and contact:** DFINITY Stiftung; . -Publication venue: `https://mcp.internetcomputer.org/privacy-policy`, served -by the MCP server itself. The page and its route shipped in -[#112](https://github.com/dfinity/imcp2/pull/112) (effective date August 3, -2026), the landing page's footer links it, and the page is live on -production (verified 2026-08-27: HTTP 200). What remains: enter that URL in -the portal, and cut the next `release-*` first — the live text is one -revision behind `main` (the current draft's identifier-linkability wording -lands with that release). The reviewed source +Publication venue: `https://internetcomputer.org/icp-mcp/privacy-policy/` — +the page's one home, maintained in dfinity/internetcomputer-org +(`public/icp-mcp/privacy-policy/`) and live there +(dfinity/internetcomputer-org#77 refreshes its text to the current draft). +The MCP server no longer serves a copy: from the release that ships +[#165](https://github.com/dfinity/imcp2/pull/165), +`https://mcp.internetcomputer.org/privacy-policy` answers with a permanent +redirect to that home (until that release it still serves the previous +revision itself). Either URL works in the portal; prefer the canonical one. +The reviewed source text is [`icp-mcp-privacy-policy-draft.md`](icp-mcp-privacy-policy-draft.md). ### 2. Financial-transactions policy (resolved in code) @@ -274,10 +276,13 @@ Paste-and-adapt; portal limits in parentheses. > lookalike domains are refused rather than resolved. - **Categories** (1–5): Developer tools; plus whatever the portal offers closest to data/productivity/web3. -- **Documentation URL:** `https://mcp.internetcomputer.org` (landing page; - README as backup: `https://github.com/dfinity/imcp2#readme`) -- **Privacy policy URL:** `https://mcp.internetcomputer.org/privacy-policy` - — enter it only once the page is live (blocker 1); a missing or incomplete +- **Documentation URL:** `https://internetcomputer.org/icp-mcp/` (the landing + page's home; `https://mcp.internetcomputer.org` permanently redirects there + from the release that ships #165. README as backup: + `https://github.com/dfinity/imcp2#readme`) +- **Privacy policy URL:** `https://internetcomputer.org/icp-mcp/privacy-policy/` + (live; the old `https://mcp.internetcomputer.org/privacy-policy` permanently + redirects there from the release that ships #165). A missing or incomplete policy is documented as immediate rejection. Do not substitute the foundation-wide `dfinity.org/privacy`. - **Support contact:** `mcp@dfinity.org` @@ -360,7 +365,7 @@ conversation beyond tool arguments and generates no media. ## Submission-day checklist -- [ ] Privacy policy entered in the portal — the page is already live at `https://mcp.internetcomputer.org/privacy-policy` (verified 2026-08-27); the next `release-*` refreshes its text to the current draft (blocker 1) +- [ ] Privacy policy entered in the portal — enter `https://internetcomputer.org/icp-mcp/privacy-policy/`, the page's one home (live; dfinity/internetcomputer-org#77 refreshes its text to the current draft, and the old mcp.internetcomputer.org URL redirects there from the release that ships #165) (blocker 1) - [x] Financial-transactions question resolved in code (blocker 2) — no mcp-review reply is needed; if one arrives, answer with the shipped posture. The first-party-API/data-handling question was NOT in the 2026-07-31 email: raise it with mcp-review only if the portal's data-handling options don't fit - [x] Reviewer access settled: self-serve Internet Identity, instructions in the test-credentials field (blocker 3) — if a reviewer asks for a populated account, provision a demo-app account (no funding needed: there are no funding or canister-management tools) - [ ] `release-*` tag cut; `/version` on production shows the intended commit (blocker 4) diff --git a/docs/icp-mcp-privacy-policy-draft.md b/docs/icp-mcp-privacy-policy-draft.md index deb6184..feed54c 100644 --- a/docs/icp-mcp-privacy-policy-draft.md +++ b/docs/icp-mcp-privacy-policy-draft.md @@ -1,8 +1,10 @@ # ICP MCP Privacy Policy (source text) > This is the source text for the page served at -> `https://mcp.internetcomputer.org/privacy-policy` -> (`src/assets/privacy-policy.html`). Keep the two in sync: the served page is +> `https://internetcomputer.org/icp-mcp/privacy-policy/` +> (dfinity/internetcomputer-org, `public/icp-mcp/privacy-policy/index.html`; +> the old `https://mcp.internetcomputer.org/privacy-policy` permanently +> redirects there). Keep the two in sync: the served page is > what users and directory reviews actually see. Technical claims are drawn > from this repository's behaviour; re-verify them against the deployed > release whenever the policy is republished (and update section 3 if the diff --git a/docs/openai-directory-submission.md b/docs/openai-directory-submission.md index b7b8a0a..ae8b552 100644 --- a/docs/openai-directory-submission.md +++ b/docs/openai-directory-submission.md @@ -57,9 +57,9 @@ add details not published in the docs. | Tools explicitly annotated `readOnlyHint` / `destructiveHint` / `openWorldHint` — "incorrect or missing action labels are a common cause of rejection" | ✅ set on all 11 tools. The unit test enforces annotation presence and the `readOnlyHint`/`destructiveHint` values; `openWorldHint` is declared everywhere but not asserted by the test, so re-check it in the portal's Scan Tools step | | Tool names "human-readable, specific, and descriptive"; accurate descriptions; minimum-information requests | ✅ reviewed against the same bar for the Anthropic listing | | Public HTTPS production endpoint, stable and complete ("trial or demo plugins will not be accepted") | ✅ production deployment | -| Privacy policy disclosing "categories of personal data collected, purposes of use, categories of recipients, data retention timelines" | ✅ the rewritten policy matches these four required disclosures exactly and `https://mcp.internetcomputer.org/privacy-policy` is live (verified 2026-08-27); the next `release-*` refreshes its text to the current draft | -| Customer support contact (OpenAI asks for a URL) | ✅ `https://mcp.internetcomputer.org/support` — merged and live on production; routes users to , the status dashboard, id.ai access management, GitHub issues, and the security policy | -| Terms of Service URL | ✅ `https://mcp.internetcomputer.org/terms` — merged and live on production; Swiss-law terms covering the non-custodial model, user responsibility for authorized actions, irreversibility of network actions, as-is/liability limits with the Art. 100 CO carve-out. Needs the same legal pass as the privacy policy | +| Privacy policy disclosing "categories of personal data collected, purposes of use, categories of recipients, data retention timelines" | ✅ the rewritten policy matches these four required disclosures exactly; its one home is `https://internetcomputer.org/icp-mcp/privacy-policy/` (dfinity/internetcomputer-org#77 refreshes its text to the current draft, and the old mcp.internetcomputer.org URL permanently redirects there from the release that ships #165) | +| Customer support contact (OpenAI asks for a URL) | ✅ `https://internetcomputer.org/icp-mcp/support/` — the page's one home (the old mcp.internetcomputer.org URL permanently redirects there from the release that ships #165); routes users to , the status dashboard, id.ai access management, GitHub issues, and the security policy | +| Terms of Service URL | ✅ `https://internetcomputer.org/icp-mcp/terms/` — the page's one home (the old mcp.internetcomputer.org URL permanently redirects there from the release that ships #165); Swiss-law terms covering the credentials-never-held session model, the user's sole responsibility for authorized actions, irreversibility of network actions, app-developer acceptance via service discoverability, and as-is/liability limits with the Art. 100 CO carve-out. Needs the same legal pass as the privacy policy | | Logo | ✅ [`docs/assets/icp-logo-1024.png`](assets/icp-logo-1024.png) | Note the legacy redirect `chatgpt.com/connector_platform_oauth_redirect` is @@ -178,7 +178,7 @@ Negative: - [ ] Submitter holds the Apps Management write permission - [ ] Production runs a release cut from current `main`: `curl https://mcp.internetcomputer.org/version` reports a commit that contains #153–#158 — verify immediately before submitting, since the deploy workflow also accepts older tags/SHAs (rollbacks), so a deployed challenge token alone does not prove the compliant build is live - [ ] Repository secret `OPENAI_APPS_CHALLENGE_TOKEN` set to the portal's token and deployed; `curl https://mcp.internetcomputer.org/.well-known/openai-apps-challenge` returns exactly the token (blocker 1 — the route is merged and deployed; it 404s until the variable is set, by design) -- [x] Privacy policy live at `https://mcp.internetcomputer.org/privacy-policy` (verified 2026-08-27; the next release refreshes its text to the current draft) +- [x] Privacy policy live at `https://internetcomputer.org/icp-mcp/privacy-policy/`, its one home (dfinity/internetcomputer-org#77 refreshes its text to the current draft; the old mcp.internetcomputer.org URL redirects there from the release that ships #165) - [ ] Tools re-scanned in the portal after any server change; annotations verified in the scan - [ ] 5+ positive and 3+ negative test cases entered, verified on web and mobile - [ ] Starter prompts entered; country availability chosen diff --git a/src/assets/index.html b/src/assets/index.html deleted file mode 100644 index e14e7fa..0000000 --- a/src/assets/index.html +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - Bundled Page - - - - -
- - - - - - - - - - - - - - - - - - - - - -
-
Unpacking...
- - - - - - - - - - - - \ No newline at end of file diff --git a/src/assets/privacy-policy.html b/src/assets/privacy-policy.html deleted file mode 100644 index 5bec6b4..0000000 --- a/src/assets/privacy-policy.html +++ /dev/null @@ -1,521 +0,0 @@ - - - -ICP MCP Privacy Policy - - -
-

ICP MCP

-

ICP MCP Privacy Policy

-

Effective date: August 28, 2026

- -

We, DFINITY Stiftung, Genferstrasse 11, 8002 Zürich, Switzerland -("DFINITY Foundation") disclose in this ICP MCP Privacy Policy ("Privacy -Policy") how we process personal data in connection with the ICP MCP server -(the "Service"), which lets an AI assistant interact with the Internet -Computer and its ecosystem of applications on your behalf.

- -

This Privacy Policy covers the Service at -mcp.internetcomputer.org, together with any pre-release -deployments of the same software that we operate.

- -

It covers both authenticated use, where you have signed in with Internet -Identity, and unauthenticated use: browsing the Service's public pages or -using the tools that read public network information generates requests and -technical logs even when you never sign in.

- -

This Privacy Policy does not apply to other data processing, including: -processing by your AI assistant's provider (for example, Anthropic for -Claude), under its own terms and privacy policy; processing by Internet -Identity, which is covered by the -Internet -Identity Privacy Policy; and processing by the applications you choose to -interact with through the Service, which are operated by third parties under -their own policies.

- -

How signing in works, and what the Service holds

- -

The Internet Computer is a public network that runs applications reliably -using a consensus protocol. The Service lets an AI assistant read public -information from it without any sign-in.

- -

For an assistant to act on your behalf, you sign in once with Internet -Identity. Your long-term credentials are never sent to the -Service. Passkey private keys remain with your authenticator, -recovery material remains with you, and linked-account credentials remain -with their respective providers; none of them are entered into the chat or -shared with the Service.

- -

What the Service does hold, once you approve a connection, is a -delegated session signing key that the Service generates -itself, inside the server. No secret key crosses a network in either -direction: your credentials stay where they already live, and the Service's -key never leaves the Service. What travels is only the key's public half, -which Internet Identity signs, issuing a time-limited, scope-limited -authorization for that key to act as you. The Service uses the session key -with Internet Identity to obtain, for each application you interact with, a -further short-lived authorization and per-application key (the connection -material described in section 1), and those per-application keys are what -actually sign your requests. All of these are signing credentials the -Service generated itself, so it is worth being precise about their limits: -none of them is your Internet Identity key, none can be used to sign in as -you anywhere else, and revoking the connection stops them in the two steps -described in section 7. Separately, your AI assistant holds an OAuth access -token that lets it reach the Service; that token's lifetime is capped by the -same authorization.

- -

On the Internet Identity consent screen you make two explicit choices: how -long the connection lasts (from 10 minutes up to 30 days), and its access -level:

- -
    -
  • "Questions only" lets the assistant read. Read access is -not the same as harmless: acting as you, the Service can retrieve data that -applications show only to you, including account balances, holdings, and -activity history, and those results are returned to your assistant. What this -level prevents is changing anything.
  • -
  • "Actions & questions" additionally lets the assistant -submit actions that change state.
  • -
- -

For a "Questions only" connection, the Internet Computer network itself -rejects action requests; the restriction does not depend on the Service -alone.

- -

The authorization is not restricted to particular -applications. Whichever level you choose applies to any application -your assistant is directed to: the Service derives a per-application identity -on demand for whichever application is named at the time. Combined with a -duration of up to 30 days, that is a broad credential, so choose the shortest -duration that suits your task, and revoke connections you are no longer using -at id.ai/manage/settings.

- -

The Service does not profile individual users, does not use advertising, -and does not use tracking cookies. It sets one transient, security-purpose -cookie during sign-in, described below.

- -

1. Data Categories Processed, Purposes, and Legal Bases

- -

We process the following categories, solely to provide, secure, and improve -the Service.

- -

Session and authorization data: the delegated session -signing key and the session duration and access level you chose. Purpose: -performing the requests your assistant makes. Legal basis: performance of the -service you requested. During sign-in the Service also sets one transient cookie -(mcp_connect; HttpOnly, Secure, SameSite=Lax), set and read by the -server only and not accessible to scripts in web pages. It binds the sign-in -to the browser that started it, protecting you against session-fixation -attacks, and is not used for tracking. Legal basis: our legitimate interest in -securing the sign-in, and it is strictly necessary for a function you -requested.

- -

Requests and their results: the requests your assistant -makes (application and canister identifiers, method names, query and call -arguments) and the results returned for them. Both can -contain arbitrary personal data: your own, and data about third parties held -by the applications you query. Depending on what you ask for, results may -include account names and numbers, balances and holdings, activity and -timestamps, identifiers, web addresses, and application-specific records. The -Service encodes, signs, forwards, and returns this data; it processes it -transiently to execute each call and does not store it after the call -completes, with one exception, the per-application connection material -described below. It does not store conversation content. Legal basis: -performance of the service you requested.

- -

Account information: when your assistant uses tools that -list your Internet Identity accounts at an application, or that ask which -identity you use there, the Service processes those account names, numbers, -and last-used timestamps, and the per-application identity in question. A -per-application identity is a pseudonym specific to that application; -depending on how you use that application, you may consider it private. Not -stored after the session; the account number and application used for an -authenticated call are kept during the session as part of the connection -material described next. Legal basis: performance of the service you -requested.

- -

Per-application connection material: the first time your -assistant acts at a given application, the Service derives the identity you -use there and keeps, for the remainder of the session: the application's -domain, the account number used, the per-application key it generated, and -Internet Identity's signed authorization for that application (itself valid -for at most one hour). Keeping this avoids re-deriving an authorization on -every call to the same application. All of this is -held in memory only, bounded in size, and discarded when the session ends or -the Service restarts. Legal basis: performance of the service you -requested.

- -

Connection (OAuth) data: when an assistant connects, the -Service stores its registration: a generated client identifier, the -assistant's redirect address, and a last-used timestamp, plus short-lived -authorization codes and access tokens. A registration describes assistant -software rather than you, but we treat it as pseudonymous personal data, -because a redirect address can identify an organisation, a deployment, or a -device. Legal basis: performance of the service you requested.

- -

Technical logs: see section 5 for exactly what these -contain. Purposes: operating, securing, and debugging the Service, and abuse -prevention. Legal basis: our legitimate interest in keeping the Service -available, secure, and free of abuse.

- -

Aggregated operational metrics: counts such as how many -connections are active and how often errors occur. These are aggregates and do -not identify individual users. Purpose: operating and improving the Service. -Legal basis: our legitimate interest in understanding and improving how the -Service performs.

- -

2. Who Receives Data

- -

Using the Service necessarily sends data to others. In order of how -directly each one is involved:

- -

Your AI assistant, and its provider. Everything the -Service returns goes back to the assistant that asked for it. That includes -results you requested that contain private, account-specific data, your -account names, and your per-application identities. Your assistant's provider -(for example, Anthropic for Claude) processes that data under its own terms -and privacy policy, not this one. This is inherent to using an AI assistant as -the interface.

- -

The Internet Computer network. Your requests are executed -by the network's nodes, which are operated by independent node providers in a -number of countries, and reach it through public API boundary nodes -(icp-api.io). Internet Identity (id.ai) -authenticates you. These are part of the Internet Computer and are governed by -its DAO, the Network Nervous System, rather than operated by DFINITY -Foundation.

- -

The applications you choose. A question or action you -direct at an application carries your request and your per-application -identity to that application and its operator, who may be anyone. What an -application records, retains, or publishes is governed by that application, -not by this Privacy Policy, and may be publicly accessible. Actions that -change state become part of that application's state on a public network. -Developers who want their applications' operations supported through the -Service accept the ICP MCP Terms of Service by -implementing the -service -discoverability instructions, and under those Terms are responsible for -handling the data described in this paragraph lawfully and under their own -privacy policy.

- -

Websites you ask the Service to look up. When you ask it -to find the application behind a web address, the Service fetches metadata -from that address, which discloses the request to whoever runs that site.

- -

Two services DFINITY Foundation operates: the public -canister-metadata service at dashboard.internetcomputer.org and -the developer-skills service at skills.internetcomputer.org.

- -

Our hosting provider. The Service runs on servers we rent -from Amazon Web Services, which processes data on our behalf as a processor -under its data-processing terms. We use no other infrastructure processors -today; if an observability or error-reporting provider is ever introduced, it -will be named here first.

- -

Public authorities, where the law requires it. If we are -legally obliged to disclose personal data, for example by a court order or a -binding request from a competent authority, we disclose the minimum -required.

- -

We do not sell personal data or disclose it for advertising. We disclose -it only to the recipients described above, as necessary to perform your -requests, secure the Service, or comply with law.

- -

3. International Transfers

- -

The Service's servers, and the technical logs they hold, are located in -Germany (Amazon Web Services' Frankfurt region). Germany is a -member of the European Economic Area, whose countries are recognised by the -Swiss Federal Council as providing adequate data protection, so no additional -transfer safeguard is required for this hosting. Amazon Web Services may have -limited remote access from other countries for support and operations; that -access is governed by the AWS Data Processing Addendum, which incorporates -the EU Standard Contractual Clauses, as extended to Swiss transfers in line -with the FDPIC's guidance, for any processing from a country without an -adequate level of data protection.

- -

Separately, the Internet Computer is a global public network: its nodes -are operated by independent providers in many countries, whose locations are -set by the network's governance, not by us, and can include countries that -Switzerland and the EEA do not recognise as providing adequate data -protection. When you direct the Service to read from or act on an -application, the Service submits that request to the network deliberately, -on your instruction, and the network processes it internationally to execute -what you asked. What travels is the content of your request and its results -(section 1). Authenticated requests also carry the pseudonymous -per-application identifier described in section 6; unauthenticated public -reads use the Internet Computer's shared anonymous principal, which -identifies no one. This processing is inherent to the network's design and -applies to every request, so take it into account when deciding which -applications to direct the Service at.

- -

4. Data Retention

- - - - - - - - - - -
CategoryRetained
Session and authorization data (signing key, access level, -duration)In volatile memory only, never written to disk. Discarded -when the duration you chose elapses (at most 30 days) or when the Service -restarts, whichever comes first.
Requests and resultsProcessed transiently; not retained -after the call completes.
Per-application connection material (application domain, account -number, per-application key, signed authorization)In memory, per -session; an entry is replaced when refreshed and all are discarded when the -session ends or the Service restarts. The signed authorization itself expires -at most one hour after issue.
Authorization codes and access tokensIn memory only; codes -expire after two minutes, tokens no later than the session duration you -chose.
Connection (OAuth) registrationsStored on disk so an -assistant can reconnect across restarts. There is currently no time -limit: a registration is kept until it is displaced once the store -reaches its cap of 10,000, which for a rarely-used deployment can mean -indefinitely.
Technical logsUp to three months, then deleted.
Aggregated operational metricsHeld in memory only, never -written to disk: gauges are computed on demand, and the status dashboard -keeps its most recent health report in memory until the next one replaces it -or the process restarts.
- -

Revoking a connection takes effect in the two steps described in section -7. Revocation does not by itself erase the session record: the delegated -signing key and the per-application connection material stay in memory until -the duration you originally chose elapses or the Service restarts. We intend -to discard them as soon as a revocation is observed; until that ships, the -table above describes the actual behaviour.

- -

5. What Our Logs Contain

- -

We would rather be specific than make sweeping promises, so:

- -
    -
  • Every request that reaches the Service's application produces one log -line with the HTTP method, the path, the response status, and how long it -took. Query strings and request bodies are never included, which keeps -single-use codes and delegations out of these logs. Requests to the status -dashboard (/status/) bypass the application and produce no -routine log line at all.
  • -
  • Sign-in and session events log the session identifier, a per-connection -identifier derived from that connection's key, the access level, and expiry -times. The per-connection identifier is new for every connection, so log -entries from different sessions cannot be linked to each other through -it.
  • -
  • No log line records the applications or canisters you interact -with, the arguments you send, or the results you receive. The parts -of the Service that perform tool calls do no logging at all.
  • -
  • Our web server is configured without access logging, so client IP -addresses and browser user agents are not recorded as a matter of course. -They are unavoidably processed in transit in order to serve a request, and -its diagnostics for failed requests can include connection details and the -requested address, including any query string, when something goes -wrong.
  • -
  • Logs are held by the operating system's journal on our hosts, subject to -the retention bound above.
  • -
- -

6. Identifiers, and What They Could Link

- -

Internet Identity gives each application a different identity for you, so -applications cannot recognise you across applications. The Service is a -participant in that design and we want to be plain about what it can see. -While a session is live it holds, in memory, the per-application identities -it has derived for you. Internet Identity derives the same identity each time -for a given application and account, so software holding one of them could in -principle tell that two separate sessions visiting that application belong to -the same user. Three things limit what the Service could link -in practice: none of these identifiers is written to -logs, which carry only a per-connection identifier that is new for every -connection (section 5); the applications you visit are not written to logs -either; and nothing the Service discloses to an application lets that -application recognise you anywhere else.

- -

7. Your Rights

- -

Subject to the conditions in applicable law, you have the right to -access the personal data we hold about you, to have -inaccurate data rectified, to have data -erased, to restrict or object -to processing (including processing based on our legitimate -interests), and to receive data you provided in a portable form. To exercise -any of these, email mcp@dfinity.org.

- -

You can also act directly, without contacting us:

- -
    -
  • Disconnect the connector in your assistant. This stops -that assistant using the connection, but does not by itself end the underlying -authorization.
  • -
  • Revoke the authorization at -id.ai/manage/settings, which lists -every active connection for your Internet Identity so you can review and -revoke access even if you no longer remember which assistant sessions are -active. Revocation is carried out by Internet Identity and takes effect in -two steps: within at most five minutes, Internet Identity stops honouring the -connection, so the Service can no longer obtain authorizations for any -application; authorizations already issued for particular applications remain -usable until they expire, at most one hour after they were issued. The -practical worst case between revoking and all activity stopping is therefore -about one hour.
  • -
- -

If you believe we have handled your personal data unlawfully, you may lodge -a complaint with the Swiss Federal Data Protection and Information -Commissioner (FDPIC), and, where the GDPR applies to you, with the supervisory -authority of your EU or EEA country of residence or workplace.

- -

Data you have submitted to applications on the Internet Computer through -your own requests is held by those applications, not by the Service; requests -about it should be directed to the relevant application operator.

- -

8. Changes to This Policy

- -

We may update this Privacy Policy. When we do, we will change the effective -date at the top of this page. For changes that materially affect how we use -your data or what an authorization permits, we will give reasonable advance -notice before the change takes effect, and will not apply them retroactively -to data already collected.

- -

9. Contact

- -

For questions about this Privacy Policy or the Service, or to exercise any -of the rights in section 7, email -mcp@dfinity.org.

-
-
Hosted by -
- diff --git a/src/assets/support.html b/src/assets/support.html deleted file mode 100644 index b0d9928..0000000 --- a/src/assets/support.html +++ /dev/null @@ -1,170 +0,0 @@ - - - -ICP MCP Support - - -
-

ICP MCP

-

Support

-

Help with the ICP MCP connector, hosted by DFINITY.

- -

Contact us

- -

Email mcp@dfinity.org -for any question about the connector: something not working, a question about -what it can do, feedback, or a data-protection request. To help us help you -quickly, include which AI assistant you were using (Claude, ChatGPT, or -another MCP client), what you asked it to do, roughly when it happened, and -any error message you saw. Please never include recovery phrases, private -keys, or passwords in an email; we will never ask for them.

- -

Check the Service first

- -

The live status of this deployment and the Internet Identity service it -depends on is at mcp.internetcomputer.org/status/. If -something just stopped working, that page usually says why.

- -

Manage or revoke access

- -

You can review every AI-assistant connection authorized for your Internet -Identity, and revoke any of them, at -id.ai/manage/settings. Revoking there -works even if you no longer remember which assistant or session holds the -connection.

- -

Report a bug

- -

The connector is developed in the open at -github.com/dfinity/imcp2; -bug reports are welcome as -GitHub issues. If you -are unsure whether something is a bug, email us instead.

- -

Report a security vulnerability

- -

Please do not report security vulnerabilities in public issues. Disclose -them through the -Internet -Computer bug bounty on Hackenproof, per the project's -security -policy.

- -

Privacy

- -

How the Service handles personal data is described in the -ICP MCP Privacy Policy. Requests to exercise -your data-protection rights go to -mcp@dfinity.org.

-
-
Hosted by -
- diff --git a/src/assets/terms.html b/src/assets/terms.html deleted file mode 100644 index 6e452cf..0000000 --- a/src/assets/terms.html +++ /dev/null @@ -1,249 +0,0 @@ - - - -ICP MCP Terms of Service - - -
-

ICP MCP

-

ICP MCP Terms of Service

-

Effective date: August 28, 2026

- -

These Terms of Service ("Terms") govern your use of the ICP MCP server -(the "Service"), operated by DFINITY Stiftung, Genferstrasse 11, 8002 -Zürich, Switzerland ("DFINITY Foundation", "we"), at -mcp.internetcomputer.org, together with any pre-release -deployments of the same software that we operate. By connecting an AI -assistant to the Service, or -by using its pages and tools, you agree to these Terms. How we handle -personal data is described separately in the -ICP MCP Privacy Policy.

- -

1. The Service

- -

The Service is a Model Context Protocol server that lets an AI assistant -read public information from the Internet Computer and, once you authorize a -connection through Internet Identity, act on your behalf: read data -applications hold for you and submit actions to them. -The Service is provided free of charge.

- -

The Service never holds your long-term credentials. What -it holds during a session is a delegated, time-limited authorization, as -described in the -Privacy Policy.

- -

2. Your Responsibilities

- -

The Service acts on instructions your AI assistant sends under your -authorization, and it cannot tell a wise instruction from an unwise one. -Requests made under your authorization are treated as yours and are -your sole responsibility. -You are responsible for: what you ask your assistant to do; the access level -("Questions only" or "Actions & questions") and duration you grant on the -Internet Identity consent screen; the security of your own devices and -Internet Identity; and revoking connections you no longer want, at -id.ai/manage/settings.

- -

You agree to use the Service lawfully, and not to: disrupt or overload it; -attempt to circumvent its authorization, rate, or safety mechanisms; or probe -it for vulnerabilities outside the -Internet -Computer bug bounty's rules.

- -

3. Actions Can Be Irreversible

- -

Actions execute on the Internet Computer, a public network that neither we -nor anyone else can roll back. Depending on what you authorize, an action can -change or permanently delete data an application holds for you, and what an -action does is defined by the application that receives it, not by us. -We cannot undo an action after the network accepts it. -Prefer "Questions only" sessions when you do not need actions, keep -authorization durations short, and review what your assistant intends before -approving state-changing calls.

- -

4. Third Parties

- -

The applications and canisters you interact with through the Service are -operated by third parties under their own terms; the Internet Computer and -Internet Identity are governed by the Network Nervous System; and your AI -assistant is provided under its provider's terms. We are not a party to your -relationship with any of them, and data returned by the Service originates -from them, not from us.

- -

Application developers who want their applications' operations supported -through the Service accept these Terms, and assert that acceptance by -implementing the -service -discoverability instructions in the Internet Computer developer -documentation. Under these Terms they are responsible for handling any -personal data that reaches them through the Service lawfully and under their -own privacy policy, and for keeping that policy accurate and available to -their users. The ICP MCP Privacy Policy -describes what the Service discloses to an application; it does not govern -what the application then does with it.

- -

5. Availability and Changes to the Service

- -

We aim to keep the Service available but do not promise it: we may change, -suspend, or discontinue the Service or any of its tools at any time, and we -may suspend access that we reasonably believe is abusive or a security -risk. Current operational status is published at -mcp.internetcomputer.org/status/.

- -

6. Intellectual Property

- -

The Service's software is open source under the Apache License 2.0 at -github.com/dfinity/imcp2. -These Terms grant no rights to DFINITY Foundation's names, logos, or other -trademarks.

- -

7. Disclaimer of Warranties

- -

The Service is provided "as is" and "as available", -without warranties of any kind, whether express or implied, including -availability, fitness for a particular purpose, and the accuracy, -completeness, or timeliness of data it returns, which comes from the network -and the applications you query.

- -

8. Limitation of Liability

- -

To the maximum extent permitted by law, DFINITY Foundation is not liable -for damages arising from your use of the Service, including indirect or -consequential damages and loss of data, whether resulting from actions taken -under your authorization, from third-party applications, from the behaviour -of the Internet Computer or Internet Identity, or from the Service being -unavailable, changed, or discontinued. Nothing in these Terms excludes or -limits liability that cannot be excluded under applicable law, including -liability under Swiss law for damage caused by unlawful intent or gross -negligence.

- -

9. Changes to These Terms

- -

We may update these Terms. When we do, we will change the effective date -at the top of this page, and for changes that materially affect your rights -or obligations we will give reasonable advance notice before they take -effect.

- -

10. Governing Law and Jurisdiction

- -

These Terms are governed by Swiss substantive law, excluding its conflict -of law rules. The exclusive place of jurisdiction is Zürich, -Switzerland, subject to any mandatory statutory forum available to you as a -consumer.

- -

11. Contact

- -

Questions about these Terms: mcp@dfinity.org. -For everything else, see Support.

-
-
Hosted by -
- diff --git a/src/main.rs b/src/main.rs index b077a7f..02ffb84 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ //! Deployment binary for the [`imcp2`] library: serves the production Internet -//! Identity instance at `/mcp` and adds the deployment niceties (the landing -//! page, a `/version` probe with live-session gauges, request logging, -//! env-driven config, drained graceful shutdown). +//! Identity instance at `/mcp` and adds the deployment niceties (permanent +//! redirects to the landing site's home on internetcomputer.org, a `/version` +//! probe with live-session gauges, request logging, env-driven config, drained +//! graceful shutdown). //! //! * `/mcp`: the MCP endpoint against **production** Internet Identity, with //! its OAuth AS at `/mcp/oauth/*` (issuer `/mcp`). Always @@ -22,10 +23,10 @@ //! `$OPENAI_APPS_CHALLENGE_TOKEN` (serve the OpenAI Apps domain-verification //! token at `/.well-known/openai-apps-challenge`; 404 while unset). //! -//! Also serves `/sitemap.xml` and `/robots.txt`, both built from `$PUBLIC_URL` -//! so each deployment advertises its own origin. +//! Also serves `/robots.txt` (keeping crawlers off the machine surface) and +//! `/favicon.svg` (the tab icon the connect screens link). -use axum::{response::Html, routing::get, Json, Router}; +use axum::{routing::get, Json, Router}; use imcp2::{auth_callbacks_router, Agent, IiInstance, McpConfig, McpServer, SharedClients, IC_URL}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; @@ -90,21 +91,39 @@ fn serve_metrics() -> bool { .unwrap_or(false) } -/// The landing page served at `/`: a self-contained design bundle exported from -/// Claude Design (`assets/index.html`, compiled in via `include_str!`, no -/// runtime file I/O). It is a single HTML document that inlines its own fonts, -/// images, styles, and render runtime as an embedded resource bundle and unpacks -/// itself client-side — so it stays self-contained (no external fonts, scripts, -/// or images) despite the richer look. It shares the connect flow's ICP identity -/// — parchment grid, editorial serif, rust accent, "Hosted by DFINITY" mark — so -/// the root page and the connect screens read as one product, and walks through -/// what an agent can do: discovery, identity, on-network queries, actions, skills. -/// -/// Patched over the raw export with the `` for [`FAVICON_SVG`], -/// in the shell `` and again in the head fragment the unpack routine -/// injects — the unpack replaces `documentElement`, so only the second copy -/// survives to render. A re-export drops both. -const INDEX_HTML: &str = include_str!("assets/index.html"); +/// The landing site's home. The human-facing pages this origin used to serve +/// itself — the landing page and its `/privacy-policy`, `/support` and +/// `/terms` subpages — are maintained in one place, dfinity/internetcomputer-org +/// (`public/icp-mcp/`), and served at internetcomputer.org under this prefix. +/// This origin answers their old paths with permanent redirects instead of +/// copies, so every published link keeps working — the directory listings' +/// policy URLs, old bookmarks, search results — while the content exists +/// exactly once. `/status/` is unaffected: the live dashboard is this +/// deployment's own monitoring surface, published by the fronting proxy. +const LANDING_SITE: &str = "https://internetcomputer.org/icp-mcp"; + +/// The page paths this origin used to serve, each answered with a permanent +/// redirect (308) to its home under [`LANDING_SITE`]. The targets carry the +/// trailing slash the static site canonicalizes to, so a client lands in one +/// hop. +fn landing_redirects_router() -> Router { + const PAGES: &[(&str, &str)] = &[ + ("/", "/"), + ("/privacy-policy", "/privacy-policy/"), + ("/support", "/support/"), + ("/terms", "/terms/"), + ]; + let mut router = Router::new(); + for (path, target) in PAGES { + router = router.route( + path, + get(move || async move { + axum::response::Redirect::permanent(&format!("{LANDING_SITE}{target}")) + }), + ); + } + router +} /// `GET /metrics` — the Prometheus exposition for `registry`. Its own router so /// the gate stays one line at the call site (see [`serve_metrics`]) and the @@ -165,78 +184,25 @@ fn metrics_router(registry: prometheus::Registry, metrics: imcp2::metrics::Metri /// reads against both the light and dark browser chrome. const FAVICON_SVG: &str = include_str!("assets/favicon.svg"); -/// The public, human-facing pages this origin serves, as absolute-path -/// suffixes. This is the sitemap's and robots.txt's shared idea of "content": -/// every other route is machine surface that a crawler has no use for and that -/// we do not want indexed — `/mcp` (+ `/mcp-beta`) answer 401 to an -/// unauthenticated fetch, `/version` is an operations probe, `/status/` is the -/// dashboard, and `/.well-known/*` documents are for clients, not readers. -/// Keep in step with the page routes registered in `main`. -const PUBLIC_PAGES: &[&str] = &["/", "/privacy-policy", "/terms", "/support"]; - -/// Escape the five XML metacharacters. `PUBLIC_URL` is operator-supplied, so a -/// stray `&` in it must not produce a malformed sitemap that a crawler rejects -/// wholesale. -fn xml_escape(s: &str) -> String { - s.replace('&', "&") - .replace('<', "<") - .replace('>', ">") - .replace('"', """) - .replace('\'', "'") -} - -/// `GET /sitemap.xml` — a [sitemaps.org] 0.9 urlset naming the public pages. -/// -/// The entries must be absolute, so they are built from `PUBLIC_URL` at -/// startup rather than baked in: staging and production then each advertise -/// their own origin instead of both claiming production's. A trailing slash on -/// the configured value is trimmed so the joins can't yield `//privacy-policy`. -/// -/// Deliberately ``-only: `` would have to come from build time, -/// which changes on every redeploy whether or not a page did, and `changefreq` -/// and `priority` are ignored by the major crawlers. -/// -/// [sitemaps.org]: https://www.sitemaps.org/protocol.html -fn sitemap_xml(public_url: &str) -> String { - let origin = xml_escape(public_url.trim_end_matches('/')); - let urls: String = PUBLIC_PAGES - .iter() - .map(|p| format!(" {origin}{}\n", if *p == "/" { "/" } else { p })) - .collect(); - format!( - "\n\ - \n\ - {urls}\n" - ) -} - -/// `GET /robots.txt` — points crawlers at the sitemap (its only discovery -/// path, short of submitting it to each search console by hand) and keeps them -/// off the machine surface. Nothing here is a security control: the paths it -/// names are already either authenticated or harmless, and robots.txt is -/// advisory. It exists so crawl budget goes to the four pages that are worth -/// reading and so the MCP and probe endpoints stay out of search results. -fn robots_txt(public_url: &str) -> String { - let origin = public_url.trim_end_matches('/'); - format!( - "User-agent: *\n\ - Allow: /\n\ - Disallow: /mcp\n\ - Disallow: /mcp-beta\n\ - Disallow: /version\n\ - Disallow: /status/\n\ - Disallow: /.well-known/\n\ - \n\ - Sitemap: {origin}/sitemap.xml\n" - ) -} - -/// Serve `/sitemap.xml`, `/robots.txt` and `/favicon.svg` for the given public -/// origin, each with the content type its consumers expect (`application/xml`, -/// `text/plain` and `image/svg+xml`). -fn site_metadata_router(public_url: &str) -> Router { - let sitemap = sitemap_xml(public_url); - let robots = robots_txt(public_url); +/// `GET /robots.txt` — keeps crawlers off the machine surface. Nothing here is +/// a security control: the paths it names are already either authenticated or +/// harmless, and robots.txt is advisory. There is no sitemap any more — the +/// human-facing pages moved to [`LANDING_SITE`] and this origin answers their +/// paths with redirects a crawler may follow — so this exists purely so the +/// MCP and probe endpoints stay out of search results. +const ROBOTS_TXT: &str = "User-agent: *\n\ + Allow: /\n\ + Disallow: /mcp\n\ + Disallow: /mcp-beta\n\ + Disallow: /version\n\ + Disallow: /status/\n\ + Disallow: /.well-known/\n"; + +/// Serve `/robots.txt` and `/favicon.svg`, each with the content type its +/// consumers expect (`text/plain` and `image/svg+xml`). The favicon stays +/// served with the landing pages gone: the connect and error screens link +/// `/favicon.svg` for their tab icon. +fn site_metadata_router() -> Router { Router::new() .route( "/favicon.svg", @@ -250,20 +216,10 @@ fn site_metadata_router(public_url: &str) -> Router { ) }), ) - .route( - "/sitemap.xml", - get(move || { - let sitemap = sitemap.clone(); - async move { ([(axum::http::header::CONTENT_TYPE, "application/xml")], sitemap) } - }), - ) .route( "/robots.txt", - get(move || { - let robots = robots.clone(); - async move { - ([(axum::http::header::CONTENT_TYPE, "text/plain; charset=utf-8")], robots) - } + get(|| async { + ([(axum::http::header::CONTENT_TYPE, "text/plain; charset=utf-8")], ROBOTS_TXT) }), ) } @@ -297,46 +253,6 @@ fn openai_apps_challenge_router(token: Option) -> Router { ) } -/// The privacy policy served at `/privacy-policy` — the URL the Anthropic -/// connectors-directory listing points at, and the target of the landing -/// page's footer link. The markup lives in -/// `assets/privacy-policy.html` (compiled in via `include_str!`, no runtime -/// file I/O) and shares the connect flow's ICP identity so it reads as the -/// same product. Its one substitution is the shared DFINITY wordmark -/// (`assets/dfinity-logo.svg`), inlined once on first use so the served page -/// stays fully self-contained (no external fonts, scripts, or images). -const PRIVACY_POLICY_HTML: &str = include_str!("assets/privacy-policy.html"); -const DFINITY_LOGO_SVG: &str = imcp2_core::iiconnect::CONNECT_LOGO_SVG; - -fn privacy_policy_page() -> &'static str { - static PAGE: std::sync::OnceLock = std::sync::OnceLock::new(); - PAGE.get_or_init(|| PRIVACY_POLICY_HTML.replace("__LOGO__", DFINITY_LOGO_SVG)) -} - -/// The support page served at `/support` — the customer-support URL the -/// directory listings (OpenAI requires a URL, not just an address) point at. -/// Same construction as `/privacy-policy`: a self-contained document sharing -/// the connect flow's ICP identity, with the DFINITY wordmark as its one -/// substitution. It routes users to mcp@dfinity.org, the status dashboard, -/// id.ai's access management, GitHub issues, and the security policy. -const SUPPORT_HTML: &str = include_str!("assets/support.html"); - -fn support_page() -> &'static str { - static PAGE: std::sync::OnceLock = std::sync::OnceLock::new(); - PAGE.get_or_init(|| SUPPORT_HTML.replace("__LOGO__", DFINITY_LOGO_SVG)) -} - -/// The Terms of Service served at `/terms` — the terms URL the directory -/// listings point at, and the usage contract the privacy policy's -/// performance-of-service legal basis rests on. Same construction as -/// `/privacy-policy` and `/support`. -const TERMS_HTML: &str = include_str!("assets/terms.html"); - -fn terms_page() -> &'static str { - static PAGE: std::sync::OnceLock = std::sync::OnceLock::new(); - PAGE.get_or_init(|| TERMS_HTML.replace("__LOGO__", DFINITY_LOGO_SVG)) -} - #[tokio::main] async fn main() -> anyhow::Result<()> { // When this process started — i.e. when the deployment last (re)started. @@ -449,10 +365,8 @@ async fn main() -> anyhow::Result<()> { ); let mut app = Router::new() - .route("/", get(|| async { Html(INDEX_HTML) })) - .route("/privacy-policy", get(|| async { Html(privacy_policy_page()) })) - .route("/support", get(|| async { Html(support_page()) })) - .route("/terms", get(|| async { Html(terms_page()) })) + // The old landing-site paths: permanent redirects to their one home. + .merge(landing_redirects_router()) // Unauthenticated build/version probe so operators and the status // dashboard can confirm exactly which deployment is live: the running // commit (baked in at build time via GIT_SHA), the build time @@ -518,8 +432,8 @@ async fn main() -> anyhow::Result<()> { .merge(openai_apps_challenge_router( std::env::var("OPENAI_APPS_CHALLENGE_TOKEN").ok(), )) - // /sitemap.xml + /robots.txt, built from this deployment's PUBLIC_URL. - .merge(site_metadata_router(&public_url)); + // /robots.txt + /favicon.svg (the icon the connect screens link). + .merge(site_metadata_router()); // Prometheus exposition, only when $MCP_SERVE_METRICS opts in — see // `serve_metrics()`. The recording middleware below stays on either way, so @@ -627,8 +541,8 @@ async fn shutdown_signal() { #[cfg(test)] mod tests { use super::{ - metrics_router, openai_apps_challenge_router, serve_metrics, site_metadata_router, - sitemap_xml, PUBLIC_PAGES, + landing_redirects_router, metrics_router, openai_apps_challenge_router, serve_metrics, + site_metadata_router, }; use axum::http::{Request, StatusCode}; use http_body_util::BodyExt; @@ -698,8 +612,8 @@ mod tests { assert!(body.contains("imcp2_metrics_scrape_duration_seconds_count 1"), "{body}"); } - async fn fetch(public_url: &str, path: &str) -> (StatusCode, String, Option) { - let resp = site_metadata_router(public_url) + async fn fetch(path: &str) -> (StatusCode, String, Option) { + let resp = site_metadata_router() .oneshot(Request::get(path).body(axum::body::Body::empty()).unwrap()) .await .unwrap(); @@ -710,62 +624,47 @@ mod tests { (status, String::from_utf8(body.to_vec()).unwrap(), content_type) } - // Every public page must appear exactly once, as an ABSOLUTE url on the - // configured origin: a sitemap of relative paths, or one naming another - // deployment's origin, is rejected or ignored by crawlers. + // With the pages moved out, robots.txt only keeps crawlers off the machine + // surface — and must no longer advertise a sitemap this origin doesn't + // serve. #[tokio::test] - async fn sitemap_lists_every_public_page_as_an_absolute_url() { - let (status, body, content_type) = - fetch("https://mcp.internetcomputer.org", "/sitemap.xml").await; + async fn robots_excludes_the_machine_surface_and_names_no_sitemap() { + let (status, body, content_type) = fetch("/robots.txt").await; assert_eq!(status, StatusCode::OK); - assert_eq!(content_type.as_deref(), Some("application/xml")); - for page in PUBLIC_PAGES { - let loc = format!("https://mcp.internetcomputer.org{page}"); - assert_eq!(body.matches(&loc).count(), 1, "{page} should appear once in:\n{body}"); - } - assert_eq!(body.matches("").count(), PUBLIC_PAGES.len()); - // The machine surface stays out: these must never be advertised. - for hidden in ["/version", "/status/", "/.well-known", "/mcp<", "/mcp-beta"] { - assert!(!body.contains(hidden), "sitemap must not list {hidden}:\n{body}"); + assert_eq!(content_type.as_deref(), Some("text/plain; charset=utf-8")); + for path in ["/mcp", "/mcp-beta", "/version", "/status/", "/.well-known/"] { + assert!(body.contains(&format!("Disallow: {path}\n")), "{path} missing:\n{body}"); } + assert!(!body.contains("Sitemap:"), "no sitemap is served any more:\n{body}"); } - // A trailing slash on PUBLIC_URL must not produce `//privacy-policy`, and - // the root entry must stay exactly one slash. - #[tokio::test] - async fn sitemap_normalizes_a_trailing_slash_on_the_public_url() { - let body = sitemap_xml("https://example.test/"); - assert!(body.contains("https://example.test/")); - assert!(body.contains("https://example.test/terms")); - assert!(!body.contains("//terms")); - } - - // An operator-supplied origin is escaped, so a stray metacharacter cannot - // emit a malformed document that a crawler discards wholesale. - #[tokio::test] - async fn sitemap_escapes_xml_metacharacters_in_the_origin() { - let body = sitemap_xml("https://example.test/?a=1&b=2"); - assert!(body.contains("&b=2"), "{body}"); - assert!(!body.contains("&b=2")); - } - - // robots.txt is the sitemap's only discovery path for a crawler that was - // never handed the URL directly, so the absolute reference must be there. + // Every page this origin used to serve itself answers with a permanent + // redirect to its one home on the landing site — canonical trailing-slash + // form, so a client lands in one hop. The absolute targets are pinned: a + // typo'd LANDING_SITE would otherwise ship a working-looking 308 to + // nowhere. #[tokio::test] - async fn robots_points_at_the_sitemap_and_excludes_the_machine_surface() { - let (status, body, content_type) = - fetch("https://mcp.internetcomputer.org/", "/robots.txt").await; - assert_eq!(status, StatusCode::OK); - assert_eq!(content_type.as_deref(), Some("text/plain; charset=utf-8")); - assert!(body.contains("Sitemap: https://mcp.internetcomputer.org/sitemap.xml"), "{body}"); - for path in ["/mcp", "/mcp-beta", "/version", "/status/", "/.well-known/"] { - assert!(body.contains(&format!("Disallow: {path}\n")), "{path} missing:\n{body}"); + async fn old_page_paths_redirect_permanently_to_the_landing_site() { + for (path, target) in [ + ("/", "https://internetcomputer.org/icp-mcp/"), + ("/privacy-policy", "https://internetcomputer.org/icp-mcp/privacy-policy/"), + ("/support", "https://internetcomputer.org/icp-mcp/support/"), + ("/terms", "https://internetcomputer.org/icp-mcp/terms/"), + ] { + let resp = landing_redirects_router() + .oneshot(Request::get(path).body(axum::body::Body::empty()).unwrap()) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::PERMANENT_REDIRECT, "{path}"); + let location = + resp.headers().get("location").and_then(|v| v.to_str().ok()).unwrap_or(""); + assert_eq!(location, target, "{path}"); } } #[tokio::test] async fn favicon_is_served_as_a_cacheable_svg() { - let resp = site_metadata_router("https://mcp.internetcomputer.org") + let resp = site_metadata_router() .oneshot(Request::get("/favicon.svg").body(axum::body::Body::empty()).unwrap()) .await .unwrap(); @@ -780,30 +679,6 @@ mod tests { assert!(!svg.contains(") -> (StatusCode, String, Option) { let app = openai_apps_challenge_router(token.map(str::to_string)); let resp = app