@atikk-co-jp/notion-mcp-server 0.2.2 → 0.3.0

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.
Files changed (35) hide show
  1. package/README.ja.md +64 -0
  2. package/README.md +64 -0
  3. package/dist/src/converters/__tests__/markdown-to-blocks.test.d.ts +2 -0
  4. package/dist/src/converters/__tests__/markdown-to-blocks.test.d.ts.map +1 -0
  5. package/dist/src/converters/__tests__/markdown-to-blocks.test.js +245 -0
  6. package/dist/src/converters/index.d.ts +1 -0
  7. package/dist/src/converters/index.d.ts.map +1 -1
  8. package/dist/src/converters/index.js +2 -0
  9. package/dist/src/converters/markdown-to-blocks.d.ts +21 -0
  10. package/dist/src/converters/markdown-to-blocks.d.ts.map +1 -0
  11. package/dist/src/converters/markdown-to-blocks.js +244 -0
  12. package/dist/src/tools/append-block-children.d.ts.map +1 -1
  13. package/dist/src/tools/append-block-children.js +4 -10
  14. package/dist/src/tools/append-blocks-simple.d.ts +4 -0
  15. package/dist/src/tools/append-blocks-simple.d.ts.map +1 -0
  16. package/dist/src/tools/append-blocks-simple.js +36 -0
  17. package/dist/src/tools/create-comment.d.ts.map +1 -1
  18. package/dist/src/tools/create-comment.js +4 -10
  19. package/dist/src/tools/create-database.d.ts.map +1 -1
  20. package/dist/src/tools/create-database.js +8 -21
  21. package/dist/src/tools/create-page-simple.d.ts +4 -0
  22. package/dist/src/tools/create-page-simple.d.ts.map +1 -0
  23. package/dist/src/tools/create-page-simple.js +54 -0
  24. package/dist/src/tools/create-page.d.ts.map +1 -1
  25. package/dist/src/tools/create-page.js +6 -15
  26. package/dist/src/tools/index.d.ts +3 -1
  27. package/dist/src/tools/index.d.ts.map +1 -1
  28. package/dist/src/tools/index.js +5 -1
  29. package/dist/src/tools/query-database.d.ts.map +1 -1
  30. package/dist/src/tools/query-database.js +8 -24
  31. package/dist/src/tools/update-database.d.ts.map +1 -1
  32. package/dist/src/tools/update-database.js +10 -31
  33. package/dist/src/tools/update-page.d.ts.map +1 -1
  34. package/dist/src/tools/update-page.js +6 -18
  35. package/package.json +1 -1
package/README.ja.md CHANGED
@@ -14,6 +14,7 @@ Notion API用のMCP(Model Context Protocol)サーバー。AIアシスタン
14
14
  - **検索**: ページとデータベースの横断検索
15
15
  - **コメント**: ページへのコメント追加
16
16
  - **トークン効率化**: マークダウン/シンプル形式でトークン使用量を約96%削減
17
+ - **Markdown入力対応**: Markdownでコンテンツを作成・追加(出力トークン80%削減)
17
18
 
18
19
  ## インストール
19
20
 
