@zackbart/connecta 0.24.2 → 0.24.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +18 -20
- package/CHANGELOG.md +205 -1
- package/README.md +5 -6
- package/dist/auth/bearer.js +2 -0
- package/dist/auth/downstream-oauth.d.ts +12 -1
- package/dist/auth/downstream-oauth.js +147 -35
- package/dist/branding.d.ts +31 -2
- package/dist/branding.js +116 -8
- package/dist/call-admission.d.ts +4 -0
- package/dist/call-admission.js +26 -0
- package/dist/catalog-drift.js +9 -4
- package/dist/catalog-service.d.ts +2 -0
- package/dist/catalog-service.js +25 -8
- package/dist/catalog.d.ts +2 -0
- package/dist/catalog.js +246 -121
- package/dist/connectors/api.d.ts +1 -1
- package/dist/connectors/api.js +21 -3
- package/dist/connectors/guarded-fetch.d.ts +6 -2
- package/dist/connectors/guarded-fetch.js +61 -24
- package/dist/connectors/remote-mcp.js +92 -57
- package/dist/errors.d.ts +28 -3
- package/dist/errors.js +60 -1
- package/dist/execute.js +85 -23
- package/dist/executor-result.js +3 -1
- package/dist/executors/quickjs-child.js +5 -1
- package/dist/executors/quickjs-protocol.d.ts +4 -0
- package/dist/executors/quickjs-runtime.d.ts +1 -1
- package/dist/executors/quickjs-runtime.js +38 -21
- package/dist/executors/quickjs.js +68 -27
- package/dist/index.d.ts +15 -1
- package/dist/index.js +36 -4
- package/dist/invocation.js +134 -93
- package/dist/mcp-result.js +3 -2
- package/dist/meta-tools.js +191 -36
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/view.d.ts +38 -1
- package/dist/operator-ui/view.js +71 -0
- package/dist/providers/cloudflare.d.ts +14 -2
- package/dist/providers/cloudflare.js +107 -16
- package/dist/providers/linear.d.ts +26 -4
- package/dist/providers/linear.js +19 -4
- package/dist/providers/mixpanel.d.ts +16 -3
- package/dist/providers/mixpanel.js +13 -2
- package/dist/providers/notion.d.ts +8 -1
- package/dist/providers/notion.js +83 -10
- package/dist/providers/revenuecat.d.ts +30 -4
- package/dist/providers/revenuecat.js +42 -4
- package/dist/providers/stripe.d.ts +7 -1
- package/dist/providers/stripe.js +30 -4
- package/dist/providers/vercel.js +11 -1
- package/dist/registry.d.ts +22 -2
- package/dist/registry.js +101 -13
- package/dist/routes/mcp.d.ts +4 -1
- package/dist/routes/mcp.js +84 -13
- package/dist/routes/oauth.js +4 -0
- package/dist/routes/shared.d.ts +1 -0
- package/dist/routes/shared.js +4 -4
- package/dist/server.js +15 -3
- package/dist/skills.js +6 -5
- package/dist/storage/file.d.ts +6 -2
- package/dist/storage/file.js +312 -34
- package/dist/storage/memory.js +12 -1
- package/dist/types.d.ts +37 -0
- package/dist/ui.js +18 -10
- package/dist/validate.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +193 -165
- package/documentation/auth.md +199 -145
- package/documentation/code-mode.md +433 -316
- package/documentation/meta-tools.md +363 -355
- package/examples/worker/AGENTS.md +2 -1
- package/examples/worker/README.md +12 -10
- package/examples/worker/src/index.ts +12 -15
- package/package.json +1 -2
- package/templates/node/.env.example +3 -3
- package/templates/node/AGENTS.md +5 -4
- package/templates/node/README.md +2 -1
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +23 -22
- package/documentation/call-admission.md +0 -142
- package/documentation/cloudflare.md +0 -471
- package/documentation/connector-guides.md +0 -176
- package/documentation/connectors.md +0 -389
- package/documentation/linear.md +0 -193
- package/documentation/mixpanel.md +0 -160
- package/documentation/notion.md +0 -308
- package/documentation/operations.md +0 -359
- package/documentation/operator-ui.md +0 -135
- package/documentation/optional-modules-upgrade.md +0 -243
- package/documentation/provider-conventions.md +0 -722
- package/documentation/request-admission.md +0 -170
- package/documentation/revenuecat.md +0 -305
- package/documentation/storage-and-credentials.md +0 -201
- package/documentation/stripe.md +0 -262
- package/documentation/upgrading.md +0 -754
- package/documentation/vercel.md +0 -241
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
# Mixpanel prebuilt connection
|
|
2
|
-
|
|
3
|
-
Import `mixpanel()` independently from
|
|
4
|
-
`@zackbart/connecta/providers/mixpanel`. It wraps Mixpanel's hosted MCP server
|
|
5
|
-
with regional endpoint selection, OAuth by default, a task-oriented usage
|
|
6
|
-
guide, and a vetted safety classification. It adds no provider dependency and
|
|
7
|
-
is not reachable from Connecta's root entry.
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
import { mixpanel } from "@zackbart/connecta/providers/mixpanel";
|
|
11
|
-
|
|
12
|
-
const analytics = mixpanel("product_analytics", {
|
|
13
|
-
title: "Production product analytics",
|
|
14
|
-
purpose: "Product and growth decisions for the production app",
|
|
15
|
-
region: "us",
|
|
16
|
-
instructions: "Use the Core Product project unless the request says otherwise.",
|
|
17
|
-
});
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
The `id` owns the ordinary connector namespaces; use a different id for every
|
|
21
|
-
Mixpanel account. `purpose` is required because an agent choosing between two
|
|
22
|
-
instances needs to know which account answers the question. Account
|
|
23
|
-
`instructions` are appended to the maintained guide and cannot change the
|
|
24
|
-
connector's safety classification.
|
|
25
|
-
|
|
26
|
-
`region` accepts `"us"` (the default), `"eu"`, or `"in"` and selects the
|
|
27
|
-
corresponding [official hosted endpoint](https://docs.mixpanel.com/docs/mcp#mcp-server-urls).
|
|
28
|
-
A project lives in exactly one residency, so the region also decides what this
|
|
29
|
-
connection can see at all: a question pointed at the wrong one comes back empty
|
|
30
|
-
rather than wrong, which reads as the project having no data. That makes it a
|
|
31
|
-
routing fact, so it rides the default `title` (`Mixpanel (us)`, `Mixpanel
|
|
32
|
-
(eu)`, `Mixpanel (in)`) and opens the usage guide — `search_tools` renders a
|
|
33
|
-
connector's title and guide summary and never its description.
|
|
34
|
-
OAuth is the recommended default and keeps each connector instance's flow and
|
|
35
|
-
tokens in its connector-scoped storage. Mixpanel service accounts are also
|
|
36
|
-
supported with an explicit header override:
|
|
37
|
-
|
|
38
|
-
```ts
|
|
39
|
-
mixpanel("automation_analytics", {
|
|
40
|
-
purpose: "Headless release-health reporting",
|
|
41
|
-
auth: {
|
|
42
|
-
type: "headers",
|
|
43
|
-
headers: { Authorization: `Bearer Basic ${env.MIXPANEL_SA_TOKEN}` },
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Keep that encoded service-account value in the runtime's secret store; it is a
|
|
49
|
-
password, not ordinary configuration. Mixpanel currently labels service-account
|
|
50
|
-
MCP authentication beta. Prefer OAuth unless the deployment is intentionally
|
|
51
|
-
headless.
|
|
52
|
-
|
|
53
|
-
The same service account can arrive from the connection UI at `/` instead, and there the
|
|
54
|
-
operator pastes the readable pair rather than an encoded blob:
|
|
55
|
-
|
|
56
|
-
```ts
|
|
57
|
-
mixpanel("automation_analytics", {
|
|
58
|
-
purpose: "Headless release-health reporting",
|
|
59
|
-
auth: { type: "credential" },
|
|
60
|
-
});
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
The slot renders as "Service account" and takes `username:secret`. Connecta
|
|
64
|
-
base64-encodes it and sends Mixpanel's documented `Bearer Basic` framing, so the
|
|
65
|
-
operator never has to encode anything by hand. **The two paths take different
|
|
66
|
-
strings:** the `headers` example above wants the already-encoded blob
|
|
67
|
-
(`echo -n "username:secret" | base64`), and this one wants the plaintext pair.
|
|
68
|
-
Migrating from one to the other means decoding, not copying. Until a value is
|
|
69
|
-
saved the connector is present and reports `auth_required`. See
|
|
70
|
-
[storage and credentials](./storage-and-credentials.md#a-remote-mcp-connectors-static-credential).
|
|
71
|
-
|
|
72
|
-
## Conditional input contracts
|
|
73
|
-
|
|
74
|
-
Mixpanel's hosted descriptions enforce three cross-field conditions that its
|
|
75
|
-
input schemas do not encode. Connecta preserves those schemas unchanged under
|
|
76
|
-
[P1](./provider-conventions.md#p1--normalize-by-adding-never-by-rewriting), so
|
|
77
|
-
the maintained guide carries the missing call guidance:
|
|
78
|
-
|
|
79
|
-
- `Get-Business-Context` requires `project_id` or `organization_id`.
|
|
80
|
-
- `Get-Property-Values` requires `properties` or the deprecated `property`
|
|
81
|
-
alias. Event property values also require `event`.
|
|
82
|
-
- `List-Properties` accepts `names` or `query`, never both.
|
|
83
|
-
|
|
84
|
-
A read-only live audit on 2026-08-13 confirmed all three refusals against the
|
|
85
|
-
US hosted endpoint. They are reported upstream as
|
|
86
|
-
[`mixpanel/mixpanel-headless#202`](https://github.com/mixpanel/mixpanel-headless/issues/202).
|
|
87
|
-
The vetted catalog records current schema digests for all 64 tools,
|
|
88
|
-
so a later schema correction or regression increments runtime drift when an
|
|
89
|
-
ordinary catalog refresh observes it. The live definition is still served
|
|
90
|
-
unchanged. The credential-free provider check does not depend on those digests.
|
|
91
|
-
The guide can shrink when the downstream schema becomes complete; Connecta does
|
|
92
|
-
not absorb the defect permanently.
|
|
93
|
-
|
|
94
|
-
The wrapper classifies the documented observational tools as reads and the
|
|
95
|
-
documented create, update, edit, merge, dismiss, duplicate, and delete tools as
|
|
96
|
-
writes. An unfamiliar tool the downstream leaves unannotated fails closed onto
|
|
97
|
-
`call_destructive_tool` until a Connecta release reviews it.
|
|
98
|
-
|
|
99
|
-
That classification fills in downstream silence and otherwise preserves
|
|
100
|
-
explicit annotations. A tool on the read allowlist arriving with
|
|
101
|
-
`destructiveHint: true` or `readOnlyHint: false` keeps exactly what the
|
|
102
|
-
downstream said and stays behind `call_destructive_tool`. A tool on neither
|
|
103
|
-
maintained list arriving with `readOnlyHint: true` keeps that too, and stays
|
|
104
|
-
callable from `execute_code`. Both are the downstream telling you this
|
|
105
|
-
release's allowlist is stale, and on a name no release has reviewed its word is
|
|
106
|
-
the only evidence there is. One narrow fail-closed exception applies to a name
|
|
107
|
-
this release reviewed and filed destructive: a `Delete-Dashboard` claiming
|
|
108
|
-
`readOnlyHint: true` is a downstream bug rather than news, and stays on the
|
|
109
|
-
approval path. Maintained writes that only create
|
|
110
|
-
something new (`Create-Dashboard`, `Create-Cohort`, `Create-Metric`, and the
|
|
111
|
-
rest) leave `destructiveHint` unset; `readOnlyHint: false` already routes them
|
|
112
|
-
through the destructive path, and asserting destruction only inflates the
|
|
113
|
-
approval copy the host shows a human.
|
|
114
|
-
|
|
115
|
-
Experiments and Feature Flags are Mixpanel beta surfaces. Their three changed
|
|
116
|
-
schemas were reviewed again on 2026-08-30. The same review added
|
|
117
|
-
`Fill-Event-Metadata` as a destructive write because it applies generated names
|
|
118
|
-
and descriptions to existing Lexicon events.
|
|
119
|
-
|
|
120
|
-
## Rate limits
|
|
121
|
-
|
|
122
|
-
Mixpanel meters its MCP server **per user per hour**, shared with everything
|
|
123
|
-
else that credential does. Connecta's counter is per runtime, not per user, and
|
|
124
|
-
the two cannot be reconciled in either direction: one runtime serving several
|
|
125
|
-
users under-counts, and several Worker isolates or Node processes sharing one
|
|
126
|
-
credential each admit a full budget. A hardcoded ceiling would therefore either
|
|
127
|
-
throttle a healthy deployment or fail to protect a busy one, so this connection
|
|
128
|
-
declares **no call-admission budget by default**. An operator who knows the
|
|
129
|
-
account can supply one explicitly:
|
|
130
|
-
|
|
131
|
-
```ts
|
|
132
|
-
mixpanel("product_analytics", {
|
|
133
|
-
purpose: "Product and growth decisions for the production app",
|
|
134
|
-
callAdmission: {
|
|
135
|
-
rules: [
|
|
136
|
-
{ budget: { kind: "rolling-window", maxCalls: 300, windowMs: 3_600_000 } },
|
|
137
|
-
],
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
A budget-only rule needs no queue. If you add `maxConcurrency` you are asking
|
|
143
|
-
for a queue, and the admission controller then requires the rest of the queue
|
|
144
|
-
settings at construction. Discovery traffic is outside connector call admission
|
|
145
|
-
either way and still needs restrained use.
|
|
146
|
-
|
|
147
|
-
## Public contract check
|
|
148
|
-
|
|
149
|
-
`npm run drift:check -- --docs --provider mixpanel` compares Mixpanel's
|
|
150
|
-
official Available Tools table with the vetted manifest and checks all three
|
|
151
|
-
regional endpoints plus OAuth support. The current table lists 63 tools. It
|
|
152
|
-
omits `Fill-Event-Metadata`, which remains classified from the last
|
|
153
|
-
authenticated review and is reported as `not documented`, not silently removed.
|
|
154
|
-
|
|
155
|
-
## Conventions
|
|
156
|
-
|
|
157
|
-
This connection is audited against
|
|
158
|
-
[the provider conventions](./provider-conventions.md). Its verdict per
|
|
159
|
-
convention, including every recorded exception, is the Mixpanel section of
|
|
160
|
-
[the provider audit](https://github.com/zackbart/connecta/blob/main/records/provider-audit.md).
|
package/documentation/notion.md
DELETED
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
# Notion prebuilt connection
|
|
2
|
-
|
|
3
|
-
Import `notion()` independently from `@zackbart/connecta/providers/notion`. It
|
|
4
|
-
offers two deployment-time interfaces. The default is a hand-written `api()`
|
|
5
|
-
surface over Notion's public REST API: fifteen deliberate tools, lean
|
|
6
|
-
projections of Notion's famously bloated payloads, typed failures, a rate
|
|
7
|
-
budget matched to the documented limit, and a required usage guide. The other
|
|
8
|
-
choice is Notion's official hosted MCP with live provider-owned schemas and a
|
|
9
|
-
broader workspace, files, views, agents, and sessions catalog. Neither is
|
|
10
|
-
reachable from Connecta's root entry.
|
|
11
|
-
|
|
12
|
-
```ts
|
|
13
|
-
import { notion } from "@zackbart/connecta/providers/notion";
|
|
14
|
-
|
|
15
|
-
const wiki = notion("engineering_wiki", {
|
|
16
|
-
surface: "api", // optional; this is the backward-compatible default
|
|
17
|
-
title: "Engineering wiki",
|
|
18
|
-
purpose: "Runbooks, specs, and on-call notes for the platform team",
|
|
19
|
-
instructions: "Prefer the Runbooks database; specs live under Projects.",
|
|
20
|
-
});
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Use Notion's hosted MCP instead:
|
|
24
|
-
|
|
25
|
-
```ts
|
|
26
|
-
const workspace = notion("notion_mcp", {
|
|
27
|
-
surface: "mcp",
|
|
28
|
-
purpose: "Workspace search, files, views, and asynchronous agent sessions",
|
|
29
|
-
});
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
The `id` owns the ordinary connector namespaces; use a different id for every
|
|
33
|
-
Notion workspace. `purpose` is required because an agent choosing between two
|
|
34
|
-
instances needs to know which workspace answers the question. Workspace
|
|
35
|
-
`instructions` are appended to the maintained guide and cannot change the
|
|
36
|
-
connector's safety classification.
|
|
37
|
-
|
|
38
|
-
## Choosing an interface
|
|
39
|
-
|
|
40
|
-
Use the API interface for its compact, stable projections. A single Notion
|
|
41
|
-
page returns every property as a
|
|
42
|
-
discriminated wrapper object, every string as an array of rich-text runs each
|
|
43
|
-
carrying its own annotations block, and every user reference as a nested
|
|
44
|
-
object. A twenty-five row database query is tens of kilobytes of structure
|
|
45
|
-
around a few hundred bytes of meaning. Hand-writing the surface is what makes
|
|
46
|
-
the projections possible, and the projections are the point.
|
|
47
|
-
|
|
48
|
-
Use `surface: "mcp"` for Notion's wider official capabilities, including
|
|
49
|
-
connected-source search, attachments, saved views, Notion Skills, agents, and
|
|
50
|
-
asynchronous sessions. Tool names and schemas come from the live server.
|
|
51
|
-
Connecta preserves them and only fills in release-reviewed safety annotations
|
|
52
|
-
when Notion is silent. OAuth is the hosted server's authentication contract.
|
|
53
|
-
Do not apply the hand-written REST schemas to similarly named MCP tools. The
|
|
54
|
-
MCP interface accepts `callAdmission` for an operator-supplied runtime policy;
|
|
55
|
-
it does not assume the REST interface's endpoint budget describes MCP traffic.
|
|
56
|
-
|
|
57
|
-
## API authentication
|
|
58
|
-
|
|
59
|
-
One operator-managed credential: an internal integration token from
|
|
60
|
-
[notion.so/profile/integrations](https://www.notion.so/profile/integrations).
|
|
61
|
-
The deployment needs `vault: encryptedCredentialVault(storage, encryptionKey)` configured, or the token
|
|
62
|
-
cannot be stored and every call fails `auth_required` at use.
|
|
63
|
-
|
|
64
|
-
Two Notion-specific facts decide whether a working token is enough:
|
|
65
|
-
|
|
66
|
-
- **Sharing is per object.** A token reaches only what has been explicitly
|
|
67
|
-
shared with its integration. An unshared page returns 404, not 403.
|
|
68
|
-
- **Capabilities are per integration**, and the comment capabilities are off by
|
|
69
|
-
default. `list_comments` and `add_comment` fail with 403 until an operator
|
|
70
|
-
turns them on in Notion.
|
|
71
|
-
|
|
72
|
-
The connection UI at `/` offers a Test action, which calls `GET /v1/users/me` — the
|
|
73
|
-
cheapest call that proves a token is live — and reports the workspace it
|
|
74
|
-
authenticated into.
|
|
75
|
-
|
|
76
|
-
The MCP interface uses Notion OAuth instead of the integration-token form. An
|
|
77
|
-
`auth_required` failure means the grant is absent or expired and must be
|
|
78
|
-
completed again through `authorize_connector`.
|
|
79
|
-
|
|
80
|
-
## The pinned API version
|
|
81
|
-
|
|
82
|
-
The connection pins `Notion-Version: 2026-03-11` and offers no override. That
|
|
83
|
-
is deliberate rather than lazy. Notion's versions are date-named and old ones
|
|
84
|
-
keep working indefinitely, so an override would look harmless; it is not.
|
|
85
|
-
`2026-03-11` is the version in which databases split into data sources,
|
|
86
|
-
`archived` became `in_trash`, and block append took a `position` object instead
|
|
87
|
-
of an `after` string. Every projection and write body here assumes those
|
|
88
|
-
shapes, so a deployment that pinned an older version would get quietly wrong
|
|
89
|
-
results instead of a loud failure.
|
|
90
|
-
|
|
91
|
-
Notion also ships *additive* changes to every version simultaneously, which is
|
|
92
|
-
why the property and block projections fall back to unwrapping an unknown
|
|
93
|
-
type's payload rather than switching exhaustively. A property type that ships
|
|
94
|
-
after this release degrades to its raw value, and a block type that does keeps
|
|
95
|
-
its payload under `raw`; neither vanishes.
|
|
96
|
-
|
|
97
|
-
The remaining sections document the hand-written API interface. MCP tool
|
|
98
|
-
arguments and results are intentionally read from the live server instead.
|
|
99
|
-
|
|
100
|
-
## API tools
|
|
101
|
-
|
|
102
|
-
Ten reads, all annotated `readOnlyHint: true`:
|
|
103
|
-
|
|
104
|
-
| Tool | What it is for |
|
|
105
|
-
| --- | --- |
|
|
106
|
-
| `search` | Find pages and data sources by **title**. Never searches content. |
|
|
107
|
-
| `get_page` | One page's metadata and flattened property values. |
|
|
108
|
-
| `get_page_content` | A page's body as flat blocks reduced to plain text. |
|
|
109
|
-
| `get_page_property` | One property in full, past the 25-entry truncation. |
|
|
110
|
-
| `get_database` | A database container and the data sources inside it. |
|
|
111
|
-
| `get_data_source_schema` | Property ids, types, and select/status options. |
|
|
112
|
-
| `query_data_source` | Filtered, sorted rows with properties already flattened. |
|
|
113
|
-
| `list_users` | Workspace users and bots with their ids. |
|
|
114
|
-
| `get_self` | Which integration and workspace this connector authenticates as. |
|
|
115
|
-
| `list_comments` | Unresolved comments as plain text with discussion ids. |
|
|
116
|
-
|
|
117
|
-
Five writes, none read-only, so all of them route through
|
|
118
|
-
`call_destructive_tool`:
|
|
119
|
-
|
|
120
|
-
| Tool | Classification |
|
|
121
|
-
| --- | --- |
|
|
122
|
-
| `create_page` | additive |
|
|
123
|
-
| `append_blocks` | additive |
|
|
124
|
-
| `add_comment` | additive |
|
|
125
|
-
| `update_page_properties` | `destructiveHint: true` — replaces existing values |
|
|
126
|
-
| `trash_page` | `destructiveHint: true` — removes a page from reads |
|
|
127
|
-
|
|
128
|
-
The additive three leave `destructiveHint` unset: `readOnlyHint: false` already
|
|
129
|
-
routes them through the approval path, and claiming a create destroys something
|
|
130
|
-
only inflates the copy a host shows a human. `update_page_properties`
|
|
131
|
-
deliberately has no `in_trash` argument, so an update can never trash a page by
|
|
132
|
-
accident; trashing is its own named, reversible tool.
|
|
133
|
-
|
|
134
|
-
## Lean projections, and the raw escape hatch
|
|
135
|
-
|
|
136
|
-
Every read projects. A page becomes ids, plain text, and flattened values:
|
|
137
|
-
`title` and `rich_text` collapse to strings, `select` and `status` to their
|
|
138
|
-
option name, `multi_select` to an array of names, `relation` to an array of
|
|
139
|
-
page ids, `people` to `{ id, name }`, `unique_id` to `"RL-12"`, and
|
|
140
|
-
`formula`/`rollup` to their computed value. No `plain_text` runs, no
|
|
141
|
-
`annotations` blocks, no property wrappers survive.
|
|
142
|
-
|
|
143
|
-
Two projections are opinionated enough to call out:
|
|
144
|
-
|
|
145
|
-
- **`search` returns identity fields only** — no properties at all. A
|
|
146
|
-
twenty-five result search across a populated database would otherwise drag
|
|
147
|
-
back several hundred flattened values for results the agent is about to
|
|
148
|
-
discard. `get_page` fetches properties for the one that matched.
|
|
149
|
-
- **`get_page` reports what Notion hid.** Notion paginates four property types
|
|
150
|
-
— `title`, `rich_text`, `relation`, and `people` — cutting each off at 25
|
|
151
|
-
entries and signalling it only with a `has_more` flag on the property itself.
|
|
152
|
-
The projection surfaces those in `truncated_properties` as `{ name, id }`,
|
|
153
|
-
which is what stops an agent from confidently reasoning about 25 of 300
|
|
154
|
-
relations. The `id` is there because the handoff needs it: `get_page_property`
|
|
155
|
-
addresses a property by id, not by name.
|
|
156
|
-
|
|
157
|
-
Where the dropped detail can matter — `search`, `get_page`, `get_page_content`,
|
|
158
|
-
`get_page_property`, `get_data_source_schema`, `query_data_source`,
|
|
159
|
-
`list_comments` — `raw: true` returns Notion's untouched response instead. It is
|
|
160
|
-
much larger; it exists so a missing field is never a dead end. When the goal is
|
|
161
|
-
*fewer* fields rather than more, `get_page` and `query_data_source` also take a
|
|
162
|
-
`properties` array to project only the named ones.
|
|
163
|
-
|
|
164
|
-
One caveat with `get_page_content`: `raw: true` returns the requested level
|
|
165
|
-
exactly as Notion sent it and does not walk nested children, so `depth` is
|
|
166
|
-
ignored alongside it. A raw read of a deep page yields one level, not three.
|
|
167
|
-
Unmodelled *block* types are covered without it — a block whose type this
|
|
168
|
-
projection does not know, and whose payload is not plain rich text, keeps that
|
|
169
|
-
payload verbatim under `raw` on the block, so nothing collapses to an empty
|
|
170
|
-
string.
|
|
171
|
-
|
|
172
|
-
## Databases contain data sources
|
|
173
|
-
|
|
174
|
-
This is the trap the guide is marked `required` for. A Notion database is a
|
|
175
|
-
container; the rows and the schema live in a *data source* inside it, and the
|
|
176
|
-
two ids are not interchangeable. The id in a database's URL is a **database
|
|
177
|
-
id**, and passing it to `query_data_source` fails. The sequence is
|
|
178
|
-
`get_database` → `get_data_source_schema` → `query_data_source`; `search`
|
|
179
|
-
returns data sources directly and skips the first step.
|
|
180
|
-
|
|
181
|
-
`create_page` needs the same distinction: a row is created under
|
|
182
|
-
`parent_data_source_id`, never a database id. Its title also needs
|
|
183
|
-
`title_property` from the schema, because a database's title column is rarely
|
|
184
|
-
called "title".
|
|
185
|
-
|
|
186
|
-
## Typed failures
|
|
187
|
-
|
|
188
|
-
The mapping is deliberately not one-to-one with Notion's error codes. Notion's
|
|
189
|
-
`code` says what its API thinks happened; Connecta's says what the caller
|
|
190
|
-
should do next, and two of Notion's are easy to mistranslate.
|
|
191
|
-
|
|
192
|
-
| Notion | Connecta | Why |
|
|
193
|
-
| --- | --- | --- |
|
|
194
|
-
| 400 (`validation_error`, `invalid_json`, `invalid_request`, `missing_version`, …) | `invalid_args` | every documented 400 is a malformed request |
|
|
195
|
-
| 401 `unauthorized` | `auth_required` | the token is missing or invalid |
|
|
196
|
-
| 403 `restricted_resource` | `connector_call_failed`, non-retryable | **not** `auth_required` |
|
|
197
|
-
| 404 `object_not_found` | `connector_call_failed`, non-retryable | overloaded — deliberately **not** `not_found`; see below |
|
|
198
|
-
| 409 `conflict_error` | `unavailable`, retryable | Notion says to retry |
|
|
199
|
-
| 429 `rate_limited` | `rate_limited` + `retryAfterMs` | `Retry-After` seconds → ms |
|
|
200
|
-
| 529 `service_overload` | `unavailable` + `retryAfterMs` | back off like a 429 |
|
|
201
|
-
| 5xx | `unavailable`, retryable | upstream failure |
|
|
202
|
-
|
|
203
|
-
The two that matter:
|
|
204
|
-
|
|
205
|
-
**403 is not an authentication failure.** The token is fine; the integration
|
|
206
|
-
lacks a capability or was never shared the object. Routing it to
|
|
207
|
-
`auth_required` would send an agent to `authorize_connector`, which cannot
|
|
208
|
-
grant a Notion capability or share a page. It is a non-retryable call failure
|
|
209
|
-
whose message says an operator must change it in Notion.
|
|
210
|
-
|
|
211
|
-
**404 does not prove absence.** Notion returns `object_not_found` both for an
|
|
212
|
-
object that does not exist and for one that exists but has not been shared with
|
|
213
|
-
the integration, and it will not say which. The message says both, because
|
|
214
|
-
treating it as deletion is exactly how an agent concludes a page is gone when
|
|
215
|
-
it was simply never shared. This is why the row above does not use `not_found`,
|
|
216
|
-
which exists precisely to say "it is not there": the qualifier on that code
|
|
217
|
-
([H11](./provider-conventions.md#h11--errors-are-mapped-to-what-the-caller-does-next))
|
|
218
|
-
is that the provider must tell absence apart from a permission gap, and Notion
|
|
219
|
-
does not. A program that skipped this id as missing would be right about half
|
|
220
|
-
the time, which is the half that matters.
|
|
221
|
-
|
|
222
|
-
## Rate limiting
|
|
223
|
-
|
|
224
|
-
Notion documents "an average of three requests per second, with some bursts
|
|
225
|
-
beyond the average allowed" per connection, plus a separate per-workspace limit
|
|
226
|
-
scaled to the plan. The connection declares a rolling budget of 180 calls per
|
|
227
|
-
minute — the same average expressed over a window short bursts pass and a
|
|
228
|
-
sustained loop does not — **paired with `maxConcurrency: 3`**.
|
|
229
|
-
|
|
230
|
-
The concurrency cap is the load-bearing half. A budget alone is an average, and
|
|
231
|
-
an averaged budget cannot stop a program from firing forty calls in the same
|
|
232
|
-
tick; the cap keeps a burst shaped roughly like the one Notion documents.
|
|
233
|
-
Neither half is a guarantee, because admission meters *tool calls* rather than
|
|
234
|
-
requests — a single admitted `get_page_content` can spend twenty fetches, so
|
|
235
|
-
180 calls per minute is a floor on the real request rate, not a ceiling.
|
|
236
|
-
Declaring the cap is also what makes the queue settings legal — the admission
|
|
237
|
-
controller refuses queue settings without a queue at construction.
|
|
238
|
-
|
|
239
|
-
Like every connector budget this is per-runtime: N Worker isolates or Node
|
|
240
|
-
processes serving one deployment each keep their own counter. It approximates
|
|
241
|
-
the provider's limit; it does not enforce it.
|
|
242
|
-
|
|
243
|
-
One tool can turn a single agent call into several downstream requests:
|
|
244
|
-
`get_page_content` with `depth > 0` walks nested blocks, and call admission
|
|
245
|
-
meters tool calls, not the fetches inside them. That walk stops at an internal
|
|
246
|
-
ceiling of twenty requests and reports `truncated: true` rather than spending
|
|
247
|
-
the whole budget invisibly.
|
|
248
|
-
|
|
249
|
-
## Pagination
|
|
250
|
-
|
|
251
|
-
List-shaped tools take `page_size` (1–100) and `start_cursor`, and return
|
|
252
|
-
`has_more` with `next_cursor`. The default page size is 25 rather than Notion's
|
|
253
|
-
100, because a first read should be cheap; `defaultPageSize` raises it for a
|
|
254
|
-
deployment that pages a lot.
|
|
255
|
-
|
|
256
|
-
Cursors are opaque. Notion's own versioning page is explicit that they may
|
|
257
|
-
change in length, format, and structure at any time and must be passed back
|
|
258
|
-
verbatim — never parsed, validated, or constructed.
|
|
259
|
-
|
|
260
|
-
## What the API interface does not do
|
|
261
|
-
|
|
262
|
-
No file uploads, no database or data-source creation, no schema editing, no
|
|
263
|
-
block updates or deletes, no page moves. Those are all real Notion endpoints
|
|
264
|
-
and all deliberately absent: this is a deliberate tool surface, not a mirror of
|
|
265
|
-
the API. Some are present on Notion's hosted MCP interface. Anything still
|
|
266
|
-
missing is reachable through a custom `api()` connector beside this one, which
|
|
267
|
-
remains a first-class path.
|
|
268
|
-
|
|
269
|
-
The 2026-03-11 contract also offers more fields on create and update. They were
|
|
270
|
-
reviewed after the 0.17.0 drift check and remain deliberately absent:
|
|
271
|
-
|
|
272
|
-
- `create_page` does not create workspace-private pages, apply templates,
|
|
273
|
-
choose page placement, or accept expanded icon and cover forms. Those change
|
|
274
|
-
ownership, start asynchronous content work, control ordering, or depend on
|
|
275
|
-
file surfaces. They are not extensions of the maintained page/row authoring
|
|
276
|
-
contract (#408).
|
|
277
|
-
- `update_page_properties` does not lock pages, apply templates, or erase page
|
|
278
|
-
content. Locking is coordination state, templates finish asynchronously, and
|
|
279
|
-
`erase_content` permanently deletes every child block through the API. None
|
|
280
|
-
belongs under an approval named for property replacement (#409).
|
|
281
|
-
|
|
282
|
-
`trash_page` stays separate and reversible. The current `create_page`,
|
|
283
|
-
`update_page_properties`, and `trash_page` request subsets remain valid against
|
|
284
|
-
the expanded published contract.
|
|
285
|
-
|
|
286
|
-
There is also **no guarded raw-REST escape hatch** — no `notion_api_get`, no
|
|
287
|
-
`notion_api_mutate`. The convention that permits one
|
|
288
|
-
([H14](./provider-conventions.md#h14--a-named-tool-must-beat-the-escape-hatch-and-the-escape-hatch-splits-by-safety))
|
|
289
|
-
also permits a small provider to have none, provided it says so, and Notion's
|
|
290
|
-
public API is finite and slow-moving enough that a named surface can cover it.
|
|
291
|
-
The usage guide says it too, because an agent that assumes a hatch exists
|
|
292
|
-
spends a search proving it does not: absent from the tool list means absent
|
|
293
|
-
from this connection, not hidden behind a generic call.
|
|
294
|
-
|
|
295
|
-
## Contract checks
|
|
296
|
-
|
|
297
|
-
`npm run providers:check` compares the 14 fixed REST endpoints with Notion's
|
|
298
|
-
published OpenAPI document and the 34 MCP names, endpoint, and OAuth support
|
|
299
|
-
with Notion's official pages. It needs no Notion credential. The MCP schemas
|
|
300
|
-
are not vendored or reconstructed: the live `tools/list` response remains the
|
|
301
|
-
contract agents receive.
|
|
302
|
-
|
|
303
|
-
## Conventions
|
|
304
|
-
|
|
305
|
-
This connection is audited against
|
|
306
|
-
[the provider conventions](./provider-conventions.md). Its verdict per
|
|
307
|
-
convention, including every recorded exception, is the Notion section of
|
|
308
|
-
[the provider audit](https://github.com/zackbart/connecta/blob/main/records/provider-audit.md).
|