@everfur/cli 0.2.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,9 +14,14 @@ template) includes a 2 KB loader and gets the chat in an iframe on Everfur's ori
14
14
  a Shopify theme or app gets the Everfur app's `Everfur chat` block, with Shopify vouching for the
15
15
  signed-in customer so no backend mints. `--embedding in-page|frame` overrides the detected embedding.
16
16
  A capability the web cannot render (photo in the page, anything but chat in the frame) is reported as
17
- blocked by the platform, beside the steps for the rest. `everfur verify --origin https://your.page`
18
- adds the checks a web integration needs: the embed allowlist for that origin, the CORS exposure a
19
- browser depends on, the tenant's session policy, and the loader of the key's environment.
17
+ blocked by the platform, beside the steps for the rest. For a page that includes the loader,
18
+ `everfur verify --origin https://your.page --embedding frame` adds the checks a framed page needs: the embed
19
+ allowlist for that origin, the CORS exposure a browser depends on, the tenant's session policy, and the loader
20
+ of the key's environment. An in-page integration calls the API from its own origin, so it passes `--origin` too, with
21
+ `--embedding in-page`: the preflight is of a widget request with the headers the SDK sends, the exposure and the
22
+ session policy are checked, and the embed fence and the loader, which it never uses, are reported `N/A` rather than
23
+ failed. Only a named `--embedding in-page` does that: with none named every check runs, and a project whose page
24
+ includes the Everfur loader is checked as the frame it embeds.
20
25
 
21
26
  **The generated surface matches your own theme when the plan can read it, and says so when it cannot.** The
22
27
  plan reads, statically and never by executing anything, a Tailwind v4 `@theme` block, a `tailwind.config.{js,ts}`
@@ -37,14 +42,22 @@ config that runs code when it loads) is low: no theme is written, and the step n
37
42
  offers the manual `theme={{ ... }}` snippet. Over a remote MCP pass the config or CSS text as `theme_source`. The
38
43
  tenant's console branding is merged with the emitted theme per field (a locked field keeps the console's value;
39
44
  the console fills what the theme leaves unset); the plan holds no branding credential and never changes it.
45
+ Branding v2 (SDK 0.9.0): a success, warning or info colour and a heading stack the theme declares by name
46
+ (`--success`, `--color-warning`, `--font-heading`, `--font-display`, Tailwind v3 `colors.info` or
47
+ `fontFamily.heading`) are written too, as `successColor`, `warningColor`, `infoColor` (with their dark twins) and
48
+ `headingFontFamily`; a theme that declares none gets none. The logo and the chat's assistant avatar are uploaded in
49
+ the partner console, the only images the SDK shows, so a logo read from the project is named as the image to upload
50
+ rather than written. Custom fonts apply in-page only: the hosted frame keeps Everfur's fonts.
40
51
 
41
52
  **The chat and records mounts match the Everfur app, and the host seams are generated with them.** Chat has the
42
53
  header pet switcher (it appears by itself once two or more pets are registered), quick prompts, citations, thumbs
43
54
  feedback, the urgency banner and the history drawer built in. On an emergency or schedule-soon reply the banner's
44
55
  Find a vet pill opens the Everfur vet visit entry when Everfur has it on for your client, else calls `onFindVet`, else
45
- does not render: every in-page chat mount passes `onFindVet={findVet}`, which opens a maps search until you replace
46
- it with your own vet finder. A frame takes no `onFindVet`, so there the pill appears only while Everfur has its vet
47
- visit entry on for your client. The SDK bundles no picker and no
56
+ does not render. No generated mount passes `onFindVet`, so the plan writes no vet finder of its own that sends the
57
+ member to a page outside your app; the commented `onFindVet` line beside each in-page chat mount is for an app with
58
+ its own in-app vet finder (on React Native, passing it also replaces the Everfur vet entry on the check-in card). A
59
+ frame takes no `onFindVet`,
60
+ so there the pill appears only while Everfur has its vet visit entry on for your client. The SDK bundles no picker and no
48
61
  clipboard module, so an EXPO plan mounts `<EverfurChat attachments={{ pick: pickChatPhoto }}
49
62
  onCopy={copyText} />` and writes both helpers into `everfur/EverfurRoot.tsx`, the module the app mounts from its
