Skip to content

Add page-as-Markdown tools (retrieve + update)#320

Merged
ksinder merged 1 commit into
mainfrom
ksinder-markdown-tools
Jun 17, 2026
Merged

Add page-as-Markdown tools (retrieve + update)#320
ksinder merged 1 commit into
mainfrom
ksinder-markdown-tools

Conversation

@ksinder

@ksinder ksinder commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the two page-content-as-Markdown endpoints from the public Notion API, which were missing from the spec and are the highest-value additions for agentic/MCP clients:

Tool Method Endpoint
retrieve-page-markdown GET /v1/pages/{page_id}/markdown
update-page-markdown PATCH /v1/pages/{page_id}/markdown

These read/write page content as enhanced Markdown instead of block JSON — far more token-efficient for LLMs (the README already advertises "editing pages in Markdown"; this makes it literally true via the dedicated endpoints).

update-page-markdown supports replace_content (overwrite whole page), update_content (targeted find-and-replace, up to 100 edits), and the deprecated insert_content / replace_content_range variants.

The version problem & how it's solved

The markdown endpoints require Notion-Version: 2026-03-11, but the rest of the API is on 2025-09-03. A global bump to 2026-03-11 is genuinely breaking (archivedin_trash, append-children afterposition, transcriptionmeeting_notes) and would be a separate migration. Instead, this PR makes Notion-Version per-operation, sourced from the spec:

  • HttpClient now applies each operation's Notion-Version header-parameter default as a real request header (resolving $ref'd params). Markdown ops pin 2026-03-11; everything else stays 2025-09-03. An explicitly-configured Notion-Version (via OPENAPI_MCP_HEADERS) still takes precedence.
  • proxy.ts no longer hardcodes Notion-Version for the NOTION_TOKEN path — it comes from the spec.
  • parser.ts no longer exposes header params as model-visible tool inputs. Previously Notion-Version appeared (inertly) on 21 of 22 tools as schema noise; now 0.

Empirically confirmed openapi-client-axios does not inject header-param defaults on its own, so this wiring is what actually puts the right version on the wire.

Tests

  • http-client.headers.test.ts — per-operation version injection + user-override precedence.
  • notion-markdown-tools.test.ts — markdown tools generate with correct inputs and no Notion-Version leakage, against the real spec.
  • Full suite: 86 passing, build green. Spec diff is purely additive.

Notes for review

🤖 Generated with Claude Code

@ksinder ksinder marked this pull request as ready for review June 17, 2026 21:30
@ksinder ksinder requested review from hallie and vshen-notion June 17, 2026 21:30
@ksinder ksinder force-pushed the ksinder-markdown-tools branch from da1bdc9 to eb9a0eb Compare June 17, 2026 21:59
Adds two tools from the public Notion API that fit agentic clients and
were missing from the spec:

- retrieve-page-markdown (GET /v1/pages/{page_id}/markdown)
- update-page-markdown  (PATCH /v1/pages/{page_id}/markdown)

They read/write page content as enhanced Markdown instead of block JSON,
which is far more token-efficient for LLMs.

These endpoints require Notion-Version 2026-03-11 while the rest of the
API stays on 2025-09-03, so Notion-Version is now sourced per operation
from the OpenAPI spec: HttpClient applies each operation's header-param
default (resolving $refs), an explicitly-configured Notion-Version still
wins, the proxy no longer hardcodes it, and the parser no longer exposes
header params as model-visible tool inputs.

Also hardens the server-based integration tests against flakiness
(root cause, not retries): a shared startTestServer/stopTestServer helper
binds an ephemeral port (0), awaits the `listening` event before issuing
requests, awaits full shutdown, and uses 127.0.0.1 to avoid localhost
IPv4/IPv6 mismatch. This removes the intermittent ECONNRESET / "socket
hang up" failures from random-port collisions and request-before-bind
races.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ksinder ksinder force-pushed the ksinder-markdown-tools branch from eb9a0eb to 1be339c Compare June 17, 2026 22:03
@ksinder

ksinder commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Added a root-cause fix for the flaky server-based integration tests (was intermittently failing CI with socket hang up / ECONNRESET).

Three real races, now fixed via a shared startTestServer/stopTestServer helper:

  1. Request-before-bind — tests issued requests before app.listen() finished binding. Now we await the listening event.
  2. Port collisions — random 3000 + rand(1000) ports clashed across vitest's parallel workers. Now we bind to ephemeral port 0 (OS-assigned, guaranteed free).
  3. Unawaited teardownafterEach didn't await server.close(). Now it does.
    Also switched the base URL to 127.0.0.1 to avoid localhost::1 IPv4/IPv6 mismatch.

Verified with 5 consecutive local full-suite runs (89/89 each). This touches http-client.integration.test.ts (pre-existing) and the new header test only — no production code change beyond the markdown work.

@ksinder ksinder merged commit 9a8a94c into main Jun 17, 2026
9 checks passed
@ksinder ksinder deleted the ksinder-markdown-tools branch June 17, 2026 22:06
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