diff --git a/README.md b/README.md index 1d89350..b2d4984 100644 --- a/README.md +++ b/README.md @@ -402,27 +402,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 c7358d2..2e2dad1 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,17 @@ 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 behind -`main` (the current draft's identifier-linkability wording and the updated -third-party list land 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 identifier-linkability wording and the updated third-party list). +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 +277,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` @@ -358,7 +364,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 e721d17..d0f76f9 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 f2a666d..0000000 --- a/src/assets/privacy-policy.html +++ /dev/null @@ -1,513 +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.

- -

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.

- -

A service DFINITY Foundation operates: the public -canister-metadata service at dashboard.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 e94182c..0000000 --- a/src/assets/terms.html +++ /dev/null @@ -1,235 +0,0 @@ - - - -ICP MCP Terms of Service - - -
-

ICP MCP

-

ICP MCP Terms of Service

-

Effective date: August 3, 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, submit actions, and manage canisters you control. -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 -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. -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, 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.

- -

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.

- -

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, 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.

- -

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 2f00e36..0f27f43 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, ii_app_metadata_router, Agent, IiInstance, McpConfig, McpServer, SharedClients, IC_URL, @@ -93,21 +94,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 @@ -168,78 +187,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", @@ -253,20 +219,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) }), ) } @@ -300,46 +256,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. @@ -452,10 +368,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 @@ -521,8 +435,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 @@ -632,8 +546,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; @@ -703,8 +617,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(); @@ -715,62 +629,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(); @@ -785,30 +684,6 @@ mod tests { assert!(!svg.contains(") -> (StatusCode, String, Option) { let app = openai_apps_challenge_router(token.map(str::to_string)); let resp = app