50
63
  own entry (`app/_layout.tsx` in an Expo Router app, `App.tsx` otherwise): in an Expo app `pickChatPhoto` opens the system
@@ -70,24 +83,52 @@ because it asked for one) and always carries the size when the picker reports it
70
83
  records upload, so no image picker is wired there. The system document UI is not the media library and needs no
71
84
  permission on either platform, so unlike the chat photo picker there is nothing to request before it opens. On the web the
72
85
  browser is the picker and the speech engine: photo attachments and voice dictation are on by default, and
73
- `attachments={false}` and `dictation={false}` turn them off. Records is the app's screen flow: `<EverfurRecords>`
74
- opens on the pet's dashboard and owns its own stack (the hardware back key pops it), every screen is exported by
75
- name for your own navigator, the mount passes `petName={selectedPet.name}` for headings such as
76
- `{petName}'s lab trends` (and `onCopy={copyText}` on React Native, for the share sheet), and
77
- `initialScreen={{ name: 'request', requestId }}` opens a request directly, which is what the generated
78
- notification router's `record_request` handler says to do. Lab markers, share links, the record PDF link, fact
86
+ `attachments={false}` and `dictation={false}` turn them off. On React Native records is the app's screen flow:
87
+ `<EverfurRecords>` opens on the pet's dashboard and owns its own stack (the hardware back key pops it), and
88
+ `initialScreen={{ name: 'request', requestId }}` opens a request directly, which is what the generated notification
89
+ router's `record_request` handler says to do. On the web records is a page, the shape of Everfur's own web records
90
+ page: the generated `EverfurRecordsSurface` renders `EverfurRecordsPage` and opens what it links to (the full record,
91
+ the timeline, an upload) in the records flow in the same place, with no route added to your app; the page holds no clinic
92
+ request of its own, so `records-clinic` mounts beside it. Every screen is exported by name for your own navigator,
93
+ and the plan's `mount-records-screens` step names them per platform. The mount passes `petName={selectedPet.name}` for
94
+ headings such as `{petName}'s lab trends` (and `onCopy={copyText}` on React Native, for the share sheet). An Expo
95
+ records mount also passes `onOpenUrl={openRecordUrl}`, which handles the share sheet the way the Everfur app does: a
96
+ share link goes to the OS share sheet, and the record PDF is downloaded into the app's cache (`expo-file-system`),
97
+ shared as a file (`expo-sharing`) and deleted, so neither opens a browser. A bare React Native mount passes none, and
98
+ the SDK's default opens both in the system browser until you pass your own. Lab markers, share links, the record PDF link, fact
79
99
  edits and the signature gallery need the platform flag `partner.records_full_depth_enabled`; while it is off the
80
100
  SDK hides them.
81
101
 
