@matimo/notion 0.1.0-alpha.10

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tallclub
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,335 @@
1
+ # @matimo/notion — Notion Tools for Matimo
2
+
3
+ Notion workspace integration tools for Matimo's universal AI tools ecosystem. Query databases, create pages, manage content, search workspaces, add comments, and retrieve user information through YAML-defined tools that work with any AI framework.
4
+
5
+ ## 📦 Installation
6
+
7
+ ```bash
8
+ npm install @matimo/notion
9
+ # or
10
+ pnpm add @matimo/notion
11
+ ```
12
+
13
+ ## 🛠️ Available Tools (7 Total)
14
+
15
+ | Tool | Method | Description |
16
+ |------|--------|-------------|
17
+ | **notion_list_databases** | POST | List all databases (data sources) in workspace |
18
+ | **notion_query_database** | POST | Query pages from database with filters and sorting |
19
+ | **notion_create_page** | POST | Create new page in database or as child page |
20
+ | **notion_update_page** | PATCH | Update page properties, icon, status, archive |
21
+ | **notion_search** | POST | Search workspace pages and databases by title |
22
+ | **notion_create_comment** | POST | Add comments to pages, blocks, or discussions |
23
+ | **notion_get_user** | GET | Retrieve user information and profile details |
24
+
25
+ ## 🚀 Quick Start
26
+
27
+ ```typescript
28
+ import { MatimoInstance } from '@matimo/core';
29
+
30
+ const matimo = await MatimoInstance.init({ autoDiscover: true });
31
+
32
+ // Query a database
33
+ const results = await matimo.execute('notion_query_database', {
34
+ database_id: 'a1d8501e-1ac1-43e9-a6bd-ea9fe6c8822b'
35
+ });
36
+
37
+ console.log('Found pages:', results.results.length);
38
+ ```
39
+
40
+ ## 🔐 Authentication Setup
41
+
42
+ ### Get Your Notion API Token
43
+
44
+ 1. Go to [Notion Integrations](https://www.notion.so/my-integrations)
45
+ 2. Click "Create new integration"
46
+ 3. Set workspace and name your integration
47
+ 4. Grant required capabilities:
48
+ - ✅ Read content
49
+ - ✅ Update content
50
+ - ✅ Insert content
51
+ - ✅ Read user information
52
+ - ✅ Insert comments
53
+ 5. Copy your "Internal Integration Secret" (token)
54
+ 6. Set environment variable:
55
+ ```bash
56
+ export NOTION_API_KEY=secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
57
+ ```
58
+
59
+ ### Share Resources with Your Integration
60
+
61
+ For each database/page you want to access:
62
+ 1. Click the ••• menu (top-right)
63
+ 2. Scroll to "Add connections"
64
+ 3. Search for and select your integration
65
+
66
+ ## 🤖 How AI Agents Use These Tools
67
+
68
+ AI agents (Claude, ChatGPT, etc.) don't need to know internal API details. They discover tools automatically:
69
+
70
+ ```typescript
71
+ // AI agent discovers all available tools
72
+ const tools = matimo.listTools();
73
+
74
+ // Each tool has:
75
+ // - name: "notion_list_databases" ← Tool identifier
76
+ // - description: "List all databases..." ← What it does
77
+ // - parameters: { ← What inputs it accepts
78
+ // database_id: { type: "string", description: "Get from notion_list_databases..." }
79
+ // }
80
+ // - examples: [...] ← How to use it
81
+
82
+ // AI agent reads descriptions and automatically knows:
83
+ // 1. Use notion_list_databases() first to get database IDs
84
+ // 2. Pass those IDs to notion_query_database() for queries
85
+ // 3. Use returned page IDs with notion_create_page(), etc.
86
+ ```
87
+
88
+ **No memorization needed.** Tools are self-describing through their schemas, descriptions, and examples.
89
+
90
+ ## 📖 Integration Examples
91
+
92
+ ### Factory Pattern (Simple)
93
+
94
+ ```typescript
95
+ import { MatimoInstance } from '@matimo/core';
96
+
97
+ const matimo = await MatimoInstance.init({ autoDiscover: true });
98
+
99
+ // AI agent autonomously discovers and uses tools
100
+ // Step 1: List databases
101
+ const databases = await matimo.execute('notion_list_databases', { page_size: 10 });
102
+ const myDb = databases.data.results[0];
103
+
104
+ // Step 2: Query the database
105
+ const pages = await matimo.execute('notion_query_database', {
106
+ database_id: myDb.id,
107
+ page_size: 5
108
+ });
109
+
110
+ // Step 3: Create a new page
111
+ const newPage = await matimo.execute('notion_create_page', {
112
+ parent: { database_id: myDb.id },
113
+ markdown: '# New Page\n\nContent here'
114
+ });
115
+
116
+ console.log('Created:', newPage.data.url);
117
+ ```
118
+
119
+ ### Decorator Pattern (Class-Based)
120
+
121
+ ```typescript
122
+ import { MatimoInstance, setGlobalMatimoInstance, tool } from '@matimo/core';
123
+
124
+ const matimo = await MatimoInstance.init('./tools');
125
+ setGlobalMatimoInstance(matimo);
126
+
127
+ class NotionManager {
128
+ @tool('notion_query_database')
129
+ async queryDatabase(database_id: string, filter?: Record<string, any>) {
130
+ // Auto-executed by decorator
131
+ }
132
+
133
+ @tool('notion_create_page')
134
+ async createPage(
135
+ parent: Record<string, string>, // e.g., { database_id: '...' } or { page_id: '...' }
136
+ markdown?: string,
137
+ icon?: Record<string, string>
138
+ ) {
139
+ // Auto-executed by decorator
140
+ }
141
+
142
+ @tool('notion_search')
143
+ async search(query: string, sort_by?: string) {
144
+ // Auto-executed by decorator
145
+ }
146
+ }
147
+
148
+ const manager = new NotionManager();
149
+ const results = await manager.queryDatabase('db-id');
150
+ ```
151
+
152
+ ### LangChain Integration (AI Framework)
153
+
154
+ ```typescript
155
+ import { MatimoInstance } from '@matimo/core';
156
+
157
+ const matimo = await MatimoInstance.init('./tools');
158
+
159
+ // Get tool schemas for LangChain
160
+ const notionTools = matimo.listTools()
161
+ .filter(t => t.name.startsWith('notion_'))
162
+ .map(tool => ({
163
+ type: 'function',
164
+ function: {
165
+ name: tool.name,
166
+ description: tool.description,
167
+ parameters: {
168
+ type: 'object',
169
+ properties: tool.parameters || {},
170
+ required: Object.entries(tool.parameters || {})
171
+ .filter(([_, p]: [string, any]) => p.required)
172
+ .map(([name]) => name),
173
+ },
174
+ },
175
+ }));
176
+
177
+ // Use with your LLM/agent
178
+ console.log('Available tools for LangChain:', notionTools.map(t => t.function.name));
179
+ ```
180
+
181
+ ## 📚 API Reference
182
+
183
+ ### notion_query_database
184
+
185
+ Query pages in a Notion database with optional filtering and sorting.
186
+
187
+ **Parameters:**
188
+ - `database_id` (required): UUID of the database
189
+ - `filter` (optional): JSON filter object (e.g., `{"property": "Status", "status": {"equals": "Done"}}`)
190
+ - `sort` (optional): Array of sort objects
191
+ - `page_size` (optional): Results per page (1-100, default 100)
192
+ - `start_cursor` (optional): Pagination cursor
193
+
194
+ **Returns:** Paginated list of pages with `results`, `has_more`, `next_cursor`
195
+
196
+ ---
197
+
198
+ ### notion_create_page
199
+
200
+ Create a new page, optionally with properties and content.
201
+
202
+ **Parameters:**
203
+ - `parent` (required): Object describing where to create the page. Provide ONE of:
204
+ - `{ "database_id": "..." }` to create inside a database
205
+ - `{ "page_id": "..." }` to create as a child page
206
+ - `properties` (optional): JSON properties matching parent schema
207
+ - `markdown` (optional): String content using Markdown (easiest way to add content)
208
+ - `icon` (optional): Object for page icon, e.g. `{ type: 'emoji', emoji: '✅' }`
209
+ - `children` (optional): Array of Notion block objects to add to the page
210
+
211
+ > **Note:** This tool accepts a single `parent` object (for example `{ "database_id": "..." }` or `{ "page_id": "..."`) — do not pass separate `parent_id` / `parent_type` parameters. This matches the tool's YAML definition (`packages/notion/tools/notion_create_page/definition.yaml`).
212
+
213
+ **Returns:** Created page object with `id`, `url`, and `properties`
214
+
215
+ ---
216
+
217
+ ### notion_update_page
218
+
219
+ Update page properties, status, archive status, or icon.
220
+
221
+ **Parameters:**
222
+ - `page_id` (required): UUID of page to update
223
+ - `properties` (optional): JSON properties to update
224
+ - `icon` (optional): Object for page icon (e.g., { "type": "emoji", "emoji": "✅" } or { "type": "external", "external": { "url": "https://..." } })
225
+ - `archived` (optional): Archive/unarchive page (boolean)
226
+ - `in_trash` (optional): Move to/restore from trash
227
+ - `is_locked` (optional): Lock/unlock page editing
228
+
229
+ **Returns:** Updated page object
230
+
231
+ ---
232
+
233
+ ### notion_search
234
+
235
+ Search workspace pages and databases by title.
236
+
237
+ **Parameters:**
238
+ - `query` (optional): Search text (omit to return all items)
239
+ - `filter_object` (optional): 'page' or 'data_source'
240
+ - `sort_timestamp` (optional): 'last_edited_time' or 'created_time'
241
+ - `sort_direction` (optional): 'ascending' or 'descending'
242
+ - `page_size` (optional): Results per page (1-100)
243
+ - `start_cursor` (optional): Pagination cursor
244
+
245
+ **Returns:** Paginated list of pages/databases matching search
246
+
247
+ ---
248
+
249
+ ### notion_create_comment
250
+
251
+ Add a comment to a page, block, or discussion thread.
252
+
253
+ **Parameters:**
254
+ - `parent` (optional): Object describing the comment target. Provide ONE of:
255
+ - `{ "page_id": "..." }` — comment on a page
256
+ - `{ "block_id": "..." }` — comment on a block
257
+ - omit to reply to a `discussion_id` (use `discussion_id` parameter)
258
+ - `discussion_id` (optional): Discussion thread to reply to (alternative to `parent`)
259
+ - `rich_text` (required): Array of rich text objects representing the comment content
260
+ - `attachments` (optional): Array of file objects to attach to the comment
261
+ - Formatting/annotations should be provided within the `rich_text` objects (bold/italic/code via annotations)
262
+
263
+ **Returns:** Created comment object with `id` and `created_time`
264
+
265
+ ---
266
+
267
+ ### notion_get_user
268
+
269
+ Retrieve user information including name, avatar, and email.
270
+
271
+ **Parameters:**
272
+ - `user_id` (required): UUID of user to retrieve
273
+
274
+ **Returns:** User object with `id`, `name`, `avatar_url`, `email`
275
+
276
+ ## ⚙️ Environment Variables
277
+
278
+ ```bash
279
+ # Required
280
+ NOTION_API_KEY=secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
281
+
282
+ # Optional (for OAuth2 - Phase 2)
283
+ NOTION_OAUTH_CLIENT_ID=your_client_id
284
+ NOTION_OAUTH_CLIENT_SECRET=your_client_secret
285
+ NOTION_OAUTH_REDIRECT_URI=https://yourdomain.com/callback
286
+ ```
287
+
288
+ ## 🔧 Troubleshooting
289
+
290
+ ### "403 Forbidden" Error
291
+
292
+ **Cause:** Integration lacks required capability or resource not shared.
293
+
294
+ **Solutions:**
295
+ 1. Check integration has "Read/Update/Insert content" capabilities in Notion dashboard
296
+ 2. Share the database/page with your integration (••• → Add connections → select integration)
297
+ 3. Verify token is current (refresh in Notion integrations dashboard)
298
+
299
+ ### "404 Not Found" Error
300
+
301
+ **Cause:** Database/page ID is incorrect or integration doesn't have access.
302
+
303
+ **Solutions:**
304
+ 1. Verify database ID from Notion URL: `https://www.notion.so/{database_id}`
305
+ 2. Ensure resource is shared with integration
306
+ 3. Check API key is correct
307
+
308
+ ### Filters Not Working
309
+
310
+ **Cause:** Incorrect filter syntax for property type.
311
+
312
+ **Solutions:**
313
+ - Use Notion's property filtering UI as reference
314
+ - Filters depend on property type (status, checkbox, date, etc.)
315
+ - See [Notion Filter Reference](https://developers.notion.com/reference/post-database-query-filter)
316
+
317
+ ### Rate Limiting
318
+
319
+ **Info:** Notion API has rate limits (~3-4 requests/second per integration). Implement exponential backoff for production systems.
320
+
321
+ ## 📖 Learning Resources
322
+
323
+ - [Notion API Docs](https://developers.notion.com)
324
+ - [Notion Integrations Dashboard](https://www.notion.so/my-integrations)
325
+ - [Working with Databases](https://developers.notion.com/guides/data-apis/working-with-databases)
326
+ - [Creating Pages](https://developers.notion.com/guides/data-apis/working-with-page-content)
327
+ - [Notion Developers Slack](https://join.slack.com/t/notiondevs/shared_invite/zt-20b5996xv-DzJdLiympy6jP0GGzu3AMg)
328
+
329
+ ## 🤝 Contributing
330
+
331
+ Found a bug or want to suggest a feature? See [CONTRIBUTING.md](/CONTRIBUTING.md).
332
+
333
+ ---
334
+
335
+ **Part of the Matimo ecosystem** — Write YAML once, use everywhere.
@@ -0,0 +1,34 @@
1
+ name: notion-provider
2
+ type: provider
3
+ version: '1.0.0'
4
+ description: |
5
+ Notion Provider Configuration and authentication metadata.
6
+ This file is used by the OAuth2ProviderLoader to register Notion as an
7
+ OAuth2 provider and to document runtime environment variables.
8
+
9
+ provider:
10
+ name: notion
11
+ displayName: Notion
12
+ endpoints:
13
+ authorizationUrl: https://api.notion.com/v1/oauth/authorize
14
+ tokenUrl: https://api.notion.com/v1/oauth/token
15
+ defaultScopes:
16
+ - read
17
+ - write
18
+ documentation: https://developers.notion.com/docs
19
+
20
+ # Legacy/auxiliary OAuth metadata (kept for backward compatibility with earlier tooling)
21
+ oauth2:
22
+ client_id_env: NOTION_OAUTH_CLIENT_ID
23
+ client_secret_env: NOTION_OAUTH_CLIENT_SECRET
24
+ redirect_uri_env: NOTION_OAUTH_REDIRECT_URI
25
+
26
+ authentication:
27
+ type: bearer
28
+ location: header
29
+ name: Authorization
30
+
31
+ notes:
32
+ env: NOTION_API_KEY
33
+ setup_docs: "https://developers.notion.com/guides/get-started/getting-started"
34
+ caution: "Notion API requires Bearer token authentication. Ensure your integration has the necessary capabilities enabled in the Notion dashboard."
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@matimo/notion",
3
+ "version": "0.1.0-alpha.10",
4
+ "description": "Notion workspace tools for Matimo",
5
+ "type": "module",
6
+ "files": [
7
+ "tools",
8
+ "README.md",
9
+ "definition.yaml"
10
+ ],
11
+ "peerDependencies": {
12
+ "matimo": "0.1.0-alpha.10"
13
+ },
14
+ "devDependencies": {
15
+ "axios": "^1.13.4",
16
+ "@matimo/core": "0.1.0-alpha.10"
17
+ }
18
+ }
@@ -0,0 +1,97 @@
1
+ name: notion_create_comment
2
+ description: Add a comment to a Notion page, block, or discussion thread
3
+ version: "1.0.0"
4
+ parameters:
5
+ parent:
6
+ type: object
7
+ required: false
8
+ description: Parent target - provide one of page_id, block_id, or neither if using discussion_id
9
+ discussion_id:
10
+ type: string
11
+ required: false
12
+ description: The ID of existing discussion thread to respond to (alternative to parent)
13
+ rich_text:
14
+ type: array
15
+ required: true
16
+ description: Array of rich text objects representing comment content
17
+ attachments:
18
+ type: array
19
+ required: false
20
+ description: Array of file objects to attach to comment (max 3 allowed)
21
+ display_name:
22
+ type: object
23
+ required: false
24
+ description: Display name configuration for the comment author
25
+
26
+ execution:
27
+ type: http
28
+ method: POST
29
+ url: "https://api.notion.com/v1/comments"
30
+ headers:
31
+ Authorization: "Bearer {NOTION_API_KEY}"
32
+ "Notion-Version": "2025-09-03"
33
+ Content-Type: application/json
34
+ body:
35
+ parent: "{parent}"
36
+ discussion_id: "{discussion_id}"
37
+ rich_text: "{rich_text}"
38
+ attachments: "{attachments}"
39
+ display_name: "{display_name}"
40
+ timeout: 15000
41
+
42
+ authentication:
43
+ type: bearer
44
+ location: header
45
+ name: Authorization
46
+ notes:
47
+ env: NOTION_API_KEY
48
+ caution: "Ensure 'Insert comments' capability is enabled. Either page_id, block_id, or discussion_id required. Cannot start new inline discussions via API - must respond to existing threads or comment on pages/blocks."
49
+
50
+ output_schema:
51
+ type: object
52
+ properties:
53
+ object:
54
+ type: string
55
+ id:
56
+ type: string
57
+ created_time:
58
+ type: string
59
+ last_edited_time:
60
+ type: string
61
+ rich_text:
62
+ type: array
63
+ parent:
64
+ type: object
65
+ required: ["object", "id"]
66
+
67
+ examples:
68
+ - name: "Comment on page with simple text"
69
+ params:
70
+ parent:
71
+ page_id: be633bf1-dfa0-436d-b259-571129a590e5
72
+ rich_text:
73
+ - type: text
74
+ text:
75
+ content: "This looks great! Consider adding more examples."
76
+ expected_result: "Comment posted on the page"
77
+ - name: "Reply to discussion thread"
78
+ params:
79
+ discussion_id: "discussion-thread-id"
80
+ rich_text:
81
+ - type: text
82
+ text:
83
+ content: "I agree with this suggestion"
84
+ annotations:
85
+ bold: true
86
+ expected_result: "Reply added to existing discussion thread"
87
+ - name: "Comment on block with attachment"
88
+ params:
89
+ parent:
90
+ block_id: block-id-uuid
91
+ rich_text:
92
+ - type: text
93
+ text:
94
+ content: "Attached document:"
95
+ attachments:
96
+ - url: https://example.com/document.pdf
97
+ expected_result: "Comment posted on block with file attachment"
@@ -0,0 +1,127 @@
1
+ name: notion_create_page
2
+ description: Create a new page in a Notion database or as a child of an existing page
3
+ version: "1.0.0"
4
+ parameters:
5
+ parent:
6
+ type: object
7
+ required: true
8
+ description: |
9
+ Where to create the page. Provide ONE of:
10
+ - {"database_id": "..."} to create in a database
11
+ - {"page_id": "..."} to create as a sub-page
12
+ Get database_id from notion_list_databases.
13
+ properties:
14
+ type: object
15
+ required: false
16
+ description: |
17
+ Optional page properties (title, fields, etc).
18
+ Properties must match the parent database schema.
19
+ Simpler: just use 'markdown' parameter instead for content.
20
+ icon:
21
+ type: object
22
+ required: false
23
+ description: Page icon - object with type (emoji, external, file) and icon content
24
+ cover:
25
+ type: object
26
+ required: false
27
+ description: Page cover image - object with type (file, external) and image details
28
+ children:
29
+ type: array
30
+ required: false
31
+ description: Array of block objects to add to the page. Max 100 items
32
+ markdown:
33
+ type: string
34
+ required: false
35
+ description: |
36
+ Page content using Markdown syntax. Simple and works anywhere.
37
+ Example: "# My Title\n\nSome content here"
38
+ EASIEST WAY TO ADD CONTENT - use this instead of 'properties' for simplicity.
39
+ template:
40
+ type: object
41
+ required: false
42
+ description: Template to use for the page. Specify type and optional template_id
43
+ position:
44
+ type: object
45
+ required: false
46
+ description: Position of page in parent. Can specify before_id or after_id
47
+
48
+ execution:
49
+ type: function
50
+ code: './index.ts'
51
+
52
+ authentication:
53
+ type: bearer
54
+ location: header
55
+ name: Authorization
56
+ notes:
57
+ env: NOTION_API_KEY
58
+ caution: "Ensure 'Insert content' capability is enabled on the parent. Properties must match parent database schema. For database parents, properties are required. For page parents, only title is supported via properties. Either children or markdown can be used, not both. When using template, children parameter is not allowed."
59
+
60
+ output_schema:
61
+ type: object
62
+ properties:
63
+ object:
64
+ type: string
65
+ id:
66
+ type: string
67
+ created_time:
68
+ type: string
69
+ last_edited_time:
70
+ type: string
71
+ archived:
72
+ type: boolean
73
+ in_trash:
74
+ type: boolean
75
+ is_locked:
76
+ type: boolean
77
+ url:
78
+ type: string
79
+ public_url:
80
+ type: string
81
+ parent:
82
+ type: object
83
+ properties:
84
+ type: object
85
+ icon:
86
+ type: object
87
+ cover:
88
+ type: object
89
+ required: ["object", "id", "url"]
90
+
91
+ examples:
92
+ - name: "Create page in database"
93
+ params:
94
+ parent:
95
+ database_id: a1d8501e-1ac1-43e9-a6bd-ea9fe6c8822b
96
+ properties:
97
+ Name:
98
+ title:
99
+ - text:
100
+ content: "New Page"
101
+ expected_result: "Creates a new page in the database with the specified properties"
102
+ - name: "Create page under existing page"
103
+ params:
104
+ parent:
105
+ page_id: be633bf1-dfa0-436d-b259-571129a590e5
106
+ children:
107
+ - object: block
108
+ type: paragraph
109
+ paragraph:
110
+ rich_text:
111
+ - type: text
112
+ text:
113
+ content: "Page content"
114
+ expected_result: "Creates a subpage with content blocks"
115
+ - name: "Create page with emoji icon"
116
+ params:
117
+ parent:
118
+ database_id: a1d8501e-1ac1-43e9-a6bd-ea9fe6c8822b
119
+ properties:
120
+ Name:
121
+ title:
122
+ - text:
123
+ content: "Task"
124
+ icon:
125
+ type: emoji
126
+ emoji: "✅"
127
+ expected_result: "Creates a page with an emoji icon"
@@ -0,0 +1,259 @@
1
+ import axios from 'axios';
2
+ import { MatimoError, ErrorCode } from '@matimo/core';
3
+
4
+ interface Params {
5
+ parent?: Record<string, unknown>;
6
+ properties?: Record<string, unknown>;
7
+ icon?: Record<string, unknown>;
8
+ cover?: Record<string, unknown>;
9
+ children?: unknown[];
10
+ markdown?: string;
11
+ template?: Record<string, unknown>;
12
+ position?: Record<string, unknown>;
13
+ }
14
+
15
+ function markdownToChildren(md: string): unknown[] {
16
+ // Very small converter: split paragraphs and handle headings (#, ##, ###)
17
+ const parts = md.split(/\n\n+/).map((p) => p.trim()).filter(Boolean);
18
+ const blocks: unknown[] = [];
19
+
20
+ for (const part of parts) {
21
+ if (part.startsWith('# ')) {
22
+ blocks.push({
23
+ object: 'block',
24
+ type: 'heading_1',
25
+ heading_1: { rich_text: [{ type: 'text', text: { content: part.replace(/^#\s+/, '') } }] },
26
+ });
27
+ } else if (part.startsWith('## ')) {
28
+ blocks.push({
29
+ object: 'block',
30
+ type: 'heading_2',
31
+ heading_2: { rich_text: [{ type: 'text', text: { content: part.replace(/^##\s+/, '') } }] },
32
+ });
33
+ } else if (part.startsWith('### ')) {
34
+ blocks.push({
35
+ object: 'block',
36
+ type: 'heading_3',
37
+ heading_3: { rich_text: [{ type: 'text', text: { content: part.replace(/^###\s+/, '') } }] },
38
+ });
39
+ } else {
40
+ blocks.push({
41
+ object: 'block',
42
+ type: 'paragraph',
43
+ paragraph: { rich_text: [{ type: 'text', text: { content: part } }] },
44
+ });
45
+ }
46
+ }
47
+
48
+ return blocks;
49
+ }
50
+
51
+ export default async function createPage(params: Params) {
52
+ const {
53
+ parent: userProvidedParent,
54
+ properties,
55
+ icon,
56
+ cover,
57
+ children,
58
+ markdown,
59
+ template,
60
+ position,
61
+ } = params as Params;
62
+
63
+ const apiKey = process.env.NOTION_API_KEY;
64
+ if (!apiKey) {
65
+ throw new MatimoError('NOTION_API_KEY not set', ErrorCode.AUTH_FAILED, {
66
+ envVar: 'NOTION_API_KEY',
67
+ });
68
+ }
69
+
70
+ // If no parent provided, auto-discover a database
71
+ let parent = userProvidedParent;
72
+ if (!parent || typeof parent !== 'object') {
73
+ try {
74
+ const response = await axios.get('https://api.notion.com/v1/databases', {
75
+ headers: {
76
+ Authorization: `Bearer ${apiKey}`,
77
+ 'Notion-Version': '2025-09-03',
78
+ },
79
+ timeout: 15000,
80
+ params: { page_size: 1 },
81
+ });
82
+ const databases = response.data?.results || [];
83
+ if (databases.length > 0) {
84
+ parent = { database_id: databases[0].id };
85
+ } else {
86
+ throw new MatimoError(
87
+ 'No databases found in workspace. Create a database first or provide `parent` parameter.',
88
+ ErrorCode.VALIDATION_FAILED
89
+ );
90
+ }
91
+ } catch (err) {
92
+ if (axios.isAxiosError(err)) {
93
+ throw new MatimoError(
94
+ `Failed to auto-discover database: ${(err.response?.data as Record<string, unknown>)?.message || err.message}`,
95
+ ErrorCode.EXECUTION_FAILED
96
+ );
97
+ }
98
+ throw new MatimoError('Failed to auto-discover database', ErrorCode.EXECUTION_FAILED);
99
+ }
100
+ }
101
+
102
+ // Validate parameters according to tool contract:
103
+ // - If creating inside a database (`parent.database_id`), `properties` is required
104
+ // - Either `children` or `markdown` can be provided, not both (empty children array is treated as not provided)
105
+ // - When using `template`, `children` is not allowed
106
+ const isDatabaseParent = parent && typeof parent === 'object' && Object.prototype.hasOwnProperty.call(parent, 'database_id');
107
+
108
+ if (Array.isArray(children) && children.length > 0 && typeof markdown === 'string' && markdown.trim().length > 0) {
109
+ throw new MatimoError('Provide either `children` or `markdown`, not both', ErrorCode.VALIDATION_FAILED, {
110
+ parameters: { children: children.length, markdown: markdown.length },
111
+ });
112
+ }
113
+
114
+ if (template && Array.isArray(children) && children.length > 0) {
115
+ throw new MatimoError('`template` cannot be used together with `children`. Omit `children` when using `template`', ErrorCode.VALIDATION_FAILED, {
116
+ parameters: { template: !!template, children: children.length },
117
+ });
118
+ }
119
+
120
+ // Allow generating a minimal title property from `markdown` when creating in a database.
121
+ // If caller didn't provide `properties`, we'll attempt several common title property names
122
+ // (e.g., 'Name', 'Title') derived from the first markdown line. If none succeed,
123
+ // we return the API error to the caller.
124
+ const resolvedProperties = properties as Record<string, unknown> | undefined;
125
+ let titleCandidates: string[] | undefined;
126
+
127
+ if (isDatabaseParent) {
128
+ const hasProperties = resolvedProperties && typeof resolvedProperties === 'object' && Object.keys(resolvedProperties).length > 0;
129
+
130
+ if (!hasProperties) {
131
+ if (typeof markdown === 'string' && markdown.trim().length > 0) {
132
+ // Candidate property names to try when the database title property name is unknown
133
+ titleCandidates = ['Name', 'Title', 'title', 'name'].map((k) => k);
134
+
135
+ // We'll construct properties later per candidate when sending the request.
136
+ // Use resolvedProperties only if caller provided it explicitly.
137
+ } else {
138
+ throw new MatimoError('Creating a page in a database requires `properties` (at minimum a title property)', ErrorCode.VALIDATION_FAILED, {
139
+ parent,
140
+ });
141
+ }
142
+ }
143
+ }
144
+
145
+ // Convert markdown to children if provided and children not explicitly set
146
+ let resolvedChildren = children;
147
+ if ((!resolvedChildren || (Array.isArray(resolvedChildren) && resolvedChildren.length === 0)) && markdown) {
148
+ resolvedChildren = markdownToChildren(markdown);
149
+ }
150
+
151
+ const baseBody: Record<string, unknown> = {
152
+ parent,
153
+ };
154
+
155
+ if (resolvedProperties) baseBody.properties = resolvedProperties;
156
+ if (icon) baseBody.icon = icon;
157
+ if (cover) baseBody.cover = cover;
158
+ if (resolvedChildren) baseBody.children = resolvedChildren;
159
+ if (template) baseBody.template = template;
160
+ if (position) baseBody.position = position;
161
+
162
+ // Helper to send request with a given body
163
+ const sendRequest = async (requestBody: Record<string, unknown>) => {
164
+ return axios.post('https://api.notion.com/v1/pages', requestBody, {
165
+ headers: {
166
+ Authorization: `Bearer ${apiKey}`,
167
+ 'Notion-Version': '2025-09-03',
168
+ 'Content-Type': 'application/json',
169
+ },
170
+ timeout: 15000,
171
+ });
172
+ };
173
+
174
+ // If we have title candidates, try each one until a request succeeds.
175
+ if (titleCandidates && titleCandidates.length > 0) {
176
+ let lastError: unknown = null;
177
+ const firstLine = markdown!.split(/\r?\n/)[0].replace(/^#+\s*/, '').trim() || 'New Page';
178
+
179
+ for (const candidate of titleCandidates) {
180
+ const candidateProps: Record<string, unknown> = {
181
+ [candidate]: {
182
+ title: [
183
+ {
184
+ text: { content: firstLine },
185
+ },
186
+ ],
187
+ },
188
+ };
189
+
190
+ const tryBody = { ...baseBody, ...{ properties: candidateProps } } as Record<string, unknown>;
191
+
192
+ try {
193
+ const resp = await sendRequest(tryBody);
194
+ return {
195
+ success: true,
196
+ statusCode: resp.status,
197
+ data: resp.data,
198
+ };
199
+ } catch (err: unknown) {
200
+ lastError = err;
201
+ // If API returns validation error about missing property name, continue to next candidate.
202
+ if (axios.isAxiosError(err)) {
203
+ type NotionError = { message?: string; code?: string; request_id?: string };
204
+ const errData = err.response?.data as NotionError | undefined;
205
+ const message = errData?.message || '';
206
+ if (typeof message === 'string' && /is not a property that exists/i.test(message)) {
207
+ // try next candidate
208
+ continue;
209
+ }
210
+ }
211
+ // For other errors, break and return immediately
212
+ break;
213
+ }
214
+ }
215
+
216
+ // All candidates failed — return last error in a structured form
217
+ if (axios.isAxiosError(lastError)) {
218
+ type NotionError = { message?: string; code?: string; request_id?: string };
219
+ const errData = lastError.response?.data as NotionError | undefined;
220
+ const statusCode = lastError.response?.status || 0;
221
+ return {
222
+ success: false,
223
+ statusCode,
224
+ error: errData || (lastError as Error).message,
225
+ };
226
+ }
227
+
228
+ return {
229
+ success: false,
230
+ statusCode: 0,
231
+ error: String(lastError),
232
+ };
233
+ }
234
+
235
+ // No candidate loop — send single request using baseBody (which may include provided properties)
236
+ try {
237
+ const resp = await sendRequest(baseBody);
238
+ return {
239
+ success: true,
240
+ statusCode: resp.status,
241
+ data: resp.data,
242
+ };
243
+ } catch (err: unknown) {
244
+ if (axios.isAxiosError(err)) {
245
+ // Wrap HTTP error from Notion in a MatimoError so callers can programmatically inspect it
246
+ type NotionError = { message?: string; code?: string; request_id?: string };
247
+ const errData = err.response?.data as NotionError | undefined;
248
+ const details: Record<string, unknown> = {
249
+ status: err.response?.status,
250
+ code: errData?.code,
251
+ request_id: errData?.request_id,
252
+ };
253
+ throw new MatimoError(errData?.message || 'Notion API error', ErrorCode.EXECUTION_FAILED, details);
254
+ }
255
+
256
+ // Non-HTTP error (network, unexpected) — wrap and throw
257
+ throw new MatimoError(String(err), ErrorCode.UNKNOWN_ERROR);
258
+ }
259
+ }
@@ -0,0 +1,55 @@
1
+ name: notion_get_user
2
+ description: Retrieve information about a Notion user
3
+ version: "1.0.0"
4
+ parameters:
5
+ user_id:
6
+ type: string
7
+ required: true
8
+ description: The UUID of the user to retrieve
9
+
10
+ execution:
11
+ type: http
12
+ method: GET
13
+ url: "https://api.notion.com/v1/users/{user_id}"
14
+ headers:
15
+ Authorization: "Bearer {NOTION_API_KEY}"
16
+ "Notion-Version": "2025-09-03"
17
+ timeout: 10000
18
+
19
+ authentication:
20
+ type: bearer
21
+ location: header
22
+ name: Authorization
23
+ notes:
24
+ env: NOTION_API_KEY
25
+ caution: "Ensure 'Read user information' capability is enabled. Returns user details including name, avatar, email (if available). User IDs can be obtained from page.created_by, page.last_edited_by, or database results."
26
+
27
+ output_schema:
28
+ type: object
29
+ properties:
30
+ object:
31
+ type: string
32
+ id:
33
+ type: string
34
+ type:
35
+ type: string
36
+ name:
37
+ type: string
38
+ avatar_url:
39
+ type: string
40
+ person:
41
+ type: object
42
+ properties:
43
+ email:
44
+ type: string
45
+ required: ["object", "id", "type"]
46
+
47
+ examples:
48
+ - name: "Get user information"
49
+ params:
50
+ user_id: "e79a0b74-3aba-4149-9f74-0bb5791a6ee6"
51
+ expected_result: "Returns user details with name, avatar, email"
52
+ - name: "Get bot user info"
53
+ params:
54
+ user_id: "c2f20311-9e54-4d11-8c79-7398424ae41e"
55
+ expected_result: "Returns bot/integration user information"
@@ -0,0 +1,70 @@
1
+ name: notion_list_databases
2
+ description: |
3
+ List all databases (data sources) available in your Notion workspace.
4
+ Use this to discover what databases exist before querying them.
5
+ Each database has an id, title, icon, and URL that you can use for further operations.
6
+ version: "1.0.0"
7
+
8
+ parameters:
9
+ page_size:
10
+ type: number
11
+ required: true
12
+ description: How many databases to return (1-100). Set this explicitly to avoid sending a literal placeholder when omitted.
13
+
14
+ execution:
15
+ type: http
16
+ method: POST
17
+ url: "https://api.notion.com/v1/search"
18
+ headers:
19
+ Authorization: "Bearer {NOTION_API_KEY}"
20
+ "Notion-Version": "2025-09-03"
21
+ Content-Type: application/json
22
+ body:
23
+ filter:
24
+ property: object
25
+ value: data_source
26
+ page_size: "{page_size}"
27
+ timeout: 15000
28
+
29
+ authentication:
30
+ type: bearer
31
+ location: header
32
+ name: Authorization
33
+ notes:
34
+ env: NOTION_API_KEY
35
+ caution: "Lists only databases shared with your integration. Respects integration's read capabilities."
36
+
37
+ output_schema:
38
+ type: object
39
+ properties:
40
+ object:
41
+ type: string
42
+ results:
43
+ type: array
44
+ items:
45
+ type: object
46
+ properties:
47
+ id:
48
+ type: string
49
+ title:
50
+ type: array
51
+ icon:
52
+ type: object
53
+ url:
54
+ type: string
55
+ has_more:
56
+ type: boolean
57
+ next_cursor:
58
+ type:
59
+ - string
60
+ - "null"
61
+
62
+ examples:
63
+ - name: "List all databases in workspace"
64
+ params:
65
+ page_size: 10
66
+ expected_result: "Returns array of database objects with id, title, icon, and url"
67
+ - name: "List first database only"
68
+ params:
69
+ page_size: 1
70
+ expected_result: "Returns single database object"
@@ -0,0 +1,100 @@
1
+ name: notion_query_database
2
+ description: Query pages from a Notion database to retrieve content. Use notion_list_databases first to get the database ID.
3
+ version: "1.0.0"
4
+ parameters:
5
+ database_id:
6
+ type: string
7
+ required: true
8
+ description: The ID of the database to query. Get from notion_list_databases (item.id)
9
+ sorts:
10
+ type: array
11
+ required: false
12
+ description: Array of sort objects to order results by properties or timestamps
13
+ filter:
14
+ type: object
15
+ required: false
16
+ description: Filter criteria as JSON object to narrow results
17
+ page_size:
18
+ type: number
19
+ required: false
20
+ description: Number of results per request (default 100, max 100)
21
+ start_cursor:
22
+ type: string
23
+ required: false
24
+ description: Cursor for pagination to retrieve next set of results
25
+ archived:
26
+ type: boolean
27
+ required: false
28
+ description: If true, include archived pages in results
29
+ in_trash:
30
+ type: boolean
31
+ required: false
32
+ description: If true, include pages in trash in results
33
+ result_type:
34
+ type: string
35
+ required: false
36
+ description: Filter results by type - page or data_source (for wikis only)
37
+
38
+ execution:
39
+ type: http
40
+ method: POST
41
+ url: "https://api.notion.com/v1/data_sources/{database_id}/query"
42
+ headers:
43
+ Authorization: "Bearer {NOTION_API_KEY}"
44
+ "Notion-Version": "2025-09-03"
45
+ Content-Type: application/json
46
+ body:
47
+ sorts: "{sorts}"
48
+ filter: "{filter}"
49
+ page_size: "{page_size}"
50
+ start_cursor: "{start_cursor}"
51
+ archived: "{archived}"
52
+ in_trash: "{in_trash}"
53
+ result_type: "{result_type}"
54
+ timeout: 15000
55
+
56
+ authentication:
57
+ type: bearer
58
+ location: header
59
+ name: Authorization
60
+ notes:
61
+ env: NOTION_API_KEY
62
+ api_version: "2025-09-03"
63
+ caution: "Uses current Notion API (2025-09-03). Ensure 'Read content' capability is enabled on the integration. Filter and sort are optional - omit if not needed. API returns max 100 items per call."
64
+
65
+ output_schema:
66
+ type: object
67
+ properties:
68
+ object:
69
+ type: string
70
+ type:
71
+ type: string
72
+ results:
73
+ type: array
74
+ has_more:
75
+ type: boolean
76
+ next_cursor:
77
+ type: string
78
+ required: ["object", "results", "has_more"]
79
+
80
+ examples:
81
+ - name: "Query data source - all pages"
82
+ params:
83
+ database_id: "d9824bdc-8445-4327-be8b-5b47500af6ce"
84
+ expected_result: "Returns paginated list of all pages in the database"
85
+ - name: "Query data source - with filter"
86
+ params:
87
+ database_id: "d9824bdc-8445-4327-be8b-5b47500af6ce"
88
+ filter:
89
+ property: Status
90
+ status:
91
+ equals: Done
92
+ expected_result: "Returns pages where the Status property equals 'Done'"
93
+ - name: "Query data source - with sorting"
94
+ params:
95
+ database_id: "d9824bdc-8445-4327-be8b-5b47500af6ce"
96
+ sorts:
97
+ - property: Created
98
+ direction: descending
99
+ page_size: 25
100
+ expected_result: "Returns up to 25 pages sorted by creation date (descending)"
@@ -0,0 +1,87 @@
1
+ name: notion_search
2
+ description: Search across all pages and data sources in Notion workspace by title
3
+ version: "1.0.0"
4
+ parameters:
5
+ query:
6
+ type: string
7
+ required: false
8
+ description: Search text to find in page/data_source titles - omit to return all shared items
9
+ filter_object:
10
+ type: object
11
+ required: false
12
+ description: Filter results by object type - page or data_source
13
+ sort_direction:
14
+ type: string
15
+ required: false
16
+ description: Sort direction - ascending or descending
17
+ sort_timestamp:
18
+ type: string
19
+ required: false
20
+ description: Sort by timestamp - last_edited_time (default) or created_time
21
+ page_size:
22
+ type: number
23
+ required: false
24
+ description: Number of results per request
25
+ start_cursor:
26
+ type: string
27
+ required: false
28
+ description: Cursor for pagination to retrieve next set of results
29
+
30
+ execution:
31
+ type: http
32
+ method: POST
33
+ url: "https://api.notion.com/v1/search"
34
+ headers:
35
+ Authorization: "Bearer {NOTION_API_KEY}"
36
+ "Notion-Version": "2025-09-03"
37
+ Content-Type: application/json
38
+ body:
39
+ query: "{query}"
40
+ filter: "{filter_object}"
41
+ sort:
42
+ direction: "{sort_direction}"
43
+ timestamp: "{sort_timestamp}"
44
+ page_size: "{page_size}"
45
+ start_cursor: "{start_cursor}"
46
+ timeout: 15000
47
+
48
+ authentication:
49
+ type: bearer
50
+ location: header
51
+ name: Authorization
52
+ notes:
53
+ env: NOTION_API_KEY
54
+ caution: "Omit 'query' parameter to return all pages/data sources shared with integration. Filter and sort are optional. Results respect integration's read capabilities. API version 2025-09-03."
55
+
56
+ output_schema:
57
+ type: object
58
+ properties:
59
+ object:
60
+ type: string
61
+ type:
62
+ type: string
63
+ page_or_data_source:
64
+ type: object
65
+ results:
66
+ type: array
67
+ has_more:
68
+ type: boolean
69
+ next_cursor:
70
+ type: string
71
+ required: ["object", "type", "results", "has_more"]
72
+
73
+ examples:
74
+ - name: "Search for pages by title"
75
+ params:
76
+ query: "meeting notes"
77
+ expected_result: "Returns all pages with 'meeting notes' in title"
78
+ - name: "Search data sources only"
79
+ params:
80
+ filter_object: "data_source"
81
+ sort_timestamp: "last_edited_time"
82
+ sort_direction: "descending"
83
+ expected_result: "Returns recently edited data sources only"
84
+ - name: "Get all shared items"
85
+ params:
86
+ page_size: 50
87
+ expected_result: "Returns first 50 pages and data sources shared with integration"
@@ -0,0 +1,117 @@
1
+ name: notion_update_page
2
+ description: Update properties, icon, cover, or other attributes of an existing Notion page
3
+ version: "1.0.0"
4
+ parameters:
5
+ page_id:
6
+ type: string
7
+ required: true
8
+ description: The ID of the page to update (UUID format, dashes optional)
9
+ properties:
10
+ type: object
11
+ required: false
12
+ description: Page properties to update as JSON object - must match parent data source schema
13
+ icon:
14
+ type: object
15
+ required: false
16
+ description: Page icon object with type and content (emoji, external URL, or file)
17
+ cover:
18
+ type: object
19
+ required: false
20
+ description: Page cover image object with type and file/external URL details
21
+ is_locked:
22
+ type: boolean
23
+ required: false
24
+ description: Whether to lock page from editing in Notion UI
25
+ template:
26
+ type: object
27
+ required: false
28
+ description: Template object to apply - type default or template_id with specific template ID
29
+ erase_content:
30
+ type: boolean
31
+ required: false
32
+ description: Whether to erase all existing page content (use with template to replace content)
33
+ archived:
34
+ type: boolean
35
+ required: false
36
+ description: Whether to archive or restore the page
37
+ in_trash:
38
+ type: boolean
39
+ required: false
40
+ description: Whether to move page to trash or restore it
41
+
42
+ execution:
43
+ type: http
44
+ method: PATCH
45
+ url: "https://api.notion.com/v1/pages/{page_id}"
46
+ headers:
47
+ Authorization: "Bearer {NOTION_API_KEY}"
48
+ "Notion-Version": "2025-09-03"
49
+ Content-Type: application/json
50
+ body:
51
+ properties: "{properties}"
52
+ icon: "{icon}"
53
+ cover: "{cover}"
54
+ is_locked: "{is_locked}"
55
+ template: "{template}"
56
+ erase_content: "{erase_content}"
57
+ archived: "{archived}"
58
+ in_trash: "{in_trash}"
59
+ timeout: 15000
60
+
61
+ authentication:
62
+ type: bearer
63
+ location: header
64
+ name: Authorization
65
+ notes:
66
+ env: NOTION_API_KEY
67
+ caution: "Ensure 'Update content' capability is enabled. Only include parameters you want to change. Cannot update parent, created_by, created_time, last_edited_by, last_edited_time, or rollup properties via API."
68
+
69
+ output_schema:
70
+ type: object
71
+ properties:
72
+ object:
73
+ type: string
74
+ id:
75
+ type: string
76
+ created_time:
77
+ type: string
78
+ last_edited_time:
79
+ type: string
80
+ archived:
81
+ type: boolean
82
+ in_trash:
83
+ type: boolean
84
+ is_locked:
85
+ type: boolean
86
+ properties:
87
+ type: object
88
+ required: ["object", "id", "last_edited_time"]
89
+
90
+ examples:
91
+ - name: "Update page title"
92
+ params:
93
+ page_id: "be633bf1-dfa0-436d-b259-571129a590e5"
94
+ properties:
95
+ Name:
96
+ title:
97
+ - text:
98
+ content: Updated Title
99
+ expected_result: "Page title updated to 'Updated Title'"
100
+ - name: "Update page icon"
101
+ params:
102
+ page_id: "be633bf1-dfa0-436d-b259-571129a590e5"
103
+ icon:
104
+ type: emoji
105
+ emoji: "🎉"
106
+ expected_result: "Page icon changed to celebration emoji"
107
+ - name: "Update page with cover and apply template"
108
+ params:
109
+ page_id: "be633bf1-dfa0-436d-b259-571129a590e5"
110
+ cover:
111
+ type: external
112
+ external:
113
+ url: https://example.com/image.png
114
+ template:
115
+ type: default
116
+ erase_content: true
117
+ expected_result: "Page cover set, default template applied, existing content replaced"