notion-mcp-remote

notion-mcp-remote forgejo

Notes

Project Page 1
  • notion-mcp-remote project-notion-mcp-remote

    notion-mcp-remote

    Remote MCP server that brings Notion to claude.ai (and any MCP-over-HTTPS client) via Notion's OAuth 2.0 flow. A user grants the app access to their workspace once; the server then handles per-request workspace isolation via a ContextVar-based NotionClient so one deployment can serve many workspaces without token bleed.

    Vision

    Deliver a production-deployed, SOP-compliant notion-mcp-remote service that the operator can paste as a public URL into claude.ai's MCP settings, complete OAuth, and immediately use all 26 Notion tools in chat — no local stdio server, no integration-token juggling, no per-workspace redeploys. Success = OAuth round-trip under 60s and a search tool call returns live results from the authorized workspace on first try.

    User Stories

    Key Story Note Role Success Metric
    claude-ai-connect story-notion-mcp-remote-claude-ai-connect claude.ai user (Lucas) Paste URL, complete OAuth in <60s, Notion tool works first try
    multi-workspace-safety story-notion-mcp-remote-multi-workspace-safety Operator Zero cross-tenant incidents; concurrency test returns workspace-correct results 100%
    ops-deploy-gitops story-notion-mcp-remote-ops-deploy-gitops Operator pre-deploy-validation checklist 100% green; zero manual kubectl edits post-deploy

    Architecture

    1. arch-domain-notion-mcp-remote — entities: DynamicClient, AuthCode, AccessToken, NotionWorkspace, User (erDiagram)
    2. arch-dataflow-notion-mcp-remote — DCR, OAuth handshake, tool-call sequence with ContextVar injection (sequenceDiagram)
    3. arch-deployment-notion-mcp-remote — Tailscale Funnel → ingress → pod, Harbor/Woodpecker/ArgoCD GitOps chain (graph TB)

    Board

    Primary kanban: board-notion-mcp-remote.

    Columns: Backlog → Todo (review gate) → Next Up → In Progress → Done. Each ticket carries story:{key} and arch:{component} labels for traceability.

    Status

    • Local dev: working; .venv installs deps, make run serves on :8000, OAuth tested end-to-end locally.
    • CI / k8s manifests: merged (commits 455b62c, 837e2f2, cb91a12). Woodpecker pipeline registry URL now points at internal cluster Harbor (harbor-core.harbor.svc.cluster.local) per service-onboarding-sop. Pipeline activation still pending (ticket #1048).
    • Prod deploy: PRs MERGED (services #72, overlay #138, both 2026-05-01). Local k3s.tfvars updated with the new entry. tofu plan ran 2026-05-03 — paused at operator review: 7 expected new resources cleanly identified, but 14 unrelated drift items surfaced (pre-existing label drift on harbor_creds across 7 namespaces + provider write-only attr migration). Per PR #72 test plan, escalated rather than auto-applied. See pal-e-platform#296 comment for full plan output + 3 decision options.
    • claude.ai integration: not yet; blocked on public URL (#1049 is the E2E verification ticket).
    • Discovered scope: spike #11 — auto-bump remote-base ?ref= SHA via Woodpecker (mirroring Image Updater pattern). Plus 2 doc updates queued for next /update-docs cycle: convention-kustomize-overlay Remote Base Variant section and sop-validation 3-PR merge-order documentation. Plus 1 platform-board ticket pending: audit + reconcile pal-e-services drift (14 items).

    Milestones

    • No milestones yet. Target M1: first prod deploy with public URL reachable from claude.ai (drives all three user stories).

    Repos

    Repo Platform Role Status
    notion-mcp-remote Forgejo the service — FastMCP app, OAuth proxy, k8s manifests active
    mcp-remote-auth Forgejo shared OAuth infrastructure (consumed as mcp-remote-auth-ldraney) active, v0.1.0 on public PyPI
    notion-mcp Forgejo / PyPI upstream tool surface (consumed as notion-mcp-ldraney) active, v0.1.13
    pal-e-services Forgejo terraform service registration (var.services) pending PR
    pal-e-deployments Forgejo kustomize overlay for prod pending PR
Review 15
  • Verdict: APPROVED

    Third review of board item #1044 (Forgejo forgejo_admin/pal-e-deployments#132) after micro-fix round addressing the two residuals from review-1044-2026-04-21-v2. Both fixes landed cleanly in the live issue body. Ticket is ready to advance from backlog.

    Verification of Micro-Fixes

    # Fix Requested (v2) Landed? Evidence in live issue body
    1 [BODY] Remote-base URL .git/k8s.git//k8s (blocker -- kustomize requires // separator) YES Context section now shows https://forgejo.tail5b443a.ts.net/forgejo_admin/notion-mcp-remote.git//k8s?ref=main inside a fenced code block. Double-slash verified by direct string search of the issue body.
    2 [BODY] Drop AC #4 (Image Updater annotations in kustomization.yaml) -- annotations live on the ArgoCD Application, not the overlay YES AC list now has 6 checkboxes (down from 7): (1) kubectl kustomize renders clean, (2) resources: remote URL, (3) images: transformer with SHA-regex newTag, (4) no stringData/data, (5) no env/envFrom redefinition, (6) PR reviewed and merged. No mention of "annotations present in kustomization.yaml" anywhere in the AC section.

    Refinement Side-Effect Check

    Spot-checked that the micro-fix did not regress anything else:

    • File Targets still references "the pinned remote URL above" -- correct single-source-of-truth factoring, no stale URL left in that section.
    • Context "Base factoring decision" still present with correct rationale (remote-base, not bases/standard/).
    • Secrets subsection unchanged: base wires env via valueFrom: secretKeyRef: name: notion-mcp-secrets with kebab-case keys; overlay must not override.
    • Ingress subsection unchanged: Funnel provisioned by pal-e-services terraform, no ingress.yaml in overlay.
    • Sibling ticket reference forgejo_admin/notion-mcp-remote#7 (board item #1047) intact in Context and Related.
    • Related section still lists convention-kustomize-overlay, arch-deployment-notion-mcp-remote, project-notion-mcp-remote, service-onboarding-sop, story-notion-mcp-remote-ops-deploy-gitops.
    • Checklist still enforces SHA regex ^[0-9a-f]{7,40}$ (not latest).
    • Issue remains open; labels on the board item preserved: type:feature,story:ops-deploy-gitops,arch:kustomize-overlay.

    Template Completeness

    • [x] Type (Feature)
    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Acceptance Criteria (6 items)
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    Traceability

    • [x] story:ops-deploy-gitops label present; story entry exists on project-notion-mcp-remote user-stories
    • [x] arch:kustomize-overlay label present; backing doc convention-kustomize-overlay
    • [x] type:feature matches Type header
    • [x] Forgejo issue forgejo_admin/pal-e-deployments#132 open

    File Targets

    • [x] overlays/notion-mcp-remote/prod/kustomization.yaml -- new file, parent path aligns with sibling overlays
    • [x] overlays/notion-mcp-remote/prod/deployment-patch.yaml -- new file, standard overlay pattern
    • [x] Remote base URL verified renderable: forgejo_admin/notion-mcp-remote.git//k8s?ref=main with correct // separator

    Repo Placement

    Correct. Overlay created in pal-e-deployments; no cross-repo scope creep.

    Dependencies

    • Sibling #1047 (forgejo_admin/notion-mcp-remote#7) -- Secret notion-mcp-secrets, not blocking overlay merge
    • Downstream #1045 (pal-e-platform#296) -- ArgoCD Application
    • Downstream #1043 (pal-e-services#57) -- var.services entry with source_path = "overlays/notion-mcp-remote/prod"

    Acceptance Criteria

    All 6 AC now testable and unambiguous. Previous AC #4 ambiguity (annotations in overlay vs Application) is resolved by removal. Image Updater write-back is covered by AC #3's SHA-regex constraint on newTag, which is sufficient because Image Updater's write-back to kustomization requires only the images: entry and the SHA format -- the annotations that drive the write-back live on the ArgoCD Application (provisioned by ticket #1043 via pal-e-services terraform).

    Blast Radius

    Low. Overlay inert until #1045 (ArgoCD Application) points at it AND #1047 (Secret) exists. Rollback: git revert.

    Decomposition Assessment

    3 points, 2 files, 1 repo, 6 AC. Fits single agent pass (<5 min). No decomposition needed.

    Recommendation

    No action needed. Ticket is APPROVED.

    • Tag board item #1044 with review,ready.
    • Ticket eligible to advance backlog → todo per sop-board-workflow on next ops pass.
  • Verdict: NEEDS_REFINEMENT

    Re-review of board item #1044 (Forgejo forgejo_admin/pal-e-deployments#132) after refinement. Six of seven fixes from review-1044-2026-04-21 landed cleanly. One new issue found: the remote-base kustomize URL uses single-slash form, which kustomize cannot parse -- it requires // as the separator between git repo root and subpath. This will fail at kubectl kustomize time.

    Template Completeness

    • [x] Type (Feature)
    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    Verification of Prior Fixes

    # Fix Requested Landed? Evidence
    1 Base factoring: remote base referencing service repo's k8s/ YES Context explicitly picks remote-base pattern, gives URL, rejects bases/standard/. File Targets "Files NOT to touch" excludes bases/notion-mcp-remote/.
    2 Remove envFrom; base uses valueFrom: secretKeyRef: name: notion-mcp-secrets YES Context "Secrets" subsection states base already wires env via valueFrom: secretKeyRef: name: notion-mcp-secrets with kebab-case keys and instructs overlay not to override. AC includes "Overlay does not redefine env vars or envFrom".
    3 Name sibling ticket #7 / board #1047 explicitly YES Context names forgejo_admin/notion-mcp-remote#7 (board item #1047); Related section repeats it.
    4 Remove ingress.yaml (Funnel via pal-e-services terraform) YES Context "Ingress" subsection states Funnel is provisioned by pal-e-services terraform and forbids ingress.yaml. No ingress.yaml in File Targets.
    5 Replace newTag: latest with hex SHA YES (placeholder) File Targets specifies "a bootstrap newTag of a real commit SHA (placeholder <7-40 hex chars, first SHA from notion-mcp-remote main>)"; Checklist requires SHA matching ^[0-9a-f]{7,40}$, not latest.
    6 AC adjustments: drop envFrom AC, drop container-port AC, add images: transformer AC YES envFrom AC removed; container-port AC removed; new AC for images: transformer with name: harbor.tail5b443a.ts.net/notion-mcp-remote/notion-mcp-remote and SHA newTag.
    7 Body references convention-kustomize-overlay and arch-deployment-notion-mcp-remote YES Lineage cites convention-kustomize-overlay; Context cites arch-deployment-notion-mcp-remote; both listed in Related.

    Traceability

    • [x] story:ops-deploy-gitops label -- verified present on board item; story entry in project-notion-mcp-remote user-stories
    • [x] arch:kustomize-overlay label -- present; backing note is convention-kustomize-overlay. Prior review noted [SCOPE] ambiguity (convention vs arch); not blocking this review.
    • [x] type:feature label matches Type header
    • [x] Forgejo issue open (forgejo_admin/pal-e-deployments#132)

    File Targets

    • [x] overlays/notion-mcp-remote/prod/kustomization.yaml -- new file; parent path aligns with sibling overlays (overlays/pal-e-docs/prod/, etc.)
    • [x] overlays/notion-mcp-remote/prod/deployment-patch.yaml -- new file; standard overlay pattern per convention-kustomize-overlay
    • [x] Remote base source verified: forgejo_admin/notion-mcp-remote/k8s/ on main contains deployment.yaml, service.yaml, pvc.yaml, servicemonitor.yaml, kustomization.yaml. Base deployment image: harbor.tail5b443a.ts.net/notion-mcp-remote/notion-mcp-remote:latest. Base secret wiring uses valueFrom: secretKeyRef: name: notion-mcp-secrets with kebab-case keys. All matches issue body claims.

    New Issue: Remote-Base URL Format (BLOCKER for dev agent)

    The issue body specifies the remote kustomize resources: URL as:

    https://forgejo.tail5b443a.ts.net/forgejo_admin/notion-mcp-remote.git/k8s?ref=main

    Kustomize's remote-source syntax requires a // separator between the git repository URL and the subdirectory within the repo. With single-slash form, kustomize will either try to clone .../notion-mcp-remote.git/k8s as the repo URL (fails) or parse the entire path as the repo with no subpath. Sibling overlays in pal-e-deployments don't use remote bases (they use filesystem paths ../../../bases/standard), so there's no in-repo exemplar, but kustomize behaviour is consistent on this.

    Correct form:

    https://forgejo.tail5b443a.ts.net/forgejo_admin/notion-mcp-remote.git//k8s?ref=main

    Note the .git//k8s (double slash). This syntax is required for kubectl kustomize overlays/notion-mcp-remote/prod/ to render clean, which is AC #1. Without this fix, the dev agent will either (i) hit a render error, (ii) "fix" the URL themselves and guess, or (iii) silently use a different factoring. One-character BODY edit avoids all three.

    Sanity Check: images Transformer vs Real Harbor Path

    Base deployment (fetched from notion-mcp-remote/k8s/deployment.yaml on main) uses image harbor.tail5b443a.ts.net/notion-mcp-remote/notion-mcp-remote:latest.

    AC specifies images transformer with name: harbor.tail5b443a.ts.net/notion-mcp-remote/notion-mcp-remote. Matches exactly. Because the overlay consumes the base directly (not via name: app placeholder as in bases/standard), only ONE images entry is needed (not two as in overlays/pal-e-docs/prod/, which needs both app-image and the Harbor URL). AC correctly reflects this.

    Repo Placement

    Correct. Issue filed on pal-e-deployments, all File Targets under overlays/notion-mcp-remote/prod/*.

    Dependencies

    • [pending] Sibling secret-provisioning ticket -- forgejo_admin/notion-mcp-remote#7 (board item #1047) must create Secret notion-mcp-secrets with keys base-url, oauth-client-id, oauth-client-secret, session-secret, onboard-secret. Not blocking overlay merge (inert until ArgoCD syncs).
    • [pending] Downstream -- pal-e-platform#296 (board item #1045) creates ArgoCD Application.
    • [pending] Downstream -- pal-e-services#57 (board item #1043) adds var.services entry with source_path = "overlays/notion-mcp-remote/prod". Overlay path is contractual with this ticket.
    • All dependencies documented in Context and Related sections of the issue body.

    Acceptance Criteria

    • AC #1 (kubectl kustomize renders clean) -- achievable ONCE the // URL fix lands. Network access to tailnet required to fetch remote base.
    • AC #2 (resources: references service repo k8s/ pinned to main) -- testable via file inspection.
    • AC #3 (images: transformer with correct name + SHA-regex newTag) -- testable via file inspection + regex.
    • AC #4 (Image Updater annotations present in kustomization.yaml) -- AMBIGUOUS. Per convention-kustomize-overlay "Terraform Integration", Image Updater annotations live on the ArgoCD Application (provisioned by pal-e-services terraform with source_repo set), not on the overlay's kustomization.yaml. The overlay's role is to expose images: entries that Image Updater rewrites (plus write-back-target: kustomization on the Application). Inspection of overlays/pal-e-docs/prod/kustomization.yaml confirms: no Image Updater annotations in the overlay. AC #4 as written may be unachievable in a literal reading. Suggest rephrasing to "images: transformer written in form compatible with Image Updater write-back (regex ^[0-9a-f]{7,40}$)" -- already covered by AC #3. Non-blocking for this review since an astute dev agent can infer intent, but ideally clarified.
    • AC #5 (no stringData/data secret values) -- testable via grep.
    • AC #6 (overlay does not redefine env/envFrom) -- testable by diffing rendered output against base.

    Blast Radius

    Low. Single overlay directory in pal-e-deployments; no change to other services' overlays or bases/. Overlay is inert until pal-e-services ArgoCD Application (#1045) points at it AND Secret exists (#1047). Cannot break prod by merging alone. Rollback is git revert.

    Decomposition Assessment

    3 points, 2 files, 1 repo, 6 AC. Fits single agent pass (<5 min). No decomposition needed.

    Recommendation

    One fix remaining:

    • [BODY] Correct the remote-base kustomize URL to use // as the separator between the .git repo root and the k8s subpath. Change both occurrences (in Context section and File Targets section) from https://forgejo.tail5b443a.ts.net/forgejo_admin/notion-mcp-remote.git/k8s?ref=main to https://forgejo.tail5b443a.ts.net/forgejo_admin/notion-mcp-remote.git//k8s?ref=main.

    Optional non-blocking:

    • [BODY] Clarify AC #4 ("ArgoCD Image Updater annotations present") -- per convention-kustomize-overlay these annotations live on the ArgoCD Application (via pal-e-services terraform), not on the overlay's kustomization.yaml. Suggest dropping AC #4 (redundant with #3) or rewording to reference the images: entry format only.

    Once the URL fix lands, verdict will be APPROVED and the board item can be tagged review,ready.

  • Verdict: APPROVED

    Re-review after label fix. The single gap from review-1045-2026-04-21 (arch:argocd with no backing note) is resolved. Main session relabeled board item 1045 to arch:deployment-notion-mcp-remote, which has a backing active architecture note (arch-deployment-notion-mcp-remote, id 1548). Ticket is apply-ready pending the two documented upstream dependencies.

    Fix Verification

    • [x] Board item 1045 labels confirmed via list_board_items: type:feature,story:ops-deploy-gitops,arch:deployment-notion-mcp-remote (updated 2026-04-21T17:42:44)
    • [x] arch-deployment-notion-mcp-remote note exists in pal-e-docs (status: active, type: architecture, project: notion-mcp-remote)
    • [x] Ticket body already references arch-deployment-notion-mcp-remote — the label now matches the body's architectural anchor
    • [x] Note: search_notes returned empty for both exact-slug and fuzzy queries; verified via direct get_note(slug=...) which is authoritative. Search indexing lag, not a real gap.

    Template Completeness

    • [x] Type (Feature)
    • [x] Lineage
    • [x] Repo (forgejo_admin/pal-e-platform)
    • [x] User Story
    • [x] Context
    • [x] File Targets (no-file-change apply operation; correctly scoped)
    • [x] Acceptance Criteria
    • [x] Test Expectations (tofu plan command)
    • [x] Constraints (lock-false, no -target, Lucas approval, pre-deploy checklist)
    • [x] Checklist
    • [x] Related

    Traceability

    • [x] story:ops-deploy-gitops label — "GitOps Deploy via pal-e Platform"
    • [x] story note verified — story-notion-mcp-remote-ops-deploy-gitops listed in project-notion-mcp-remote user-stories section
    • [x] arch:deployment-notion-mcp-remote label present on board item
    • [x] arch note verified — arch-deployment-notion-mcp-remote (id 1548) active in pal-e-docs; label now matches ticket body anchor
    • [x] Forgejo issue — https://forgejo.tail5b443a.ts.net/forgejo_admin/pal-e-platform/issues/296 open and valid

    File Targets

    No file changes — this is the apply operation itself. "Files NOT to touch" correctly lists network-policies.tf. No file-target verification applicable.

    Repo Placement

    OK. Apply runs against forgejo_admin/pal-e-platform terraform. Sibling tickets correctly scoped (pal-e-services #57, pal-e-deployments #132, notion-mcp-remote #7 for secrets).

    Dependencies

    Unchanged from prior review. Documented and sequenced correctly:

    • pal-e-services #57 (board 1043, backlog, 3 pts) — MUST LAND FIRST
    • pal-e-deployments #132 (board 1044, backlog, 3 pts) — MUST LAND FIRST
    • notion-mcp-remote #7 (board 1047, backlog, 2 pts) — MUST LAND BEFORE FIRST ARGOCD SYNC

    Acceptance Criteria

    Agent-verifiable: tofu plan diff inspection, no unexpected destructive changes, Lucas approval gate, kubectl get ns notion-mcp-remote, ArgoCD Application Healthy + Synced, Funnel URL reachable.

    Blast Radius

    Contained first-deploy. No shared-infra changes. Lock-false plan review + Lucas gate mitigates any surprise diffs per sop-platform-tf-changes.

    Decomposition Assessment

    No decomposition needed. Single apply with one plan-review gate. Fits 5-minute rule.

    Recommendation

    No action needed. Ticket is apply-ready once dependencies #57 and #132 merge and the pre-deploy validation checklist is green. Advance to next_up when scheduling permits.

  • Verdict: APPROVED

    Board item: #1043 on board-notion-mcp-remote — Forgejo issue forgejo_admin/pal-e-services#57. Type: Feature. Points: 3. Labels: type:feature,story:ops-deploy-gitops,arch:argocd.

    Prior review: review-1043-2026-04-21 (NEEDS_REFINEMENT) flagged four fixes. This re-review verifies each landed.

    Refinement Verification

    # Fix Requested Landed? Evidence
    1 Schema: source_repo must be forgejo_admin/pal-e-deployments (owner prefix) YES Context section documents owner-prefixed value. Schema note block explains why (services.tf URL interpolation as http://forgejo-http.forgejo.svc.cluster.local/${coalesce(source_repo, forgejo_repo)}.git). AC2 asserts the exact value. Verified against live terraform/variables.tf (services object type: source_repo = optional(string)), terraform/services.tf (argocd_application.service repo_url coalesces source_repo/forgejo_repo), and terraform/k3s.tfvars.example (example uses owner-prefixed form).
    2 Remove speculative "Possibly terraform/services.tf" File Target YES File Targets reduced to terraform/k3s.tfvars only. terraform/services.tf moved under "Files NOT to touch" with justification: module already supports per-service funnel=true via kubernetes_ingress_v1.service_funnel with for_each = { for k, v in var.services : k => v if v.funnel } — verified in live services.tf.
    3 Workflow: pal-e-services is plan-and-apply-BEFORE-merge per sop-platform-tf-changes (NOT apply-on-merge) YES Constraints section leads with "Workflow: plan-and-apply-BEFORE-merge" bullet, cites sop-platform-tf-changes, explicitly contrasts with pal-e-platform's CI-driven apply-on-merge, and prescribes the sequence (open PR → review → plan → apply → verify → merge). Matches sop-platform-tf-changes § standard-workflow §§ pal-e-services block exactly.
    4 Blast-radius guard: verify tofu plan diff isolated to new resources; halt on drift YES Constraints section has dedicated "Blast-radius guard" bullet. Names expected resources (namespace, Harbor project + robots, pull secret, ArgoCD application, funnel ingress). Instructs STOP/do-not-apply/escalate on drift. Cites precedent review-1064-2026-04-20 pal-e-services state-drift incident. Duplicated in Checklist as "tofu plan reviewed; diff isolated to notion-mcp-remote resources".

    Template Completeness

    • [x] Type (Feature)
    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context (with Schema note and explicit key values)
    • [x] File Targets (+ explicit "Files NOT to touch")
    • [x] Acceptance Criteria (5 items, all verifiable)
    • [x] Test Expectations (with run command)
    • [x] Constraints (workflow + blast-radius + style)
    • [x] Checklist
    • [x] Related Architecture (references arch-deployment-notion-mcp-remote)
    • [x] Related

    Traceability

    • [x] story:ops-deploy-gitops label — maps to story-notion-mcp-remote-ops-deploy-gitops
    • [x] story note verified — listed in project-notion-mcp-remote user-stories table (confirmed in prior review)
    • [x] arch:argocd label — component: ArgoCD
    • [ ] arch note arch-argocd MISSING — [SCOPE] carried over from prior review. get_note(slug="arch-argocd") returns 404. This remains a backing-note gap but does not block ticket advancement (per skill-review-ticket, [SCOPE] items flag follow-up work without requiring NEEDS_REFINEMENT). Related Architecture pointer arch-deployment-notion-mcp-remote is present and verified.
    • [x] arch note arch-deployment-notion-mcp-remote verified — exists, active, documents the service topology including pal-e-services var.services --> ACD edge this ticket implements
    • [x] Forgejo issue — forgejo_admin/pal-e-services#57, open

    File Targets

    • [x] terraform/k3s.tfvars — correct. File is gitignored (expected; plan-and-apply-before-merge pattern).
    • [x] "Files NOT to touch" list — correctly enumerates terraform/services.tf, network-policies.tf, other service entries. Prevents scope creep.

    Repo Placement

    Correct. Issue on forgejo_admin/pal-e-services where schema and tfvars live.

    Dependencies

    Unchanged from prior review. Upstream blocker pal-e-deployments#132 (overlay, board #1044) correctly documented. Downstream pal-e-platform#296 (board #1045) no longer required per the corrected workflow — apply is manual on pal-e-services, so pal-e-platform is not implicated.

    Acceptance Criteria

    Five ACs, all verifiable:

    • AC1 (entry present) — grep-verifiable
    • AC2 (source_repo owner-prefixed) — NEW; grep-verifiable and directly addresses prior review's fix #1
    • AC3 (plan diff shows expected resources, no unexpected diff) — runnable; resource list explicit
    • AC4 (values match pre-deploy-validation checklist) — references service-onboarding-sop
    • AC5 (PR reviewed and merged) — process criterion

    Blast Radius

    Now explicitly documented in the ticket itself with STOP/escalate procedure and precedent citation. The original concern is now the agent's responsibility to verify in-flight, as it should be.

    Decomposition Assessment

    1 file edit (one map entry in k3s.tfvars), 5 acceptance criteria, estimated agent work ~5-10 min including plan review. No decomposition needed. 3 points remains reasonable.

    Recommendation

    All four refinement fixes landed and verified against live codebase. Ticket is ready to advance from backlog.

    • No action needed on the ticket body.
    • [SCOPE] carry-over: arch-argocd note still missing. Recommend filing separately — do not block #1043 on it. Same pattern as arch-tailscale-funnel gap flagged in review-788-2026-04-03; these platform-level arch notes are a documentation backlog concern, not a per-ticket blocker.
    • review-1043-2026-04-21 — prior review (NEEDS_REFINEMENT)
    • forgejo_admin/pal-e-services#57 — issue
    • sop-platform-tf-changes — workflow authority
    • review-1064-2026-04-20 — state-drift precedent cited in blast-radius guard
    • arch-deployment-notion-mcp-remote — related architecture
  • Verdict: APPROVED

    Re-review of board item #1049 after label swap. Prior review review-1049-2026-04-21 returned NEEDS_REFINEMENT with one blocking [SCOPE] item (orphan arch:tailscale-funnel label) and one optional [BODY] nit. The label was swapped to arch:deployment-notion-mcp-remote. Traceability gap is resolved. Body is otherwise strong. Ticket is ready to advance.

    Changes Since Prior Review

    • Board item 1049 labels now: type:feature,story:claude-ai-connect,arch:deployment-notion-mcp-remote (confirmed via list_board_items, updated_at=2026-04-21T17:42:44).
    • Prior arch:tailscale-funnel label has been removed — no longer an orphan.

    Traceability

    • [x] story:claude-ai-connect label — story "Connect Notion to claude.ai"
    • [x] story note verified — story-notion-mcp-remote-claude-ai-connect listed in project-notion-mcp-remote user-stories section (unchanged from prior review)
    • [x] arch:deployment-notion-mcp-remote label — deployment architecture for the service
    • [x] arch note verified — arch-deployment-notion-mcp-remote exists in pal-e-docs, status=active, note_type=architecture. Contains the full deployment topology including the Tailscale Funnel leg (notion-mcp-remote.tail5b443a.ts.net), Harbor, Woodpecker, ArgoCD + Image Updater, Kustomize overlay, and PVC. Covers every component exercised by the E2E validation AC.
    • [x] type:feature label — appropriate (new capability validation, not a bug fix)
    • [x] Forgejo issue — forgejo_admin/notion-mcp-remote#9 — open, body unchanged and template-compliant

    Template Completeness

    Unchanged from prior review — all 11 template sections present (Type, Lineage, Repo, User Story, Context, File Targets, Acceptance Criteria, Test Expectations, Constraints, Checklist, Related). 11 AC map cleanly to the 6 user-story AC plus platform and recordkeeping checks.

    File Targets

    Not applicable — verification-only ticket. Deliverables are a validation note (per sop-validation) and a milestone mark on the project page Status section.

    Repo Placement

    OK. Issue filed on forgejo_admin/notion-mcp-remote, which owns the validation pass. No change.

    Dependencies

    Seven prerequisite tickets still in backlog (pal-e-platform#296, pal-e-services#57, pal-e-deployments#132, notion-mcp-remote#5/#6/#7/#8). Correctly documented in the Constraints section ("Do not begin this ticket until all upstream tickets closed"). The review→todo gate is independent of the execution gate; this ticket can advance to todo once upstream work is ready to start. No refinement needed.

    Acceptance Criteria

    11 AC unchanged and testable with concrete commands. Platform checks cover Woodpecker pipeline, ArgoCD sync+health, pod Running, /health 200, /.well-known/oauth-authorization-server JSON shape, and Prometheus scrape of notion_mcp_remote_up. E2E checks cover all 6 story AC (claude.ai URL accept, Notion OAuth < 60s, search returns real results, second tool call succeeds). Recordkeeping covers validation note + Milestone M1 mark + upstream closure.

    The prior optional [BODY] nit about asserting registration_endpoint on the /.well-known AC is non-blocking and intentionally not required. Dynamic Client Registration will be implicitly exercised by the E2E AC "claude.ai accepts the connector URL" — if DCR were broken, that AC would fail. The optional nit stands as a future quality improvement but does not gate approval.

    Blast Radius

    Small — observational only, no config or code changes. Unchanged from prior review.

    Decomposition Assessment

    11 AC exceeds the default 5-AC threshold, but validation tickets are explicitly exempt — one pass must exercise every AC of the user story per sop-validation. Points=2 reflects the 15–20 min execution. No decomposition needed.

    Recommendation

    Verdict: APPROVED. No action needed. Ticket is ready to advance from backlog to todo when upstream dependencies are cleared.

    • Prior [SCOPE]resolved by label swap to arch:deployment-notion-mcp-remote; backing arch note verified present.
    • Prior [BODY] nit (registration_endpoint assertion) — acknowledged, non-blocking, not required to prove AC. DCR failure mode is caught by the existing "claude.ai accepts URL" E2E AC.
    • Board item 1049 — board-notion-mcp-remote column=backlog
    • Forgejo: forgejo_admin/notion-mcp-remote#9
    • Prior review: review-1049-2026-04-21 (NEEDS_REFINEMENT — superseded)
    • arch-deployment-notion-mcp-remote
    • story-notion-mcp-remote-claude-ai-connect
    • project-notion-mcp-remote
    • sop-validation
  • Re-Review: Register Notion OAuth app review-1046-2026-04-21-rereview

    Verdict: APPROVED

    Ticket: board item #1046 — forgejo_admin/notion-mcp-remote#6feat: register public Notion OAuth integration for remote proxy

    Prior review: review-1046-2026-04-21 (NEEDS_REFINEMENT). This is a targeted re-review checking only the three gaps flagged by the original reviewer.

    Gap Verification

    • [x] [BODY] Redirect URI /callback/oauth/callbackCLOSED. AC #3 now reads https://notion-mcp-remote.tail5b443a.ts.net/oauth/callback. AC #5 smoke curl uses the URL-encoded form %2Foauth%2Fcallback. Constraints cite the source of truth: mcp_remote_auth/routes.py:41 and provider.py:125,151.
    • [x] [BODY] /onboard/callback follow-up consideration — CLOSED (as follow-up flag, not blocker). Constraints section now includes: "Follow-up: when the onboarding flow story lands, decide whether to register /onboard/callback as a secondary redirect URI on this same integration (avoids a second UI trip) or create a separate integration." Appropriate disposition — the onboarding flow is a separate story, so registering the URI now is optional and correctly deferred with a clear decision point.
    • [x] [SCOPE] Reference arch-dataflow-notion-mcp-remoteCLOSED. Related section explicitly links arch-dataflow-notion-mcp-remote with the (OAuth handshake sequence) annotation. Confirmed the backing note exists (id 1547, active, architecture note). The "stale link" flag from the original gap list turned out to be a non-issue as reported — there was no stale link to replace, and the dataflow note is now properly cited as the arch reference.

    Template Completeness

    Unchanged from prior review — all sections of template-issue-feature still present and well-formed. No sections were dropped during refinement.

    Traceability

    • [x] story:claude-ai-connect label — Connect Notion to claude.ai, story note verified (id 1543).
    • [x] arch:notion-oauth label — body now resolves this to the existing arch-dataflow-notion-mcp-remote note (id 1547) which documents the OAuth handshake sequence. Traceability link is satisfied in the body even though a dedicated arch-notion-oauth note does not exist under that exact slug; Ava previously indicated this resolution (citing the dataflow note) is acceptable.
    • [x] Forgejo issue — forgejo_admin/notion-mcp-remote#6, open.

    File Targets

    No repo file edits expected — credential provisioning ticket. Downstream consumers (server.py:19-20, .env.example:2-3, k8s/deployment.yaml:43-52) unchanged since prior review and re-verified against the current working tree. "Do not touch" list accurate.

    Repo Placement

    OK — issue is filed on forgejo_admin/notion-mcp-remote where the credentials are consumed. Sibling tickets (#7 secret provisioning, #9 E2E verify) correctly separated into their own issues.

    Dependencies

    Unchanged: this ticket blocks #7 (arch:k8s-secret) and #9 (arch:tailscale-funnel). Not blocked by anything currently in in_progress. Safe to advance.

    Acceptance Criteria

    All 5 ACs verifiable. AC #3 and AC #5 now testable against the real /oauth/callback endpoint. The redirect URI path fix closes the critical silent-failure mode flagged in the prior review.

    Blast Radius

    Risk profile improved from the prior review: the wrong-path silent-failure mode is eliminated by the AC #3/#5 fix. Remaining risks are the same as any OAuth credential provisioning — CLIENT_SECRET must stay out of git (Constraint #3 covers this), and BASE_URL / Tailscale Funnel hostname changes will require re-editing the Notion integration in the UI. Nothing new introduced by the refinement.

    Decomposition Assessment

    1 point, 5 ACs, no code edits, single UI flow. Well within the 5-minute rule. No decomposition needed.

    Recommendation

    All flagged gaps are addressed. Ticket is ready to move backlog → todo → next_up. No action needed.

  • Verdict: APPROVED

    Re-review of board item #1047 / Forgejo issue forgejo_admin/notion-mcp-remote#7 after refinement. Prior verdict: review-1047-2026-04-21 NEEDS_REFINEMENT. All flagged gaps have been resolved. Type: Feature. Points: 2.

    Refinement Gaps Closed

    The prior review flagged a [BODY] gap: the AC secret name/shape did not match the deployment manifest on main. Verified against /home/ldraney/notion-mcp-remote/k8s/deployment.yaml:

    • [x] Secret name corrected to notion-mcp-secrets (matches manifest line 41, 46, 51, 56, 61)
    • [x] Keys rewritten in kebab-case: base-url, oauth-client-id, oauth-client-secret, session-secret, onboard-secret (matches manifest lines 42, 47, 52, 57, 62 exactly)
    • [x] Wiring model changed to valueFrom: secretKeyRef; envFrom language removed from Context
    • [x] Mapping table added: secret key → env var → consumer (matches manifest env block lines 38-62)
    • [x] AC strengthened: now requires kubectl rollout status, kubectl exec env verification of all 5 env vars inside the container, and a /health 200 probe
    • [x] Explicit constraint added: "Do not normalise to UPPER_SNAKE_CASE; secretKeyRef.key lookups are case-sensitive"

    Template Completeness

    • [x] Type (Feature)
    • [x] Lineage
    • [x] Repo
    • [x] User Story (role / want / so-that)
    • [x] Context (now internally consistent with the deployment)
    • [x] File Targets (correctly "no repo file changes")
    • [x] Acceptance Criteria (7 items, each verifiable)
    • [x] Test Expectations (runnable kubectl commands)
    • [x] Constraints (4 guardrails including the new case-sensitivity callout)
    • [x] Checklist (5 items)
    • [x] Related (project, story, SOP, arch note)

    Traceability

    • [x] story:ops-deploy-gitops label — GitOps Deploy via pal-e Platform
    • [x] story note verified — story-notion-mcp-remote-ops-deploy-gitops exists; AC row "Secrets provisioned via kubectl create secret generic before first ArgoCD sync" maps 1:1
    • [x] arch:k8s-secret label — architectural coverage present in arch-deployment-notion-mcp-remote and arch-secrets-pipeline. Prior review's optional [LABEL] suggestion was not actioned, but it was optional ("interpretable but inconsistent") — not a blocker.
    • [x] Forgejo issue — URL valid, #7 open, body well-formed

    File Targets

    No repo file changes — this is a kubectl operation. Cross-checked manifest for shape alignment:

    • [x] k8s/deployment.yaml lines 38-62 — verified all 5 env blocks use valueFrom.secretKeyRef.name: notion-mcp-secrets with the exact kebab-case keys the AC now lists. Env var names match server.py consumers.

    Repo Placement

    OK. Issue on forgejo_admin/notion-mcp-remote; downstream kustomize work on pal-e-deployments (#1044); namespace on pal-e-platform (#1045). Correctly scoped.

    Dependencies

    Unchanged from prior review and still adequately documented: blocks on #1046 (OAuth integration) for real client id/secret, and on #1045 (tofu apply) for the namespace. Blocks downstream #1044 (kustomize overlay) and #1043 (ArgoCD Application) per SOP.

    Acceptance Criteria

    Strengthened materially. Previously testable-but-shallow (jsonpath key check + secret presence). Now forces the pod to actually come up with the bound values: rollout status, in-container env verification, and /health 200. Because server.py fails loudly on missing required env, a green pod proves the secret shape matches the manifest — this is exactly the right end-to-end assertion for this ticket.

    Blast Radius

    Unchanged. First-time bootstrap — no token encryption collateral. No sibling services consume notion-mcp-secrets. Prior reviewer's optional nit about SESSION_SECRET rotation impact was not added to the body — not a blocker for first-time provisioning, but worth a follow-up doc note if this ticket triggers future rotation runbooks.

    Decomposition Assessment

    1 repo, 0 file targets, 7 AC items (still a single kubectl create + verification loop), <5 minutes of operator time. No decomposition needed.

    Recommendation

    No action needed. Ticket is ready to advance from backlog to todo.

    Summary

    All [BODY] gaps from the prior review are closed. Secret name, key casing, wiring model, and env-var mapping now exactly match k8s/deployment.yaml on main. The acceptance criteria have been hardened to verify actual pod startup rather than just secret existence, which is the correct bar for this ticket. Verdict: APPROVED.

  • Verdict: NEEDS_REFINEMENT

    Board item #1044 on board-notion-mcp-remote -- Forgejo issue forgejo_admin/pal-e-deployments#132. Scope is substantially correct but three gaps will trip the dev agent. Fixable in the issue body.

    Template Completeness

    • [x] Type (Feature)
    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    Traceability

    • [x] story:ops-deploy-gitops label -- GitOps Deploy via pal-e Platform
    • [x] story note verified -- story-notion-mcp-remote-ops-deploy-gitops listed in project-notion-mcp-remote user-stories section
    • [x] arch:kustomize-overlay label -- refers to cross-service deployment pattern
    • [ ] arch note MISSING -- search_notes(query="arch-kustomize-overlay") returns empty. convention-kustomize-overlay exists and is the canonical reference, but no arch- prefixed note. [SCOPE] Decide whether to (a) create arch-kustomize-overlay (likely a cross-service arch diagram showing bases/standard -> overlay -> ArgoCD Image Updater write-back chain), or (b) relabel this ticket to drop arch:kustomize-overlay and rely on convention-kustomize-overlay since kustomize-overlay is really a convention, not a diagrammable arch component of this service. Recommend (b) -- kustomize-overlay is a platform convention, and the service-specific arch is already captured in arch-deployment-notion-mcp-remote.
    • [x] type:feature label matches ### Type Feature header
    • [x] Forgejo issue open (forgejo_admin/pal-e-deployments#132)

    File Targets

    • [x] overlays/notion-mcp-remote/prod/kustomization.yaml -- path matches existing sibling overlays (overlays/pal-e-docs/prod/, overlays/basketball-api/prod/, etc.) confirmed in pal-e-deployments repo
    • [x] overlays/notion-mcp-remote/prod/deployment-patch.yaml -- standard overlay pattern per convention-kustomize-overlay
    • [ ] overlays/notion-mcp-remote/prod/ingress.yaml -- ISSUE: listed as "possibly" with the check "only if Tailscale Funnel ingress is not created by pal-e-services terraform". Confirmed via arch-deployment-notion-mcp-remote ("Tailscale Funnel provisioned by pal-e-services terraform when funnel=true") and pal-e-docs overlay (no ingress.yaml present). Funnel Ingress is NOT the overlay's concern. [BODY] Remove the "Possibly" line entirely and add "ingress.yaml -- NOT created here; Tailscale Funnel ingress is provisioned by pal-e-services terraform (funnel=true)" to the "Files NOT to touch" list.
    • [x] base source -- notion-mcp-remote k8s/ directory verified to contain deployment.yaml, service.yaml, pvc.yaml, servicemonitor.yaml, kustomization.yaml on main

    Base Factoring Ambiguity (CRITICAL)

    Issue body says: "references the base (either inline manifests from the service repo or a new bases/notion-mcp-remote/ if the pattern favours that)". This offers THREE options without choosing one. The convention is clear:

    • convention-kustomize-overlay specifies overlays consume bases/standard/ (placeholder name: app) + bases/servicemonitor/ with JSON6902 rename patches from app -> service name.
    • Confirmed by inspecting overlays/pal-e-docs/prod/kustomization.yaml -- uses ../../../bases/standard and ../../../bases/servicemonitor plus rename JSON6902 patches.
    • The existing notion-mcp-remote/k8s/ base already uses name: notion-mcp-remote (not app), so it CANNOT be consumed by the standard overlay pattern without modification.
    • The service also has a PVC and custom liveness/readiness probes on /health (not /healthz), plus Recreate strategy and custom port, so it doesn't neatly fit bases/standard either.

    [BODY] Pick ONE approach and state it explicitly. Two viable choices:

    1. Reference the service repo's k8s/ as a remote base (e.g. resources: - https://forgejo.tail5b443a.ts.net/forgejo_admin/notion-mcp-remote.git//k8s?ref=main). Overlay patches tag via images: transformer and tweaks prod-specific bits. This matches the terraform source_repo/source_path rollback story in convention-kustomize-overlay. Simpler for a service with non-standard shape (PVC, Recreate, /health probe).
    2. Factor a new bases/notion-mcp-remote/-shaped base with the service's resources and rename them in the overlay. More work, diverges from bases/standard, and puts two copies of the deployment spec in two repos (service repo k8s/ + deployment repo bases/).

    Recommend option 1. Update the File Targets section to spell it out.

    Secret Name + Shape Mismatch (CRITICAL)

    Issue body says:

    Real secrets (NOTION_OAUTH_CLIENT_ID, NOTION_OAUTH_CLIENT_SECRET, SESSION_SECRET, ONBOARD_SECRET, BASE_URL) are provisioned via kubectl create secret generic ... Overlay references the secret by name via envFrom. ... envFrom: secretRef: name: notion-mcp-remote

    But the existing base notion-mcp-remote/k8s/deployment.yaml on main already defines each env var via valueFrom: secretKeyRef: name: notion-mcp-secrets, key: {kebab-case}:

    • Secret name: notion-mcp-secrets (NOT notion-mcp-remote)
    • Shape: per-key secretKeyRef with kebab-case keys (oauth-client-id, oauth-client-secret, session-secret, onboard-secret, base-url) -- NOT envFrom
    • Env var names uppercase SCREAMING_SNAKE (NOTION_OAUTH_CLIENT_ID etc.) are injected via the per-key binding, not via envFrom (which would require matching uppercase keys in the secret)

    So the overlay CANNOT just add envFrom: secretRef: name: notion-mcp-remote. Either:

    • Align the overlay with the existing base (keep secretKeyRef shape, keep name notion-mcp-secrets), in which case the deployment-patch doesn't need to touch env/secrets at all and the overlay is just image tag + prod tweaks; or
    • Change the base deployment in notion-mcp-remote/k8s/ to use envFrom and uppercase keys, AND change the sibling secret-creation ticket (notion-mcp-remote #7, arch:k8s-secret) to match. This is a bigger scope change that affects two repos.

    [BODY] Resolve the mismatch. Recommended: pick option 1 -- state that the overlay does NOT need to patch secrets because the base already wires them correctly; the overlay only needs to pin image tag, set replicas/resources, and carry Image Updater annotations. Update Acceptance Criteria bullets about envFrom and secret name notion-mcp-remote to match: the secret name to be provisioned (per sibling ticket) must be notion-mcp-secrets with kebab-case keys.

    Repo Placement

    OK. Issue is filed on forgejo_admin/pal-e-deployments and File Targets are all overlays/notion-mcp-remote/prod/*. Correct repo. Service repo k8s manifests already exist (commits 455b62c, 837e2f2).

    Dependencies

    • Hard prerequisite: Secret notion-mcp-secrets must exist in the target namespace BEFORE first ArgoCD sync (service-onboarding-sop step 3 warning). Tracked by notion-mcp-remote issue #7 (board item #1047, story:ops-deploy-gitops, arch:k8s-secret) -- NOT named in the issue body. [BODY] Name the sibling issue explicitly.
    • Downstream consumer: pal-e-services var.services entry (board item #1043, Forgejo pal-e-services#57) will set source_path = "overlays/notion-mcp-remote/prod". Overlay path name is contractual with that ticket -- must not change after this merges.
    • Downstream consumer: pal-e-platform issue #296 (board item #1045, arch:argocd) -- ArgoCD Application creation. Order: overlay lands -> secret provisioned -> tofu apply creates Application -> ArgoCD syncs.
    • Image tag: First-deploy bootstrap -- Image Updater writes newTag to overlay after Woodpecker pushes. Per convention, must set a placeholder newTag (issue says latest, but convention says full SHA format ^[0-9a-f]{7,40}$). [BODY] Change newTag: latest to "set newTag to the SHA from a successful Woodpecker build on main (Image Updater then rewrites). Do NOT use latest -- violates the tag regex ^[0-9a-f]{7,40}$ that Image Updater expects."

    Acceptance Criteria

    Mostly testable, but three gaps:

    • No AC covers the images: transformer entries (per convention there should be two: app-image -> harbor.../notion-mcp-remote/notion-mcp-remote with SHA, plus the Image Updater write-back target). [BODY] Add: "images: transformer in kustomization.yaml has both entries (app-image mapping and Harbor URL mapping) with matching newTag in full-SHA format."
    • "Container port 8000 matches Dockerfile EXPOSE" -- port 8000 is already set in the base deployment; unless overlay is overriding it, this AC is testing the base, not the overlay. [BODY] Either remove or rephrase as "Rendered Deployment keeps containerPort 8000 (no port override in overlay)."
    • AC about envFrom and secret name notion-mcp-remote will be obsolete after the secret-shape refinement above. [BODY] Rewrite to match whichever approach is chosen.

    Blast Radius

    Low. Single overlay in pal-e-deployments; no change to bases/standard, no change to other services' overlays. Overlay is inert until the pal-e-services ArgoCD Application points at it (#1043) AND the secret exists (#1047). Cannot break prod by merging this alone.

    Follow-on risk: if base factoring option 2 (new bases/notion-mcp-remote/) is chosen, future changes to the service's deployment shape must be kept in sync across two repos -- avoid.

    Decomposition Assessment

    3 points, 2-3 files, 1 repo, ~6 AC. Fits single agent pass (<5 min). No decomposition needed.

    Recommendation

    NEEDS_REFINEMENT. Apply these fixes to the Forgejo issue body:

    • [BODY] Resolve base factoring ambiguity: pick option 1 (reference service repo's k8s/ as a remote base via git URL) and state it explicitly. Remove the "inline manifests from the service repo or a new bases/notion-mcp-remote/" alternatives.
    • [BODY] Fix secret name + shape mismatch. The existing base already uses secretKeyRef with secret name notion-mcp-secrets and kebab-case keys. The overlay should not patch env/secrets; the secret-provisioning sibling ticket (notion-mcp-remote#7) must create notion-mcp-secrets with keys oauth-client-id, oauth-client-secret, session-secret, onboard-secret, base-url.
    • [BODY] Name the sibling secret-provisioning ticket by URL in the Context/Dependencies section (notion-mcp-remote#7) so the dev agent can cross-check.
    • [BODY] Remove the "Possibly ingress.yaml" line from File Targets. Add an explicit "NOT to touch" entry: ingress.yaml is provisioned by pal-e-services terraform (funnel=true).
    • [BODY] Replace newTag: latest with SHA-format bootstrap per convention-kustomize-overlay Image Management section.
    • [BODY] Update Acceptance Criteria to (a) drop the envFrom/secret-name AC, (b) add an AC that the images: transformer has both entries with full-SHA newTag.
    • [SCOPE] Decide: drop the arch:kustomize-overlay label on the board item (kustomize-overlay is a platform convention, not a service-specific arch diagram; the service's arch is covered by arch-deployment-notion-mcp-remote). If kept, create arch-kustomize-overlay as a cross-service platform-level arch note.

    Non-blocking: the ticket is well-structured and the sequence is correct; refinements above bring it into alignment with the existing pal-e-deployments pattern and the already-merged service base so the dev agent doesn't have to make these calls itself.

  • Verdict: NEEDS_REFINEMENT

    Board item #1047 / Forgejo issue forgejo_admin/notion-mcp-remote#7feat: provision app secrets in notion-mcp-remote namespace (pre-sync). Type: Feature. Points: 2. Labels: type:feature,story:ops-deploy-gitops,arch:k8s-secret.

    Template Completeness

    Checked against template-issue-feature expected sections:

    • [x] Type — Feature
    • [x] Lineage — standalone, cites project + story + SOP row
    • [x] Repo — forgejo_admin/notion-mcp-remote
    • [x] User Story — role / want / so-that present
    • [x] Context — references SOP, envFrom pattern, dependencies called out
    • [x] File Targets — correctly marked "No repo file changes. This is a kubectl operation."
    • [x] Acceptance Criteria — 5 items, each verifiable
    • [x] Test Expectations — present, with runnable kubectl command
    • [x] Constraints — three guardrails (no git-stored manifests, generated entropy, namespace prereq)
    • [x] Checklist — 4 items
    • [x] Related — links to project, story, SOP, arch note

    Traceability

    • [x] story:ops-deploy-gitops label — GitOps Deploy via pal-e Platform
    • [x] story note verified — story-notion-mcp-remote-ops-deploy-gitops exists; listed in project-notion-mcp-remote user-stories section; AC row "Secrets provisioned via kubectl create secret generic before first ArgoCD sync" is the exact line this ticket fulfills
    • [~] arch:k8s-secret label — no dedicated arch-k8s-secret note exists. The relevant architecture coverage lives in arch-deployment-notion-mcp-remote (key decision: "Secrets created via kubectl before first sync") and the platform-wide arch-secrets-pipeline. The label is unusual for this project (other tickets on this board use service-scoped arch labels like arch:notion-oauth, arch:tailscale-funnel).
    • [x] Forgejo issue — URL valid, issue #7 is open, body is well-formed

    File Targets

    No repo file targets (Feature ticket is a pure operator/kubectl action). However, to verify that the ticket's secret shape matches what the code actually consumes, I spot-checked the deployment manifest and server envvars.

    • [x] server.py — confirmed reads NOTION_OAUTH_CLIENT_ID, NOTION_OAUTH_CLIENT_SECRET, SESSION_SECRET, BASE_URL, ONBOARD_SECRET (lines 19–25). No Fernet/token-encryption key is read beyond SESSION_SECRET which TokenStore uses for encryption. Env-var names in the ticket match the code.
    • [!] k8s/deployment.yamlMISMATCH. Currently references Secret name notion-mcp-secrets (not notion-mcp-remote as the ticket AC specifies), via valueFrom: secretKeyRef (not envFrom), with keys in kebab-case: base-url, oauth-client-id, oauth-client-secret, session-secret, onboard-secret. If an operator follows the ticket AC literally (kubectl create secret generic notion-mcp-remote with UPPER_SNAKE keys), the pod will CreateContainerConfigError because the secret the Deployment references does not exist and the keys won't line up.

    This is the primary refinement blocker. See Recommendations.

    Repo Placement

    OK. Issue lives on forgejo_admin/notion-mcp-remote; the kustomize overlay that would reference the secret lives on forgejo_admin/pal-e-deployments (tracked by board item #1044); the namespace lives on forgejo_admin/pal-e-platform (issue #296, board item #1045). Cross-repo linkage is correct — this ticket correctly scopes only the kubectl provisioning step.

    Dependencies

    • Blocks on issue #6 (board #1046) — "feat: register public Notion OAuth integration for remote proxy" — produces the real CLIENT_ID / CLIENT_SECRET values. Currently in backlog. Dependency is explicitly documented in the Context section. OK.
    • Blocks on pal-e-platform issue #296 (board #1045) — "tofu apply" that creates the notion-mcp-remote namespace. Currently in backlog. Dependency is explicitly documented in Constraints. OK.
    • Blocks pal-e-deployments #132 (board #1044) kustomize overlay and pal-e-services #57 (board #1043) ArgoCD Application — both must not sync until secrets exist, or they will be replaced with placeholders per SOP. Not explicitly documented as downstream blockees in the issue; the SOP reference implies it.
    • All dependencies are documented adequately for an operator to sequence the work.

    Acceptance Criteria

    Criteria are testable and each has a runnable verification command. The final AC "Disaster-recovery copy stored in ~/secrets/notion-mcp-remote/" aligns with sop-secrets-management Layer 2 (plaintext .env host backup). The issue picks the legacy manual kubectl path (documented in sop-secrets-management → "Adding a new app secret (manual path — legacy)"), which is still explicitly blessed by the pre-deploy-validation checklist row for "Application secrets."

    The SOP does note that SOPS+Age is the preferred path for new services. The ticket chose manual kubectl, which is a valid strategic choice for a pre-sync bootstrap (SOPS still requires ArgoCD to run decryption, so manual is the only option strictly before the first sync). No refinement needed on this front — calling it out for visibility.

    Blast Radius

    • Other remote MCP services on the platform (e.g. gmail-mcp-remote pattern) likely use the same pre-sync manual path. No collateral risk.
    • Regenerating SESSION_SECRET invalidates any existing encrypted tokens.json — since this is the first-time bootstrap (no prior tokens), no data loss. Worth a callout in the issue body for future rotations.
    • If the Secret name / keys are fixed (see refinement below), no code change is needed; if envFrom is adopted instead, the Deployment manifest changes and a new image need not be rebuilt but the Deployment patch will churn.

    Decomposition Assessment

    1 repo, 0 file targets, 5 AC items, single kubectl command plus backup copy. Estimated operator time once dependencies are met: <5 minutes. No decomposition needed.

    Recommendation

    • [BODY] Reconcile the secret name and key shape with k8s/deployment.yaml. The deployment currently references Secret notion-mcp-secrets with kebab-case keys via valueFrom: secretKeyRef. Pick one of two paths and update the AC accordingly:
      • Option A (preferred, matches current manifest): change AC to kubectl create secret generic notion-mcp-secrets with keys base-url, oauth-client-id, oauth-client-secret, session-secret, onboard-secret. Update the jsonpath AC accordingly. Zero code / manifest churn.
      • Option B: keep the ticket's notion-mcp-remote / UPPER_SNAKE / envFrom shape, and add a file target: patch k8s/deployment.yaml to use envFrom: - secretRef: name: notion-mcp-remote. This aligns with the Context paragraph ("envFrom") but is a bigger change and would overlap scope with board item #1044 (kustomize overlay).
    • [LABEL] Consider replacing arch:k8s-secret with a more specific, existing arch note reference. arch:deployment-notion-mcp-remote would map to the existing arch note that already documents the "secrets before first sync" decision; arch:secrets-pipeline would map to the platform-wide note. Optional — the current label is interpretable but inconsistent with the rest of this board.
    • [BODY] (nit) The Context paragraph references envFrom as the reference mechanism, which contradicts both the current deployment (valueFrom) and Option A above. Update whichever language survives the refinement.
    • [BODY] (nit) Consider adding a post-provision callout to the issue body: "If SESSION_SECRET is ever rotated, any persisted encrypted tokens in the PVC become unreadable and users must re-OAuth." This is first-time bootstrap so it is not a blocker, but future readers benefit.

    Summary

    Scope, traceability, dependencies, and SOP alignment are all solid. One concrete, fixable mismatch between the AC's secret shape and what the deployment manifest actually reads is the reason this ticket is NEEDS_REFINEMENT rather than READY. Once the AC is reconciled with k8s/deployment.yaml (Option A is the least churn), the ticket can proceed to todo.

  • Verdict: NEEDS_REFINEMENT

    Board item: #1043 on board-notion-mcp-remote -- Forgejo issue forgejo_admin/pal-e-services#57. Type: Feature. Points: 3. Labels: type:feature,story:ops-deploy-gitops,arch:argocd.

    Template Completeness

    • [x] Type (Feature)
    • [x] Lineage (standalone, scoped from project-notion-mcp-remote)
    • [x] Repo (forgejo_admin/pal-e-services)
    • [x] User Story
    • [x] Context (detailed, includes key values)
    • [x] File Targets
    • [x] Acceptance Criteria
    • [x] Test Expectations (with run command)
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    Traceability

    • [x] story:ops-deploy-gitops label -- maps to story-notion-mcp-remote-ops-deploy-gitops
    • [x] story note verified -- listed in project-notion-mcp-remote user-stories table (AC: "pal-e-services var.services entry landed via PR")
    • [x] arch:argocd label -- component: ArgoCD (entry drives ArgoCD Application discovery)
    • [ ] arch note MISSING -- [SCOPE] No arch-argocd note exists in pal-e-docs. search_notes(query="arch-argocd") returned zero results. Create arch-argocd describing ArgoCD's role as the GitOps sync engine for pal-e services (driven by argocd_application.service in terraform/services.tf).
    • [x] Forgejo issue -- forgejo_admin/pal-e-services#57, open

    File Targets

    • [x] terraform/k3s.tfvars -- verified path. File is gitignored (per repo .gitignore: *.tfvars excluded, only *.tfvars.example tracked). This is expected: sop-platform-tf-changes documents pal-e-services as "plan-and-apply-before-merge" with k3s.tfvars edited locally. Agent must have local k3s.tfvars available.
    • [ ] terraform/services.tf -- [BODY] imprecise. Issue says "Possibly terraform/services.tf -- if the module signature needs adjustment to pass funnel=true". This is not needed. services.tf already handles per-service funnel via kubernetes_ingress_v1.service_funnel with for_each = { for k, v in var.services : k => v if v.funnel }, and variables.tf already exposes funnel = bool in the var.services object type. Remove the speculative "possibly services.tf" bullet -- it's already supported. A dev agent could burn time "inspecting" a file that needs no change.

    Repo Placement

    Correct. Issue filed on forgejo_admin/pal-e-services where terraform/k3s.tfvars and the var.services schema live. Single-repo change; no multi-repo coordination within this ticket.

    Dependencies

    • Upstream (blocking): forgejo_admin/pal-e-deployments#132 -- kustomize overlay at overlays/notion-mcp-remote/prod. Currently OPEN, board item #1044 in backlog. Ticket correctly documents this ("Sibling overlay PR on pal-e-deployments must land first"). ArgoCD sync will fail if the overlay path doesn't exist in the pal-e-deployments repo on the branch referenced by target_revision = main.
    • Downstream: forgejo_admin/pal-e-platform#296 (tofu apply to provision) -- board item #1045 in backlog. Ticket does not name this explicitly, but it is implied by "PR reviewed and merged" -- the actual tofu apply against infrastructure is a separate board item handled after this PR lands. [BODY] optional: add an explicit "Downstream: pal-e-platform#296 handles the tofu apply" bullet to Related for dev-agent clarity, since this ticket's AC includes "tofu plan" but not "tofu apply" (the SOP requires manual apply-before-merge on pal-e-services).

    Schema precision issues (BODY fixes)

    • [BODY] source_repo value is underspecified. Issue body says source_repo: pal-e-deployments. Verified against variables.tf (variable "services" schema) and services.tf (coalesce(each.value.source_repo, each.value.forgejo_repo) interpolated as http://forgejo-http.forgejo.svc.cluster.local/${source_repo}.git), plus the k3s.tfvars.example example which uses forgejo_admin/pal-e-deployments in forgejo_repo. The value MUST include the forgejo_admin/ owner prefix: source_repo = "forgejo_admin/pal-e-deployments". Without the prefix, ArgoCD will attempt to sync from an invalid URL forgejo-http.forgejo.svc.cluster.local/pal-e-deployments.git and fail. Fix in issue body.
    • [BODY] Clarify workflow expectation. Per sop-platform-tf-changes, pal-e-services uses plan-and-apply-before-merge (manual tofu apply), not CI-driven apply-on-merge (which is the pal-e-platform pattern). The ticket's AC "tofu plan ... no unexpected diff" + "PR reviewed and merged" is correct, but the agent needs to know the apply is manual and happens BEFORE PR merge. Add a sentence to Constraints: "pal-e-services uses plan-and-apply-before-merge per sop-platform-tf-changes -- do NOT rely on CI apply; a human operator runs tofu apply -lock=false -var-file=k3s.tfvars after plan review."

    Acceptance Criteria

    Four ACs, all verifiable by an agent:

    • AC1 (entry present) -- grep-verifiable.
    • AC2 (plan diff shows expected resources) -- runnable; the expected resource list is explicit (ArgoCD Application, namespace, Harbor project + robots, image pull secret, Tailscale Funnel ingress). Matches services.tf resources exactly.
    • AC3 (values match pre-deploy-validation checklist rows) -- references service-onboarding-sop (slug not found via search_notes; may live under a different slug but is referenced consistently across platform docs -- treat as present).
    • AC4 (PR reviewed and merged) -- process criterion, verifiable via PR URL.

    Blast Radius

    Additive-only change to a single map key in var.services. The services.tf resources are all for_each = var.services, so no existing service's resources are touched. Harbor project names are unique (notion-mcp-remote has not been taken). Namespace collision risk: low (namespace notion-mcp-remote should not exist yet). The only subtle risk is: if any other service in the current k3s.tfvars has drifted from its declared state, tofu plan may show unexpected diffs beyond notion-mcp-remote -- the agent must verify the diff is isolated to new resources only. The recent review-1064-2026-04-20 (tf state drift) incident on pal-e-services makes this a concrete concern; flag to agent.

    Decomposition Assessment

    1 file edit (k3s.tfvars, one map entry), 4 acceptance criteria, estimated agent work ~5-10 minutes including plan review. No decomposition needed. 3 points is reasonable given the plan-review + manual-apply coordination overhead specific to pal-e-services.

    Recommendation

    • [BODY] Fix source_repo value: pal-e-deployments -> forgejo_admin/pal-e-deployments (full owner/repo form; required by services.tf URL construction).
    • [BODY] Remove the "Possibly terraform/services.tf" speculation under File Targets. The module already supports per-service funnel=true; no module adjustment is needed. Replace with explicit "File Targets: terraform/k3s.tfvars only" to prevent the agent from opening/editing services.tf.
    • [BODY] Add workflow clarification to Constraints: pal-e-services uses plan-and-apply-before-merge (manual tofu apply), not CI-driven apply-on-merge. Reference sop-platform-tf-changes.
    • [BODY] Add a "Blast Radius" note: verify tofu plan diff is isolated to new notion-mcp-remote resources only; if other services' resources show drift, stop and escalate (reference review-1064-2026-04-20).
    • [SCOPE] Create architecture note arch-argocd describing ArgoCD's role in the platform (GitOps sync engine, argocd_application.service per-service via pal-e-services Terraform, Image Updater write-back pattern). Without this note, the arch:argocd label points to a missing document -- same pattern as the missing arch-tailscale-funnel gap flagged in review-788-2026-04-03.

    Once refinements land

    Re-review expected to verdict READY. The scope is otherwise sound, file path correct, dependencies mapped, ACs verifiable, blast radius understood.

  • Verdict: APPROVED

    Scope is solid. Feature template fully satisfied, traceability triangle complete (minus the non-blocking arch-woodpecker note — same gap flagged in review-273 and review-882 and treated as non-blocking per precedent). Dependency on issue #5 (Harbor URL fix) is explicitly captured. File-target absence is correct for this ticket type — it is a Woodpecker-admin ticket, not a code change.

    One notable field-state observation: the repo already reports active: true in Woodpecker (id 16). AC1 may already be satisfied mechanically. The implementing agent must verify activation is functional (webhook actually fires on a test commit) rather than just read the flag — see advisory below.

    Template Completeness

    • [x] Type — Feature
    • [x] Lineage — standalone, scoped from project-notion-mcp-remote, cites service-onboarding-sop steps 7–8
    • [x] Repo — forgejo_admin/notion-mcp-remote
    • [x] User Story — operator wants activation + Harbor credentials so main push triggers build-and-push
    • [x] Context — explains why activation is manual (woodpecker-sdk #6 gap), why MCP tool is required for secrets
    • [x] File Targets — explicitly "No repo file changes. Woodpecker UI + MCP tool calls only." Correct for this ticket type.
    • [x] Acceptance Criteria — 5 concrete, machine-verifiable conditions
    • [x] Test Expectations — mcp__woodpecker__list_pipelines and build step log inspection
    • [x] Constraints — waits on tofu apply, requires MCP not UI
    • [x] Checklist — 6 steps covering prereqs and verification
    • [x] Related — project, story, SOP

    Traceability

    • [x] story:ops-deploy-gitops label — matches story-notion-mcp-remote-ops-deploy-gitops
    • [x] story note verified — listed in project-notion-mcp-remote user-stories table
    • [x] arch:woodpecker label — identifies the Woodpecker CI component
    • [ ] arch note MISSING — arch-woodpecker search returned zero results. Non-blocking per precedent (review-273, review-882 flagged the same gap as non-blocking). See [SCOPE] recommendation below.
    • [x] Forgejo issue — forgejo_admin/notion-mcp-remote#8, state: open

    File Targets

    Issue states: "No repo file changes. Woodpecker UI + MCP tool calls only." This is correct — activation and secret provisioning are out-of-band platform operations, not repo edits. No file-target verification needed. Skipped per skill-review-ticket guidance for tickets with no file targets (Task-style scope inside a Feature wrapper).

    Repo Placement

    Issue is filed on forgejo_admin/notion-mcp-remote, the repo being activated. Correct placement. No cross-repo file changes required. The prerequisite Harbor robot lives in pal-e-platform (tracked separately in pal-e-platform#296 / pal-e-services#57) — the issue correctly treats those as upstream dependencies rather than pulling them into scope.

    Dependencies

    • Hard prereq: Issue #5 (Harbor URL fix) must merge before AC5 can pass. Acknowledged in the Checklist. AC4 is satisfiable without it — a feature-branch push will reach the build-and-push step and fail at registry URL, which still proves activation + secrets are wired.
    • Soft prereq: tofu apply on pal-e-platform must produce the Harbor robot credentials before AC3 (values match robot, not admin). Tracked in board items #1045 (pal-e-platform#296) and #1043 (pal-e-services#57). Acknowledged in Constraints and Checklist.
    • Board state: All three dependencies (#5, pal-e-platform#296, pal-e-services#57) are in backlog. None in in_progress. Ordering is captured correctly in the ticket Checklist but no item is yet blocking this one on the board layer — the implementer must respect the Checklist ordering.

    Acceptance Criteria

    All 5 AC are concrete and machine-verifiable:

    • AC1 "Repo visible as active in Woodpecker UI" — verifiable via mcp__woodpecker__get_repo (active: true).
    • AC2 "Secrets harbor_username and harbor_password visible" — verifiable via mcp__woodpecker__list_repo_secrets.
    • AC3 "Values match Harbor robot (not admin)" — verifiable by cross-referencing Harbor robot account from tofu output.
    • AC4 "Feature branch push triggers pipeline that reaches build-and-push step" — verifiable via mcp__woodpecker__list_pipelines. Proves activation + secrets without needing the Harbor URL fix to be in place.
    • AC5 "Main push produces full green pipeline once Harbor URL fix lands" — gated on #5 merging; explicitly deferred.

    AC4 is the critical validation step — it decouples activation verification from the Harbor URL fix dependency. Well-designed.

    Blast Radius

    Low. Pattern used by 7+ onboarded services (basketball-api, forgejo-mcp, gcal-mcp, gmail-mcp, pal-e-api, notion-mcp, westside-app, etc. — all visible as active: true in Woodpecker). No novel work. Failure mode is limited to this one repo: a bad secret prevents pipeline from pushing, which is non-destructive and trivially reversible via mcp__woodpecker__update_repo_secret.

    Sibling-service scan: no other MCP-remote services in this fleet are missing activation. No pattern to spread.

    Decomposition Assessment

    File targets: 0 (platform ops only). AC count: 5 (within limit). Estimated agent time: 2–5 minutes (one activation check, two create_repo_secret calls, one test-commit push, one pipeline-list check). No subtask independence — linear sequential work.

    No decomposition needed.

    Field-State Observations (for the implementer)

    • Repo already reports active: true in Woodpecker (id 16). This means the activation button was already pressed at some point. However, list_pipelines for this repo returns [] — no run has ever executed. Possible causes: (a) no push has occurred since activation (recent activation), (b) webhook is misconfigured and never fires (see todo-woodpecker-webhook-token-fix — a known class of failure post-Postgres migration). The implementer MUST verify activation is functional by pushing a test commit and confirming a pipeline row appears, not just trust the active flag.
    • list_repo_secrets returns [] — harbor_username and harbor_password are genuinely missing. AC2 is real, outstanding work.
    • Dockerfile.k8s exists (referenced by .woodpecker.yaml:25). Pipeline config is ready to run; the only block is secrets + the Harbor URL fix.
    • .woodpecker.yaml is at .woodpecker.yaml (not .woodpecker.yml) — confirmed present on main at commit 837e2f2.

    Recommendation

    APPROVED. Ready to advance from backlog to todo, with the understanding that the implementer respects the Checklist ordering (Harbor robot from tofu must exist before AC3 can pass).

    Advisory (non-blocking):

    • [SCOPE] Create architecture note arch-woodpecker in pal-e-docs. This is a cross-project gap surfaced by multiple recent reviews (review-273, review-882) and not unique to this ticket. Owner is the pal-e-platform project, not this one. Do not block this ticket on it.
    • [BODY] AC1 is mechanically already satisfied (active: true in Woodpecker). Suggest tightening AC1 to "Repo is active in Woodpecker and a test push produces a visible pipeline row in mcp__woodpecker__list_pipelines" so the implementer does not claim completion on a stale flag. Non-blocking — AC4 already covers the functional check, so an alert implementer will catch this anyway.
  • Verdict: NEEDS_REFINEMENT

    Note: Minor scope gap — one missing backing note for the arch:tailscale-funnel label. Everything else is in order. The ticket itself is well-scoped as a verification-only validation pass for the claude-ai-connect user story.

    Template Completeness

    Issue type: Feature (Type header = "Feature"). Routed against template-issue-feature.

    • [x] Type — "Feature"
    • [x] Lineage — standalone E2E verification ticket, proves Milestone M1
    • [x] Repo — forgejo_admin/notion-mcp-remote
    • [x] User Story — inline, matches story-notion-mcp-remote-claude-ai-connect
    • [x] Context — explains dependency chain + references sop-validation
    • [x] File Targets — explicitly "No repo file changes; verification + validation-note creation only" (appropriate for this ticket type)
    • [x] Acceptance Criteria — 11 checks across Platform / E2E / Recordkeeping
    • [x] Test Expectations — present with concrete pass conditions
    • [x] Constraints — present, including "do not begin until all upstream tickets closed"
    • [x] Checklist — present
    • [x] Related — links to project, user story, ops-deploy-gitops story, sop-validation

    Traceability

    • [x] story:claude-ai-connect label — story "Connect Notion to claude.ai"
    • [x] story note verified — story-notion-mcp-remote-claude-ai-connect exists and is listed in the project-notion-mcp-remote user-stories section
    • [x] arch:tailscale-funnel label present on board item
    • [ ] arch note MISSING — no arch-tailscale-funnel note exists in pal-e-docs. Tailscale Funnel behavior is covered piecewise in arch-deployment-notion-mcp-remote, arch-deployment-gdocs-daily-mcp-remote, arch-deployment-westside-mcp, and host-inventory-archbox, but there is no shared architecture note backing the arch:tailscale-funnel label. This is a recurring gap — the same finding was raised on review-788-2026-04-03 (playme2k funnel + kustomize). [SCOPE] Create shared arch-tailscale-funnel note documenting the Funnel proxy pattern (proxy pod in tailscale namespace, *.tail5b443a.ts.net hostnames, TLS termination, ingress handoff) so all services using that label link to one canonical diagram.
    • [x] type:feature label — appropriate (new capability, not a bug fix)
    • [x] Forgejo issue — forgejo_admin/notion-mcp-remote#9 — open, body matches template

    File Targets

    Not applicable — ticket is verification-only. Deliverables are a validation note (per sop-validation) and a milestone mark on the project page Status section. This is the correct shape for the claude-ai-connect validation ticket.

    Repo Placement

    OK. The Forgejo issue is filed on forgejo_admin/notion-mcp-remote, which is the correct owning service for the E2E validation. The validation note will live in pal-e-docs, and the milestone mark lands on project-notion-mcp-remote — both are separate surfaces, not file changes in the repo.

    Dependencies

    Seven prerequisite tickets must close before this ticket executes. All are currently open / in backlog:

    • pal-e-platform#296 — tofu apply to provision notion-mcp-remote (open)
    • pal-e-services#57 — add notion-mcp-remote to var.services (open)
    • pal-e-deployments#132 — add overlays/notion-mcp-remote/prod kustomize overlay (open)
    • notion-mcp-remote#5 — switch Woodpecker image push to internal Harbor URL (open)
    • notion-mcp-remote#6 — register public Notion OAuth integration for remote proxy (open)
    • notion-mcp-remote#7 — provision app secrets in notion-mcp-remote namespace (open)
    • notion-mcp-remote#8 — activate repo in Woodpecker + add Harbor push secrets (open)

    Dependencies are documented correctly in the Constraints section ("Do not begin this ticket until all upstream tickets closed"). The review->todo gate on backlog is fine for now; execution must not start until upstream work closes. No refinement needed on dependency documentation itself.

    Acceptance Criteria

    AC are testable with concrete commands and tool calls:

    • Platform checks (6): Woodpecker pipeline status, ArgoCD sync+health, pod Running, /health 200, /.well-known/oauth-authorization-server JSON, Prometheus scrape. All verifiable via mcp__woodpecker__list_pipelines, kubectl, curl, and Prometheus UI. The notion_mcp_remote_up metric referenced in AC is confirmed present in server.py.
    • E2E checks (4): claude.ai accepts URL, Notion OAuth consent < 60s, search tool returns real results, second tool call (get_page) succeeds. Matches all 6 AC of the user story.
    • Recordkeeping (3): validation note per sop-validation, Milestone M1 marked on project page Status, all prior tickets closed.

    One small tightening opportunity — [BODY] The AC currently say "curl https://notion-mcp-remote.tail5b443a.ts.net/.well-known/oauth-authorization-server returns valid JSON with the expected issuer, authorization_endpoint, token_endpoint." Consider also asserting the registration_endpoint is present since Dynamic Client Registration is how claude.ai onboards. Minor; not a blocker.

    Blast Radius

    Small. Ticket is observational — no config changes, no code merges. Risk is only that a failing check here exposes a real defect in upstream tickets (which is the point). If any check fails, the validation verdict is PARTIAL/FAIL and the appropriate upstream ticket gets reopened, not this one.

    Decomposition Assessment

    AC count is 11, which exceeds the default 5-AC threshold — but this is the explicit, intended shape of a validation ticket: one pass that exercises every AC of the user story, per sop-validation. Estimated time is 15–20 minutes of browser + kubectl + curl work, not 5, but decomposing a validation ticket defeats its purpose (it would split cross-cutting concerns back into per-layer checks that already belong on the upstream tickets). Points=2 reflects this correctly.

    No decomposition recommended.

    Recommendation

    Verdict: NEEDS_REFINEMENT — one concrete gap to close before this moves to todo:

    • [SCOPE] Create shared architecture note arch-tailscale-funnel documenting the Funnel proxy pattern used by this and other MCP services. This unblocks not just #1049 but every current/future ticket labeled arch:tailscale-funnel. Can reuse content already in host-inventory-archbox, doc-network-traffic-map, and arch-deployment-notion-mcp-remote.
    • [BODY] (optional, nice-to-have) Add registration_endpoint to the list of OAuth metadata fields asserted in the Platform checks AC, since DCR is on the handshake hot path.

    Upstream dependencies (7 open tickets) are correctly documented; no action on those from this review — they will be closed by their own tracks.

    • Board item 1049 — board-notion-mcp-remote column=backlog
    • Forgejo: forgejo_admin/notion-mcp-remote#9
    • story-notion-mcp-remote-claude-ai-connect
    • project-notion-mcp-remote
    • sop-validation
    • Prior funnel-arch-missing finding: review-788-2026-04-03
  • Verdict: NEEDS_REFINEMENT

    Single gap: the arch:argocd label has no backing architecture note in pal-e-docs. The ticket's actual architectural anchor is arch-deployment-notion-mcp-remote, which does exist. Everything else is solid and apply-ready pending the two listed dependencies.

    Template Completeness

    • [x] Type (Feature)
    • [x] Lineage (references project-notion-mcp-remote, service-onboarding-sop step 5)
    • [x] Repo (forgejo_admin/pal-e-platform)
    • [x] User Story
    • [x] Context (resources provisioned, lock-false + no -target rationale)
    • [x] File Targets ("no file changes in this repo" — apply operation; follow-up-ticket guidance included)
    • [x] Acceptance Criteria
    • [x] Test Expectations (tofu fmt, validate, plan command)
    • [x] Constraints (pre-deploy checklist, secrets pre-provisioned, lock-false, no -target)
    • [x] Checklist
    • [x] Related

    Traceability

    • [x] story:ops-deploy-gitops label — "GitOps Deploy via pal-e Platform"
    • [x] story note verified — story-notion-mcp-remote-ops-deploy-gitops listed in project-notion-mcp-remote user-stories section
    • [x] arch:argocd label present on board item
    • [ ] arch note MISSING — no arch-argocd note exists in pal-e-docs. The ticket body references arch-deployment-notion-mcp-remote (which does exist and is the correct architectural anchor for this apply operation)
    • [x] Forgejo issue — https://forgejo.tail5b443a.ts.net/forgejo_admin/pal-e-platform/issues/296 open and valid

    File Targets

    Issue explicitly states no file changes in pal-e-platform — this is the apply operation itself plus recordkeeping. "Files NOT to touch" (network-policies.tf) is correctly called out. Follow-up-ticket guidance for any plumbing gaps surfaced by tofu plan is appropriate. No file-target verification applicable.

    Repo Placement

    OK. Apply runs against forgejo_admin/pal-e-platform terraform. Sibling tickets correctly scoped to pal-e-services (#57 var.services entry) and pal-e-deployments (#132 overlay). Application secret provisioning tracked separately on the notion-mcp-remote board. No repo-boundary confusion.

    Dependencies

    Explicitly documented and present on board-notion-mcp-remote:

    • pal-e-services #57 (var.services entry) — board item 1043, backlog, 3 pts — MUST LAND FIRST
    • pal-e-deployments #132 (kustomize overlay) — board item 1044, backlog, 3 pts — MUST LAND FIRST
    • notion-mcp-remote #7 (secrets kubectl create secret generic) — board item 1047, backlog, 2 pts — MUST LAND BEFORE FIRST ARGOCD SYNC (sibling, not blocker of tofu apply itself, but called out in Constraints to avoid ArgoCD clobbering)

    Dependency chain is complete and correctly sequenced.

    Acceptance Criteria

    Verifiable and concrete:

    • tofu plan -lock=false diff inspection (expected resources enumerated)
    • No unexpected destructive changes
    • Lucas approval gate (explicit)
    • kubectl get ns notion-mcp-remote
    • ArgoCD Application Healthy + Synced
    • Funnel URL (notion-mcp-remote.tail5b443a.ts.net) resolves and returns a pod response

    Each criterion is agent-checkable post-apply. Test Expectations section provides the exact plan command: cd terraform && tofu plan -lock=false -var-file=k3s.tfvars.

    Blast Radius

    Contained. First-deploy apply for a new service with no shared infrastructure modifications:

    • Creates namespace, ArgoCD Application, Harbor project + robots, image pull secret, Tailscale Funnel ingress — all scoped to this service
    • No NetworkPolicy changes (service only egresses to api.notion.com)
    • No Keycloak client (Notion OAuth end-to-end)
    • Risk: a terraform refactor since last apply could surface unrelated diff — the AC "no unexpected destructive changes on unrelated services" covers this and Lucas-gated plan review is the mitigation
    • Aligns with sop-platform-tf-changes (lock-false, Lucas approval, no -target) and service-onboarding-sop step 5 (pre-deploy-validation checklist must be 100% green first)

    Decomposition Assessment

    No decomposition needed. Single apply operation with one pre-check (plan review) and one gate (Lucas approval). File count: 0. AC count: 7 criteria but all verifiable in a single post-apply pass. Estimated agent work: ~3 min for plan + validation + apply + smoke check. Fits 5-minute rule.

    Recommendation

    One issue to resolve before this advances out of review:

    • [LABEL] OR [SCOPE] — The arch:argocd label has no backing arch-argocd note. Two options:
      1. Preferred (LABEL fix): Replace arch:argocd with arch:deployment-notion-mcp-remote on the board item so the label points at the real backing note (arch-deployment-notion-mcp-remote) that the ticket body already references. This matches the sibling tickets' spirit (arch:kustomize-overlay, arch:woodpecker are component-specific) but acknowledges this ticket's anchor is the service-level deployment diagram.
      2. Alternative (SCOPE fix): Create a shared arch-argocd note describing ArgoCD as a platform component (Application resource lifecycle, Image Updater integration, sync modes) — reusable by pal-e-services #57 which uses the same label.

    All other scope elements (template, dependencies, AC, guardrails, blast radius) are solid. After the label/arch-note decision, this ticket is apply-ready once #57 and #132 merge and the pre-deploy validation checklist is green.

  • Review: Register Notion OAuth app review-1046-2026-04-21

    Verdict: NEEDS_REFINEMENT

    Ticket: board item #1046 — forgejo_admin/notion-mcp-remote#6feat: register public Notion OAuth integration for remote proxy

    Labels: type:feature, story:claude-ai-connect, arch:notion-oauth — points: 1 — column: backlog

    Template Completeness

    Validated against template-issue-feature:

    • [x] ### Type — Feature
    • [x] ### Lineage — standalone, scoped from project
    • [x] ### Repoforgejo_admin/notion-mcp-remote
    • [x] ### User Story — present (AS/I want/So that)
    • [x] ### Context — clear, explains three-legged flow and Public-vs-Internal distinction
    • [x] ### File Targets — present (explicitly "no repo file changes") and lists target secrets plus files NOT to touch
    • [x] ### Acceptance Criteria — 5 ACs, testable
    • [x] ### Test Expectations — manual browser smoke + curl smoke
    • [x] ### Constraints — three real constraints listed
    • [x] ### Checklist — present
    • [x] ### Related — project + story + arch

    Traceability

    • [x] story:claude-ai-connect label — Connect Notion to claude.ai
    • [x] story note verified — entry exists in project-notion-mcp-remote user-stories section, linking to story-notion-mcp-remote-claude-ai-connect
    • [x] arch:notion-oauth label — OAuth handshake component
    • [ ] arch note MISSING under that exact slug — search_notes("arch-notion-oauth") returned no results. The OAuth handshake is documented, but under arch-dataflow-notion-mcp-remote (sequenceDiagram explicitly covering authorize/callback/token). [SCOPE] Either rename the label to arch:dataflow-notion-mcp-remote or create a dedicated arch-notion-oauth component note. Recommend re-labeling since the dataflow note already covers this material.
    • [x] Forgejo issue — https://forgejo.tail5b443a.ts.net/forgejo_admin/notion-mcp-remote/issues/6, open, created 2026-04-21

    File Targets

    This is credential provisioning — no repo file edits expected. The ticket correctly states this and lists downstream consumers:

    • [x] ~/secrets/notion-mcp-remote/credentials.env — verified directory exists at ~/secrets/notion-mcp-remote/
    • [x] server.py:19-20 already reads NOTION_OAUTH_CLIENT_ID / NOTION_OAUTH_CLIENT_SECRET from env — verified via Grep
    • [x] .env.example:2-3 uses the same two key names — verified
    • [x] k8s/deployment.yaml:43-52 mounts both from secret notion-mcp-secrets keys oauth-client-id / oauth-client-secret — verified
    • [x] "Do not touch" list is accurate — server.py and requirements.txt correctly already consume these env vars

    Repo Placement

    OK — the issue is filed on forgejo_admin/notion-mcp-remote and the credentials feed this service's secret. Sibling ticket #7 (k8s secret provisioning) and #8 (Woodpecker activation) are correctly split into separate issues because they touch different systems.

    Dependencies

    Board scan (board-notion-mcp-remote):

    • This ticket blocks:
      • #7 (issue 1047, arch:k8s-secret) — provisions app secrets in notion-mcp-remote namespace, needs CLIENT_ID/SECRET values
      • #9 (issue 1049, arch:tailscale-funnel) — E2E verify / claude.ai connect, needs the integration to exist
    • Not blocked by anything in in_progress — safe to pull.
    • Dependencies implied in context ("Blocks ticket #5 / #8") per router brief do not match the actual board — ticket #5 is the Harbor/Woodpecker CI bug, and #8 is Woodpecker repo activation, neither of which consume Notion OAuth credentials directly. [BODY] Clarify blocker chain in the issue body: this ticket unblocks #7 (secret provisioning) and #9 (E2E verify), not #5 and #8. (Or correct the router brief — this is a router-context issue, not a ticket-body issue, so noting for Ava.)

    Acceptance Criteria

    All 5 ACs are verifiable by an agent/human:

    1. Integration exists in Daily workspace — verifiable by loading the Notion integrations UI.
    2. Distribution is Public — same UI verification.
    3. Redirect URI exact — ISSUE: wrong path. The ticket specifies https://notion-mcp-remote.tail5b443a.ts.net/callback, but the running service advertises and consumes /oauth/callback (confirmed in mcp-remote-auth/src/mcp_remote_auth/routes.py:41 and provider.py:125,151). Using /callback will cause Notion to reject the token exchange. [BODY] Fix redirect URI to https://notion-mcp-remote.tail5b443a.ts.net/oauth/callback in the AC, the smoke curl, and the Constraints section.
    4. Creds captured in ~/secrets/notion-mcp-remote/credentials.env — testable by file presence + key names.
    5. Smoke curl returns consent HTML — valid once redirect URI is fixed.

    [BODY] Optional consideration: the service also exposes /onboard/callback (onboarding flow, onboarding.py:136). Out of scope for the claude-ai-connect story, but if Lucas plans to use the same public integration for onboarding, that redirect URI should also be registered now to avoid a second manual trip to the Notion UI.

    Blast Radius

    • Credential leakage: Constraint already calls out "do not commit CLIENT_SECRET to any git repo." Good.
    • Redirect URI mismatch: If the wrong path (/callback) is configured with Notion, the entire OAuth leg fails silently at the token exchange. The ACs as written would be marked green by an agent but the service would still be broken end-to-end — #9 (E2E verify) would catch it, but a lot of work happens in between. Fixing AC #3 prevents this.
    • Base URL drift: BASE_URL env var is the single source of truth; if the Tailscale Funnel hostname ever changes, the Notion integration redirect URI must be updated in the Notion UI. Worth a note in Constraints.

    Decomposition Assessment

    1 point, 5 ACs, no code edits, one UI flow. Well within the 5-minute rule. No decomposition needed.

    Recommendation

    Two concrete fixes required before this can move to todo:

    • [BODY] Fix the redirect URI path throughout the issue (AC #3, smoke curl URL in AC #5, Constraints, Test Expectations): /callback/oauth/callback. This is the critical fix — the current path will silently break the handshake.
    • [SCOPE] Resolve the arch:notion-oauth label: either (a) change the label to arch:dataflow-notion-mcp-remote to match the existing architecture note, or (b) create a dedicated arch-notion-oauth component note. Option (a) is lighter weight and the existing note already captures the handshake.

    Optional (non-blocking):

    • [BODY] Add /onboard/callback as a second registered redirect URI if the same integration will back onboarding (future story).
    • [BODY] Correct the blocker references in the ticket's lineage/related context (blocks #7 and #9, not #5 and #8) — or clarify router brief.
  • Verdict: APPROVED

    Scope is solid. File target verified. SOP violation is real and the fix is single-line, single-file, testable. Ready to advance from backlog to next_up (this project uses backlog as the todo gate).

    Template Completeness

    Bug template (template-issue-bug) — all required sections present:

    • [x] Type: Bug
    • [x] Lineage (standalone, cross-referenced to service-onboarding-sop)
    • [x] Repo: forgejo_admin/notion-mcp-remote
    • [x] What Broke (with offending snippet quoted)
    • [x] Repro Steps (3 concrete steps)
    • [x] Expected Behavior (explicit target value + SOP reference)
    • [x] Environment (cluster/namespace, commit SHA, alerts state)
    • [x] Acceptance Criteria (4 items, all testable)
    • [x] Related (project, SOP, story)

    Traceability

    • [x] story:ops-deploy-gitops label — GitOps Deploy via pal-e Platform
    • [x] story note verified — story-notion-mcp-remote-ops-deploy-gitops listed in project-notion-mcp-remote user-stories section. First AC on that story is literally "Woodpecker pipeline builds on push to main, pushes to internal Harbor URL" — this ticket fulfills that AC.
    • [x] arch:woodpecker label — Woodpecker CI component
    • [~] arch note — no dedicated arch-woodpecker note in pal-e-docs. Shared Woodpecker architecture is documented in arch-ci-pipeline (pal-e-platform) and the service-specific deployment wiring is in arch-deployment-notion-mcp-remote (which explicitly calls out "must use internal Harbor URL per SOP" for Woodpecker). Non-blocking — consistent with prior review precedent (see review-882-2026-04-07-v2).
    • [x] Forgejo issue — forgejo_admin/notion-mcp-remote#5, open

    File Targets

    • [x] .woodpecker.yaml — verified exists at repo root. Offending value confirmed: registry: harbor.tail5b443a.ts.net.
    • [~] Line reference mismatch — issue body says .woodpecker.yaml:15 but the actual line is :22 (line 15 is ruff format --check .). Trivial — an implementing agent will find it via the quoted snippet or grep. Not worth blocking.

    Repo Placement

    Correct. .woodpecker.yaml lives in forgejo_admin/notion-mcp-remote, which is where the issue is filed. No multi-repo spread.

    Dependencies

    No blocking dependencies. This ticket is a leaf — nothing in in_progress blocks it. It logically unblocks the rest of story-ops-deploy-gitops:

    • #1043 (pal-e-services var.services entry) — wants a working CI push target
    • #1044 (pal-e-deployments kustomize overlay) — needs images in Harbor to reference
    • #1048 (another Woodpecker-tagged item) — adjacent CI work

    Recommend keeping this at the head of the ops-deploy-gitops ordering.

    Acceptance Criteria

    All 4 AC are verifiable by an agent:

    • AC1/AC2 — grep-checkable (harbor-core.harbor.svc.cluster.local present, harbor.tail5b443a.ts.net absent in .woodpecker.yaml).
    • AC3 — requires an actual Woodpecker pipeline run on a test branch. Implementing agent should open a test branch, push, and observe the build-and-push step succeed. Note: this assumes the Woodpecker agent can resolve harbor-core.harbor.svc.cluster.local (i.e., it runs inside the cluster). That is the documented assumption per SOP; if it turns out the agent runs externally, AC3 will fail and the fix needs a different shape (e.g., a $HARBOR_INTERNAL secret). The issue already lists $HARBOR_INTERNAL as an acceptable alternative, so the scope covers this contingency.
    • AC4 — regression check on lint/test steps, observable from pipeline output.

    Blast Radius

    One out-of-scope reference to the external Harbor URL elsewhere in the repo:

    • k8s/deployment.yaml:23image: harbor.tail5b443a.ts.net/notion-mcp-remote/notion-mcp-remote:latest

    This is the runtime image reference for the Deployment manifest (resolved by kubelet / ArgoCD Image Updater), not a CI push target. The SOP "CI registry URL" check applies only to .woodpecker.yaml. Whether deployment.yaml should also switch to the internal URL is a separate design question (depends on whether kubelet/ArgoCD can resolve cluster-internal DNS for image pulls, and on Image Updater's configured registry). Flagging as informational — do NOT expand this ticket's scope. If it needs fixing, file a separate issue.

    No similar pattern in sibling repos within this project (shared infra repos out of scope).

    Decomposition Assessment

    1 file modified, 4 AC, estimated <5 min agent work. Well under the decomposition threshold. No decomposition needed.

    Recommendation

    APPROVED — no blocking changes required. Two optional polish items the implementing agent may handle inline without re-review:

    • [BODY-OPTIONAL] Update the line reference in the issue body from .woodpecker.yaml:15 to .woodpecker.yaml:22. Trivial; the quoted snippet makes the target unambiguous regardless.
    • [SCOPE-FUTURE] Consider filing a follow-up ticket to evaluate whether k8s/deployment.yaml should also use the internal Harbor URL for image pulls. Out of scope for this bug; do not bundle.

    Item #1042 is ready to advance from backlog to next_up.

Architecture 3
  • Deployment: notion-mcp-remote arch-deployment-notion-mcp-remote

    Deployment: notion-mcp-remote

    Diagram

    graph TB
        CAI[claude.ai]
        TF[Tailscale Funnelnotion-mcp-remote.tail5b443a.ts.net]
        ING[Traefik ingress]
        SVC[Service notion-mcp-remote]
        POD[Pod notion-mcp-remote :8000]
        PVC[(PVC tokens.json)]
        NAPI[api.notion.com]
        HAR[Harbor Registry]
        WP[Woodpecker CI]
        ACD[ArgoCD]
        IU[ArgoCD Image Updater]
        OVL[overlays/notion-mcp-remote/prodpal-e-deployments]
        TFVAR[pal-e-services var.services]
    
        CAI -->|HTTPS| TF
        TF --> ING
        ING --> SVC
        SVC --> POD
        POD --> PVC
        POD -->|egress 443| NAPI
    
        WP -->|build + push| HAR
        IU -->|watch tags| HAR
        IU -->|write newTag| OVL
        ACD -->|sync| OVL
        ACD -->|apply| POD
    
        TFVAR -->|terraform| ACD
        TFVAR -->|terraform| HAR
        TFVAR -->|terraform| TF
    

    Components

    Component Purpose Notes
    Tailscale Funnel Public ingress at notion-mcp-remote.tail5b443a.ts.net Provisioned by pal-e-services terraform when funnel=true
    Namespace + Service + Pod Standard k8s trio; app listens on :8000 Dockerfile.k8s EXPOSE 8000
    PVC Persists data/tokens.json across pod restarts k8s/pvc.yaml; single-writer
    Harbor Private image registry; project notion-mcp-remote Created by tofu apply; robot creds via Woodpecker secrets
    Woodpecker CI Builds on push to main, pushes SHA + latest tags .woodpecker.yaml; must use internal Harbor URL per SOP
    ArgoCD + Image Updater Watches Harbor tags, writes newTag to overlay kustomization.yaml, syncs Application managed by pal-e-services terraform
    pal-e-services var.services Declarative service registration: image, port, funnel, overlay path k3s.tfvars; PR-gated
    pal-e-deployments overlay Kustomize overlay at overlays/notion-mcp-remote/prod/ kustomization.yaml + deployment-patch.yaml; no secrets in git

    Key Decisions

    • Tailscale Funnel for public exposure — same pattern as other remote MCP services. No separate domain or cert management.
    • Kustomize overlay, not inline in pal-e-services — per convention-kustomize-overlay. Image Updater writes newTag into the overlay so ArgoCD sees declarative tag pins.
    • Secrets created via kubectl before first sync — overlay must not contain real secret data or ArgoCD will clobber on sync (service-onboarding-sop warning).
    • No Keycloak client — auth is Notion OAuth end-to-end; no pal-e identity layer needed for this service.
    • No NetworkPolicy changes — service egresses only to api.notion.com; no MinIO/Postgres/Keycloak dependencies.
    • arch-domain-notion-mcp-remote
    • arch-dataflow-notion-mcp-remote
    • project-notion-mcp-remote
    • service-onboarding-sop
    • convention-kustomize-overlay
  • Data Flow: notion-mcp-remote arch-dataflow-notion-mcp-remote

    Data Flow: notion-mcp-remote

    Diagram

    sequenceDiagram
        participant C as claude.ai
        participant R as notion-mcp-remote
        participant S as TokenStore
        participant N as Notion OAuth
        participant API as Notion API
    
        Note over C,R: 1. Dynamic Client Registration
        C->>R: POST /register
        R->>S: persist DynamicClient
        R-->>C: client_id
    
        Note over C,R: 2. OAuth authorize
        C->>R: GET /authorize (code_challenge, state)
        R->>N: 302 to Notion consent
        N-->>C: consent UI
        C->>N: approve
        N->>R: GET /callback (code)
        R->>N: POST /v1/oauth/token (code)
        N-->>R: access_token + owner + workspace
        R->>S: persist AccessToken (notion_token, workspace, 1y)
        R-->>C: 302 with MCP auth code
        C->>R: POST /token (code)
        R-->>C: access_token (mcp_token), ttl 1y
    
        Note over C,R: 3. Tool call
        C->>R: POST /mcp (Bearer mcp_token)
        R->>S: load_access_token(mcp_token)
        R->>R: _setup_notion_client — ContextVar.set(NotionClient(notion_token))
        R->>R: MCP tool handler runs
        R->>API: REST call with notion_token
        API-->>R: JSON result
        R-->>C: MCP tool response
    

    Components

    Component Purpose Notes
    Dynamic Client Registration claude.ai registers itself with no prior configuration Standard MCP DCR; handled by mcp-remote-auth
    Authorize/Callback Three-legged OAuth proxy: claude.ai → notion-mcp-remote → Notion notion-mcp-remote is both an OAuth server (to claude.ai) and an OAuth client (to Notion)
    Token exchange Converts Notion OAuth response into an MCP access token that wraps the notion_token upstream_token_key = "notion_token"
    ContextVar injection Per-request NotionClient isolation set via client_patch.set_client_for_request
    Tool handlers The 26 notion_mcp tools, imported unchanged apply_patch() swaps their get_client before tools register

    Key Decisions

    • OAuth proxy (not pass-through) — issue our own MCP tokens rather than handing the raw Notion token to claude.ai. Lets us rotate independently and audit per-client.
    • ContextVar over global state — required because multiple concurrent requests may hit the same process with different user tokens (story: multi-workspace-safety).
    • apply_patch before notion_mcp import — notion_mcp registers tools eagerly at import; the monkey-patch must land first or tool modules capture the unpatched get_client.
    • stateless_http = False — claude.ai needs session affinity; fixed in PR #17/18.
    • arch-domain-notion-mcp-remote
    • arch-deployment-notion-mcp-remote
    • project-notion-mcp-remote
  • Domain Model: notion-mcp-remote arch-domain-notion-mcp-remote

    Domain Model: notion-mcp-remote

    Diagram

    erDiagram
        DynamicClient ||--o{ AuthCode : issues
        DynamicClient ||--o{ AccessToken : owns
        AuthCode ||--o| AccessToken : "exchanges for"
        AccessToken }o--|| NotionWorkspace : "grants access to"
        AccessToken }o--|| User : "belongs to"
        User }o--|| NotionWorkspace : "member of"
        AccessToken {
            string token PK
            datetime expires_at "1 year default"
            string notion_token "upstream OAuth token"
            string identity "user (workspace)"
        }
        DynamicClient {
            string client_id PK
            string redirect_uri
            string client_name
        }
        AuthCode {
            string code PK
            datetime expires_at "short lived"
        }
        NotionWorkspace {
            string workspace_id PK
            string workspace_name
        }
        User {
            string user_id PK
            string name
        }
    

    Components

    Component Purpose Notes
    DynamicClient An MCP client registered via Dynamic Client Registration (e.g., claude.ai) Persisted by TokenStore; one per MCP client instance
    AuthCode Short-lived code issued during authorize, exchanged for an access token Standard OAuth authz code flow
    AccessToken Long-lived (1 yr) MCP access token; wraps the upstream Notion token and workspace identity PR #20; lifetime = 31536000s
    NotionWorkspace The workspace an AccessToken grants access to Bound at OAuth consent time; 1:1 with the integration install
    User The Notion user who granted consent Extracted via extract_identity_from_token_response

    Key Decisions

    • AccessToken owns an upstream notion_token — MCP token and Notion OAuth token are distinct. MCP token is the client's key; the notion_token is consumed server-side. Lets us rotate MCP tokens without re-auth against Notion.
    • 1-year access token lifetime — PR #20. Chosen so claude.ai users rarely re-auth. No refresh tokens because Notion OAuth does not issue them for integrations.
    • File-backed TokenStore — JSON on PVC today. Moving to a DB or cluster Secret is future work if multi-replica or HA becomes necessary.
    • arch-dataflow-notion-mcp-remote
    • arch-deployment-notion-mcp-remote
    • project-notion-mcp-remote
User Story 3
  • GitOps Deploy via pal-e Platform story-notion-mcp-remote-ops-deploy-gitops

    story: GitOps Deploy via pal-e Platform

    Role

    Operator — responsible for keeping notion-mcp-remote running, observable, and SOP-compliant inside the pal-e cluster.

    Key

    ops-deploy-gitops

    Want

    As an operator, I want notion-mcp-remote to deploy via the standard pal-e pipeline (Woodpecker → Harbor → ArgoCD) with health + metrics endpoints wired to ServiceMonitor.

    So That

    So that the service meets service-onboarding-sop, is visible in the platform's observability stack, and has zero bespoke deploy paths to maintain.

    Acceptance Criteria

    • [ ] Woodpecker pipeline builds on push to main, pushes to internal Harbor URL
    • [ ] pal-e-services var.services entry landed via PR
    • [ ] pal-e-deployments kustomize overlay landed via PR
    • [ ] tofu apply creates namespace, ArgoCD Application, Harbor project + robots, Tailscale Funnel ingress
    • [ ] Secrets provisioned via kubectl create secret generic before first ArgoCD sync
    • [ ] ArgoCD reports Application healthy and synced
    • [ ] ServiceMonitor scrapes /metrics and Prometheus sees notion_mcp_remote_up 1

    Success Metric

    pre-deploy-validation checklist from service-onboarding-sop is 100% green before first apply. Zero manual kubectl edits post-deploy.

    • arch-deployment-notion-mcp-remote
    • project-notion-mcp-remote
    • board-notion-mcp-remote
    • service-onboarding-sop
  • Per-Request Workspace Isolation story-notion-mcp-remote-multi-workspace-safety

    story: Per-Request Workspace Isolation

    Role

    Operator — responsible for tenant safety when multiple users or workspaces connect to the same deployment.

    Key

    multi-workspace-safety

    Want

    As an operator, I want each OAuth-authenticated request to use only its own user's Notion token.

    So That

    So that one deployment can serve multiple users and workspaces without any cross-tenant data bleed.

    Acceptance Criteria

    • [ ] The NotionClient for a request is set via a ContextVar at auth-resolution time and cleared at request end
    • [ ] notion_mcp.server.get_client and all 6 tool modules are patched to read the ContextVar, not a shared global
    • [ ] Two concurrent requests with different workspaces return the correct per-request data in tests
    • [ ] No global singleton NotionClient exists anywhere in the runtime path

    Success Metric

    Zero cross-tenant incidents. A concurrency test with 2+ workspaces returns workspace-correct results 100% of the time.

    • arch-domain-notion-mcp-remote — AccessToken binds to a single NotionWorkspace and User
    • arch-dataflow-notion-mcp-remote — per-request ContextVar set in the tool-call sequence
    • project-notion-mcp-remote
    • board-notion-mcp-remote
  • Connect Notion to claude.ai story-notion-mcp-remote-claude-ai-connect

    story: Connect Notion to claude.ai

    Role

    Operator (Lucas) using claude.ai as an MCP client.

    Key

    claude-ai-connect

    Want

    As a claude.ai user, I want to paste a public URL into claude.ai's MCP integration settings and complete Notion OAuth.

    So That

    So that I can use Notion tools inside claude.ai chats without running a local stdio server or juggling integration tokens.

    Acceptance Criteria

    • [ ] Public HTTPS URL resolves and /health returns 200
    • [ ] /.well-known/oauth-authorization-server returns valid MCP OAuth metadata
    • [ ] claude.ai accepts the URL as an MCP server
    • [ ] Notion OAuth handshake completes (consent screen → code → token) without error
    • [ ] After OAuth, a search tool call in a claude.ai chat returns live results from the authorized workspace
    • [ ] Access token lifetime is 1 year so reconnection is rare

    Success Metric

    Lucas pastes the URL into claude.ai, completes OAuth in under 60 seconds, and runs a Notion tool successfully on the first attempt.

    • arch-dataflow-notion-mcp-remote — OAuth handshake and tool-call sequences
    • arch-deployment-notion-mcp-remote — Tailscale Funnel and ingress that expose the URL
    • project-notion-mcp-remote — project page
    • board-notion-mcp-remote — board that executes this story
Board 1