Skip to content

Switch docs to sandbox.ottu.net (display, demos, API reference, wallet seed)#94

Merged
jab3z merged 20 commits into
mainfrom
dev
Jun 10, 2026
Merged

Switch docs to sandbox.ottu.net (display, demos, API reference, wallet seed)#94
jab3z merged 20 commits into
mainfrom
dev

Conversation

@jab3z

@jab3z jab3z commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

What this does

Moves everything on docs.ottu.com to the public sandbox merchant sandbox.ottu.net — display, live demos, API reference, and the wallet demo's seed backend. Replaces the earlier KSA-pointed state.

Changes

  • Displayed hostsandbox.ottu.net: Checkout SDK merchant_id ×11, all curl/JSON samples (via OTTU_CONNECT_BASE_URL), "shared sandbox" prose.
  • Live demos (ACTIVE_CONNECT = SANDBOX): Checkout / Recurring / PaymentJourney / Wallet now run on sandbox.ottu.net.
  • Wallet demo: currency KWD → USD (the sandbox wallet PG ottu-sandbox-usd is USD-only); stale "docs" discovery-tag guidance fixed to "demo".
  • API reference: explorer base URL + auto-generated MCP curls → sandbox.ottu.net. The enrich script now forces servers from _variables.yaml sandboxUrl (fetch-proof); explorer demo key tracks ACTIVE_CONNECT.
  • Wallet seed backend (mcp-server): new SANDBOX_OTTU_NET config (merchant sandbox.ottu.net, shared wallet.ottu.dev + auth.ottu.dev, realm sandbox.ottu.net, client backend, secret env SANDBOX_KEYCLOAK_CLIENT_SECRET).

Verification

  • Wallet seed verified locally and on the deployed docs.ottu.devPOST /seed-wallet → 201, balance minted.
  • Full wallet demo verified in-browser on docs.ottu.dev: discover → seed → session → SDK shows "Available balance: $10.00", charges $8.00.
  • npm run build + npm run typecheck pass; API reference regenerated (0 ksa.ottu.dev).
  • SANDBOX_KEYCLOAK_CLIENT_SECRET set on the mcp component of both DO apps (docs-dev + docs-prod).

🤖 Generated with Claude Code

suliman-ottu and others added 20 commits June 2, 2026 03:31
Added a description_append to the retrieve_payment_transaction_checkout
enrichment overlay so a static curl block is baked into the page at build
time and picked up by the MCP indexer. Previously the curl snippet was
rendered client-side by ApiExplorer and never appeared in docs_fetch
responses from docs.ottu.com/mcp.
…pages

The MCP server indexes the static HTML of generated .api.mdx pages so AI
tools (via docs_fetch) can retrieve usable request examples. The OpenAPI
plugin's interactive ApiExplorer already renders a curl example for human
readers, so duplicating it visibly in the page body would be redundant and
hurt the reading experience.

This adds a clean two-stage pipeline to thread per-operation MCP hints
through without surfacing them to users:

- enrich-api-spec.ts: support an optional `mcp_hint` field on operation
  enrichment overlays, copied verbatim onto the operation in the enriched
  spec (alongside permissions/description enrichments already supported)
- inject-mcp-hints.ts (new): reads `mcp_hint` back out of the enriched spec
  after gen-api-docs runs, and patches the matching .api.mdx with a
  visually-hidden <span style={{display:"none"}}> just before the Request
  heading, so the MCP's static-HTML indexer picks up the text while it
  stays invisible in the rendered page
- package.json: wire `inject-mcp-hints` as the final step of `gen-api`,
  after extract-schemas, so generated docs always carry their hints

Keeping enrichment (write mcp_hint to the enriched spec) and injection
(read mcp_hint, patch .mdx) as separate scripts mirrors the existing
fetch -> enrich -> generate separation of concerns in this pipeline.
…te page

Populates the mcp_hint overlay field (added in the previous commit's
enrichment-engine support) for retrieve_payment_transaction_checkout — the
first operation to carry one — with a runnable curl example pointing at the
sandbox host:

  curl -X GET "{{sandboxUrl}}/b/checkout/v1/pymt-txn/{session_id}/" \
    -H "Authorization: Api-Key YOUR_API_KEY"

Regenerating via `npm run gen-api` resolves the {{sandboxUrl}} template
variable to the real sandbox host, threads the hint through to the enriched
spec's operation object, and lets inject-mcp-hints.ts patch it into
retrieve-payment-transaction-checkout.api.mdx as a visually-hidden span —
giving the MCP server's docs_fetch a concrete request example for this
operation without changing what human readers see on the page.
…operation

The hand-curated mcp_hint approach doesn't scale — it requires an overlay
author to write and maintain a runnable curl string per operation, and only
the one operation that gets a hint shows up in docs_fetch results. Every
other operation's MCP example would stay missing indefinitely.