82
- **Every plan that writes code names its environment.** `--environment staging|production` (or
102
+ **The vet visit runs inside your app.** A plan that mounts `televet` installs `@everfur/sdk@^0.9.0`, a release
103
+ whose `VetVisitButton`, `useVetVisit().open()` and `openVisit()`, and every vet entry inside chat open the visit in
104
+ the app by default, and writes the two things that default needs: `registerEverfurDaily(...)` once at load, from
105
+ `@everfur/sdk/televet/call` over `@daily-co/react-native-daily-js` (React Native) or from
106
+ `@everfur/sdk/web/televet/call` over `import('@daily-co/daily-js')` (the web), and one `<EverfurTelevetHost />`
107
+ inside the provider, where every visit opens: a full-screen modal on React Native, a dialog in the SDK's own DOM
108
+ subtree on the web. The button passes no `returnTo` and no `prefer`, the file registers no presenter and reads no
109
+ return URL, and the chat mount gets the member's state as `vetUsState`. On React Native the plan installs the Daily
110
+ modules the consumer app runs and the native setup (the Expo config plugin and a clean prebuild, or the bare
111
+ manifest and Info.plist edits), and the notifications file opens a tapped visit in the app through the same hook.
112
+ The booking lists vets only for the member's US state and the SDK never reads a device location, so the file's
113
+ `YOUR_USER_US_STATE` reaches the button, the chat and the notifications bridge, and `everfur verify --project-root`
114
+ fails while it is still `null` as shipped. The hosted visit in the system browser or a new tab is the SDK's explicit
115
+ `prefer="redirect"`, which no generated file passes. A plan without `televet` installs the same `@everfur/sdk@^0.9.0`
116
+ and none of the call.
117
+
118
+ <!-- Owner-approved 2026-10-01 (production sandbox plan, PC-1/PC-2): what `test` and the key prefixes mean. -->
119
+ **Every plan that writes code names its environment.** `--environment test|staging|production` (or
83
120
  `environment` over MCP) is required and never assumed; generated files read the publishable key and base
84
121
  URL from the framework's own variables (`NEXT_PUBLIC_`, `VITE_`, `EXPO_PUBLIC_`) and the plan writes the
85
- names into `.env.example`. Test mode is not a third deployment: it is your **sandbox tenant on the staging
86
- API** (`https://api-staging.everfur.com/api/v1`), issued an ordinary `pk_live_` key, whose entitlements
87
- report `livemode: false`. No deployed environment accepts a `pk_test_` key. `--environment test` is
88
- accepted and resolves to that same staging deployment. The web framework is detected from the project: the Next.js App Router gets a
122
+ names into `.env.example`. `test` is your **sandbox tenant on the production API**
123
+ (`https://api.everfur.com/api/v1`), issued a `pk_test_` key, whose entitlements report `livemode: false`;
124
+ `staging` is your sandbox tenant on the staging API (`https://api-staging.everfur.com/api/v1`), for early
125
+ access; `production` is your live tenant and its `pk_live_` key. The key prefix names the tenant mode: a
126
+ sandbox created before `pk_test_` keys were issued may still carry `pk_live_`. The web framework is detected from the project: the Next.js App Router gets a
89
127
  `'use client'` surface and a Route Handler, the Pages Router a component and an API route, Vite with React
90
- reads `import.meta.env`. `everfur verify --surfaces chat,records,televet,theme` exercises each surface
128
+ reads `import.meta.env` and gets `server/dev-server.ts`, a development server for its session route (a Vite build has
129
+ no server, so the plan also says where the route goes in production). A signed-in React Native app gets the same server
130
+ on its own route, with the URL that reaches it from the iOS simulator, the Android emulator and a device, and its
131
+ server-only names in `server/.env.example`, never in the app's env file. `everfur verify --surfaces chat,records,televet,theme` exercises each surface
91
132
  with the minted session (Televet mints a handoff only on a sandbox tenant, and a surface Everfur has not
92
133
  switched on is reported `GATED`, not failed); `--project-root <path>` adds offline checks for a secret key
93
134
  in source or client build output and for `'use client'`. `everfur diagnose --bundle` prints one support
@@ -97,28 +138,33 @@ document with no credential value in it.
97
138
 
98
139
  ### Hosted MCP: nothing to install
99
140
 
100
- Everfur runs the MCP server for you, so there is no key, token, variable or file to set. Today it is
101
- reachable at one endpoint:
141
+ Everfur runs the MCP server for you, so there is no key, token, variable or file to set. It is reachable at
142
+ two endpoints, one per deployment:
102
143
 
103
144
  ```
104
- https://mcp-staging.everfur.com/mcp
145
+ https://mcp.everfur.com/mcp production: your sandbox (test) tenant and your live tenant
146
+ https://mcp-staging.everfur.com/mcp staging: an early-access sandbox tenant on the staging API
105
147
  ```
106
148
 
107
- That is the staging deployment, and it is where your sandbox tenant lives while you build. **The production
108
- endpoint `https://mcp.everfur.com/mcp` is not available yet:** the hostname does not resolve, so a client
109
- pointed at it fails to connect rather than falling back to anything. Use the staging URL until Everfur tells
110
- you production is live.
149
+ Use the one for the deployment your tenant is on. A client pointed at the other one signs in to a different
150
+ deployment and does not fall back to anything.
111
151
 
112
152
  Add the URL to your editor as a remote (Streamable HTTP) MCP server. You paste the URL yourself; there is
113
153
  nothing to search for in a connector directory. Adding it is the whole configuration step:
114
154
 
115
- - **Claude Code:** `claude mcp add --transport http everfur https://mcp-staging.everfur.com/mcp`
116
- - **Cursor:** `.cursor/mcp.json` with `{"mcpServers":{"everfur":{"url":"https://mcp-staging.everfur.com/mcp"}}}`
117
- - **VS Code:** `.vscode/mcp.json` with `{"servers":{"everfur":{"type":"http","url":"https://mcp-staging.everfur.com/mcp"}}}`
155
+ - **Claude Code:** `claude mcp add --transport http everfur https://mcp.everfur.com/mcp`
156
+ - **Cursor:** `.cursor/mcp.json` with `{"mcpServers":{"everfur":{"url":"https://mcp.everfur.com/mcp"}}}`
157
+ - **VS Code:** `.vscode/mcp.json` with `{"servers":{"everfur":{"type":"http","url":"https://mcp.everfur.com/mcp"}}}`
118
158
  - **Codex:** a streamable HTTP server entry with that URL in `~/.codex/config.toml` (`[mcp_servers.everfur]`, `url`)
119
- - **claude.ai:** add it as a **custom connector** and paste the URL into the dialog. Click by click,
120
- including what the sign-in asks and how to tell it worked:
121
- [docs/ADD_THE_EVERFUR_CONNECTOR_IN_CLAUDE.md](docs/ADD_THE_EVERFUR_CONNECTOR_IN_CLAUDE.md)
159
+ - **claude.ai:** add it as a **custom connector** at <https://claude.ai/customize/connectors> (**+**, then
160
+ **Add custom connector**): name it `Everfur` and paste the URL exactly, all lowercase, `https://`, `/mcp` on
161
+ the end, no trailing slash. Leave **Request headers** empty: Everfur never wants a key there. Click
162
+ **Connect** and sign in to the partner console. The approval page names Claude as the application asking
163
+ and shows the address Claude will be sent back to, `https://claude.ai/api/mcp/auth_callback` or
164
+ `https://claude.com/api/mcp/auth_callback`; anything else is not Everfur's consent page. Connecting does not
165
+ switch it on for every conversation: in a chat, **+**, then **Connectors**, then toggle **Everfur**. To check
166
+ it worked, ask Claude to explain the Everfur error code `authExpired`: an answer through
167
+ `everfur_explain_error` means the connection itself is good.
122
168
 
