mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-31 15:37:49 +00:00
mcp: project context, constraints, and one-time infra request
Adds mcp/CLAUDE.md as the standing context for the Artcraft MCP server: hard constraints (no Rust changes, no webapp changes without approval, library OAuth server, Workers hosting, read-only M1), the verify-as-you-go working method, the environment model (bindings not branches, fake upstream), the architecture seams, verified upstream API facts, client requirements, security rules, and the API-shape source-of-truth rule. mcp/docs/infra-request.md batches the one-time Cloudflare, repo-secret, and Google console asks for the backend team. Root CLAUDE.md gains the mcp/ entry in the project tree. Claude-Session: https://claude.ai/code/session_01H3gfN2NCx3hBXSrZXYHUQ3
This commit is contained in:
1 parent
915728e9c7
commit
984e8d3845
3 files changed
+383
No files matched your search
@@ -51,6 +51,7 @@ artcraft/
|
||||
│ │ ├── artcraft/ # ArtCraft the Tauri app's frontend. Used in conjunction with `artcraft` the Rust crate.
|
||||
│ │ └── artcraft-website/ # The website for https://getartcraft.com
|
||||
│ └── libs/ # Support libraries, reusable React components, etc.
|
||||
├── mcp/ # Artcraft MCP server (Cloudflare Workers, TypeScript). Standalone; see mcp/CLAUDE.md
|
||||
└── Cargo.toml # Rust monorepo workspace
|
||||
```
|
||||
|
||||
|
||||
+329
@@ -0,0 +1,329 @@
|
||||
# Artcraft MCP Server (`mcp/`)
|
||||
|
||||
A standalone TypeScript service at `mcp.getartcraft.com` that lets people connect Claude,
|
||||
ChatGPT, Gemini CLI and other MCP clients to their own Artcraft account. Read-only in M1
|
||||
(credits, account, job status, model catalog, cost estimates). It talks to the production
|
||||
Artcraft API (`storyteller_web`) exactly like a browser would, on the user's behalf.
|
||||
|
||||
Design doc (decisions, auth flow, tool table, milestones):
|
||||
https://claude.ai/code/artifact/ba27f019-b444-4d93-ba45-b6703edec913
|
||||
|
||||
This file is the standing context for anyone (human or Claude) working in `mcp/`. The rules
|
||||
below are constraints, not suggestions. When a rule and convenience conflict, the rule wins;
|
||||
raise it with the owner rather than working around it.
|
||||
|
||||
## Hard constraints (decided 2026-08-24)
|
||||
|
||||
- **No changes to the Rust backend.** Ever, from this project. Every gap is absorbed here.
|
||||
Findings that need backend work go in `mcp/docs/backend-handoff.md`, not in a PR to `crates/`.
|
||||
- **No changes to the webapp (`frontend/apps/artcraft-webapp`) without explicit owner approval.**
|
||||
The only candidate changes are (a) a "Connected AI apps" settings link and (b) the M2
|
||||
`/connect` sign-in hand-off. Bring the exact diff and the justification *before* writing it.
|
||||
- **Do not hand-roll the OAuth authorization server.** Use `@cloudflare/workers-oauth-provider`.
|
||||
It is a solved problem; owning one is not a good use of this project's time. Wrap it behind
|
||||
our interfaces; never fork it.
|
||||
- **Minimize involvement of other teams and surface area outside `mcp/`.** DNS, Google Cloud
|
||||
console, repo secrets are other people's; design so their absence delays nothing (ship on
|
||||
`*.workers.dev`, fall back to password sign-in until the Google origin is added, etc.).
|
||||
- **Hosting is Cloudflare Workers.** KV for OAuth state. `wrangler deploy` is the release.
|
||||
- **Read-only in M1.** No tool may spend credits, upload, mutate, or delete. The upstream path
|
||||
allowlist enforces this in code (see Architecture) — not tool descriptions, not model behaviour.
|
||||
|
||||
## Environments — one code path, bindings not branches
|
||||
|
||||
The MCP Worker has exactly one code path. Which upstream it talks to, which KV namespace it
|
||||
uses, and which Google client id it presents are **Worker bindings** read once at
|
||||
construction (`UPSTREAM_API_HOST`, KV id, `GOOGLE_CLIENT_ID`). There is no `if (env)` in
|
||||
`mcp/src`, no debug mode, no test-only behaviour. Configuration lives in `wrangler.toml`
|
||||
`[env.*]` blocks and is reviewed in git like code.
|
||||
|
||||
| Env | MCP server | Upstream | Used for |
|
||||
|--------------|-----------------------------------------|-------------------------------------------|-----------------------------------------------------------------|
|
||||
| `local` | `wrangler dev` | `fake-upstream` on `localhost:12345` | the daily loop: unit / contract / OAuth tests, MCP Inspector, SDK client |
|
||||
| `preview` | PR deploy on `*.workers.dev`, own KV | deployed `fake-upstream` Worker | real Claude.ai / ChatGPT / Gemini CLI end-to-end, fake data and credits |
|
||||
| `production` | `mcp.getartcraft.com`, own KV | `https://api.storyteller.ai` | read-only smoke with the demo account, post-deploy and weekly |
|
||||
|
||||
### `mcp/fake-upstream/` — a fake of the API, not of this server
|
||||
|
||||
A separate package with its own `wrangler.toml` and Worker name (`artcraft-api-fake`):
|
||||
in-memory, seeded fixture, stateful for a session, no database, no third-party calls,
|
||||
sign-in as `localdev1` / `localdev1pass`. It implements only the allowlisted routes plus
|
||||
sign-in/sign-out, and every response it emits is validated against the published
|
||||
`api.json` in CI so it cannot drift silently.
|
||||
|
||||
It is **ported from** the unmerged `infra/fake-storyteller-web` branch (same seeded-fixture
|
||||
approach, same test account) — not copied wholesale, and not maintained as a second copy of
|
||||
anything. If a route already exists there, port it; do not reinvent it.
|
||||
|
||||
Boundary rules, all enforced mechanically:
|
||||
- `mcp/src` never imports from `mcp/fake-upstream` (eslint `no-restricted-imports` plus a
|
||||
test that the production bundle contains no fake-upstream module).
|
||||
- `[env.production]` pins the literal `https://api.storyteller.ai`; a CI test parses
|
||||
`wrangler.toml` and asserts production → real host, preview → not the real host.
|
||||
- The Worker asserts at startup that the configured upstream host matches its environment's
|
||||
expected host; on mismatch every request fails 500 naming the mismatch. An invariant, not
|
||||
a switch.
|
||||
- The production deploy job deploys `artcraft-mcp --env production` only; the fake deploys
|
||||
only from the preview job.
|
||||
- Credentials cannot cross: a fake session never validates against production (different
|
||||
signing) and a real session never validates against the fake (seeded users only).
|
||||
|
||||
### Keeping the fake honest
|
||||
|
||||
- Contract tests validate the fake's responses and our fixtures against `api.json` (Ajv).
|
||||
- A scheduled CI job runs the allowlisted read-only calls against production with the demo
|
||||
account, redacts, and diffs response *shapes* against the fake's fixtures. Production is
|
||||
touched by that job, not by people during development.
|
||||
|
||||
### What still needs production
|
||||
|
||||
Post-deploy read-only smoke, the weekly e2e run, the scheduled recorder, directory review.
|
||||
Nothing else. The demo account's credentials live only in `mcp/.dev.vars` (gitignored) and
|
||||
in repo secrets (`SMOKE_USERNAME`, `SMOKE_PASSWORD`) — never in code, fixtures, or test
|
||||
names. Tests assert shapes, not values, and never assume specific data on that account.
|
||||
|
||||
### Other-team dependencies
|
||||
|
||||
Cloudflare account, GitHub repo secrets, and the Google Cloud console are held by the
|
||||
backend team. Setup is one-time: batch every ask into `mcp/docs/infra-request.md` and hand it
|
||||
over once. Until it lands nothing blocks development — `local` needs nothing external,
|
||||
preview deploys can run from a developer's `wrangler login`, and sign-in is password-only
|
||||
until the Google origin is added. All MCP work happens on `mcp/*` branches in the dedicated
|
||||
worktree (a sibling checkout such as `../storyteller-mcp`), never on frontend or Rust branches.
|
||||
|
||||
## Working method — verify as you go
|
||||
|
||||
This is auth code fronting a live product. Errors here are account-security errors, and they
|
||||
must never arrive in bulk.
|
||||
|
||||
1. **Build in small units and verify each one before moving on.** A function, a route, a
|
||||
schema. Write or extend its test, run the test, read the output, *then* start the next unit.
|
||||
Never implement a whole feature and only then check whether it works — the scope is too big
|
||||
to debug, and a wrong assumption in the first unit poisons everything built on it.
|
||||
2. **Every PR ships with the tests that prove it.** No "tests in a follow-up". A PR without
|
||||
tests for its new behaviour is not mergeable. Tests are part of the change, not a companion.
|
||||
3. **Keep PRs small and single-purpose.** Scaffold; upstream client + types; OAuth provider
|
||||
wiring; consent page; one tool group; connections page — each its own PR. If a PR needs
|
||||
more than one paragraph to describe, split it.
|
||||
4. **Do a broader debug pass after the feature is assembled** — run the full suite, the e2e
|
||||
client test, and MCP Inspector against `wrangler dev`. This pass is *in addition to*
|
||||
step 1, never a replacement for it.
|
||||
5. **Verify claims about the upstream API against the spec or the Rust source, not memory.**
|
||||
The published spec is https://storyteller-docs.netlify.app/api.json; the handlers are under
|
||||
`crates/service/web/storyteller_web/src/http_server/`. Read-only reference — see constraints.
|
||||
6. **When something is uncertain, say so in the PR description** and pin it with a test that
|
||||
documents the assumed behaviour, so a wrong assumption fails loudly later instead of silently.
|
||||
|
||||
## Architecture
|
||||
|
||||
Layered so that the three things most likely to change are behind interfaces and can be
|
||||
swapped without touching tools or transport.
|
||||
|
||||
```
|
||||
MCP client ──► Worker entry (OAuthProvider wraps the Hono app)
|
||||
│
|
||||
├── /.well-known/*, /authorize, /token, /register, /revoke (library)
|
||||
├── /authorize UI ──► Authenticator ← swap point 1
|
||||
├── /mcp, /sse ──► TokenResolver → Principal ← swap point 2
|
||||
│ │
|
||||
│ ▼
|
||||
│ UpstreamClient (allowlist + UpstreamCredential) ← swap point 3
|
||||
│ │
|
||||
│ ▼
|
||||
│ tools/* (pure functions of Principal + input)
|
||||
├── /connections (list / revoke grants; later: personal tokens)
|
||||
└── /, /healthz
|
||||
```
|
||||
|
||||
### Interfaces (the seams)
|
||||
|
||||
- **`Authenticator`** — turns a consent interaction into an `UpstreamCredential`.
|
||||
M1 implementation: `MpcHostFormAuthenticator` (Google Sign-In primary, username/password
|
||||
fallback, both proxied server-side to `POST /v1/accounts/google_sso` / `POST /v1/login`).
|
||||
Planned M2: `WebappRedirectAuthenticator` (`app.getartcraft.com/connect` hand-off). The
|
||||
consent route must not know which one it has.
|
||||
- **`UpstreamCredential`** — what we hold for a grant and how it is attached to a request.
|
||||
M1: `{ kind: "session", signedSession }` sent as the `session` header. Designed to admit
|
||||
`{ kind: "api_key" }` or `{ kind: "backend_token" }` later if the backend grows a proper
|
||||
token system. Store `kind` in grant props from day one; never assume "session" outside
|
||||
the credential module. `revoke()` is part of the interface (session → `POST /v1/logout`).
|
||||
- **`TokenResolver`** — `resolveToken(bearer) → Principal | null`. M1: OAuth access tokens
|
||||
via the library. M2: personal tokens (`artcraft_mcp_` prefix) via `resolveExternalToken`.
|
||||
Tools and the upstream client receive a `Principal` and must not know which token kind
|
||||
produced it.
|
||||
- **`GrantStore`** — thin adapter over KV. Not because we plan to leave Workers, but so tests
|
||||
run against an in-memory implementation and the encryption scheme lives in one place.
|
||||
- **`UpstreamClient`** — `openapi-fetch` client built from generated types; refuses any path
|
||||
not in `upstream/allowlist.ts`. Adding a path is a deliberate, reviewed change with a test.
|
||||
|
||||
### Principal
|
||||
|
||||
```ts
|
||||
type Principal = {
|
||||
grantId: string;
|
||||
scopes: Scope[]; // "read:account" | "read:jobs" | "read:catalog" | (later) "generate"
|
||||
credential: UpstreamCredential;
|
||||
client: { label: string }; // "Claude", "ChatGPT", "personal token · Gemini API"
|
||||
};
|
||||
```
|
||||
|
||||
### Lifetimes (why 90 days)
|
||||
|
||||
An Artcraft session cannot be refreshed — there is no endpoint — only held or deleted. So:
|
||||
access tokens 1 h; refresh tokens rotate, 30 days idle, **90 days absolute**, then re-consent.
|
||||
Personal tokens (M2) inherit the same 90-day ceiling. On expiry or revoke, always call the
|
||||
credential's `revoke()`. The 90-day window is also the runway for swapping `UpstreamCredential`
|
||||
to a stronger backend token system if one appears: grants naturally roll over.
|
||||
|
||||
## Verified facts about the upstream API (2026-08-24)
|
||||
|
||||
Cite these; re-verify if the file references stop matching.
|
||||
|
||||
- Sessions: signed JWT, accepted as the `session` **cookie or** a lowercase `session` **header**
|
||||
(`crates/lib/actix_artcraft/src/sessions/user_sessions/http_user_session_manager.rs:143-163`).
|
||||
`POST /v1/login` and `POST /v1/accounts/google_sso` return `signed_session` in the body.
|
||||
Each login creates a new `user_sessions` row. `POST /v1/logout` with the header deletes
|
||||
that row and purges its Redis cache (`endpoints/users/logout_handler.rs:40-48`).
|
||||
- No captcha or bot gate on `/v1/login`. No upstream rate limit on it either — ours is required.
|
||||
- Session-only endpoints (unreachable with an API key): `/v1/credits/namespace/{ns}`,
|
||||
`/v1/session`, `/v1/jobs/session`, `/v1/subscriptions/namespace/{ns}`. This is why the
|
||||
forwarded credential is a session, not an API key.
|
||||
- `POST /v1/omni_gen/cost/*` is anonymous → estimates are plan-blind. Label them so.
|
||||
`GET /v1/omni_gen/models/*` is plan-aware when a session is sent.
|
||||
- Google Sign-In audience is one hardcoded client id checked server-side
|
||||
(`endpoints/users/google_sso/check_claims.rs`). Using that client id from
|
||||
`mcp.getartcraft.com` only needs the origin added in the Google Cloud console.
|
||||
- Job tokens are `jinf_…`; batch status accepts mixed prefixes (`jinf_`, `batch_g_`).
|
||||
- Error bodies: `{ success: false, error_code, error_code_str, message? }`.
|
||||
|
||||
## Client requirements that bite
|
||||
|
||||
- Unauthenticated `/mcp` → **401** with
|
||||
`WWW-Authenticate: Bearer resource_metadata="https://mcp.getartcraft.com/.well-known/oauth-protected-resource"`.
|
||||
Claude ignores this header on any status other than 401.
|
||||
- Protected-resource `resource` must equal the MCP URL byte-for-byte as users paste it.
|
||||
One canonical URL: `https://mcp.getartcraft.com/mcp`; 301 everything else.
|
||||
- Authorization-server metadata must advertise `code_challenge_methods_supported: ["S256"]`,
|
||||
`client_id_metadata_document_supported: true`, and `"none"` in
|
||||
`token_endpoint_auth_methods_supported`, or Claude falls back to DCR per connection.
|
||||
- `/token` accepts `application/x-www-form-urlencoded`; `/register` accepts JSON. Dead
|
||||
refresh tokens return `invalid_grant`, not a custom code.
|
||||
- Redirect allowlist: `https://claude.ai/api/mcp/auth_callback`, ChatGPT's callback, and
|
||||
loopback `http://localhost/callback` + `http://127.0.0.1/callback` with **any port**.
|
||||
- Claude waits 10 s for discovery/register/token, 30 s for refresh. Anthropic egress is
|
||||
`160.79.104.0/21`; never WAF-block it on either host.
|
||||
- Keep a legacy `GET /sse` alongside Streamable HTTP `/mcp`.
|
||||
|
||||
## Security rules
|
||||
|
||||
- Credentials (sessions, tokens, passwords) are never logged, never in URLs, never in error
|
||||
messages. The fetch wrapper redacts the `session` header; logs carry `grantId` only.
|
||||
- Grant props stay encrypted with the library's token-wrapped keys. Do not add a plaintext
|
||||
copy anywhere "for convenience".
|
||||
- Bearer tokens only in the `Authorization` header. Query-string tokens are rejected.
|
||||
- Rate limits: per token on `/mcp`; per IP **and** per username on the consent POST; per IP
|
||||
on `/register`, `/authorize`, `/token`.
|
||||
- Scopes are enforced at the tool boundary and at the allowlist. Reserve `generate` now; no
|
||||
M1 grant may carry it.
|
||||
|
||||
## Tool conventions
|
||||
|
||||
- One file per tool in `src/mcp/tools/`, exporting `{ name, description, inputSchema,
|
||||
outputSchema, annotations, handler }`. Handlers are pure functions of `(principal, input)`
|
||||
and call only `UpstreamClient`.
|
||||
- Annotations in M1: `readOnlyHint: true`, `idempotentHint: true`, `openWorldHint: false`.
|
||||
- Return `structuredContent` **and** a short text block (ChatGPT needs both).
|
||||
- Descriptions are written for the model: what it returns, when to call it, what the
|
||||
arguments mean, what tokens look like. Not marketing copy.
|
||||
- Error mapping: upstream 401 → MCP 401 (metadata pointer); upstream 400 → tool error with
|
||||
the server's `message`; upstream 5xx → one retry then tool error. Never swallow.
|
||||
- Upstream types are **generated** from the published `api.json` into
|
||||
`src/upstream/schema.d.ts` (`pnpm gen:api`). Never hand-write a DTO for an upstream shape.
|
||||
|
||||
### Source of truth for API shape
|
||||
|
||||
- **The published OpenAPI document is the source of truth**:
|
||||
https://storyteller-docs.netlify.app/api.json (rendered at
|
||||
https://storyteller-docs.netlify.app/). It is generated from the Rust handlers by
|
||||
`cargo run --bin docs-cli`, so it is the backend's own statement of the contract.
|
||||
- App usage (`frontend/libs/api` = `@storyteller/api`, the desktop app's `ApiManager`, the
|
||||
webapp's call sites) is a **secondary reference** for how endpoints are used in practice —
|
||||
which fields matter, what values are sent, sequencing — never for shape.
|
||||
- **When the two disagree, surface it; don't silently pick one.** Note the discrepancy in
|
||||
the PR description and in `docs/backend-handoff.md` (endpoint, what the spec says, what the
|
||||
app sends/expects, which we followed). Known examples at time of writing: the TS
|
||||
`OmniGen{Image,Video}CostResponse` omit `failures_are_refunded`; the TS image-generate
|
||||
response omits `all_job_tokens`. Build against the spec; flag the drift.
|
||||
- If the spec itself is wrong (a response observed from production doesn't validate against
|
||||
it), that is a backend bug: record it in the handoff note with the captured, redacted
|
||||
response, and pin our behaviour with a test that documents the observed shape.
|
||||
|
||||
## Testing
|
||||
|
||||
Four layers; all run offline in CI (`pnpm test`), inside the Workers runtime via
|
||||
`@cloudflare/vitest-pool-workers`.
|
||||
|
||||
1. **Unit** — tool handlers, credential module, allowlist, resolver — against an MSW-mocked
|
||||
upstream with fixtures captured from real responses.
|
||||
2. **Contract** — every fixture validated with Ajv against `api.json`. A regenerated spec that
|
||||
changes a shape fails here first.
|
||||
3. **OAuth conformance** — metadata shape, CIMD flags, DCR, PKCE S256, form-encoded `/token`,
|
||||
refresh rotation and reuse detection, `invalid_grant`, the 401 header, loopback matching.
|
||||
4. **End-to-end** — the real `@modelcontextprotocol/sdk` client with an `OAuthClientProvider`
|
||||
that completes our consent page, against the Worker in miniflare.
|
||||
|
||||
Manual: MCP Inspector against `wrangler dev`. Post-deploy: `pnpm smoke` with a dedicated test
|
||||
account against production. Weekly scheduled e2e run against production.
|
||||
|
||||
## Code style
|
||||
|
||||
- TypeScript strict, ESM, two-space indent, Node 22 / Workers runtime, pnpm.
|
||||
- Top-to-bottom reading order in every file, matching the repo convention: constants, then
|
||||
the primary exported type/function, then supporting types, then helpers below their callers.
|
||||
- No `any`; no `as` casts to silence the generated types — fix the fixture or the schema.
|
||||
- `maybe_` is the upstream's optional-field prefix; keep upstream field names verbatim in
|
||||
generated types, camelCase only in our own domain types.
|
||||
- eslint 10 flat config (`typescript-eslint` strict + stylistic, type-checked) + prettier at
|
||||
100 columns. `pnpm check` runs typecheck → lint → format → test and must be green before a
|
||||
PR is opened; CI runs the same four steps.
|
||||
- Binding types come from `wrangler types` (`pnpm types`), not from `@cloudflare/workers-types`.
|
||||
Rerun it after any `wrangler.toml` change; `pnpm typecheck` does so automatically.
|
||||
- No feature flags or environment-dependent behaviour in production code paths (repo-wide
|
||||
rule). Configuration is secrets and bindings, not branches.
|
||||
|
||||
## Deferred, not removed
|
||||
|
||||
Keep these possible; do not build them in M1.
|
||||
|
||||
- Personal tokens for header-based clients (Gemini API/Vertex, OpenAI Responses API,
|
||||
Anthropic Messages API connector): `resolveExternalToken` + `/connections` UI. Read-only
|
||||
scopes only, 90-day max, shown once, hashed at rest.
|
||||
- `WebappRedirectAuthenticator` (`app.getartcraft.com/connect`) replacing the password form.
|
||||
- `generate` scope and generation tools with cost confirmation via elicitation.
|
||||
- Swapping `UpstreamCredential` to a backend-issued token if the backend ever offers one.
|
||||
- Directory submissions (Claude connector directory, ChatGPT apps) — keep annotations and
|
||||
output schemas review-ready from day one so this is a form, not a refactor.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
mcp/
|
||||
├── CLAUDE.md # this file
|
||||
├── package.json # packageManager: pnpm@11 (via corepack), engines.node >=22, type: module
|
||||
├── pnpm-workspace.yaml # workspace = root server package + fake-upstream; pnpm build-script allowlist lives here
|
||||
├── wrangler.toml # local (top-level) / preview / production blocks; the security boundary for upstream hosts
|
||||
├── worker-configuration.d.ts # GENERATED by `pnpm types` (wrangler types); gitignored; regenerated in typecheck and CI
|
||||
├── docs/backend-handoff.md # findings for the storyteller-web owner; not our work
|
||||
├── docs/infra-request.md # one-time batched asks for the backend team (Cloudflare, secrets, Google)
|
||||
├── fake-upstream/ # separate Worker faking the allowlisted API routes; never imported by src/
|
||||
├── scripts/gen-api.ts # fetch api.json → openapi-typescript
|
||||
├── src/
|
||||
│ ├── index.ts # Worker entry: OAuthProvider wrapping the Hono app
|
||||
│ ├── app.ts # Hono routes
|
||||
│ ├── auth/ # provider config, Authenticator impls, consent page (Hono JSX)
|
||||
│ ├── tokens/ # TokenResolver, Principal, GrantStore adapter
|
||||
│ ├── upstream/ # UpstreamClient, UpstreamCredential, allowlist, generated schema
|
||||
│ ├── mcp/ # McpServer factory, tools/, resources, prompts
|
||||
│ └── pages/ # landing, connections
|
||||
└── test/ # unit/, contract/, oauth/, e2e/, fixtures/
|
||||
```
|
||||
@@ -0,0 +1,53 @@
|
||||
# Artcraft MCP — one-time infrastructure request
|
||||
|
||||
For the backend team, who hold the Cloudflare account, the GitHub repo secrets, and the
|
||||
Google Cloud console. Everything below is one-time setup; nothing recurring. Items are
|
||||
ordered so that partial completion still unblocks something. None of it changes
|
||||
`storyteller-web` or any Rust code.
|
||||
|
||||
Context: `mcp/CLAUDE.md` and the design doc linked there.
|
||||
|
||||
## 1. Cloudflare (unblocks real deploys)
|
||||
|
||||
| Item | Value / notes |
|
||||
|------------------------------|-------------------------------------------------------------------------------|
|
||||
| Workers project | name `artcraft-mcp` (matches `mcp/wrangler.toml`) |
|
||||
| KV namespace | one, named `artcraft-mcp-oauth`; id goes into `wrangler.toml` `[[kv_namespaces]]` |
|
||||
| API token | scoped to: Workers Scripts:Edit, Workers KV Storage:Edit, Account Settings:Read (for the account id). Not a global key. |
|
||||
| Account id | needed alongside the token |
|
||||
| Custom domain (can be later) | `mcp.getartcraft.com` → Workers custom domain if the zone is on Cloudflare; otherwise a CNAME to the `workers.dev` hostname we'll provide |
|
||||
| Secrets on the Worker | we set these ourselves via `wrangler secret put` once we have the token; nothing for you to enter |
|
||||
|
||||
## 2. GitHub repository secrets (unblocks CI deploys)
|
||||
|
||||
| Secret name | Value |
|
||||
|-------------------------|----------------------------------------------|
|
||||
| `CLOUDFLARE_API_TOKEN` | the token from §1 |
|
||||
| `CLOUDFLARE_ACCOUNT_ID` | the account id from §1 |
|
||||
| `SMOKE_USERNAME` | the production demo account's username |
|
||||
| `SMOKE_PASSWORD` | its password |
|
||||
|
||||
The workflow that reads them is `.github/workflows/mcp.yml`, path-filtered on `mcp/**`.
|
||||
It only deploys on `main`; pull requests run lint/typecheck/tests and nothing else.
|
||||
|
||||
## 3. Google Cloud console (unblocks "Continue with Google" on the consent page)
|
||||
|
||||
On the existing OAuth client `788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com`
|
||||
(the one `storyteller-web` already verifies against — no backend change needed):
|
||||
|
||||
- Add `https://mcp.getartcraft.com` to **Authorized JavaScript origins**.
|
||||
- Add `https://artcraft-mcp.<account-subdomain>.workers.dev` as well, for previews.
|
||||
|
||||
No new client, no new secret. Until this is done, the consent page offers password sign-in
|
||||
only.
|
||||
|
||||
## 4. Demo account
|
||||
|
||||
Confirm the production demo account may be used for automated read-only smoke tests
|
||||
after each deploy and a weekly scheduled run. The tests never generate, upload, or delete,
|
||||
so credits are not consumed.
|
||||
|
||||
## What we'll send back once §1 is done
|
||||
|
||||
- The `workers.dev` hostname (for the CNAME in §1 and the origin in §3).
|
||||
- The KV namespace id confirmation in `wrangler.toml`.
|
||||
Reference in new issue
Block a user