Rewrites inject-mcp-hints.ts to auto-generate a curl example for ALL
operations in the enriched spec, using the exact same conversion and
snippet-rendering pipeline the interactive ApiExplorer uses client-side:

- openapi-to-postmanv2 (SchemaPack) converts the spec into a Postman
  collection, mirroring the plugin's own createPostmanCollection step
- postman-code-generators renders the curl snippet, with the same default
  cURL variant options the ApiExplorer uses (mirrored from
  docusaurus-theme-openapi-docs's CodeSnippets/languages.json)
- the openapi-docs plugin's own sampleRequestFromSchema builds the JSON
  request body example for write endpoints, so the body in the generated
  curl matches the example already shown in the page's request schema
  instead of a garbled schema-faker dump
- @apidevtools/json-schema-ref-parser dereferences the spec first, since
  sampleRequestFromSchema can't fake a bare unresolved $ref

The generated curl is injected as a JSON.stringify-wrapped JS string
expression inside the hidden span — {${JSON.stringify(curl)}} rather than
raw text — because the curl text contains literal `{`/`}` (path templates,
JSON bodies) that MDX's acorn-based expression parser would otherwise choke
on ("Could not parse expression with acorn").

Operation matching reuses the plugin's own path-to-regex templating
(bindCollectionToApiItems / pathTemplateToRegex) to bind each Postman
collection item back to its OpenAPI operation by method + path shape.
The new auto-generation injector (previous commit) produces a curl example
for every operation directly from the spec — it no longer reads mcp_hint.
The hand-curated overlay field and the enrichment-engine support that wrote
it through to the spec are now dead weight that would only mislead future
overlay authors into thinking they need to write one per operation.

Removes:
- the mcp_hint value from the retrieve_payment_transaction_checkout overlay
- the mcp_hint?: string field from OperationEnrichment and the block in
  enrichOperations() that copied it onto the operation

Net effect on enrich-api-spec.ts and checkout-api.yaml is a clean revert to
their pre-mcp_hint state; the stale hint span left behind in
retrieve-payment-transaction-checkout.api.mdx gets replaced by the
auto-generated one in the next commit's full regeneration.
Runs the upgraded inject-mcp-hints.ts (two commits back) across a fresh
gen-api-docs build, so every one of the 28 operations now carries a hidden,
auto-generated curl example — not just the single operation that previously
had a hand-written mcp_hint.

For read endpoints this is a plain authenticated request against the
sandbox host (e.g. retrieve-payment-transaction-checkout's GET on
/b/checkout/v1/pymt-txn/{session_id}/); for write endpoints
(create/full-update/partial-update-payment-transaction-checkout, etc.) the
curl now also carries a clean JSON body generated by the same
sampleRequestFromSchema function the page's own request-schema example uses
— e.g. {"type": "e_commerce", "amount": "10.000", "currency_code": "KWD",
"pg_codes": ["knet"]} — instead of the garbled schema-faker dumps a naive
conversion would produce.

Each example is invisible to human readers (display:none) but gets indexed
by the MCP server's static-HTML crawler, so docs_fetch now returns a
copy-paste-ready request example for every endpoint in the API reference,
matching what the interactive ApiExplorer shows on the page.
@apidevtools/json-schema-ref-parser, openapi-to-postmanv2,
postman-collection, and postman-code-generators are imported directly by
scripts/inject-mcp-hints.ts but were only present as transitive dependencies
of docusaurus-plugin-openapi-docs. That's fragile: a future major bump of
docusaurus-plugin-openapi-docs could drop or replace any of them, silently
breaking curl generation at gen-api time with no build failure (the script
just stops finding any operations to inject).

Adds all four as explicit devDependencies, pinned to the versions already
resolved in package-lock.json. No transitive resolution changes.
applyServerAndAuth previously stuffed the full base URL (including
scheme) into a single-element host array and cleared protocol, which
works but isn't how postman-collection's Url model expects host/protocol
to be structured. Parse the base URL and set protocol and host (as
dot-separated segments) the canonical way instead.

No change in generated output: regenerated all 28 .api.mdx files and
diffed against the committed versions — byte-identical.
The MCP plugin converts page HTML to Markdown via rehype-remark and
remark-stringify, which escape `_` and `:` in plain text nodes per
CommonMark rules. Since the hidden curl example was a <span>, its text
content came out of docs_fetch with stray backslashes (https\://,
session\_id, YOUR\_API\_KEY), making it unsafe to copy-paste directly.

Code blocks aren't escaped by remark-stringify, so switching to <pre>
makes rehype-remark emit a fenced code block instead, preserving the
curl verbatim with real line breaks.
Output of npm run gen-api after the <pre> injection fix — every hidden
curl example switches from <span> to <pre> so docs_fetch returns a
clean, copy-paste-ready command instead of one with stray markdown
escape backslashes.
INJECTION_ANCHOR required <Heading and id={"request"} to be on
separate lines with a specific indentation pattern. If
docusaurus-plugin-openapi-docs ever reformats this JSX (single line,
different indentation, etc.), the anchor silently stops matching and
the curl example is dropped without warning.

\s+ matches any whitespace including newlines, so the anchor now
matches regardless of line breaks or indentation between the two
tokens.
…n failures

Previously a missing servers[0].url silently returned {injected: 0,
failures: []} from processSource, which main() treated as success even
though every operation in that source was skipped. Add an optional
SourceResult.error field for source-level failures (distinct from
per-operation failures) and report both separately. Either kind of
failure now exits with code 1, and the "Done — N file(s) patched"
summary only prints on a fully clean run.
Policy: docs.ottu.com must advertise only the public sandbox merchant
sandbox.ottu.net. This switches every DISPLAYED host to sandbox.ottu.net:

- checkout-sdk/web.mdx: merchant_id x11 -> sandbox.ottu.net
- src/constants/api.ts: OTTU_CONNECT_BASE_URL -> sandbox.ottu.net
  (flows into the reports / psq / invoices / wallet curl + response samples)
- getting-started/api-fundamentals.md: "shared sandbox" prose -> sandbox.ottu.net

NOT changed here (the LIVE surfaces): the interactive demos (ACTIVE_CONNECT),
the API-explorer "try it", and the wallet-seed backend still run on
ksa.ottu.dev. They can only move once wallet exists on sandbox.ottu.net —
verified today it does NOT (0 wallet gateways on sandbox.ottu.net vs 15 on
ksa.ottu.dev). See the ACTIVE_CONNECT comment in src/utils/sandbox.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The WalletDemo filters wallet gateways with tags: ["demo"]
(WALLET_DEMO.pgFilter), but the "no wallet gateway" error message told
users to tag the PG "docs". The API rejects tags: ["docs"] outright
(HTTP 400, "docs" is not a valid choice), so the guidance was actively
wrong. Align the message with the actual filter + the working KSA config.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
injectSpan had no way to detect an existing injection, so re-running
inject-mcp-hints standalone (without a clean-api-docs pass first) would
prepend a second hidden curl block into every already-patched .api.mdx
file. Skip files that already contain the injection marker.
docs(api): add curl example as hidden content in the APIs generated pages to let MCP detect it and return it in the response for all APIS
Wallet is now live on sandbox.ottu.net (PG `ottu-sandbox-usd`, USD), so the
last KSA-pinned surfaces move to the public sandbox merchant:

- ACTIVE_CONNECT -> SANDBOX: Checkout / Recurring / PaymentJourney / Wallet
  demos now run against sandbox.ottu.net
- WALLET_DEMO currency KWD -> USD (the sandbox wallet PG is USD-only)
- API-explorer "try it" + auto-generated MCP curls: base URL -> sandbox.ottu.net.
  The enrich script now forces `servers` from `_variables.yaml` sandboxUrl, so
  gen-api (incl. inject-mcp-hints) stays correct after a raw-spec re-fetch; the
  explorer's demo key now tracks ACTIVE_CONNECT instead of a hardcoded KSA key
- regenerated docs/developers/apis/ (servers + MCP curls now sandbox.ottu.net)
- raw static/Ottu_API.yaml servers URL -> sandbox.ottu.net

NOTE: the wallet SEED backend (mcp-server/server.mjs -> KSA_OTTU_DEV) still
mints on KSA's wallet service. The wallet demo's seed->pay flow needs
sandbox.ottu.net's wallet service URL + Keycloak realm/clientId + secret
before it works end-to-end. Discovery + session + display are already on
sandbox.ottu.net.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The docs run on sandbox.ottu.net now, so the wallet seed backend mints on
that merchant's wallet instead of KSA:

- config.mjs: add SANDBOX_OTTU_NET — merchant sandbox.ottu.net, shared
  wallet.ottu.dev + auth.ottu.dev, realm sandbox.ottu.net, client backend,
  secret env SANDBOX_KEYCLOAK_CLIENT_SECRET (confirmed in Keycloak)
- server.mjs: handleSeedWallet seeds via SANDBOX_OTTU_NET

Verified end-to-end locally: POST /seed-wallet for ottu-sandbox-usd (USD)
-> 201, balance minted. DO mcp component has SANDBOX_KEYCLOAK_CLIENT_SECRET
set on docs-dev (docs-prod set at merge time).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jab3z jab3z merged commit af26383 into main Jun 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants