notion, readwise, and the three concept extractors — all green, plus three Notion databases ready to receive published pages. About 20 minutes of work: two OAuth pop-ups, an agent prompt that creates the three Notion databases for you (or a manual click-through if you’d rather), and a kubectl rollout restart to pick up the rotated tokens.
The pipeline run itself lives on First Run; this page is purely the wiring.
Strategy gateway access. This pattern’s strategies (
highlight-distill, notion-publish) call MCP tools inline via ctx.mcp.call_tool(...) — so the strategy runner must be able to dial the gateway during a run. Since v0.5.2 the scaffold ships with strategy.gateway_access: true in the gateway ConfigMap and the runner sidecar wired with --gateway-url. If you upgraded from an older deploy, confirm the flag is present in your fracta-gateway ConfigMap before running the pipeline — the runner refuses to start (requires.mcp: true but ctx.mcp is None) without it.Prerequisites
Fracta installed and a runtime configured
Follow Installation and Your First Agent. Confirm
fracta spawn --task hello --contract "say hi" returns cleanly before continuing.A Readwise (or Reader) account with at least one synced highlight
Free accounts work. The pipeline reads through the hosted Readwise MCP — no API key is needed; fracta handles the OAuth grant.
A Notion workspace where you can create an integration and a database
You need a workspace where you (or your OAuth grant) can create new databases. Personal workspaces work; in shared workspaces, the workspace admin’s permissions during the OAuth consent determine what the integration can do.
A Kubernetes cluster reachable from your laptop
The reading-garden pattern targets the Kubernetes deployment (kind is the recommended default). The fracta gateway runs in-cluster and consumes the OAuth tokens as mounted Secrets. Confirm
kubectl cluster-info returns your kind/Docker Desktop/k3d cluster.Step 1 — Wire the hosted MCPs through fracta’s native OAuth
Both Notion and Readwise expose hosted MCP endpoints that require OAuth with PKCE. Fracta drives the OAuth dance itself: thefracta config mcp auth login <server> command opens your browser, completes the consent flow, and stores the resulting tokens in the OS keyring (macOS Keychain under the fracta.oauth service, libsecret on Linux). You then export those tokens as a Kubernetes Secret and apply it to the cluster — the gateway picks them up from a mounted file at boot.
No Node, no npx, no mcp-remote stdio bridge, no plaintext token cache.
1.1 — Register the servers in the gateway config
The gateway needs to know which servers exist before you can log into them. Both Notion and Readwise are already in the fracta catalog (mcp-servers/{notion,readwise}/server.yaml), and fracta init --scaffold k8s writes a starter gateway ConfigMap that includes both. If your scaffold predates spec-41, add them under mcp_servers.servers: in deployment/k8s/manifests/fracta-gateway.yaml’s ConfigMap data:
volumeMounts on the gateway container and volumes referencing the Secrets (the Secrets themselves don’t exist yet — optional: true lets the pod start anyway):
kubectl apply -k deployment/k8s/manifests/). The gateway will boot with auth_required on both servers — that’s expected.
1.2 — Run the OAuth flow locally
Thefracta config mcp auth login <server> command reads its server definition from a local YAML file passed via --config — it does not introspect the in-cluster gateway ConfigMap you edited in step 1.1. You’ll create two throwaway “login config” files in your fracta project root, one per server. They exist only to tell the CLI which OAuth endpoint to dance with; they hold no secrets and can be deleted (or kept for re-auth, your call) the moment step 1.4 succeeds.
Copy-paste these two files verbatim into your project root:
token_file / client_registration_file paths, because the CLI is about to write fresh tokens to your OS keyring, not read them from disk. The “real” server config — with the in-cluster token paths — already lives in the gateway ConfigMap you edited in step 1.1.
Then, from your fracta project root:
fracta.oauth, accounts <server>:token and <server>:client.
Why a separate login config? The CLI’s auth subcommand is intentionally decoupled from the in-cluster gateway — you can run logins on a laptop that has never connected to your cluster, or rotate tokens for a server that isn’t deployed yet. If you omit
--config, the CLI looks at your default fracta.yaml, which in the kubernetes scaffold only describes the thin-client control-plane connection and won’t contain the OAuth servers. The error is server "<name>" not found in config.Static Notion integration tokens do not work for the hosted MCP. The internal-integration
secret_... token issued by Notion’s “My integrations” page authenticates the REST API, not mcp.notion.com/mcp. The hosted MCP only accepts OAuth — which is exactly what fracta config mcp auth login notion performs. If you have an existing integration token wired into other tools, leave it where it is; this stack uses a separate OAuth grant.1.3 — Export the tokens as Kubernetes Secrets
Opaque Secret named fracta-mcp-<server> with two keys: token.json (access + refresh tokens) and client-registration.json (the dynamically-registered client ID). The --config flag points back at the same login YAML you used in step 1.2 — export and login share a single source of truth for which server they’re operating on.
The 2>/dev/null swallows a non-fatal warning the thin-client config validator emits about runtime.staging_dir (the login YAML omits it, since OAuth login doesn’t need staging). Without the redirect, that line lands inside the YAML you’re writing to disk and corrupts the Secret. Confirm the first line of each output file is apiVersion: v1 before applying.
1.4 — Apply the Secrets and pick them up
MCP client connected lines — one per server — each reporting a tool count (18 for notion, ~22 for readwise).
Re-authentication. The OAuth refresh tokens are long-lived (Readwise’s currently outlive the access token by months). When you do need to rotate — provider revoked, expired, or a new scope — repeat steps 1.2–1.4, reusing the same
<server>-login.yaml files with --config. There’s no separate “logout” command needed on the cluster side; kubectl apply overwrites the existing Secret and the gateway rollout picks up the new file. Locally, fracta config mcp auth logout <server> clears the keyring entries before a fresh login.Step 2 — Provision the three Notion databases
Since v0.5.2 the Reading Garden pipeline publishes into a three-database mirror — Sources, Highlights, and Concepts — linked by Notion RELATION columns. Each Notion page comes back from the API with itsdata_source_id, which is what the strategy params take. Create all three now and capture their IDs.
You have two paths: let an agent create the databases for you via the Notion MCP, or click through three create-database flows in the Notion UI. The agent path is faster and gets the schemas exactly right.
- Agent-driven (recommended)
- Manual (Notion UI)
The OAuth grant you completed in Step 1 gives the fracta gateway the same Notion permissions your user has — including creating new databases. Drive that from any agent CLI with a Notion MCP connection (Claude Code, Codex, OpenCode), or spawn a fracta worker that talks back to your gateway.Via a fracta worker (uses the gateway’s already-configured Notion connection):Via your local agent CLI (Claude Code / Codex / OpenCode with the Notion MCP wired): paste the same prompt block (the part between the
PROMPT markers) into your agent session. The agent’s Notion MCP must be the hosted mcp.notion.com/mcp server (the same one fracta uses), authenticated to the same workspace you ran OAuth against in Step 1.The data_source_id values feed straight into the First Run prompt as notion_sources_database_id, notion_highlights_database_id, and notion_concepts_database_id.No extra sharing step. The OAuth grant you completed in Step 1 gives the fracta gateway the same Notion permissions your user has in the workspace you authorised — including the ability to create databases, read and write pages, and follow relations. Anything you (the user who completed OAuth) can do in your workspace, the gateway can do. There is no per-database “Connections” / “Add connections” / integration-sharing step.
Why three databases? The v3 architecture mirrors the graph’s three-tier
DomainSource -> Document -> Highlight into Notion, with Concept as a separate dimension. Each Concept page links to the highlights that triggered it; each highlight links to its source book/article. This is what makes Notion useful as a reading garden rather than a flat dump of concept pages — you navigate from a concept back into its evidence.Step 3 — Add the concept-extractor MCPs
The three extractor servers run as containers shipped fromfracta-mcp-servers. They speak streamable-http on port 8000 at path /mcp, require no auth at the MCP layer (the gateway envelope handles identity), and each expose a single tool: keybert_extract_tool, gliner_extract_tool, and spacy_extract_tool respectively.
In Kubernetes, each runs as a Deployment + Service in the fracta namespace. Add (or confirm) these entries under mcp_servers.servers: in the gateway ConfigMap so the gateway can route to them by Service DNS:
deployment/k8s/manifests/concept-*.yaml if you scaffolded the pattern; otherwise pull them from fracta-mcp-servers.
Session pinning matters.
concept-gliner loads ~1.4 GB of DeBERTa-v3 weights once per MCP session, in its _lifespan hook. The fracta gateway must pin mcp-session-id to the same upstream pod for the lifetime of a session, or every call reloads the model (10–30 s per request — the strategy becomes unusable). The default gateway configuration already pins; if you replace it with a custom load balancer, preserve sticky sessions.Step 4 — Verify the stack
Three commands and a sanity-check spawn. By the end of this section you should see five servers reachable through the gateway, all returning their expected tool inventories.Tools: block listing notion.notion-search, readwise.list_highlights, concept-keybert.keybert_extract_tool, concept-gliner.gliner_extract_tool, concept-spacy.spacy_extract_tool among the 27+ visible tools, all prefixed with + (allowed). If a server is missing, check its mcp_servers.servers: entry in the gateway ConfigMap. If a tool is prefixed - [denied_by_policy], your tool_policy: block is restricting it.
Confirm each hosted MCP returns a real response:
verify-readwise returns a 429, you have hit the list-highlights 20-req/min ceiling — wait one minute and retry. The highlight-distill strategy paginates against this same limit using a watermark_iso parameter, so the first real run uses a recent watermark to keep total request count bounded.
Notion API limit: 3 req/s. The hosted Notion MCP enforces the documented Notion REST limit. The
notion-publish strategy spaces calls accordingly, but ad-hoc batch operations are easy to overshoot. As a rough number, publishing 100 Concept pages takes about 2.5 minutes wall-clock once retries and backoff are factored in.kubectl get pods -n fracta) and confirm the gateway can resolve the service DNS from its own container.