@@ -110,6 +111,45 @@ Claude Desktopの設定ファイル(macOSの場合: `~/.config/claude/claude_d
110
111
  }
111
112
  ```
112
113
 
114
+ ### create-page-simple ⭐
115
+
116
+ Markdownを使ってページを作成します。`create-page`と比較して**出力トークン約80%削減**。
117
+
118
+ **パラメータ:**
119
+ - `database_id` (必須): ページを作成するデータベースのID
120
+ - `title` (必須): ページタイトル(文字列)
121
+ - `content` (任意): ページ本文(Markdown形式)
122
+ - `properties` (任意): 追加のNotionプロパティ
123
+ - `icon` (任意): 絵文字アイコン(例: "🐛")
124
+
125
+ **サポートするMarkdown記法:**
126
+ - 見出し: `# ## ###` (####以上はheading_3にフォールバック)
127
+ - リスト: `- ` または `* ` (箇条書き)、`1. ` (番号付き)
128
+ - チェックボックス: `- [ ]` / `- [x]`
129
+ - コードブロック: ` ``` ` (言語指定対応)
130
+ - 引用: `> `
131
+ - 区切り線: `---`
132
+ - 画像: `![alt](url)`
133
+ - インライン: `**太字**`、`*イタリック*`、`~~取り消し線~~`、`` `コード` ``、`[リンク](url)`
134
+
135
+ ```json
136
+ {
137
+ "database_id": "データベースのUUID",
138
+ "title": "バグレポート",
139
+ "content": "## 再現手順\n\n1. ログイン\n2. 設定を開く\n\n## 期待動作\n\n正常に表示される",
140
+ "properties": {
141
+ "Status": { "status": { "name": "Open" } }
142
+ },
143
+ "icon": "🐛"
144
+ }
145
+ ```
146
+
147
+ **トークン比較:**
148
+ | 方式 | トークン数 | 削減率 |
149
+ |------|-----------|--------|
150
+ | create-page (ブロック構造) | ~152 | - |
151
+ | create-page-simple (Markdown) | ~26 | **83%** |
152
+
113
153
  ### update-page
114
154
 
115
155
  ページのプロパティを更新します。
@@ -250,6 +290,30 @@ Claude Desktopの設定ファイル(macOSの場合: `~/.config/claude/claude_d
250
290
  }
251
291
  ```
252
292
 
293
+ ### append-blocks-simple ⭐
294
+
295
+ Markdownを使ってブロックを追加します。`append-block-children`と比較して**出力トークン約80%削減**。
296
+
297
+ **パラメータ:**
298
+ - `block_id` (必須): 追加先のページまたはブロックのID
299
+ - `content` (必須): 追加するコンテンツ(Markdown形式)
300
+ - `after` (任意): このブロックIDの後に挿入
301
+
302
+ `create-page-simple`と同じMarkdown記法をサポートしています。
303
+
304
+ ```json
305
+ {
306
+ "block_id": "ページまたはブロックのUUID",
307
+ "content": "# 新しいセクション\n\nこれは**重要な**コンテンツで[リンク](https://example.com)もあります。\n\n- 項目1\n- 項目2\n\n```javascript\nconst x = 1;\n```"
308
+ }
309
+ ```
310
+
311
+ **トークン比較:**
312
+ | 方式 | トークン数 | 削減率 |
313
+ |------|-----------|--------|
314
+ | append-block-children (ブロック構造) | ~201 | - |
315
+ | append-blocks-simple (Markdown) | ~42 | **79%** |
316
+
253
317
  ### create-comment
254
318
 
255
319
  ページにコメントを追加します。
package/README.md CHANGED
@@ -14,6 +14,7 @@ MCP (Model Context Protocol) server for Notion API. Enables AI assistants to int
14
14
  - **Search**: Search across pages and databases
15
15
  - **Comments**: Add comments to pages
16
16
  - **Token-Efficient Output**: Markdown/simple format reduces token usage by ~96%
17
+ - **Markdown Input**: Create and append content using Markdown (80% fewer output tokens)
17
18
 
18
19
  ## Installation
19
20
 
@@ -110,6 +111,45 @@ Create a new page in a database.
110
111
  }
111
112
  ```
112
113
 
114
+ ### create-page-simple ⭐
115
+
116
+ Create a new page using Markdown. **~80% fewer output tokens** compared to `create-page`.
117
+
118
+ **Parameters:**
119
+ - `database_id` (required): The database ID to create the page in
120
+ - `title` (required): Page title as a simple string
121
+ - `content` (optional): Page content in Markdown
122
+ - `properties` (optional): Additional Notion properties
123
+ - `icon` (optional): Emoji icon (e.g., "🐛")
124
+
125
+ **Supported Markdown:**
126
+ - Headings: `# ## ###` (#### and beyond → heading_3)
127
+ - Lists: `- ` or `* ` (bulleted), `1. ` (numbered)
128
+ - Checkboxes: `- [ ]` / `- [x]`
129
+ - Code blocks: ` ``` ` with language
130
+ - Quotes: `> `
131
+ - Dividers: `---`
132
+ - Images: `![alt](url)`
133
+ - Inline: `**bold**`, `*italic*`, `~~strike~~`, `` `code` ``, `[link](url)`
134
+
135
+ ```json
136
+ {
137
+ "database_id": "database-uuid-here",
138
+ "title": "Bug Report",
139
+ "content": "## Steps to Reproduce\n\n1. Login\n2. Open settings\n\n## Expected Behavior\n\nShould display correctly",
140
+ "properties": {
141
+ "Status": { "status": { "name": "Open" } }
142
+ },
143
+ "icon": "🐛"
144
+ }
145
+ ```
146
+
147
+ **Token Comparison:**
148
+ | Method | Tokens | Reduction |
149
+ |--------|--------|-----------|
150
+ | create-page (blocks) | ~152 | - |
151
+ | create-page-simple (markdown) | ~26 | **83%** |
152
+
113
153
  ### update-page
114
154
 
115
155
  Update a page's properties.
@@ -250,6 +290,30 @@ Append new blocks to a page or block.
250
290
  }
251
291
  ```
252
292
 
293
+ ### append-blocks-simple ⭐
294
+
295
+ Append blocks using Markdown. **~80% fewer output tokens** compared to `append-block-children`.
296
+
297
+ **Parameters:**
298
+ - `block_id` (required): The page or block ID to append to
299
+ - `content` (required): Content in Markdown
300
+ - `after` (optional): Insert after this block ID
301
+
302
+ Same Markdown support as `create-page-simple`.
303
+
304
+ ```json
305
+ {
306
+ "block_id": "page-or-block-uuid-here",
307
+ "content": "# New Section\n\nThis is **important** content with a [link](https://example.com).\n\n- Item 1\n- Item 2\n\n```javascript\nconst x = 1;\n```"
308
+ }
309
+ ```
310
+
311
+ **Token Comparison:**
312
+ | Method | Tokens | Reduction |
313
+ |--------|--------|-----------|
314
+ | append-block-children (blocks) | ~201 | - |
315
+ | append-blocks-simple (markdown) | ~42 | **79%** |
316
+
253
317
  ### create-comment
254
318
 
255
319
  Add a comment to a page.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=markdown-to-blocks.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-to-blocks.test.d.ts","sourceRoot":"","sources":["../../../../src/converters/__tests__/markdown-to-blocks.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,245 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { markdownToBlocks, parseInlineMarkdown } from '../markdown-to-blocks.js';
3
+ describe('parseInlineMarkdown', () => {
4
+ describe('plain text', () => {
5
+ it('should return plain text as single rich text item', () => {
6
+ const result = parseInlineMarkdown('Hello world');
7
+ expect(result).toEqual([{ type: 'text', text: { content: 'Hello world' } }]);
8
+ });
9
+ it('should handle empty string', () => {
10
+ const result = parseInlineMarkdown('');
11
+ expect(result).toEqual([]);
12
+ });
13
+ });
14
+ describe('bold', () => {
15
+ it('should parse **bold** text', () => {
16
+ const result = parseInlineMarkdown('This is **bold** text');
17
+ expect(result).toHaveLength(3);
18
+ expect(result[0]).toEqual({ type: 'text', text: { content: 'This is ' } });
19
+ expect(result[1]).toEqual({
20
+ type: 'text',
21
+ text: { content: 'bold' },
22
+ annotations: { bold: true },
23
+ });
24
+ expect(result[2]).toEqual({ type: 'text', text: { content: ' text' } });
25
+ });
26
+ });
27
+ describe('italic', () => {
28
+ it('should parse *italic* text', () => {
29
+ const result = parseInlineMarkdown('This is *italic* text');
30
+ expect(result).toHaveLength(3);
31
+ expect(result[1]).toEqual({
32
+ type: 'text',
33
+ text: { content: 'italic' },
34
+ annotations: { italic: true },
35
+ });
36
+ });
37
+ });
38
+ describe('strikethrough', () => {
39
+ it('should parse ~~strikethrough~~ text', () => {
40
+ const result = parseInlineMarkdown('This is ~~deleted~~ text');
41
+ expect(result).toHaveLength(3);
42
+ expect(result[1]).toEqual({
43
+ type: 'text',
44
+ text: { content: 'deleted' },
45
+ annotations: { strikethrough: true },
46
+ });
47
+ });
48
+ });
49
+ describe('code', () => {
50
+ it('should parse `code` text', () => {
51
+ const result = parseInlineMarkdown('Use `console.log()` here');
52
+ expect(result).toHaveLength(3);
53
+ expect(result[1]).toEqual({
54
+ type: 'text',
55
+ text: { content: 'console.log()' },
56
+ annotations: { code: true },
57
+ });
58
+ });
59
+ });
60
+ describe('links', () => {
61
+ it('should parse [text](url) links', () => {
62
+ const result = parseInlineMarkdown('Visit [Google](https://google.com) now');
63
+ expect(result).toHaveLength(3);
64
+ expect(result[1]).toEqual({
65
+ type: 'text',
66
+ text: { content: 'Google', link: { url: 'https://google.com' } },
67
+ annotations: {},
68
+ });
69
+ });
70
+ });
71
+ });
72
+ describe('markdownToBlocks', () => {
73
+ describe('paragraphs', () => {
74
+ it('should convert plain text to paragraph block', () => {
75
+ const result = markdownToBlocks('Hello world');
76
+ expect(result).toHaveLength(1);
77
+ expect(result[0].type).toBe('paragraph');
78
+ expect(result[0].paragraph).toEqual({
79
+ rich_text: [{ type: 'text', text: { content: 'Hello world' } }],
80
+ });
81
+ });
82
+ it('should skip empty lines', () => {
83
+ const result = markdownToBlocks('Line 1\n\nLine 2');
84
+ expect(result).toHaveLength(2);
85
+ expect(result[0].type).toBe('paragraph');
86
+ expect(result[1].type).toBe('paragraph');
87
+ });
88
+ });
89
+ describe('headings', () => {
90
+ it('should convert # to heading_1', () => {
91
+ const result = markdownToBlocks('# Heading 1');
92
+ expect(result).toHaveLength(1);
93
+ expect(result[0].type).toBe('heading_1');
94
+ expect(result[0].heading_1).toEqual({
95
+ rich_text: [{ type: 'text', text: { content: 'Heading 1' } }],
96
+ });
97
+ });
98
+ it('should convert ## to heading_2', () => {
99
+ const result = markdownToBlocks('## Heading 2');
100
+ expect(result).toHaveLength(1);
101
+ expect(result[0].type).toBe('heading_2');
102
+ });
103
+ it('should convert ### to heading_3', () => {
104
+ const result = markdownToBlocks('### Heading 3');
105
+ expect(result).toHaveLength(1);
106
+ expect(result[0].type).toBe('heading_3');
107
+ });
108
+ });
109
+ describe('lists', () => {
110
+ it('should convert - to bulleted_list_item', () => {
111
+ const result = markdownToBlocks('- Item 1\n- Item 2');
112
+ expect(result).toHaveLength(2);
113
+ expect(result[0].type).toBe('bulleted_list_item');
114
+ expect(result[1].type).toBe('bulleted_list_item');
115
+ });
116
+ it('should convert * to bulleted_list_item', () => {
117
+ const result = markdownToBlocks('* Item 1');
118
+ expect(result).toHaveLength(1);
119
+ expect(result[0].type).toBe('bulleted_list_item');
120
+ });
121
+ it('should convert 1. to numbered_list_item', () => {
122
+ const result = markdownToBlocks('1. First\n2. Second');
123
+ expect(result).toHaveLength(2);
124
+ expect(result[0].type).toBe('numbered_list_item');
125
+ expect(result[1].type).toBe('numbered_list_item');
126
+ });
127
+ });
128
+ describe('checkboxes', () => {
129
+ it('should convert - [ ] to unchecked to_do', () => {
130
+ const result = markdownToBlocks('- [ ] Task');
131
+ expect(result).toHaveLength(1);
132
+ expect(result[0].type).toBe('to_do');
133
+ expect(result[0].to_do).toEqual({
134
+ rich_text: [{ type: 'text', text: { content: 'Task' } }],
135
+ checked: false,
136
+ });
137
+ });
138
+ it('should convert - [x] to checked to_do', () => {
139
+ const result = markdownToBlocks('- [x] Done');
140
+ expect(result).toHaveLength(1);
141
+ expect(result[0].type).toBe('to_do');
142
+ expect(result[0].to_do.checked).toBe(true);
143
+ });
144
+ it('should handle - [X] (uppercase)', () => {
145
+ const result = markdownToBlocks('- [X] Done');
146
+ expect(result[0].to_do.checked).toBe(true);
147
+ });
148
+ });
149
+ describe('code blocks', () => {
150
+ it('should convert ``` to code block', () => {
151
+ const result = markdownToBlocks('```\nconst x = 1\n```');
152
+ expect(result).toHaveLength(1);
153
+ expect(result[0].type).toBe('code');
154
+ expect(result[0].code).toEqual({
155
+ rich_text: [{ type: 'text', text: { content: 'const x = 1' } }],
156
+ language: 'plain text',
157
+ });
158
+ });
159
+ it('should parse language from code fence', () => {
160
+ const result = markdownToBlocks('```typescript\nconst x: number = 1\n```');
161
+ expect(result[0].code.language).toBe('typescript');
162
+ });
163
+ it('should handle multi-line code', () => {
164
+ const result = markdownToBlocks('```js\nline1\nline2\nline3\n```');
165
+ const code = result[0].code;
166
+ expect(code.rich_text[0].text.content).toBe('line1\nline2\nline3');
167
+ });
168
+ });
169
+ describe('quotes', () => {
170
+ it('should convert > to quote block', () => {
171
+ const result = markdownToBlocks('> This is a quote');
172
+ expect(result).toHaveLength(1);
173
+ expect(result[0].type).toBe('quote');
174
+ expect(result[0].quote).toEqual({
175
+ rich_text: [{ type: 'text', text: { content: 'This is a quote' } }],
176
+ });
177
+ });
178
+ it('should combine consecutive quote lines', () => {
179
+ const result = markdownToBlocks('> Line 1\n> Line 2');
180
+ expect(result).toHaveLength(1);
181
+ const quote = result[0].quote;
182
+ expect(quote.rich_text[0].text.content).toBe('Line 1\nLine 2');
183
+ });
184
+ });
185
+ describe('dividers', () => {
186
+ it('should convert --- to divider', () => {
187
+ const result = markdownToBlocks('---');
188
+ expect(result).toHaveLength(1);
189
+ expect(result[0].type).toBe('divider');
190
+ expect(result[0].divider).toEqual({});
191
+ });
192
+ it('should handle ---- (multiple dashes)', () => {
193
+ const result = markdownToBlocks('----');
194
+ expect(result[0].type).toBe('divider');
195
+ });
196
+ });
197
+ describe('images', () => {
198
+ it('should convert ![alt](url) to image block', () => {
199
+ const result = markdownToBlocks('![Alt text](https://example.com/image.png)');
200
+ expect(result).toHaveLength(1);
201
+ expect(result[0].type).toBe('image');
202
+ expect(result[0].image).toEqual({
203
+ type: 'external',
204
+ external: { url: 'https://example.com/image.png' },
205
+ caption: [{ type: 'text', text: { content: 'Alt text' } }],
206
+ });
207
+ });
208
+ it('should handle image without alt text', () => {
209
+ const result = markdownToBlocks('![](https://example.com/image.png)');
210
+ expect(result[0].image.caption).toEqual([]);
211
+ });
212
+ });
213
+ describe('mixed content', () => {
214
+ it('should handle complex markdown document', () => {
215
+ const markdown = `# Title
216
+
217
+ This is a paragraph with **bold** and *italic*.
218
+
219
+ ## Section
220
+
221
+ - Item 1
222
+ - Item 2
223
+
224
+ \`\`\`javascript
225
+ const x = 1
226
+ \`\`\`
227
+
228
+ > A quote
229
+
230
+ ---
231
+
232
+ ![Image](https://example.com/img.png)`;
233
+ const result = markdownToBlocks(markdown);
234
+ expect(result[0].type).toBe('heading_1');
235
+ expect(result[1].type).toBe('paragraph');
236
+ expect(result[2].type).toBe('heading_2');
237
+ expect(result[3].type).toBe('bulleted_list_item');
238
+ expect(result[4].type).toBe('bulleted_list_item');
239
+ expect(result[5].type).toBe('code');
240
+ expect(result[6].type).toBe('quote');
241
+ expect(result[7].type).toBe('divider');
242
+ expect(result[8].type).toBe('image');
243
+ });
244
+ });
245
+ });
@@ -6,4 +6,5 @@
6
6
  export { blocksToMarkdown, blocksToMarkdownSync, type ConvertOptions, type NotionBlock, } from './block-to-markdown.js';
7
7
  export { type NotionProperty, type PropertyValue, pagePropertiesToObject, pagePropertiesToSimple, pagesToSimple, pageToSimple, type SimplePage, type SimpleProperty, } from './page-to-markdown.js';
8
8
  export { type RichTextItem, richTextToMarkdown, richTextToPlain, } from './rich-text-to-markdown.js';
9
+ export { markdownToBlocks, parseInlineMarkdown } from './markdown-to-blocks.js';
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/converters/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,WAAW,GACjB,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,aAAa,EACb,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,cAAc,GACpB,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EACL,KAAK,YAAY,EACjB,kBAAkB,EAClB,eAAe,GAChB,MAAM,4BAA4B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/converters/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,WAAW,GACjB,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,aAAa,EACb,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,cAAc,GACpB,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EACL,KAAK,YAAY,EACjB,kBAAkB,EAClB,eAAe,GAChB,MAAM,4BAA4B,CAAA;AAEnC,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA"}
@@ -9,3 +9,5 @@ export { blocksToMarkdown, blocksToMarkdownSync, } from './block-to-markdown.js'
9
9
  export { pagePropertiesToObject, pagePropertiesToSimple, pagesToSimple, pageToSimple, } from './page-to-markdown.js';
10
10
  // RichText変換
11
11
  export { richTextToMarkdown, richTextToPlain, } from './rich-text-to-markdown.js';
12
+ // Markdown→ブロック変換
13
+ export { markdownToBlocks, parseInlineMarkdown } from './markdown-to-blocks.js';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Markdown文字列をNotionブロック配列に変換するモジュール
3
+ */
4
+ import type { RichTextItem } from './rich-text-to-markdown.js';
5
+ /**
6
+ * Notionブロックの型定義(簡易版)
7
+ */
8
+ export interface NotionBlock {
9
+ type: string;
10
+ [key: string]: unknown;
11
+ }
12
+ /**
13
+ * インラインMarkdown記法をRichText配列に変換
14
+ * サポート: **bold**, *italic*, ~~strike~~, `code`, [text](url)
15
+ */
16
+ export declare function parseInlineMarkdown(text: string): RichTextItem[];
17
+ /**
18
+ * Markdown文字列をNotionブロック配列に変換
19
+ */
20
+ export declare function markdownToBlocks(markdown: string): NotionBlock[];
21
+ //# sourceMappingURL=markdown-to-blocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-to-blocks.d.ts","sourceRoot":"","sources":["../../../src/converters/markdown-to-blocks.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAO9D;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE,CA8HhE;AAmBD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,EAAE,CA2HhE"}
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Markdown文字列をNotionブロック配列に変換するモジュール
3
+ */
4
+ // 安全性のための制限値
5
+ const MAX_INPUT_LENGTH = 100_000; // 100KB
6
+ const MAX_LINE_LENGTH = 10_000; // 10KB per line
7
+ const MAX_CODE_BLOCK_LINES = 1000;
8
+ /**
9
+ * インラインMarkdown記法をRichText配列に変換
10
+ * サポート: **bold**, *italic*, ~~strike~~, `code`, [text](url)
11
+ */
12
+ export function parseInlineMarkdown(text) {
13
+ // 行長制限(ReDoS対策)
14
+ const safeText = text.length > MAX_LINE_LENGTH ? text.slice(0, MAX_LINE_LENGTH) : text;
15
+ const result = [];
16
+ // 正規表現パターン(優先順位順)
17
+ const patterns = [
18
+ // リンク: [text](url)
19
+ { regex: /\[([^\]]+)\]\(([^)]+)\)/g, type: 'link' },
20
+ // 太字: **text**
21
+ { regex: /\*\*([^*]+)\*\*/g, type: 'bold' },
22
+ // イタリック: *text* (太字でないもの)
23
+ { regex: /(?<!\*)\*([^*]+)\*(?!\*)/g, type: 'italic' },
24
+ // 取り消し線: ~~text~~
25
+ { regex: /~~([^~]+)~~/g, type: 'strikethrough' },
26
+ // インラインコード: `code`
27
+ { regex: /`([^`]+)`/g, type: 'code' },
28
+ ];
29
+ // 単純な実装: マークダウン記法を順番に処理
30
+ // 複雑なネストは非対応(Notion APIも完全なネストは非対応)
31
+ let lastIndex = 0;
32
+ // 全パターンをまとめて検索
33
+ const allMatches = [];
34
+ for (const { regex, type } of patterns) {
35
+ const re = new RegExp(regex.source, 'g');
36
+ let match = re.exec(safeText);
37
+ while (match !== null) {
38
+ allMatches.push({
39
+ index: match.index,
40
+ length: match[0].length,
41
+ content: match[1],
42
+ type,
43
+ url: type === 'link' ? match[2] : undefined,
44
+ });
45
+ match = re.exec(safeText);
46
+ }
47
+ }
48
+ // インデックス順にソート
49
+ allMatches.sort((a, b) => a.index - b.index);
50
+ // 重複を除去(先に来たものを優先)
51
+ const filteredMatches = [];
52
+ let lastEnd = 0;
53
+ for (const m of allMatches) {
54
+ if (m.index >= lastEnd) {
55
+ filteredMatches.push(m);
56
+ lastEnd = m.index + m.length;
57
+ }
58
+ }
59
+ // RichText配列を構築
60
+ lastIndex = 0;
61
+ for (const match of filteredMatches) {
62
+ // マッチ前のプレーンテキスト
63
+ if (match.index > lastIndex) {
64
+ const plainText = safeText.slice(lastIndex, match.index);
65
+ if (plainText) {
66
+ result.push({
67
+ type: 'text',
68
+ text: { content: plainText },
69
+ });
70
+ }
71
+ }
72
+ // マッチしたテキスト
73
+ const richText = {
74
+ type: 'text',
75
+ text: { content: match.content },
76
+ annotations: {},
77
+ };
78
+ switch (match.type) {
79
+ case 'bold':
80
+ richText.annotations = { bold: true };
81
+ break;
82
+ case 'italic':
83
+ richText.annotations = { italic: true };
84
+ break;
85
+ case 'strikethrough':
86
+ richText.annotations = { strikethrough: true };
87
+ break;
88
+ case 'code':
89
+ richText.annotations = { code: true };
90
+ break;
91
+ case 'link':
92
+ if (match.url) {
93
+ richText.text = { content: match.content, link: { url: match.url } };
94
+ }
95
+ break;
96
+ }
97
+ result.push(richText);
98
+ lastIndex = match.index + match.length;
99
+ }
100
+ // 残りのプレーンテキスト
101
+ if (lastIndex < safeText.length) {
102
+ const plainText = safeText.slice(lastIndex);
103
+ if (plainText) {
104
+ result.push({
105
+ type: 'text',
106
+ text: { content: plainText },
107
+ });
108
+ }
109
+ }
110
+ // 何もマッチしなかった場合
111
+ if (result.length === 0 && safeText) {
112
+ result.push({
113
+ type: 'text',
114
+ text: { content: safeText },
115
+ });
116
+ }
117
+ return result;
118
+ }
119
+ /**
120
+ * RichTextを含むブロックを生成するヘルパー
121
+ */
122
+ function createTextBlock(type, text, extra = {}) {
123
+ return {
124
+ type,
125
+ [type]: {
126
+ rich_text: parseInlineMarkdown(text),
127
+ ...extra,
128
+ },
129
+ };
130
+ }
131
+ /**
132
+ * Markdown文字列をNotionブロック配列に変換
133
+ */
134
+ export function markdownToBlocks(markdown) {
135
+ // 入力長制限(ReDoS対策)
136
+ const safeMarkdown = markdown.length > MAX_INPUT_LENGTH ? markdown.slice(0, MAX_INPUT_LENGTH) : markdown;
137
+ const blocks = [];
138
+ const lines = safeMarkdown.split('\n');
139
+ let i = 0;
140
+ while (i < lines.length) {
141
+ const line = lines[i];
142
+ // 空行はスキップ
143
+ if (!line.trim()) {
144
+ i++;
145
+ continue;
146
+ }
147
+ // コードブロック: ```language
148
+ if (line.startsWith('```')) {
149
+ const language = line.slice(3).trim() || 'plain text';
150
+ const codeLines = [];
151
+ i++;
152
+ while (i < lines.length && !lines[i].startsWith('```') && codeLines.length < MAX_CODE_BLOCK_LINES) {
153
+ codeLines.push(lines[i]);
154
+ i++;
155
+ }
156
+ // 残りの行をスキップ(制限超過時)
157
+ while (i < lines.length && !lines[i].startsWith('```')) {
158
+ i++;
159
+ }
160
+ blocks.push({
161
+ type: 'code',
162
+ code: {
163
+ rich_text: [{ type: 'text', text: { content: codeLines.join('\n') } }],
164
+ language,
165
+ },
166
+ });
167
+ i++; // closing ```
168
+ continue;
169
+ }
170
+ // 水平線: ---
171
+ if (/^-{3,}$/.test(line.trim())) {
172
+ blocks.push({ type: 'divider', divider: {} });
173
+ i++;
174
+ continue;
175
+ }
176
+ // 見出し: # ## ### (####以上はheading_3にフォールバック)
177
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
178
+ if (headingMatch) {
179
+ const rawLevel = headingMatch[1].length;
180
+ const level = Math.min(rawLevel, 3);
181
+ const text = headingMatch[2];
182
+ const type = `heading_${level}`;
183
+ blocks.push(createTextBlock(type, text));
184
+ i++;
185
+ continue;
186
+ }
187
+ // チェックボックス: - [ ] or - [x]
188
+ const todoMatch = line.match(/^-\s*\[([ xX])\]\s*(.*)$/);
189
+ if (todoMatch) {
190
+ const checked = todoMatch[1].toLowerCase() === 'x';
191
+ const text = todoMatch[2];
192
+ blocks.push(createTextBlock('to_do', text, { checked }));
193
+ i++;
194
+ continue;
195
+ }
196
+ // 箇条書き: - or *
197
+ const bulletMatch = line.match(/^[-*]\s+(.+)$/);
198
+ if (bulletMatch) {
199
+ blocks.push(createTextBlock('bulleted_list_item', bulletMatch[1]));
200
+ i++;
201
+ continue;
202
+ }
203
+ // 番号付きリスト: 1. 2. etc
204
+ const numberedMatch = line.match(/^\d+\.\s+(.+)$/);
205
+ if (numberedMatch) {
206
+ blocks.push(createTextBlock('numbered_list_item', numberedMatch[1]));
207
+ i++;
208
+ continue;
209
+ }
210
+ // 引用: >
211
+ const quoteMatch = line.match(/^>\s*(.*)$/);
212
+ if (quoteMatch) {
213
+ // 連続する引用行をまとめる
214
+ const quoteLines = [quoteMatch[1]];
215
+ i++;
216
+ while (i < lines.length && lines[i].startsWith('>')) {
217
+ quoteLines.push(lines[i].replace(/^>\s*/, ''));
218
+ i++;
219
+ }
220
+ blocks.push(createTextBlock('quote', quoteLines.join('\n')));
221
+ continue;
222
+ }
223
+ // 画像: ![alt](url)
224
+ const imageMatch = line.match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
225
+ if (imageMatch) {
226
+ blocks.push({
227
+ type: 'image',
228
+ image: {
229
+ type: 'external',
230
+ external: { url: imageMatch[2] },
231
+ caption: imageMatch[1]
232
+ ? [{ type: 'text', text: { content: imageMatch[1] } }]
233
+ : [],
234
+ },
235
+ });
236
+ i++;
237
+ continue;
238
+ }
239
+ // デフォルト: 段落
240
+ blocks.push(createTextBlock('paragraph', line));
241
+ i++;
242
+ }
243
+ return blocks;
244
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"append-block-children.d.ts","sourceRoot":"","sources":["../../../src/tools/append-block-children.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAoBvD,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAiCzF"}
1
+ {"version":3,"file":"append-block-children.d.ts","sourceRoot":"","sources":["../../../src/tools/append-block-children.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAWvD,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAiCzF"}
@@ -1,16 +1,10 @@
1
1
  import { z } from 'zod';
2
- import { BlockChildrenSchema } from '../schemas/block.js';
3
2
  import { formatResponse, handleError } from '../utils/index.js';
3
+ // Minimal schema for MCP (full validation by Notion API)
4
4
  const inputSchema = {
5
- block_id: z.string().describe('The ID of the block or page to append children to'),
6
- children: BlockChildrenSchema.describe('Array of block objects to append. ' +
7
- 'Supported types: paragraph, heading_1/2/3, bulleted_list_item, numbered_list_item, to_do, toggle, code, quote, callout, divider, bookmark, image, video, embed, table_of_contents. ' +
8
- 'Example: [{ "type": "paragraph", "paragraph": { "rich_text": [{ "text": { "content": "Hello" } }] } }]'),
9
- after: z
10
- .string()
11
- .optional()
12
- .describe('The ID of a block to insert the new children after. ' +
13
- 'If not provided, children are appended at the end.'),
5
+ block_id: z.string().describe('Block or page ID'),
6
+ children: z.array(z.any()).describe('Block objects to append'),
7
+ after: z.string().optional().describe('Insert after this block ID'),
14
8
  };
15
9
  export function registerAppendBlockChildren(server, notion) {
16
10
  server.registerTool('append-block-children', {
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { NotionClient } from '../notion-client.js';
3
+ export declare function registerAppendBlocksSimple(server: McpServer, notion: NotionClient): void;
4
+ //# sourceMappingURL=append-blocks-simple.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"append-blocks-simple.d.ts","sourceRoot":"","sources":["../../../src/tools/append-blocks-simple.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAUvD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAqCxF"}
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod';
2
+ import { markdownToBlocks } from '../converters/index.js';
3
+ import { formatResponse, handleError } from '../utils/index.js';
4
+ // Minimal schema for MCP
5
+ const inputSchema = {
6
+ block_id: z.string().describe('Page or block ID to append to'),
7
+ content: z.string().describe('Content in Markdown'),
8
+ after: z.string().optional().describe('Insert after this block ID'),
9
+ };
10
+ export function registerAppendBlocksSimple(server, notion) {
11
+ server.registerTool('append-blocks-simple', {
12
+ description: 'Append blocks to a page using Markdown. ' +
13
+ 'Simpler than append-block-children: just provide markdown text. ' +
14
+ 'Supports: headings (#), lists (- or 1.), checkboxes (- [ ]), code blocks (```), quotes (>), images (![]()), bold (**), italic (*), links ([]()), etc.',
15
+ inputSchema,
16
+ }, async ({ block_id, content, after }) => {
17
+ try {
18
+ // Convert markdown to blocks
19
+ const children = markdownToBlocks(content);
20
+ // Build params
21
+ const params = {
22
+ block_id,
23
+ children,
24
+ };
25
+ if (after) {
26
+ params.after = after;
27
+ }
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ const response = await notion.blocks.children.append(params);
30
+ return formatResponse(response);
31
+ }
32
+ catch (error) {
33
+ return handleError(error);
34
+ }
35
+ });
36
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"create-comment.d.ts","sourceRoot":"","sources":["../../../src/tools/create-comment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAoBvD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAqCnF"}
1
+ {"version":3,"file":"create-comment.d.ts","sourceRoot":"","sources":["../../../src/tools/create-comment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAUvD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAqCnF"}
@@ -1,16 +1,10 @@
1
1
  import { z } from 'zod';
2
- import { RichTextArraySchema } from '../schemas/common.js';
3
2
  import { formatResponse, handleError } from '../utils/index.js';
3
+ // Minimal schema for MCP (full validation by Notion API)
4
4
  const inputSchema = {
5
- page_id: z.string().describe('The ID of the page to add a comment to'),
6
- rich_text: RichTextArraySchema.describe('The comment content as rich text array. ' +
7
- 'Each item: { "type": "text", "text": { "content": "..." }, "annotations": { "bold": true, ... } }. ' +
8
- 'Example: [{ "type": "text", "text": { "content": "This is a comment" } }]'),
9
- discussion_id: z
10
- .string()
11
- .optional()
12
- .describe('Optional ID of an existing discussion to add the comment to. ' +
13
- 'If not provided, a new discussion is created.'),
5
+ page_id: z.string().describe('Page ID'),
6
+ rich_text: z.array(z.any()).describe('Comment content as rich text'),
7
+ discussion_id: z.string().optional().describe('Reply to existing discussion'),
14
8
  };
15
9
  export function registerCreateComment(server, notion) {
16
10
  server.registerTool('create-comment', {
@@ -1 +1 @@
1
- {"version":3,"file":"create-database.d.ts","sourceRoot":"","sources":["../../../src/tools/create-database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AA+CvD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CA+CpF"}
1
+ {"version":3,"file":"create-database.d.ts","sourceRoot":"","sources":["../../../src/tools/create-database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAavD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAgDpF"}
@@ -1,27 +1,13 @@
1
1
  import { z } from 'zod';
2
- import { CoverSchema, IconSchema, RichTextArraySchema, } from '../schemas/common.js';
3
- import { DatabasePropertiesSchema } from '../schemas/database.js';
4
2
  import { formatResponse, handleError } from '../utils/index.js';
3
+ // Minimal schema for MCP (full validation by Notion API)
5
4
  const inputSchema = {
6
- parent_page_id: z
7
- .string()
8
- .describe('The ID of the parent page where the database will be created'),
9
- title: RichTextArraySchema.optional().describe('Optional title of the database as an array of rich text objects. ' +
10
- 'Example: [{ "type": "text", "text": { "content": "My Database" } }]'),
11
- properties: DatabasePropertiesSchema.describe('Database property schema object. Keys are property names, values define the property type and configuration. ' +
12
- 'Each database must have exactly one "title" property. ' +
13
- 'Supported types: title, rich_text, number, select, multi_select, date, people, files, checkbox, url, email, phone_number, formula, relation, rollup, created_time, created_by, last_edited_time, last_edited_by. ' +
14
- 'Example: { "Name": { "title": {} }, "Description": { "rich_text": {} }, "Price": { "number": { "format": "dollar" } }, "Tags": { "multi_select": { "options": [{ "name": "Tag1", "color": "blue" }] } } }'),
15
- icon: IconSchema.optional().describe('Optional icon for the database. ' +
16
- 'Emoji: { "type": "emoji", "emoji": "📊" }. ' +
17
- 'External: { "type": "external", "external": { "url": "https://..." } }'),
18
- cover: CoverSchema.optional().describe('Optional cover image for the database. ' +
19
- 'Example: { "type": "external", "external": { "url": "https://..." } }'),
20
- is_inline: z
21
- .boolean()
22
- .optional()
23
- .describe('Optional. If true, the database will appear as an inline database within the parent page. ' +
24
- 'If false or omitted, it will be a full page database.'),
5
+ parent_page_id: z.string().describe('Parent page ID'),
6
+ title: z.array(z.any()).optional().describe('Database title'),
7
+ properties: z.record(z.string(), z.any()).describe('Property schema (must include one title property)'),
8
+ icon: z.any().optional().describe('Database icon'),
9
+ cover: z.any().optional().describe('Cover image'),
10
+ is_inline: z.boolean().optional().describe('Inline database'),
25
11
  };
26
12
  export function registerCreateDatabase(server, notion) {
27
13
  server.registerTool('create-database', {
@@ -47,6 +33,7 @@ export function registerCreateDatabase(server, notion) {
47
33
  if (is_inline !== undefined) {
48
34
  params.is_inline = is_inline;
49
35
  }
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
37
  const response = await notion.databases.create(params);
51
38
  return formatResponse(response);
52
39
  }
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { NotionClient } from '../notion-client.js';
3
+ export declare function registerCreatePageSimple(server: McpServer, notion: NotionClient): void;
4
+ //# sourceMappingURL=create-page-simple.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-page-simple.d.ts","sourceRoot":"","sources":["../../../src/tools/create-page-simple.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAYvD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CA2DtF"}
@@ -0,0 +1,54 @@
1
+ import { z } from 'zod';
2
+ import { markdownToBlocks } from '../converters/index.js';
3
+ import { formatResponse, handleError } from '../utils/index.js';
4
+ // Minimal schema for MCP
5
+ const inputSchema = {
6
+ database_id: z.string().describe('Database ID'),
7
+ title: z.string().describe('Page title'),
8
+ content: z.string().optional().describe('Page content in Markdown'),
9
+ properties: z.record(z.string(), z.any()).optional().describe('Additional properties'),
10
+ icon: z.string().optional().describe('Emoji icon (e.g. "bug")'),
11
+ };
12
+ export function registerCreatePageSimple(server, notion) {
13
+ server.registerTool('create-page-simple', {
14
+ description: 'Create a Notion page with Markdown content. ' +
15
+ 'Simpler than create-page: just provide title and markdown text. ' +
16
+ 'Supports: headings (#), lists (- or 1.), checkboxes (- [ ]), code blocks (```), quotes (>), images (![]()), bold (**), italic (*), links ([]()), etc.',
17
+ inputSchema,
18
+ }, async ({ database_id, title, content, properties, icon }) => {
19
+ try {
20
+ // Build properties with title
21
+ const pageProperties = {
22
+ ...properties,
23
+ };
24
+ // Check if any title property is already provided
25
+ // Title properties have the structure: { title: [...] }
26
+ const hasTitleProperty = Object.values(pageProperties).some((prop) => prop && typeof prop === 'object' && 'title' in prop);
27
+ // Only add Name if no title property exists
28
+ if (!hasTitleProperty) {
29
+ pageProperties.Name = {
30
+ title: [{ type: 'text', text: { content: title } }],
31
+ };
32
+ }
33
+ // Build params
34
+ const params = {
35
+ parent: { database_id },
36
+ properties: pageProperties,
37
+ };
38
+ // Convert markdown to blocks if content provided
39
+ if (content) {
40
+ params.children = markdownToBlocks(content);
41
+ }
42
+ // Add icon if provided
43
+ if (icon) {
44
+ params.icon = { type: 'emoji', emoji: icon };
45
+ }
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ const response = await notion.pages.create(params);
48
+ return formatResponse(response);
49
+ }
50
+ catch (error) {
51
+ return handleError(error);
52
+ }
53
+ });
54
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"create-page.d.ts","sourceRoot":"","sources":["../../../src/tools/create-page.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAiCvD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CA0ChF"}
1
+ {"version":3,"file":"create-page.d.ts","sourceRoot":"","sources":["../../../src/tools/create-page.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAkBvD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CA0ChF"}
@@ -1,21 +1,12 @@
1
1
  import { z } from 'zod';
2
- import { BlockChildrenSchema } from '../schemas/block.js';
3
- import { CoverSchema, IconSchema } from '../schemas/common.js';
4
- import { PropertiesSchema } from '../schemas/page.js';
5
2
  import { formatResponse, handleError } from '../utils/index.js';
3
+ // Minimal schema for MCP (full validation by Notion API)
6
4
  const inputSchema = {
7
- database_id: z.string().describe('The ID of the database where the page will be created'),
8
- properties: PropertiesSchema.describe('Page properties object. Keys are property names, values follow Notion property format. ' +
9
- 'Supported types: title, rich_text, number, select, multi_select, status, date, checkbox, url, email, phone_number, relation, people, files. ' +
10
- 'Example: { "Name": { "title": [{ "text": { "content": "My Page" } }] }, "Status": { "status": { "name": "In Progress" } } }'),
11
- children: BlockChildrenSchema.optional().describe('Optional array of block objects for the page content. ' +
12
- 'Supported types: paragraph, heading_1/2/3, bulleted_list_item, numbered_list_item, to_do, toggle, code, quote, callout, divider, bookmark, image, video, embed, table_of_contents. ' +
13
- 'Example: [{ "type": "paragraph", "paragraph": { "rich_text": [{ "text": { "content": "Hello" } }] } }]'),
14
- icon: IconSchema.optional().describe('Optional icon for the page. ' +
15
- 'Emoji: { "type": "emoji", "emoji": "🚀" }. ' +
16
- 'External: { "type": "external", "external": { "url": "https://..." } }'),
17
- cover: CoverSchema.optional().describe('Optional cover image for the page. ' +
18
- 'Example: { "type": "external", "external": { "url": "https://..." } }'),
5
+ database_id: z.string().describe('Database ID'),
6
+ properties: z.record(z.string(), z.any()).describe('Notion properties object'),
7
+ children: z.array(z.any()).optional().describe('Block objects array'),
8
+ icon: z.any().optional().describe('Page icon'),
9
+ cover: z.any().optional().describe('Cover image'),
19
10
  };
20
11
  export function registerCreatePage(server, notion) {
21
12
  server.registerTool('create-page', {
@@ -1,9 +1,11 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { NotionClient } from '../notion-client.js';
3
3
  import { registerAppendBlockChildren } from './append-block-children.js';
4
+ import { registerAppendBlocksSimple } from './append-blocks-simple.js';
4
5
  import { registerCreateComment } from './create-comment.js';
5
6
  import { registerCreateDatabase } from './create-database.js';
6
7
  import { registerCreatePage } from './create-page.js';
8
+ import { registerCreatePageSimple } from './create-page-simple.js';
7
9
  import { registerGetBlockChildren } from './get-block-children.js';
8
10
  import { registerQueryDatabase } from './query-database.js';
9
11
  import { registerRetrievePage } from './retrieve-page.js';
@@ -11,5 +13,5 @@ import { registerSearch } from './search.js';
11
13
  import { registerUpdateDatabase } from './update-database.js';
12
14
  import { registerUpdatePage } from './update-page.js';
13
15
  export declare function registerAllTools(server: McpServer, notion: NotionClient): void;
14
- export { registerRetrievePage, registerCreatePage, registerUpdatePage, registerCreateDatabase, registerUpdateDatabase, registerQueryDatabase, registerSearch, registerGetBlockChildren, registerAppendBlockChildren, registerCreateComment, };
16
+ export { registerRetrievePage, registerCreatePage, registerCreatePageSimple, registerUpdatePage, registerCreateDatabase, registerUpdateDatabase, registerQueryDatabase, registerSearch, registerGetBlockChildren, registerAppendBlockChildren, registerAppendBlocksSimple, registerCreateComment, };
15
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAA;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAA;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAErD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAoB9E;AAED,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,qBAAqB,EACrB,cAAc,EACd,wBAAwB,EACxB,2BAA2B,EAC3B,qBAAqB,GACtB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAA;AACxE,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAA;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAA;AAClE,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAA;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAErD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAsB9E;AAED,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,wBAAwB,EACxB,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,qBAAqB,EACrB,cAAc,EACd,wBAAwB,EACxB,2BAA2B,EAC3B,0BAA0B,EAC1B,qBAAqB,GACtB,CAAA"}
@@ -1,7 +1,9 @@
1
1
  import { registerAppendBlockChildren } from './append-block-children.js';
2
+ import { registerAppendBlocksSimple } from './append-blocks-simple.js';
2
3
  import { registerCreateComment } from './create-comment.js';
3
4
  import { registerCreateDatabase } from './create-database.js';
4
5
  import { registerCreatePage } from './create-page.js';
6
+ import { registerCreatePageSimple } from './create-page-simple.js';
5
7
  import { registerGetBlockChildren } from './get-block-children.js';
6
8
  import { registerQueryDatabase } from './query-database.js';
7
9
  import { registerRetrievePage } from './retrieve-page.js';
@@ -12,6 +14,7 @@ export function registerAllTools(server, notion) {
12
14
  // Page operations
13
15
  registerRetrievePage(server, notion);
14
16
  registerCreatePage(server, notion);
17
+ registerCreatePageSimple(server, notion);
15
18
  registerUpdatePage(server, notion);
16
19
  // Database operations
17
20
  registerCreateDatabase(server, notion);
@@ -22,7 +25,8 @@ export function registerAllTools(server, notion) {
22
25
  // Block operations
23
26
  registerGetBlockChildren(server, notion);
24
27
  registerAppendBlockChildren(server, notion);
28
+ registerAppendBlocksSimple(server, notion);
25
29
  // Comment operations
26
30
  registerCreateComment(server, notion);
27
31
  }
28
- export { registerRetrievePage, registerCreatePage, registerUpdatePage, registerCreateDatabase, registerUpdateDatabase, registerQueryDatabase, registerSearch, registerGetBlockChildren, registerAppendBlockChildren, registerCreateComment, };
32
+ export { registerRetrievePage, registerCreatePage, registerCreatePageSimple, registerUpdatePage, registerCreateDatabase, registerUpdateDatabase, registerQueryDatabase, registerSearch, registerGetBlockChildren, registerAppendBlockChildren, registerAppendBlocksSimple, registerCreateComment, };
@@ -1 +1 @@
1
- {"version":3,"file":"query-database.d.ts","sourceRoot":"","sources":["../../../src/tools/query-database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAwDvD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CA+CnF"}
1
+ {"version":3,"file":"query-database.d.ts","sourceRoot":"","sources":["../../../src/tools/query-database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AA8BvD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAgDnF"}
@@ -1,31 +1,14 @@
1
1
  import { z } from 'zod';
2
2
  import { pagesToSimple } from '../converters/index.js';
3
- import { FilterSchema, SortsSchema } from '../schemas/filter.js';
4
3
  import { formatPaginatedResponse, formatSimplePaginatedResponse, handleError, } from '../utils/index.js';
4
+ // Minimal schema for MCP (full validation by Notion API)
5
5
  const inputSchema = {
6
- database_id: z.string().describe('The ID of the database to query'),
7
- filter: FilterSchema.optional().describe('Filter conditions. ' +
8
- 'Property filter: { "property": "Status", "status": { "equals": "Done" } }. ' +
9
- 'Compound filter: { "and": [...] } or { "or": [...] }. ' +
10
- 'Supported property types: title, rich_text, number, checkbox, select, multi_select, status, date, relation.'),
11
- sorts: SortsSchema.optional().describe('Sort conditions array. ' +
12
- 'By property: { "property": "Name", "direction": "ascending" }. ' +
13
- 'By timestamp: { "timestamp": "created_time", "direction": "descending" }.'),
14
- start_cursor: z
15
- .string()
16
- .optional()
17
- .describe('Cursor for pagination. Use the next_cursor from previous response.'),
18
- page_size: z
19
- .number()
20
- .min(1)
21
- .max(100)
22
- .optional()
23
- .describe('Number of results to return (1-100). Default is 100.'),
24
- format: z
25
- .enum(['json', 'simple'])
26
- .optional()
27
- .default('simple')
28
- .describe("Output format: 'simple' (default) returns simplified property values with reduced token usage, 'json' returns raw Notion API response"),
6
+ database_id: z.string().describe('Database ID'),
7
+ filter: z.any().optional().describe('Filter conditions'),
8
+ sorts: z.array(z.any()).optional().describe('Sort conditions'),
9
+ start_cursor: z.string().optional().describe('Pagination cursor'),
10
+ page_size: z.number().optional().describe('Results per page (1-100)'),
11
+ format: z.enum(['json', 'simple']).optional().describe('Output format (default: simple)'),
29
12
  };
30
13
  export function registerQueryDatabase(server, notion) {
31
14
  server.registerTool('query-database', {
@@ -46,6 +29,7 @@ export function registerQueryDatabase(server, notion) {
46
29
  if (page_size) {
47
30
  params.page_size = page_size;
48
31
  }
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
33
  const response = await notion.databases.query(params);
50
34
  if (format === 'simple') {
51
35
  const simplePages = pagesToSimple(response.results);
@@ -1 +1 @@
1
- {"version":3,"file":"update-database.d.ts","sourceRoot":"","sources":["../../../src/tools/update-database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AA2DvD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CA2DpF"}
1
+ {"version":3,"file":"update-database.d.ts","sourceRoot":"","sources":["../../../src/tools/update-database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAevD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CA4DpF"}
@@ -1,37 +1,15 @@
1
1
  import { z } from 'zod';
2
- import { CoverSchema, IconSchema, RichTextArraySchema, } from '../schemas/common.js';
3
- import { DatabasePropertiesSchema } from '../schemas/database.js';
4
2
  import { formatResponse, handleError } from '../utils/index.js';
3
+ // Minimal schema for MCP (full validation by Notion API)
5
4
  const inputSchema = {
6
- database_id: z.string().describe('The ID of the database to update'),
7
- title: RichTextArraySchema.optional().describe('Optional new title for the database as an array of rich text objects. ' +
8
- 'Example: [{ "type": "text", "text": { "content": "Updated Database Title" } }]'),
9
- description: RichTextArraySchema.optional().describe('Optional new description for the database as an array of rich text objects. ' +
10
- 'Example: [{ "type": "text", "text": { "content": "Database description" } }]'),
11
- properties: DatabasePropertiesSchema.optional().describe('Optional properties to add or update. Keys are property names, values define the property type and configuration. ' +
12
- 'To add a new property, include its full schema. To update an existing property, provide the property name with its new configuration. ' +
13
- 'To rename a property, use the property ID as key with a "name" field. ' +
14
- 'To delete a property, set it to null. ' +
15
- 'Supported types: title, rich_text, number, select, multi_select, date, people, files, checkbox, url, email, phone_number, formula, relation, rollup, created_time, created_by, last_edited_time, last_edited_by. ' +
16
- 'Example: { "NewColumn": { "rich_text": {} }, "Price": { "number": { "format": "euro" } } }'),
17
- icon: IconSchema.nullable()
18
- .optional()
19
- .describe('Optional icon for the database. Set to null to remove the icon. ' +
20
- 'Emoji: { "type": "emoji", "emoji": "📊" }. ' +
21
- 'External: { "type": "external", "external": { "url": "https://..." } }'),
22
- cover: CoverSchema.nullable()
23
- .optional()
24
- .describe('Optional cover image for the database. Set to null to remove the cover. ' +
25
- 'Example: { "type": "external", "external": { "url": "https://..." } }'),
26
- is_inline: z
27
- .boolean()
28
- .optional()
29
- .describe('Optional. If true, the database will appear as an inline database. ' +
30
- 'If false, it will be a full page database.'),
31
- archived: z
32
- .boolean()
33
- .optional()
34
- .describe('Optional. Set to true to archive the database, false to unarchive.'),
5
+ database_id: z.string().describe('Database ID'),
6
+ title: z.array(z.any()).optional().describe('New title'),
7
+ description: z.array(z.any()).optional().describe('New description'),
8
+ properties: z.record(z.string(), z.any()).optional().describe('Properties to add/update/delete'),
9
+ icon: z.any().optional().describe('Icon (null to remove)'),
10
+ cover: z.any().optional().describe('Cover (null to remove)'),
11
+ is_inline: z.boolean().optional().describe('Inline database'),
12
+ archived: z.boolean().optional().describe('Archive status'),
35
13
  };
36
14
  export function registerUpdateDatabase(server, notion) {
37
15
  server.registerTool('update-database', {
@@ -64,6 +42,7 @@ export function registerUpdateDatabase(server, notion) {
64
42
  if (archived !== undefined) {
65
43
  params.archived = archived;
66
44
  }
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
46
  const response = await notion.databases.update(params);
68
47
  return formatResponse(response);
69
48
  }
@@ -1 +1 @@
1
- {"version":3,"file":"update-page.d.ts","sourceRoot":"","sources":["../../../src/tools/update-page.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAkCvD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CA4ChF"}
1
+ {"version":3,"file":"update-page.d.ts","sourceRoot":"","sources":["../../../src/tools/update-page.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAiBvD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CA4ChF"}
@@ -1,24 +1,12 @@
1
1
  import { z } from 'zod';
2
- import { CoverSchema, IconSchema } from '../schemas/common.js';
3
- import { PropertiesSchema } from '../schemas/page.js';
4
2
  import { formatResponse, handleError } from '../utils/index.js';
3
+ // Minimal schema for MCP (full validation by Notion API)
5
4
  const inputSchema = {
6
- page_id: z.string().describe('The ID of the page to update'),
7
- properties: PropertiesSchema.optional().describe('Properties to update. Keys are property names, values follow Notion property format. ' +
8
- 'Supported types: title, rich_text, number, select, multi_select, status, date, checkbox, url, email, phone_number, relation, people, files. ' +
9
- 'Example: { "Status": { "status": { "name": "Done" } } }'),
10
- archived: z.boolean().optional().describe('Set to true to archive (delete) the page'),
11
- icon: IconSchema.nullable()
12
- .optional()
13
- .describe('Icon for the page. ' +
14
- 'Emoji: { "type": "emoji", "emoji": "🚀" }. ' +
15
- 'External: { "type": "external", "external": { "url": "https://..." } }. ' +
16
- 'Set to null to remove.'),
17
- cover: CoverSchema.nullable()
18
- .optional()
19
- .describe('Cover image for the page. ' +
20
- 'Example: { "type": "external", "external": { "url": "https://..." } }. ' +
21
- 'Set to null to remove.'),
5
+ page_id: z.string().describe('Page ID'),
6
+ properties: z.record(z.string(), z.any()).optional().describe('Properties to update'),
7
+ archived: z.boolean().optional().describe('Archive the page'),
8
+ icon: z.any().optional().describe('Page icon (null to remove)'),
9
+ cover: z.any().optional().describe('Cover image (null to remove)'),
22
10
  };
23
11
  export function registerUpdatePage(server, notion) {
24
12
  server.registerTool('update-page', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atikk-co-jp/notion-mcp-server",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "MCP server for Notion API - Create, read, update pages and databases",
5
5
  "type": "module",
6
6
  "license": "MIT",