Summary
API-update-a-block (PATCH /v1/blocks/{block_id}) fails with 400 validation_error for every block type. The tool nests the block-type object under a type key in the request body, but the Notion API expects the block-type key at the body root.
Confirmed on @notionhq/notion-mcp-server@2.5.1 (latest) and on main.
Root cause
Two facts combine:
1. The request body is the tool arguments, verbatim. In src/openapi-mcp-server/client/http-client.ts:
const bodyParams: Record<string, any> = formData || { ...params } // line ~171
// ...
const response = await operationFn(urlParameters, hasBody ? bodyParams : undefined, requestConfig) // line ~218
After path params (block_id) are stripped, the remaining args are serialized straight into the body.
2. The OpenAPI spec wraps the payload under a type object. In scripts/notion-openapi.json, operation update-a-block:
{
"type": "object",
"properties": {
"type": { "type": "object", "description": "The block object `type` value ...", "properties": {} },
"archived": { "type": "boolean", "default": true }
}
}
So a caller sends { block_id, type: { paragraph: { rich_text: [...] } } } and the proxy forwards { "type": { "paragraph": {...} } } to Notion. Notion has no settable root type property and finds no block-type key at the root, so it rejects the request and lists every block type as undefined.
By contrast, patch-block-children exposes children as a root property whose items are full block objects carrying their own type key — which is why it works.
Reproduction
Update any block, e.g. a bulleted_list_item:
- Via the MCP tool — args
{ "block_id": "...", "type": { "bulleted_list_item": { "rich_text": [{ "type": "text", "text": { "content": "hi" } }] } } }
→ 400 validation_error: body.bulleted_list_item should be defined, instead was undefined (every block type listed likewise).
- Direct REST PATCH with the same inner body at the root —
{ "bulleted_list_item": { "rich_text": [{ "type": "text", "text": { "content": "hi" } }] } }
→ 200 OK, block updated.
The passing direct call proves the payload and the API are correct; the 400 comes solely from the MCP layer nesting the block-type key under type. Notion's own error message (demanding body.<block_type> at the root) is the proof that the wrapper is wrong.
Proposed fixes
A — spec-level (preferred): In scripts/notion-openapi.json, replace the single type object property on update-a-block with the real root-level shape Notion accepts — the updatable block-type properties (paragraph, heading_1..3, bulleted_list_item, numbered_list_item, to_do, toggle, quote, callout, code, embed, image, ... each an object with rich_text / checked / etc.) plus archived. This mirrors the block-type keys already used inside patch-block-children's children items, needs no proxy code changes, and fixes the misleading advertised schema.
B — code-level: Keep the spec's type convenience field but unwrap it in the proxy for this operation. After bodyParams is built in http-client.ts:
if (operation.operationId === 'update-a-block' && bodyParams.type && typeof bodyParams.type === 'object') {
const { type, ...rest } = bodyParams
bodyParams = { ...rest, ...type }
}
Fix A is cleaner (no operation-specific branching in a generic proxy) and also corrects the tool's advertised schema; Fix B is a smaller, backward-tolerant patch.
Environment
@notionhq/notion-mcp-server 2.5.1 (also verified on main)
- Notion-Version
2022-06-28
Summary
API-update-a-block(PATCH /v1/blocks/{block_id}) fails with400 validation_errorfor every block type. The tool nests the block-type object under atypekey in the request body, but the Notion API expects the block-type key at the body root.Confirmed on
@notionhq/notion-mcp-server@2.5.1(latest) and onmain.Root cause
Two facts combine:
1. The request body is the tool arguments, verbatim. In
src/openapi-mcp-server/client/http-client.ts:After path params (
block_id) are stripped, the remaining args are serialized straight into the body.2. The OpenAPI spec wraps the payload under a
typeobject. Inscripts/notion-openapi.json, operationupdate-a-block:{ "type": "object", "properties": { "type": { "type": "object", "description": "The block object `type` value ...", "properties": {} }, "archived": { "type": "boolean", "default": true } } }So a caller sends
{ block_id, type: { paragraph: { rich_text: [...] } } }and the proxy forwards{ "type": { "paragraph": {...} } }to Notion. Notion has no settable roottypeproperty and finds no block-type key at the root, so it rejects the request and lists every block type asundefined.By contrast,
patch-block-childrenexposeschildrenas a root property whose items are full block objects carrying their own type key — which is why it works.Reproduction
Update any block, e.g. a
bulleted_list_item:{ "block_id": "...", "type": { "bulleted_list_item": { "rich_text": [{ "type": "text", "text": { "content": "hi" } }] } } }→
400 validation_error:body.bulleted_list_item should be defined, instead was undefined(every block type listed likewise).{ "bulleted_list_item": { "rich_text": [{ "type": "text", "text": { "content": "hi" } }] } }→
200 OK, block updated.The passing direct call proves the payload and the API are correct; the 400 comes solely from the MCP layer nesting the block-type key under
type. Notion's own error message (demandingbody.<block_type>at the root) is the proof that the wrapper is wrong.Proposed fixes
A — spec-level (preferred): In
scripts/notion-openapi.json, replace the singletypeobject property onupdate-a-blockwith the real root-level shape Notion accepts — the updatable block-type properties (paragraph,heading_1..3,bulleted_list_item,numbered_list_item,to_do,toggle,quote,callout,code,embed,image, ... each an object withrich_text/checked/ etc.) plusarchived. This mirrors the block-type keys already used insidepatch-block-children'schildrenitems, needs no proxy code changes, and fixes the misleading advertised schema.B — code-level: Keep the spec's
typeconvenience field but unwrap it in the proxy for this operation. AfterbodyParamsis built inhttp-client.ts:Fix A is cleaner (no operation-specific branching in a generic proxy) and also corrects the tool's advertised schema; Fix B is a smaller, backward-tolerant patch.
Environment
@notionhq/notion-mcp-server2.5.1 (also verified onmain)2022-06-28