Skip to content

SynContext — Anthropic Connectors Directory Submission Proof Pack

Service: SynContext (https://syncontext.dev) Operator: Taino Software NJ — Edwin S Mejia ([email protected]) MCP transport endpoint: https://syncontext.dev/mcp Documentation: https://syncontext.dev/docs Privacy policy: https://syncontext.dev/privacy Repository: https://github.com/manin809/SynContext (private; auditable on request) Production HEAD at submission: 99dfc57e50a3c40e65644c6a41634fe5a3260a6d (99dfc57) Submission date: 2026-05-13 Spec compliance target: MCP 2025-06-18 Authorization spec + RFC 7591 (DCR) + RFC 9728 (PRM) + RFC 8414 (AS metadata) + RFC 6750 (Bearer) + RFC 8707 (audience binding) + OAuth 2.1 (PKCE-only)


1. Executive Summary

SynContext is a hosted Model Context Protocol (MCP) server providing cross-AI context sharing as a managed SaaS. It exposes a remote MCP transport at https://syncontext.dev/mcp with OAuth 2.1 + PKCE authorization, RFC 7591 Dynamic Client Registration, RFC 9728 Protected Resource Metadata, and full CORS support for browser-originated calls from https://claude.ai and https://claude.com.

All technical requirements for the Anthropic Connectors Directory are met:

  • OAuth 2.1 with PKCE (S256 challenge method, token_endpoint_auth_method: none, no implicit grant)
  • HTTPS only (HSTS preload, TLS via Cloudflare CDN edge)
  • CORS (preflight + response-side ACAO for Anthropic origins; SC-85-F1 extends the exact-match marketplace allowlist to OpenAI origins as historical/dormant support; OpenAI App Directory submission is deferred per Decision #67)
  • MCP tool annotations (21 tools, all with readOnlyHint / destructiveHint / idempotentHint / openWorldHint / title)
  • Dynamic Client Registration (RFC 7591, public endpoint /oauth/register)
  • Protected Resource Metadata (RFC 9728, public endpoint /.well-known/oauth-protected-resource; URL advertised in WWW-Authenticate per MCP 2025-06-18 §2)
  • Authorization Server Metadata (RFC 8414, public endpoint /.well-known/oauth-authorization-server)
  • Audience binding (RFC 8707, opaque access tokens bound to https://syncontext.dev/mcp resource URI via DB audience column)
  • Token revocation (RFC 7009, public endpoint /oauth/revoke)

The service is deployed on Railway Pro (US East region, us-east4-eqdc4a) on PostgreSQL 18.3. Stripe billing is live (Pro $12/mo, Team $29/mo, free tier available). 777 automated tests pass on every commit (Python 3.12 + 3.13 CI matrix; +6 deselected).


2. Verified Endpoint Captures (production HEAD 99dfc57)

All captures below were executed against the production deployment at https://syncontext.dev on 2026-05-13, against production HEAD 99dfc57. The OAuth surface was materially shipped via PR #57 (Decision #40 SC-75-G1G2 marketplace certification); consent.html copy was refined via PR #61 (SC-78-T4).

2.1 Authorization Server Metadata — RFC 8414

Request:

GET https://syncontext.dev/.well-known/oauth-authorization-server

Response — HTTP 200:

{
  "issuer": "https://syncontext.dev",
  "authorization_endpoint": "https://syncontext.dev/oauth/authorize",
  "token_endpoint": "https://syncontext.dev/oauth/token",
  "revocation_endpoint": "https://syncontext.dev/oauth/revoke",
  "registration_endpoint": "https://syncontext.dev/oauth/register",
  "scopes_supported": ["read", "write", "admin"],
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"],
  "service_documentation": "https://syncontext.dev/docs"
}

Compliance: - issuer matches the canonical origin - All advertised endpoints are live (verified §2.2-§2.4 below) - code_challenge_methods_supported: ["S256"] — PKCE-only, S256-only per Decision #14 / Entry #280 §1 - response_types_supported: ["code"] — no implicit grant per Entry #280 §3 - token_endpoint_auth_methods_supported: ["none"] — PKCE-only, no client secret per Entry #280 §2

2.2 Protected Resource Metadata — RFC 9728

Request:

GET https://syncontext.dev/.well-known/oauth-protected-resource

Response — HTTP 200:

{
  "resource": "https://syncontext.dev/mcp",
  "authorization_servers": ["https://syncontext.dev"],
  "scopes_supported": ["read", "write", "admin"],
  "bearer_methods_supported": ["header"],
  "resource_documentation": "https://syncontext.dev/docs"
}

Compliance: - resource exactly matches the MCP transport URI (audience-binding canonical URI per RFC 8707) - authorization_servers correctly references the issuer from §2.1 - bearer_methods_supported: ["header"]Authorization: Bearer only, no query-param exposure

2.3 WWW-Authenticate on /mcp 401 — MCP 2025-06-18 §2 MUST

Request (no credential, live verified 2026-05-13T11:20:16Z):

HEAD https://syncontext.dev/mcp

Response — HTTP 401:

HTTP/1.1 401 Unauthorized
www-authenticate: Bearer realm="SynContext", resource_metadata="https://syncontext.dev/.well-known/oauth-protected-resource"
x-railway-request-id: tA-VpBTIQm-YnoWlO8poTA
x-request-id: c9387f63706c4d64

Request (invalid bearer credential):

POST https://syncontext.dev/mcp
Authorization: Bearer <redacted-invalid-token>
Content-Type: application/json

{}

Response — HTTP 401:

HTTP/2 401
www-authenticate: Bearer realm="SynContext", resource_metadata="https://syncontext.dev/.well-known/oauth-protected-resource", error="invalid_token"

Compliance: - MCP 2025-06-18 §2 MUST: WWW-Authenticate advertises resource_metadata URL pointing to the PRM document from §2.2 - RFC 9728 §5.1: parameter named exactly resource_metadata - RFC 6750 §3 challenge ordering: realmresource_metadata → conditional error="invalid_token" - /api/* REST endpoints intentionally do NOT advertise resource_metadata (REST API is not an OAuth resource server per MCP spec); regression-tested by TC3 and live-verified at §2.4 below. - Implementation lives at context_hub/auth.py:_send_401 (lines 429-459); routing via context_hub/__main__.py (lines 449-460). NOT in context_hub/server.pyserver.py only hosts the tool definitions.

2.4 /api/* 401 Regression Guard (live verified 2026-05-13T11:20:16Z)

Request:

HEAD https://syncontext.dev/api/projects

Response — HTTP 401:

HTTP/1.1 401 Unauthorized
www-authenticate: Bearer realm="SynContext"
x-railway-request-id: 5hqZqeZSQF66hMIkO8poTA
x-request-id: 181ea3280b964406

Compliance: - /api/* 401 emits Bearer realm="SynContext" ONLY (no resource_metadata). MCP-spec resource metadata is scoped to the MCP transport endpoint, not REST API endpoints. - Path-specific behavior is enforced at context_hub/auth.py:_send_401 via path.startswith("/mcp") branch (line 429-459). - Regression test: tests/test_auth_middleware.py TC3 and TC12.

2.5 CORS Preflight for Anthropic Origins

https://claude.ai (live verified 2026-05-13T11:20:16Z)

Request:

OPTIONS https://syncontext.dev/mcp
Origin: https://claude.ai
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization,content-type,mcp-protocol-version

Response — HTTP 204:

HTTP/1.1 204 No Content
access-control-allow-origin: https://claude.ai
access-control-allow-methods: GET, POST, DELETE, OPTIONS
access-control-allow-headers: authorization, content-type, mcp-protocol-version, mcp-session-id
access-control-max-age: 86400
vary: Origin
x-railway-request-id: 2OTBzrBiRtqCyf5XBT7zVQ
x-request-id: 4ebbbffb8b5440e0

https://claude.com

Request:

OPTIONS https://syncontext.dev/mcp
Origin: https://claude.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization,content-type,mcp-protocol-version

Response — HTTP 204:

HTTP/2 204
access-control-allow-origin: https://claude.com
access-control-allow-methods: GET, POST, DELETE, OPTIONS
access-control-allow-headers: authorization, content-type, mcp-protocol-version, mcp-session-id
access-control-max-age: 86400
vary: Origin

Disallowed origin regression test

Request:

OPTIONS https://syncontext.dev/mcp
Origin: https://evil.com
Access-Control-Request-Method: POST

Response — HTTP 403: (no Access-Control-Allow-Origin header emitted; browser will reject)

Compliance: - Exact-match origin allowlist via Python frozenset lookup (no wildcards, no substring matching, no header injection vector) - Allowed methods cover MCP transport verbs: GET (SSE keepalive), POST (tool calls), DELETE (session termination), OPTIONS (preflight) - Allowed headers cover MCP transport spec: authorization (bearer), content-type, mcp-protocol-version, mcp-session-id - Vary: Origin set per HTTP caching spec for origin-dependent responses - Implementation lives at context_hub/__main__.py:257-353 (path-scoped /mcp CORS wrapper).

SC-85-F1 OpenAI App Directory addendum (historical / deferred per Decision #67): The same exact-match, dual-layer CORS model extends to https://chatgpt.com and https://chat.openai.com. This addendum does not alter the dated 2026-05-13 Anthropic captures above; it is retained as historical implementation evidence only. Future OpenAI submission evidence must capture fresh post-deploy preflight results for both OpenAI origins if Edwin explicitly reopens the App Directory track.

2.6 Dynamic Client Registration — RFC 7591

Request:

POST https://syncontext.dev/oauth/register
Content-Type: application/json

{
  "client_name": "SynContext Marketplace Proof Pack",
  "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"]
}

Response — HTTP 201 Created:

{
  "client_id": "yZDEC78guOzHzQtcwYCIY8afedDBe3nE",
  "client_name": "SynContext Marketplace Proof Pack",
  "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "scope": "",
  "token_endpoint_auth_method": "none",
  "created_at": "2026-05-13T00:33:33.930309+00:00"
}

Compliance: - RFC 7591 §3.2.1: HTTP 201 Created on successful registration - RFC 7591 §3.2: redirect_uris echoed exactly (no URL transformation) - token_endpoint_auth_method: "none" — PKCE-only public client per Decision #14 / Entry #280 §2 - Default grant set: authorization_code + refresh_token (no implicit, no client_credentials) - Default response type: code only - client_id is an opaque random 32-character URL-safe string

2.7 SSE Keepalive (Antigravity / future MCP notification clients)

Request:

GET https://syncontext.dev/mcp
Accept: text/event-stream

Response — HTTP 200:

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

: heartbeat

: heartbeat

Confirmed by independent Codex empirical capture (SynContext entry #1775 §2).

2.8 Operational endpoints (live verified at HEAD 99dfc57)

/health:

GET https://syncontext.dev/health
→ HTTP 200 {"status":"ok","service":"syncontext","db":"connected"}

/version: (deployed commit traceability)

GET https://syncontext.dev/version
→ HTTP 200 {"service":"syncontext","commit":"99dfc57","commit_full":"99dfc57e50a3c40e65644c6a41634fe5a3260a6d","branch":"main"}

/privacy:

HEAD https://syncontext.dev/privacy
→ HTTP 200

/docs/: (canonical documentation URL; bare /docs 301-redirects to trailing-slash)

HEAD https://syncontext.dev/docs/
→ HTTP 200

2.9 Audience Binding Code Path (RFC 8707)

Audience binding is enforced at 3 distinct code points at HEAD 99dfc57:

  1. Authorization request validationcontext_hub/oauth_handlers.py:203-207 returns invalid_target if the request's resource parameter does not equal config.OAUTH_CANONICAL_URI (https://syncontext.dev/mcp).
  2. DB propagationcontext_hub/db/operations.py:3989-4024 reads resource from the authorization code row and inserts it into oauth_access_tokens.audience and oauth_refresh_tokens.audience during code-for-token exchange.
  3. Resource-server enforcementcontext_hub/auth.py:151-188 validates mcp_at_* tokens only on the /mcp path and rejects with HTTP 401 if access_row["audience"] != config.OAUTH_CANONICAL_URI.

Terminology precision: SynContext uses opaque tokens (not JWT). The audience is enforced as a DB column on the oauth_access_tokens and oauth_refresh_tokens tables (audience binding). It is NOT exposed as a JWT-style aud field in the token response body. The /oauth/token response returns only {token_type, access_token, refresh_token, expires_in, scope} — no audience field reaches clients. Audience proof therefore requires a server-side DB lookup (see §2.11 audience binding query).

2.10 Railway DB v14 Production Verification

context_hub/db/operations.py:544-639 contains the PG v14 additive DDL block that catches individual DDL exceptions (logged warnings at lines 565, 578, 598, 615, 633) while still upserting schema_meta.version = SCHEMA_VERSION at lines 635-639. Production proof therefore requires verifying actual table and index existence — the schema_meta.version row alone is insufficient.

Canonical v14 OAuth tables (per context_hub/db/schema.py:609-680): - oauth_clients - oauth_cimd_cache - oauth_authorization_codes - oauth_access_tokens - oauth_refresh_tokens

Canonical v14 OAuth indexes: - idx_oauth_clients_ip - idx_oauth_cimd_expires - idx_oauth_codes_expires - idx_oauth_at_user - idx_oauth_at_expires - idx_oauth_rt_user - idx_oauth_rt_parent

Operator verification queries (read-only psql against Railway production; SELECT-only, no writes):

-- Q1. Context stamp: no secrets, no row data.
SELECT current_database() AS database_name, current_user AS database_user, now() AS checked_at;

-- Q2. Schema version (necessary but not sufficient).
SELECT value AS schema_version FROM public.schema_meta WHERE key = 'version';

-- Q3. Expected v14 OAuth tables.
WITH expected(table_name) AS (
  VALUES
    ('oauth_clients'),
    ('oauth_cimd_cache'),
    ('oauth_authorization_codes'),
    ('oauth_access_tokens'),
    ('oauth_refresh_tokens')
)
SELECT
  table_name,
  CASE WHEN to_regclass('public.' || table_name) IS NULL THEN 'MISSING' ELSE 'OK' END AS status,
  CASE WHEN to_regclass('public.' || table_name) IS NULL THEN NULL ELSE pg_total_relation_size(to_regclass('public.' || table_name)) END AS total_bytes
FROM expected
ORDER BY table_name;

-- Q4. Expected v14 OAuth indexes.
WITH expected(index_name, table_name) AS (
  VALUES
    ('idx_oauth_clients_ip', 'oauth_clients'),
    ('idx_oauth_cimd_expires', 'oauth_cimd_cache'),
    ('idx_oauth_codes_expires', 'oauth_authorization_codes'),
    ('idx_oauth_at_user', 'oauth_access_tokens'),
    ('idx_oauth_at_expires', 'oauth_access_tokens'),
    ('idx_oauth_rt_user', 'oauth_refresh_tokens'),
    ('idx_oauth_rt_parent', 'oauth_refresh_tokens')
)
SELECT
  e.index_name,
  e.table_name,
  CASE WHEN i.indexname IS NULL THEN 'MISSING' ELSE 'OK' END AS status,
  i.indexdef
FROM expected e
LEFT JOIN pg_indexes i
  ON i.schemaname = 'public'
 AND i.indexname = e.index_name
ORDER BY e.table_name, e.index_name;

-- Q5. Column-level shape, schema only.
SELECT table_name, ordinal_position, column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'public'
  AND table_name IN (
    'oauth_clients',
    'oauth_cimd_cache',
    'oauth_authorization_codes',
    'oauth_access_tokens',
    'oauth_refresh_tokens'
  )
ORDER BY table_name, ordinal_position;

-- Q6. Optional count-only proof. Counts reveal volume but not token/user data.
SELECT 'oauth_clients' AS table_name, COUNT(*) AS row_count FROM public.oauth_clients
UNION ALL SELECT 'oauth_cimd_cache', COUNT(*) FROM public.oauth_cimd_cache
UNION ALL SELECT 'oauth_authorization_codes', COUNT(*) FROM public.oauth_authorization_codes
UNION ALL SELECT 'oauth_access_tokens', COUNT(*) FROM public.oauth_access_tokens
UNION ALL SELECT 'oauth_refresh_tokens', COUNT(*) FROM public.oauth_refresh_tokens
ORDER BY table_name;

Operator output — captured 2026-05-13T15:30Z by Edwin S Mejia against Railway production (database railway, user postgres):

Q1 — Context stamp:

 database_name | database_user |          checked_at
---------------+---------------+-------------------------------
 railway       | postgres      | 2026-05-13 15:30:28.808474+00
(1 row)

Q2 — Schema version:

 schema_version
----------------
 14
(1 row)

Q3 — Expected v14 OAuth tables:

        table_name         | status | total_bytes
---------------------------+--------+-------------
 oauth_access_tokens       | OK     |       32768
 oauth_authorization_codes | OK     |       24576
 oauth_cimd_cache          | OK     |       24576
 oauth_clients             | OK     |       49152
 oauth_refresh_tokens      | OK     |       32768
(5 rows)

All 5 canonical v14 OAuth tables present, byte sizes non-NULL (initialized).

Q4 — Expected v14 OAuth indexes:

       index_name        |        table_name         | status |                                             indexdef
-------------------------+---------------------------+--------+---------------------------------------------------------------------------------------------------
 idx_oauth_at_expires    | oauth_access_tokens       | OK     | CREATE INDEX idx_oauth_at_expires ON public.oauth_access_tokens USING btree (expires_at)
 idx_oauth_at_user       | oauth_access_tokens       | OK     | CREATE INDEX idx_oauth_at_user ON public.oauth_access_tokens USING btree (user_id)
 idx_oauth_codes_expires | oauth_authorization_codes | OK     | CREATE INDEX idx_oauth_codes_expires ON public.oauth_authorization_codes USING btree (expires_at)
 idx_oauth_cimd_expires  | oauth_cimd_cache          | OK     | CREATE INDEX idx_oauth_cimd_expires ON public.oauth_cimd_cache USING btree (expires_at)
 idx_oauth_clients_ip    | oauth_clients             | OK     | CREATE INDEX idx_oauth_clients_ip ON public.oauth_clients USING btree (registered_by_ip)
 idx_oauth_rt_parent     | oauth_refresh_tokens      | OK     | CREATE INDEX idx_oauth_rt_parent ON public.oauth_refresh_tokens USING btree (parent_refresh_hash)
 idx_oauth_rt_user       | oauth_refresh_tokens      | OK     | CREATE INDEX idx_oauth_rt_user ON public.oauth_refresh_tokens USING btree (user_id)
(7 rows)

All 7 canonical v14 OAuth indexes present with expected indexdef.

Q5 — Column-level shape (45 rows total; abbreviated below):

Key columns relevant to audience binding (RFC 8707):

  • oauth_access_tokens.audiencetext NOT NULL (ordinal 5)
  • oauth_refresh_tokens.audiencetext NOT NULL (ordinal 6)
  • oauth_authorization_codes.resourcetext NOT NULL (ordinal 6) — input parameter that propagates to token rows

Full column shape for oauth_access_tokens (the audience-binding target):

        table_name         | ordinal_position |        column_name         |        data_type         | is_nullable
---------------------------+------------------+----------------------------+--------------------------+-------------
 oauth_access_tokens       |                1 | token_hash                 | text                     | NO
 oauth_access_tokens       |                2 | client_id                  | text                     | NO
 oauth_access_tokens       |                3 | user_id                    | text                     | NO
 oauth_access_tokens       |                4 | scope                      | text                     | NO
 oauth_access_tokens       |                5 | audience                   | text                     | NO
 oauth_access_tokens       |                6 | expires_at                 | timestamp with time zone | NO
 oauth_access_tokens       |                7 | revoked                    | boolean                  | NO
 oauth_access_tokens       |                8 | created_at                 | timestamp with time zone | NO

Full Q5 output (45 rows across 5 tables) retained operator-side as q5-column-shape-2026-05-13.txt.

Q6 — Row counts (snapshot 2026-05-13T15:30Z, pre-Claude.ai E2E test):

        table_name         | row_count
---------------------------+-----------
 oauth_access_tokens       |         0
 oauth_authorization_codes |         0
 oauth_cimd_cache          |         0
 oauth_clients             |         4
 oauth_refresh_tokens      |         0
(5 rows)

oauth_clients count of 4 reflects DCR registrations from prior testing (S78 proof-pack DCR yZDEC78guOzHzQtcwYCIY8afedDBe3nE + 3 antigravity-client test entries from S69). oauth_access_tokens count of 0 confirms no production user OAuth flows yet completed at snapshot time. Post-snapshot at 16:08Z (after the Claude.ai E2E test in §2.11 below), oauth_access_tokens count became 1 and oauth_clients count became 5 — see §2.11 evidence rows.

Verdict: Railway PG v14 production schema verified. All 5 canonical OAuth tables present, all 7 canonical indexes present, audience column type and constraint match RFC 8707 binding requirement.

2.11 Connector Callback Redirect (Claude.ai / Desktop / Code)

The Anthropic Connectors Directory requires evidence that the SynContext OAuth flow completes end-to-end via each Claude client surface. The capture plan below uses Claude.ai web as the PRIMARY HAR-bearing client and Claude Desktop + Claude Code as SHORTER corroborating witnesses.

Required visible/evidenced fields (across all three clients): - resource=https://syncontext.dev/mcp — audience-binding canonical URI in authorize URL - redirect_uri — must exactly match the client's registered callback - scope — canonical scope names (read / write / admin) - code_challenge_method=S256 — PKCE - Token response shape: {token_type:"Bearer", expires_in:<N>, scope:"..."} with token VALUES redacted - Audience binding — NOT visible in opaque token response; proved via server-side DB lookup (see audience query below)

Capture artifact set:

  1. Claude.ai (web, PRIMARY) — one sanitized HAR + 3-5 screenshots:
  2. Initiate connector → OAuth authorize page/consent
  3. Redirect back to client callback with code+state
  4. POST /oauth/token 200 response
  5. First /mcp probe / tool-list request success
  6. HAR file referenced by SHA256 + filename; raw HAR retained operator-side (NOT committed to repo)

  7. Claude Desktop (corroborating) — screenshot + log lines:

  8. Screenshot of configured/connected SynContext connector
  9. Log line(s) showing successful OAuth completion + first MCP connection
  10. Server URL https://syncontext.dev/mcp visible

  11. Claude Code (corroborating) — terminal transcript:

  12. Connector add/auth flow completion
  13. Read-only MCP list/probe succeeding
  14. Configured endpoint visible

Audience binding DB lookup (operator-only, in same Railway psql session as §2.10):

After Edwin completes the Claude.ai web OAuth flow and captures the token response, he computes the SHA256 of the access token locally (procedure in §3.3 below; plaintext token NEVER pasted into proof artifacts) and runs:

-- Replace <TOKEN_SHA256> with the locally-computed SHA256 of the access token.
SELECT client_id, scope, audience, revoked, expires_at
FROM public.oauth_access_tokens
WHERE token_hash = '<TOKEN_SHA256>';

Expected: exactly 1 row with audience = 'https://syncontext.dev/mcp' and revoked = false.

Operator output — captured 2026-05-13 between 15:45Z and 16:09Z by Edwin S Mejia:

Capture sequence:

  1. Edwin opened Settings → Connectors in https://claude.ai (logged-in account: [email protected])
  2. "Add custom connector" with Name: SynContext, Remote MCP server URL: https://syncontext.dev/mcp
  3. SynContext consent screen displayed: "Authorize Access — Claude is requesting access to your SynContext data with the following permissions: read / write / admin"
  4. Edwin clicked "Authorize" → redirect back to https://claude.ai → connector enrolled as "Connected"
  5. New chat: "Use the SynContext tool to list my projects" → Claude executed hub_list_projects and returned the 6 active SynContext projects (syncontext, smoke-test-s16, sports-intelligence-mcp, aegis-v01, smart-sport-analytics, example) — confirming end-to-end OAuth-authenticated MCP tool call success against production HEAD 99dfc57

DCR registration evidence (oauth_clients snapshot 2026-05-13T16:09Z):

            client_id             |            client_name            |          created_at
----------------------------------+-----------------------------------+-------------------------------
 mHTN8GLhHDsOPLJrNdwKzr22FkjDTBOQ | Claude                            | 2026-05-13 15:45:29.190749+00
 yZDEC78guOzHzQtcwYCIY8afedDBe3nE | SynContext Marketplace Proof Pack | 2026-05-12 00:33:33.930309+00
 VmJQ2AsjuxqId_JGBe0O2h8GoaXmvwyy | antigravity-client                | 2026-04-27 22:18:13.742937+00
 QWwaNzNcPB3NnUXhkBLlXoOoXLviL7sI | antigravity-client                | 2026-04-27 22:18:00.933204+00
 3h9ynC2sCbp3aTuhEVaOTyyF1a_oDOAm | antigravity-client                | 2026-04-27 22:17:59.677783+00
(5 rows)

The first row (client_name: Claude, client_id mHTN8GLhHDsOPLJrNdwKzr22FkjDTBOQ) is the DCR registration auto-generated by claude.ai during connector setup at 15:45:29Z — RFC 7591 §3.2.1 confirmed (token_endpoint_auth_method = "none", PKCE-only public client per Decision #14 / Entry #280 §2).

Audience binding evidence (oauth_access_tokens snapshot 2026-05-13T16:09Z, post-MCP-tool-call):

            client_id             |      scope       |          audience          | revoked |          expires_at           |          created_at
----------------------------------+------------------+----------------------------+---------+-------------------------------+-------------------------------
 mHTN8GLhHDsOPLJrNdwKzr22FkjDTBOQ | read write admin | https://syncontext.dev/mcp | f       | 2026-05-13 17:08:18.194801+00 | 2026-05-13 16:08:18.194801+00
(1 row)

The access token issued to Edwin's Claude.ai connector (client_id mHTN8GLhHDsOPLJrNdwKzr22FkjDTBOQ) was persisted with audience = https://syncontext.dev/mcp (canonical MCP resource URI) and revoked = false. Token TTL: 1 hour (16:08:18Z → 17:08:18Z). Scope: read write admin per consent granted. The token_hash column is intentionally omitted from this evidence (SHA256 of the opaque access token; not committable).

RFC 8707 audience binding chain proven E2E:

  1. oauth_clients.client_id mHTN8GLhHDsOPLJrNdwKzr22FkjDTBOQ — DCR registration by Claude.ai (15:45:29Z)
  2. ✅ Authorize request received resource=https://syncontext.dev/mcp → propagated into oauth_authorization_codes.resource per context_hub/oauth_handlers.py:203-207
  3. /oauth/token exchange propagated resourceaudience column on oauth_access_tokens per context_hub/db/operations.py:3989-4024 (issued 16:08:18Z)
  4. audience = https://syncontext.dev/mcp matches config.OAUTH_CANONICAL_URI per context_hub/auth.py:151-188 resource-server enforcement
  5. ✅ MCP tool call success (hub_list_projects returned 6 projects) confirms the audience-bound token authenticates successfully against /mcp endpoint

Architecture note on client-side Bearer header capture:

The Authorization: Bearer mcp_at_* header is NOT directly visible in browser DevTools when using the Claude.ai web client. The MCP transport flow is:

Browser (claude.ai web) → Anthropic backend → https://syncontext.dev/mcp

The OAuth access token is held server-side in Anthropic's infrastructure (as designed by the MCP authorization spec for hosted client deployments). Edwin's browser HAR therefore does not contain the Bearer header. Direct Bearer-header capture would require a non-claude.ai client (Claude Desktop or Claude Code, both of which originate MCP requests directly to syncontext.dev/mcp) or a server-side log inspection of Anthropic's egress — neither of which is required to prove audience binding given the canonical DB evidence above.

Artifact set retained operator-side (not committed per §3.1 redaction prohibition):

  • claude-ai-syncontext-tool-call.har — sanitized HAR from claude.ai web during the hub_list_projects tool call (Chrome 130+ default sanitized export excludes Cookie, Set-Cookie, Authorization headers); contains only browser ↔ claude.ai traffic (no direct syncontext.dev/mcp requests since flow is server-side proxied by Anthropic)
  • Screenshots: SynContext authorize consent screen, connector "Connected" state in Settings, chat showing successful tool call result with the 6 SynContext projects listed

Available on request from operator workstation; SHA256 hashes computed at retention time per §3.3 procedure.


3. Operator Runbook — Edwin Capture Procedure

Tracker #3 closes when Edwin appends the §2.10 + §2.11 operator outputs via a fix-forward commit. This section codifies the redaction discipline and SHA256 procedure required to keep secrets out of the repo.

3.1 Strict redaction prohibition

The following values MUST NEVER appear in committed repo files, SynContext entries, or any proof artifact:

  • Plaintext access_token values
  • Plaintext refresh_token values
  • Plaintext authorization code values from the callback redirect
  • Plaintext code_verifier values
  • Browser cookies (any Cookie: or Set-Cookie: header values)
  • Authorization: Bearer <token> header values
  • Full reusable token identifiers (e.g., complete mcp_at_xxxxx... strings)
  • Plaintext session identifiers that grant access

3.2 Values safe to commit

  • client_id (public per RFC 7591 §3.2)
  • redirect_uri (URL only)
  • scope (canonical scope names)
  • code_challenge_method=S256 (constant)
  • state value SHA256-hashed (not raw)
  • resource=https://syncontext.dev/mcp (canonical URI)
  • Token response shape: {token_type:"Bearer", expires_in:<N>, scope:"..."} with token VALUES redacted
  • x-request-id + x-railway-request-id (audit traceability only; no auth surface)
  • SHA256 hash of token (Edwin computes locally) for DB audience lookup join
  • DB row outputs showing client_id, scope, audience, revoked, expiry — TOKEN COLUMN OMITTED

3.3 SHA256 procedure (operator local-only, plaintext NEVER leaves machine)

PowerShell:

[BitConverter]::ToString(
    [Security.Cryptography.SHA256]::Create().ComputeHash(
        [Text.Encoding]::UTF8.GetBytes("<plaintext_access_token>")
    )
).Replace("-","").ToLower()

bash / zsh:

echo -n "<plaintext_access_token>" | shasum -a 256

The plaintext token NEVER touches the proof pack, SynContext entries, or any commit. Only the SHA256 hex digest + DB lookup join result.


4. Security Baseline

Every response from the production deployment includes the following security headers (verified across all endpoint captures in §2):

Header Value Purpose
strict-transport-security max-age=63072000; includeSubDomains; preload HSTS preload (2 years)
x-content-type-options nosniff MIME-type sniff protection
x-frame-options DENY Clickjacking protection
referrer-policy strict-origin-when-cross-origin Referrer minimization
permissions-policy camera=(), microphone=(), geolocation=() Feature policy denial
x-xss-protection 1; mode=block Legacy XSS filter
content-security-policy default-src 'self'; ... CSP with strict default-src
x-request-id per-request UUID request tracing / debugging

Route-Specific CSP Architecture: F3 implemented strict, path-based Content Security Policy (CSP) isolation. All application, API, and OAuth transactional routes (/, /app/, /api/*, /oauth/*, /.well-known/*, /mcp) enforce strict CSP with no unsafe-inline directives, directly mitigating XSS risks on active execution paths. A scoped exception exists solely for the /docs/* route to support static MkDocs Material styling, bounded by ASGI middleware dispatch and enforced via comprehensive route-policy test coverage. There is no user input reflection on the documentation path, effectively eliminating the injection vector.


5. Infrastructure & Deployment

Layer Detail
Application runtime Python 3.12+ (CI matrix: 3.12 + 3.13)
MCP framework FastMCP SDK
HTTP framework Starlette + uvicorn
Database (production) PostgreSQL 18.3 (Debian 18.3-1.pgdg13+1) on x86_64-pc-linux-gnu, compiled by gcc 14.2.0, 64-bit
Database driver asyncpg
Hosting Railway Pro
Region us-east4-eqdc4a (per x-railway-edge response header on all captures)
CDN / TLS Cloudflare (verified by cf-ray / server: cloudflare headers)
DNS Cloudflare DNS
Email Resend
Billing Stripe LIVE (Pro $12/mo, Team $29/mo)
Crypto at rest Fernet (AES-128-CBC + HMAC-SHA256) on user-content fields
Auth credentials bcrypt password hashing, SHA-256 API keys, opaque session tokens

6. MCP Tool Surface

The MCP server exposes 21 tools, all with full annotations per MCP 2025-06-18 spec:

  • Each tool declares readOnlyHint, destructiveHint, idempotentHint, openWorldHint, and title
  • All tools use flat parameters (ChatGPT Connectors compatibility)
  • All tools are wrapped in @safe_tool decorator (exception-safe error envelope)
  • Tier-gated capabilities use an allowlist model (not blocklist) per security policy
  • Tool count invariant verified post-deploy: len(mcp._tool_manager._tools) == 21

7. Test Coverage

Metric Value
Total automated tests 777 passed, 6 deselected (CI green on Python 3.12 + 3.13)
New marketplace-compliance tests in PR #57 14 (TC1-TC14)
Test files added/modified in PR #57 tests/test_auth_oauth_bearer.py (TC1, TC2, TC14), tests/test_auth_middleware.py (TC3, TC12), tests/test_mcp_oauth_transport.py (TC4-TC11, TC13)
CI workflows (all green at HEAD 99dfc57) test (3.12), test (3.13), ruff, dashboard-dist-drift, GitGuardian Security Checks

Highlighted falsification coverage: - TC3 + TC12 — regression guard: /api/* 401 never leaks resource_metadata; /api/* OPTIONS not affected by /mcp CORS bypass - TC6 + TC9 — exact-match origin enforcement (no wildcard, no substring, no header injection via CRLF) - TC10 — case-insensitive HTTP method handling (scope.get("method", "").upper()) - TC11 — non-OPTIONS methods (TRACE, CONNECT) cannot exploit the CORS preflight branch - TC13 — end-to-end PRM roundtrip: parse resource_metadata from WWW-Authenticate → fetch URL → confirm resource field matches canonical URI - TC14 — dynamic URL derivation via urlsplit(config.OAUTH_CANONICAL_URI), not hardcoded literal


8. Governance & Change Management

SynContext development follows a documented multi-AI governance model with:

  • 3 specialist advisors reviewing changes by lane (Codex for code, Gemini for architecture/security, Kimi K2.6 for precision/falsification)
  • Reinforced consensus protocol for High-Risk Files (3-of-3 pre-execution + 2-of-3 post-execution)
  • Decision log with binding rationale for architectural choices (44 decisions logged through S78; canonical binding range Decisions #20-#44)
  • Repository-anchored verification (every claimed line number / blob SHA verified live via hub_github_read)
  • PR-only changes (no direct commits to main); CI must be green before merge

The marketplace compliance changes in PR #57 (the SC-75-G1G2 sprint) went through the full T1 + HRF reinforced consensus protocol: - Investigation: SynContext entry #1776 - Pre-execution reviews: Codex #1778, Gemini #1777, Kimi K2.6 #1779 - Director synthesis: #1780 - Prompt review (sole reviewer Codex): #1781, #1782 - Execution: Claude Code completion #1784 - Post-execution verification: Codex #1785, Kimi #1786 (both APPROVED FOR MERGE) - Director closure: #1787 - Decision logged: Decision #40 — "SC-75-G1G2 MCP Marketplace WWW-Authenticate resource_metadata + Anthropic CORS Compliance"

Subsequent marketplace-relevant decisions: - Decision #38 §4 Tracker closures including SC-76-T9 (rate-limit 429 Retry-After compliance, PR #58) - Decision #43 — FIX Byte-Landing Audit framework hardening - Decision #44 — Procedural Typography Discipline for non-FIX executor prose


9. Submission Deliverables Quick Reference

Field Value
Service name SynContext
MCP transport URL https://syncontext.dev/mcp
OAuth Authorization Server https://syncontext.dev
AS metadata https://syncontext.dev/.well-known/oauth-authorization-server
PRM https://syncontext.dev/.well-known/oauth-protected-resource
Registration endpoint (DCR) https://syncontext.dev/oauth/register
Authorization endpoint https://syncontext.dev/oauth/authorize
Token endpoint https://syncontext.dev/oauth/token
Revocation endpoint https://syncontext.dev/oauth/revoke
Documentation https://syncontext.dev/docs/
Privacy policy https://syncontext.dev/privacy
Operator email [email protected]
Production HEAD at submission 99dfc57e50a3c40e65644c6a41634fe5a3260a6d
Allowed CORS origins (Anthropic capture) https://claude.ai, https://claude.com
Historical additional CORS origins after SC-85-F1 (OpenAI App Directory deferred per Decision #67) https://chatgpt.com, https://chat.openai.com

Prepared by Claude (Director) for Edwin S Mejia / Taino Software NJ — 2026-05-13. All captures in §2.1-§2.8 reproducible from operator workstation. §2.10 + §2.11 operator outputs to be appended via fix-forward commit post-merge per Tracker #3 closure protocol.

OAuth Schema — Live Production Existence Proof (Gate 2, Tracker #8)

  • Date: 2026-05-25T14:00:22Z UTC
  • Method: scripts/smoke_oauth.py (merged PR #85, commit 6942936; SELECT-only schema-metadata smoke) run against the live Railway production PostgreSQL (US East) via the public proxy endpoint. DSN withheld (secret).
  • Result:
  • schema_meta.version = 15 (expected 15) — PASS
  • 6 OAuth tables present: oauth_clients, oauth_cimd_cache, oauth_authorization_codes, oauth_access_tokens, oauth_refresh_tokens, oauth_user_grants — PASS
  • 9 OAuth indexes present: idx_oauth_clients_ip, idx_oauth_cimd_expires, idx_oauth_codes_expires, idx_oauth_at_user, idx_oauth_at_expires, idx_oauth_rt_user, idx_oauth_rt_parent, idx_oauth_grants_user, idx_oauth_grants_active — PASS

Raw output: SynContext OAuth schema smoke proof (2026-05-25T14:00:22Z UTC) [PASS] schema_meta.version: expected '15'; observed '15' [PASS] OAuth tables: expected 6; missing none [PASS] OAuth indexes: expected 9; missing none SUMMARY: PASS

Tracker #8 (production existence proof) closed by this evidence. The fail-fast missing-table guard (operations.py:678-696) shipped S86 (2eda96c / D#53).