123
169
  There is no key to type because the server authenticates you instead. On first connect it answers with a
124
170
  `401` naming its OAuth resource metadata, your client registers itself automatically (dynamic client
@@ -149,8 +195,7 @@ from where the server runs:
149
195
  `everfur_list_webhook_deliveries`, `everfur_get_webhook_delivery`, `everfur_list_events`,
150
196
  `everfur_get_usage_summary`, `everfur_get_records_status_summary` and `everfur_search_docs` (the error
151
197
  registry, capabilities and guide of your contract). None returns a signing secret, a key value or a
152
- member's content. The routes behind them are specified in
153
- [docs/HOSTED_TENANT_API.md](docs/HOSTED_TENANT_API.md).
198
+ member's content.
154
199
 
155
200
  Everything below is for running the server yourself, which is what you want when the agent runs beside the
156
201
  project (so `project_root` works) or when your organisation keeps its own MCP process.
@@ -188,8 +233,8 @@ The four values, and where they come from:
188
233
 
189
234
  | Variable | What it is |
190
235
  |---|---|
191
- | `EVERFUR_API_BASE_URL` | The staging API while you build. Your test tenant is a **sandbox tenant on staging**; there is no separate test deployment. Going live changes this to `https://api.everfur.com/api/v1`. |
192
- | `EVERFUR_PARTNER_KEY` | The sandbox tenant's publishable key. It is a `pk_live_` key (no deployed environment accepts `pk_test_`); what makes the tenant a sandbox is the tenant, and its entitlements report `livemode: false`. |
236
+ | `EVERFUR_API_BASE_URL` | The API of your sandbox tenant while you build: `https://api.everfur.com/api/v1` for a test (production sandbox) tenant, the staging API for an early-access one. Going live uses `https://api.everfur.com/api/v1`. |
237
+ | `EVERFUR_PARTNER_KEY` | The sandbox tenant's publishable key, `pk_test_...` (a sandbox created before `pk_test_` keys may still carry `pk_live_`); its entitlements report `livemode: false`. |
193
238
  | `EVERFUR_DOCS_KEY` | A read-only docs key (`dk_partner_...`) that opens `GET /partners/contract` and nothing else. |
194
239
  | `EVERFUR_SESSION_TOKEN` | A short-lived session your backend minted for a test user. Only `everfur_verify_integration` reads it. |
195
240
 
@@ -198,8 +243,10 @@ live changes every value in the block: the production base URL, and the producti
198
243
  key and docs key (its secret key changes on your backend at the same time). Production keys are issued only
199
244
  after `everfur_verify_integration` passes on the sandbox tenant.
200
245
 
201
- `EVERFUR_DOCS_KEY` is required. The Everfur contract (the error registry, capability packs, credential
202
- shapes and the decision trees the planner walks) is not included in this package: the tools fetch it from
246
+ `EVERFUR_DOCS_KEY` is required here, for the server you run yourself. The Everfur-hosted connector above
247
+ needs no docs key: it signs in with OAuth and is served the contract as your tenant. The Everfur contract
248
+ (the error registry, capability packs, credential shapes and the decision trees the planner walks) is not
249
+ included in this package: the tools fetch it from
203
250
  `GET /partners/contract` on `EVERFUR_API_BASE_URL`, presenting a partner docs key (`dk_partner_...`), and
204
251
  cache it on disk (see [Credentials](#credentials)). A partner admin mints a docs key with
205
252
  `POST /api/v1/partners/docs-keys` (list with `GET`, revoke with `DELETE /api/v1/partners/docs-keys/{key_id}`);
@@ -232,7 +279,12 @@ answer, naming each one:
232
279
  Node backend gets the files; any other backend gets the recipe.
233
280
 
234
281
  That is three questions for an anonymous integration and four for `partner-user-ref`. Once answered, the
235
- plan's steps include the complete integration file, ready to write.
282
+ plan's steps include the complete integration file, ready to write. Over MCP the tool first returns a small
283
+ summary with a `plan_id` and a number of `pages` (the goal, the detected facts, each step's id, title, one-line
284
+ intent and page, and the checklist); the same call with `plan_id` and `page` returns that page's steps in full,
285
+ commands and file bodies included, and page 1 also the detection, warnings and checklist in full. `steps`
286
+ (step ids) in place of `page` still returns those steps, a page at a time. `everfur plan` prints the whole plan at
287
+ once.
236
288
 
237
289
  The explicit `everfur-mcp` command is intentional: `@everfur/mcp` also ships `everfur-mcp-http` for a
238
290
  server deployment, so bare `npx @everfur/mcp` can no longer safely guess which executable you mean.
@@ -243,7 +295,7 @@ For a partner-operated cloud/server deployment, run the guarded Streamable HTTP
243
295
 
244
296
  ```bash
245
297
  export EVERFUR_API_BASE_URL="https://api-staging.everfur.com/api/v1"
246
- # No deployed environment accepts pk_test_: use the pk_live_ key of a tenant created as a sandbox.
298
+ # The publishable key of a tenant created as a sandbox (pk_test_..., or pk_live_... on an older staging sandbox).
247
299
  export EVERFUR_PARTNER_KEY="pk_live_..."
248
300
  export EVERFUR_DOCS_KEY="dk_partner_..."
249
301
  # For verification only: a short-lived session your backend minted. The server never reads the secret key.
@@ -260,14 +312,14 @@ put either secret in an agent prompt, a browser, or a client bundle. The HTTP pr
260
312
  scale behind a load balancer. It does **not** replace a platform OAuth service: use this mode when one
261
313
  partner owns the deployment and its credentials.
262
314
 
263
- For release gates, reverse-proxy requirements, negative tests, and the staged production rollout, use the
264
- [remote MCP staging and production runbook](docs/REMOTE_MCP_STAGING_PRODUCTION_RUNBOOK.md).
265
-
266
315
  Five tools are served: `everfur_plan_integration`, `everfur_verify_integration`, `everfur_diagnose`,
267
316
  `everfur_explain_error`, and `everfur_send_test_message`. Each is a body of the CLI, so the two surfaces
268
317
  answer the same question the same way. `everfur_explain_error` is the one to reach for when a response
269
- carries an error code: it needs no project, reads the registry from the fetched contract, and refuses a code
270
- it does not recognise instead of explaining it from the shape of the string.
318
+ carries an error code: it needs no project, reads the registry from the fetched contract, answers offline the SDK's
319
+ own codes (`EverfurError` and its closed sub-enums such as `AttachmentErrorCode` and `VetVisitErrorReason`), the
320
+ diagnostics reason codes, the CLI's own error codes and the `error.code` these tools return (`VERIFICATION_FAILED`,
321
+ `PLAN_CHANGED` and the rest), and refuses a code it does not recognise instead of explaining it from the shape of the
322
+ string. `everfur explain-error` gives the same answers.
271
323
 
272
324
  Every tool declares an `outputSchema` and returns `structuredContent`. A failed call carries one envelope
273
325
  whatever the tool: `error.code`, `error.retryable`, `error.next_action` (a closed vocabulary),
@@ -303,7 +355,7 @@ the package and the word after it names the binary.
303
355
  | `everfur diagnose` | Explain why each capability is or is not available to this caller. |
304
356
  | `everfur explain-error` | Explain an API error code: cause, retryability, and next action. Reads the registry from `--contract <file>` when given, otherwise from the contract fetched with `EVERFUR_DOCS_KEY`. |
305
357
  | `everfur contract validate` | Validate a contract-public projection: envelope shape, then digest. |
306
- | `everfur sandbox create` | Provision a partner secret key (Everfur-internal; requires an admin token). |
358
+ | `everfur sandbox create` | Everfur-internal (requires an admin token): mints a partner secret key and, because no claim flow exists to hand it over safely, revokes it again and exits `8`. |
307
359
 
308
360
  `--json` on any command emits a machine-readable document on stdout, which is what you want in CI.
309
361
 
@@ -315,7 +367,7 @@ CI is invited to branch on these. An existing code never changes meaning.
315
367
  |---|---|---|
316
368
  | `0` | OK | The command produced a complete answer. |
317
369
  | `1` | INTERNAL_ERROR | A bug in the CLI, or an unreadable filesystem. |
318
- | `2` | USAGE_ERROR | Malformed invocation: unknown command or flag, missing flag value. |
370
+ | `2` | USAGE_ERROR | Malformed invocation: unknown command or flag, missing flag value, or an argument value the command does not recognise (for example `explain-error <unknown code>`). |
319
371
  | `3` | UNDERSPECIFIED | The CLI refused to guess. Required facts were missing; supply them and retry. |
320
372
  | `4` | INVALID_CONTRACT | A named artifact was missing, unparseable, or failed schema validation. |
321
373
  | `5` | DIGEST_MISMATCH | A loaded artifact was well shaped but its digest did not match its content. |
@@ -323,6 +375,7 @@ CI is invited to branch on these. An existing code never changes meaning.
323
375
  | `7` | VERIFICATION_FAILED | `verify` ran and at least one hard assertion failed. |
324
376
  | `8` | SECRET_UNCLAIMABLE | `sandbox create` minted a key but no claim flow exists to hand it over safely. |
325
377
  | `9` | BLOCKED | Nothing you asked for can be built today: the contract blocks every capability you named, or the SDK ships no entry point for this project shape. |
378
+ | `10` | CONTRACT_CREDENTIAL_MISSING | The command needs the Everfur contract and has none: no `--contract` file, no `EVERFUR_CONTRACT_PUBLIC`, and no `EVERFUR_DOCS_KEY` to fetch it with. The error says where a docs key comes from, and that the Everfur-hosted MCP connector (OAuth) needs none. |
326
379
 
327
380
  ---
328
381
 
@@ -354,7 +407,7 @@ with no network call and no credential.
354
407
  | Variable | Used for |
355
408
  |---|---|
356
409
  | `EVERFUR_API_BASE_URL` | Everything that touches the network. No default host. |
357
- | `EVERFUR_PARTNER_KEY` | Your publishable key (`pk_live_...`). Identifies your tenant. A sandbox tenant is issued the same `pk_live_` form; no deployed environment accepts `pk_test_`. |
410
+ | `EVERFUR_PARTNER_KEY` | Your publishable key (`pk_test_...` or `pk_live_...`). Identifies your tenant. The prefix names the tenant mode: `pk_test_` for a sandbox tenant, `pk_live_` for a live one; a sandbox created before `pk_test_` keys may still carry `pk_live_`. |
358
411
  | `EVERFUR_PARTNER_SECRET_KEY` | Session minting, server side only. Never ship it in an app, and never put it in an MCP server's environment. `everfur verify` mints with it locally only when `EVERFUR_SESSION_TOKEN` is not set. |
359
412
  | `EVERFUR_SESSION_TOKEN` | A short-lived session your backend minted. `everfur verify` and `everfur_verify_integration` use it instead of minting (the MCP tool verifies only this way); `everfur diagnose` uses it for the signed path. |
360
413
  | `EVERFUR_DOCS_KEY` | Your partner docs key (`dk_partner_...`). Fetches the contract; accepted by no other route. Minted with `POST /api/v1/partners/docs-keys`. |
@@ -367,8 +420,9 @@ with no network call and no credential.
367
420
  | `EVERFUR_MCP_HTTP_ALLOWED_ORIGINS` | Optional browser-origin allowlist. Origins are rejected unless explicitly listed. |
368
421
  | `MCP_PUBLIC_URL` | Everfur-internal: the FULL endpoint URL of the hosted service (`everfur-mcp-hosted`), path included (`https://mcp-staging.everfur.com/mcp` on the staging deployment), exactly as a partner pastes it into Claude. It is the OAuth resource every token names and the base of every 401's metadata pointer; a bare origin is refused at start. With `EVERFUR_API_BASE_URL`, `EVERFUR_MCP_HTTP_PORT` and `MCP_TRUSTED_PROXY_HOPS` it is the hosted task's whole environment. Partners do not set this. |
369
422
  | `MCP_ALLOWED_HOSTS` | Everfur-internal, optional: extra `Host` values the hosted service accepts beside its own. `MCP_PORT` is read only when `EVERFUR_MCP_HTTP_PORT` is unset. Partners do not set these. |
370
- | `MCP_ALLOWED_ORIGINS` | Everfur-internal, optional: extra `Origin` values the hosted service's MCP endpoint accepts beside `https://claude.ai` and `https://claude.com`; any other `Origin` is refused with `403`. A browser page at an accepted origin gets its CORS preflight answered and can read the `401` challenge. Partners do not set this. |
423
+ | `MCP_ALLOWED_ORIGINS` | Everfur-internal, optional: extra `Origin` values the hosted service's MCP endpoint accepts beside `https://claude.ai`, `https://claude.com` and `https://chatgpt.com`; any other `Origin` is refused with `403`. A browser page at an accepted origin gets its CORS preflight answered and can read the `401` challenge. Partners do not set this. |
371
424
  | `MCP_TRUSTED_PROXY_HOPS` | Everfur-internal, optional: how many proxies appended to `X-Forwarded-For` (`1` behind the load balancer). Keys the hosted service's per-client budget of unauthenticated failures; `0` (default) uses the socket address. Partners do not set this. |
425
+ | `OPENAI_APPS_CHALLENGE_TOKEN` | Everfur-internal, optional: the domain verification token OpenAI's plugin portal generates. Set, the hosted service serves it as the whole plain-text body of `/.well-known/openai-apps-challenge` on its own host; unset or blank, that path is a `404`. Never logged. Partners do not set this. |
372
426
 
373
427
  **Getting keys:** Everfur keys are currently provisioned by the Everfur team during partner onboarding;
374
428
  there is no self-serve signup yet. Contact [support@everfur.com](mailto:support@everfur.com) to get set
@@ -377,8 +431,8 @@ up. The publishable key is designed to ship inside your app; the secret key is n
377
431
  ## Documentation
378
432
 
379
433
  The integration guide, API reference, error catalog and testing guide are at
380
- [everfur.com/docs](https://everfur.com/docs): adoption paths, the HTTP contract, API keys, the launch
381
- checklist and troubleshooting.
434
+ [sdk.everfur.com/v1/docs](https://sdk.everfur.com/v1/docs/index.html): adoption paths, the HTTP contract, API
435
+ keys, the launch checklist and troubleshooting.
382
436
 
383
437
  This page used to link to `github.com/Everfur-Health/everfur-sdk-rn`, which is a PRIVATE repository: the
384
438
  link returned 404 for every partner who clicked it, since a partner is outside Everfur by definition. If