@atikk-co-jp/notion-mcp-server 0.1.0 → 0.2.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.
- package/README.ja.md +95 -4
- package/README.md +95 -4
- package/dist/bin/cli.js +10 -10
- package/dist/src/converters/__tests__/block-to-markdown.test.d.ts +2 -0
- package/dist/src/converters/__tests__/block-to-markdown.test.d.ts.map +1 -0
- package/dist/src/converters/__tests__/block-to-markdown.test.js +611 -0
- package/dist/src/converters/__tests__/page-to-markdown.test.d.ts +2 -0
- package/dist/src/converters/__tests__/page-to-markdown.test.d.ts.map +1 -0
- package/dist/src/converters/__tests__/page-to-markdown.test.js +567 -0
- package/dist/src/converters/__tests__/rich-text-to-markdown.test.d.ts +2 -0
- package/dist/src/converters/__tests__/rich-text-to-markdown.test.d.ts.map +1 -0
- package/dist/src/converters/__tests__/rich-text-to-markdown.test.js +353 -0
- package/dist/src/converters/block-to-markdown.d.ts +38 -0
- package/dist/src/converters/block-to-markdown.d.ts.map +1 -0
- package/dist/src/converters/block-to-markdown.js +484 -0
- package/dist/src/converters/index.d.ts +9 -0
- package/dist/src/converters/index.d.ts.map +1 -0
- package/dist/src/converters/index.js +11 -0
- package/dist/src/converters/page-to-markdown.d.ts +64 -0
- package/dist/src/converters/page-to-markdown.d.ts.map +1 -0
- package/dist/src/converters/page-to-markdown.js +189 -0
- package/dist/src/converters/rich-text-to-markdown.d.ts +61 -0
- package/dist/src/converters/rich-text-to-markdown.d.ts.map +1 -0
- package/dist/src/converters/rich-text-to-markdown.js +95 -0
- package/dist/src/index.d.ts +6 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -5
- package/dist/src/notion-client.d.ts +58 -15
- package/dist/src/notion-client.d.ts.map +1 -1
- package/dist/src/notion-client.js +23 -13
- package/dist/src/schemas/block.d.ts +2158 -622
- package/dist/src/schemas/block.d.ts.map +1 -1
- package/dist/src/schemas/block.js +283 -76
- package/dist/src/schemas/common.d.ts +518 -6
- package/dist/src/schemas/common.d.ts.map +1 -1
- package/dist/src/schemas/common.js +120 -42
- package/dist/src/schemas/database.d.ts +687 -0
- package/dist/src/schemas/database.d.ts.map +1 -0
- package/dist/src/schemas/database.js +264 -0
- package/dist/src/schemas/filter.d.ts +509 -3
- package/dist/src/schemas/filter.d.ts.map +1 -1
- package/dist/src/schemas/filter.js +110 -13
- package/dist/src/schemas/index.d.ts +5 -4
- package/dist/src/schemas/index.d.ts.map +1 -1
- package/dist/src/schemas/index.js +7 -5
- package/dist/src/schemas/page.d.ts +2152 -19
- package/dist/src/schemas/page.d.ts.map +1 -1
- package/dist/src/schemas/page.js +216 -22
- package/dist/src/schemas/schemas.test.d.ts +2 -0
- package/dist/src/schemas/schemas.test.d.ts.map +1 -0
- package/dist/src/schemas/schemas.test.js +418 -0
- package/dist/src/server.d.ts +2 -2
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +6 -6
- package/dist/src/tools/append-block-children.d.ts +2 -2
- package/dist/src/tools/append-block-children.d.ts.map +1 -1
- package/dist/src/tools/append-block-children.js +16 -10
- package/dist/src/tools/create-comment.d.ts +2 -2
- package/dist/src/tools/create-comment.d.ts.map +1 -1
- package/dist/src/tools/create-comment.js +15 -9
- package/dist/src/tools/create-database.d.ts +4 -0
- package/dist/src/tools/create-database.d.ts.map +1 -0
- package/dist/src/tools/create-database.js +57 -0
- package/dist/src/tools/create-page.d.ts +2 -2
- package/dist/src/tools/create-page.d.ts.map +1 -1
- package/dist/src/tools/create-page.js +22 -24
- package/dist/src/tools/get-block-children.d.ts +2 -2
- package/dist/src/tools/get-block-children.d.ts.map +1 -1
- package/dist/src/tools/get-block-children.js +39 -6
- package/dist/src/tools/index.d.ts +13 -11
- package/dist/src/tools/index.d.ts.map +1 -1
- package/dist/src/tools/index.js +13 -9
- package/dist/src/tools/query-database.d.ts +2 -2
- package/dist/src/tools/query-database.d.ts.map +1 -1
- package/dist/src/tools/query-database.js +27 -18
- package/dist/src/tools/retrieve-page.d.ts +2 -2
- package/dist/src/tools/retrieve-page.d.ts.map +1 -1
- package/dist/src/tools/retrieve-page.js +44 -4
- package/dist/src/tools/search.d.ts +2 -2
- package/dist/src/tools/search.d.ts.map +1 -1
- package/dist/src/tools/search.js +18 -12
- package/dist/src/tools/update-database.d.ts +4 -0
- package/dist/src/tools/update-database.d.ts.map +1 -0
- package/dist/src/tools/update-database.js +74 -0
- package/dist/src/tools/update-page.d.ts +2 -2
- package/dist/src/tools/update-page.d.ts.map +1 -1
- package/dist/src/tools/update-page.js +23 -18
- package/dist/src/utils/error-handler.d.ts +1 -1
- package/dist/src/utils/error-handler.d.ts.map +1 -1
- package/dist/src/utils/error-handler.js +14 -14
- package/dist/src/utils/index.d.ts +2 -2
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/index.js +2 -2
- package/dist/src/utils/response-formatter.d.ts +13 -1
- package/dist/src/utils/response-formatter.d.ts.map +1 -1
- package/dist/src/utils/response-formatter.js +46 -3
- package/package.json +11 -2
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { BlockSchema, Heading1BlockSchema, ImageBlockSchema, ParagraphBlockSchema, SyncedBlockSchema, TableBlockSchema, } from './block.js';
|
|
3
|
+
import { ColorSchema, IconSchema, RichTextSchema } from './common.js';
|
|
4
|
+
import { DatabasePropertiesSchema, StatusPropertySchemaSchema } from './database.js';
|
|
5
|
+
import { FilterSchema, PropertyFilterSchema, SortSchema, TimestampFilterSchema } from './filter.js';
|
|
6
|
+
import { PropertyValueSchema, RollupPropertySchema, StatusPropertySchema, TitlePropertySchema, } from './page.js';
|
|
7
|
+
describe('Common Schemas', () => {
|
|
8
|
+
describe('RichTextSchema', () => {
|
|
9
|
+
it('should validate text rich text', () => {
|
|
10
|
+
const textRichText = {
|
|
11
|
+
type: 'text',
|
|
12
|
+
text: { content: 'Hello', link: null },
|
|
13
|
+
annotations: {
|
|
14
|
+
bold: false,
|
|
15
|
+
italic: false,
|
|
16
|
+
strikethrough: false,
|
|
17
|
+
underline: false,
|
|
18
|
+
code: false,
|
|
19
|
+
color: 'default',
|
|
20
|
+
},
|
|
21
|
+
plain_text: 'Hello',
|
|
22
|
+
};
|
|
23
|
+
expect(RichTextSchema.safeParse(textRichText).success).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
it('should validate mention rich text', () => {
|
|
26
|
+
const mentionRichText = {
|
|
27
|
+
type: 'mention',
|
|
28
|
+
mention: {
|
|
29
|
+
type: 'user',
|
|
30
|
+
user: { id: 'user-id', object: 'user' },
|
|
31
|
+
},
|
|
32
|
+
annotations: {
|
|
33
|
+
bold: false,
|
|
34
|
+
italic: false,
|
|
35
|
+
strikethrough: false,
|
|
36
|
+
underline: false,
|
|
37
|
+
code: false,
|
|
38
|
+
color: 'default',
|
|
39
|
+
},
|
|
40
|
+
plain_text: '@User',
|
|
41
|
+
};
|
|
42
|
+
expect(RichTextSchema.safeParse(mentionRichText).success).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
it('should validate equation rich text', () => {
|
|
45
|
+
const equationRichText = {
|
|
46
|
+
type: 'equation',
|
|
47
|
+
equation: { expression: 'E = mc^2' },
|
|
48
|
+
annotations: {
|
|
49
|
+
bold: false,
|
|
50
|
+
italic: false,
|
|
51
|
+
strikethrough: false,
|
|
52
|
+
underline: false,
|
|
53
|
+
code: false,
|
|
54
|
+
color: 'default',
|
|
55
|
+
},
|
|
56
|
+
plain_text: 'E = mc^2',
|
|
57
|
+
};
|
|
58
|
+
expect(RichTextSchema.safeParse(equationRichText).success).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe('IconSchema', () => {
|
|
62
|
+
it('should validate emoji icon', () => {
|
|
63
|
+
const emojiIcon = { type: 'emoji', emoji: '🚀' };
|
|
64
|
+
expect(IconSchema.safeParse(emojiIcon).success).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
it('should validate external icon', () => {
|
|
67
|
+
const externalIcon = {
|
|
68
|
+
type: 'external',
|
|
69
|
+
external: { url: 'https://example.com/icon.png' },
|
|
70
|
+
};
|
|
71
|
+
expect(IconSchema.safeParse(externalIcon).success).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe('ColorSchema', () => {
|
|
75
|
+
it('should validate valid colors', () => {
|
|
76
|
+
const validColors = [
|
|
77
|
+
'default',
|
|
78
|
+
'gray',
|
|
79
|
+
'brown',
|
|
80
|
+
'orange',
|
|
81
|
+
'yellow',
|
|
82
|
+
'green',
|
|
83
|
+
'blue',
|
|
84
|
+
'purple',
|
|
85
|
+
'pink',
|
|
86
|
+
'red',
|
|
87
|
+
'gray_background',
|
|
88
|
+
'brown_background',
|
|
89
|
+
'orange_background',
|
|
90
|
+
];
|
|
91
|
+
for (const color of validColors) {
|
|
92
|
+
expect(ColorSchema.safeParse(color).success).toBe(true);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
it('should reject invalid colors', () => {
|
|
96
|
+
expect(ColorSchema.safeParse('invalid_color').success).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
describe('Block Schemas', () => {
|
|
101
|
+
describe('ParagraphBlockSchema', () => {
|
|
102
|
+
it('should validate paragraph block', () => {
|
|
103
|
+
const paragraph = {
|
|
104
|
+
type: 'paragraph',
|
|
105
|
+
paragraph: {
|
|
106
|
+
rich_text: [{ type: 'text', text: { content: 'Hello' } }],
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
expect(ParagraphBlockSchema.safeParse(paragraph).success).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
it('should validate paragraph with color', () => {
|
|
112
|
+
const paragraph = {
|
|
113
|
+
type: 'paragraph',
|
|
114
|
+
paragraph: {
|
|
115
|
+
rich_text: [],
|
|
116
|
+
color: 'blue',
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
expect(ParagraphBlockSchema.safeParse(paragraph).success).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
describe('Heading1BlockSchema', () => {
|
|
123
|
+
it('should validate heading block with toggleable', () => {
|
|
124
|
+
const heading = {
|
|
125
|
+
type: 'heading_1',
|
|
126
|
+
heading_1: {
|
|
127
|
+
rich_text: [{ type: 'text', text: { content: 'Title' } }],
|
|
128
|
+
is_toggleable: true,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
expect(Heading1BlockSchema.safeParse(heading).success).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
describe('ImageBlockSchema', () => {
|
|
135
|
+
it('should validate external image', () => {
|
|
136
|
+
const image = {
|
|
137
|
+
type: 'image',
|
|
138
|
+
image: {
|
|
139
|
+
type: 'external',
|
|
140
|
+
external: { url: 'https://example.com/image.png' },
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
expect(ImageBlockSchema.safeParse(image).success).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
it('should validate file image', () => {
|
|
146
|
+
const image = {
|
|
147
|
+
type: 'image',
|
|
148
|
+
image: {
|
|
149
|
+
type: 'file',
|
|
150
|
+
file: {
|
|
151
|
+
url: 'https://s3.example.com/image.png',
|
|
152
|
+
expiry_time: '2024-01-01T00:00:00.000Z',
|
|
153
|
+
},
|
|
154
|
+
caption: [],
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
expect(ImageBlockSchema.safeParse(image).success).toBe(true);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
describe('TableBlockSchema', () => {
|
|
161
|
+
it('should validate table block', () => {
|
|
162
|
+
const table = {
|
|
163
|
+
type: 'table',
|
|
164
|
+
table: {
|
|
165
|
+
table_width: 3,
|
|
166
|
+
has_column_header: true,
|
|
167
|
+
has_row_header: false,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
expect(TableBlockSchema.safeParse(table).success).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
describe('SyncedBlockSchema', () => {
|
|
174
|
+
it('should validate original synced block', () => {
|
|
175
|
+
const syncedBlock = {
|
|
176
|
+
type: 'synced_block',
|
|
177
|
+
synced_block: {
|
|
178
|
+
synced_from: null,
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
expect(SyncedBlockSchema.safeParse(syncedBlock).success).toBe(true);
|
|
182
|
+
});
|
|
183
|
+
it('should validate duplicate synced block', () => {
|
|
184
|
+
const syncedBlock = {
|
|
185
|
+
type: 'synced_block',
|
|
186
|
+
synced_block: {
|
|
187
|
+
synced_from: {
|
|
188
|
+
block_id: 'block-id-123',
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
expect(SyncedBlockSchema.safeParse(syncedBlock).success).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
describe('BlockSchema (union)', () => {
|
|
196
|
+
it('should validate various block types', () => {
|
|
197
|
+
const blocks = [
|
|
198
|
+
{ type: 'paragraph', paragraph: { rich_text: [] } },
|
|
199
|
+
{ type: 'heading_1', heading_1: { rich_text: [] } },
|
|
200
|
+
{ type: 'divider', divider: {} },
|
|
201
|
+
{ type: 'breadcrumb', breadcrumb: {} },
|
|
202
|
+
];
|
|
203
|
+
for (const block of blocks) {
|
|
204
|
+
expect(BlockSchema.safeParse(block).success).toBe(true);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
describe('Filter Schemas', () => {
|
|
210
|
+
describe('PropertyFilterSchema', () => {
|
|
211
|
+
it('should validate text filter', () => {
|
|
212
|
+
const filter = {
|
|
213
|
+
property: 'Name',
|
|
214
|
+
title: { contains: 'test' },
|
|
215
|
+
};
|
|
216
|
+
expect(PropertyFilterSchema.safeParse(filter).success).toBe(true);
|
|
217
|
+
});
|
|
218
|
+
it('should validate number filter', () => {
|
|
219
|
+
const filter = {
|
|
220
|
+
property: 'Price',
|
|
221
|
+
number: { greater_than: 100 },
|
|
222
|
+
};
|
|
223
|
+
expect(PropertyFilterSchema.safeParse(filter).success).toBe(true);
|
|
224
|
+
});
|
|
225
|
+
it('should validate checkbox filter', () => {
|
|
226
|
+
const filter = {
|
|
227
|
+
property: 'Done',
|
|
228
|
+
checkbox: { equals: true },
|
|
229
|
+
};
|
|
230
|
+
expect(PropertyFilterSchema.safeParse(filter).success).toBe(true);
|
|
231
|
+
});
|
|
232
|
+
it('should validate date filter with this_week', () => {
|
|
233
|
+
const filter = {
|
|
234
|
+
property: 'Due Date',
|
|
235
|
+
date: { this_week: {} },
|
|
236
|
+
};
|
|
237
|
+
expect(PropertyFilterSchema.safeParse(filter).success).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
it('should validate rollup filter', () => {
|
|
240
|
+
const filter = {
|
|
241
|
+
property: 'Tasks',
|
|
242
|
+
rollup: {
|
|
243
|
+
any: {
|
|
244
|
+
status: { equals: 'Done' },
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
expect(PropertyFilterSchema.safeParse(filter).success).toBe(true);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
describe('TimestampFilterSchema', () => {
|
|
252
|
+
it('should validate created_time filter', () => {
|
|
253
|
+
const filter = {
|
|
254
|
+
timestamp: 'created_time',
|
|
255
|
+
created_time: { after: '2024-01-01' },
|
|
256
|
+
};
|
|
257
|
+
expect(TimestampFilterSchema.safeParse(filter).success).toBe(true);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
describe('FilterSchema (compound)', () => {
|
|
261
|
+
it('should validate AND compound filter', () => {
|
|
262
|
+
const filter = {
|
|
263
|
+
and: [
|
|
264
|
+
{ property: 'Name', title: { contains: 'test' } },
|
|
265
|
+
{ property: 'Done', checkbox: { equals: true } },
|
|
266
|
+
],
|
|
267
|
+
};
|
|
268
|
+
expect(FilterSchema.safeParse(filter).success).toBe(true);
|
|
269
|
+
});
|
|
270
|
+
it('should validate OR compound filter', () => {
|
|
271
|
+
const filter = {
|
|
272
|
+
or: [
|
|
273
|
+
{ property: 'Status', status: { equals: 'Active' } },
|
|
274
|
+
{ property: 'Status', status: { equals: 'Pending' } },
|
|
275
|
+
],
|
|
276
|
+
};
|
|
277
|
+
expect(FilterSchema.safeParse(filter).success).toBe(true);
|
|
278
|
+
});
|
|
279
|
+
it('should validate nested compound filter', () => {
|
|
280
|
+
const filter = {
|
|
281
|
+
and: [
|
|
282
|
+
{
|
|
283
|
+
or: [
|
|
284
|
+
{ property: 'Type', select: { equals: 'A' } },
|
|
285
|
+
{ property: 'Type', select: { equals: 'B' } },
|
|
286
|
+
],
|
|
287
|
+
},
|
|
288
|
+
{ property: 'Active', checkbox: { equals: true } },
|
|
289
|
+
],
|
|
290
|
+
};
|
|
291
|
+
expect(FilterSchema.safeParse(filter).success).toBe(true);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
describe('SortSchema', () => {
|
|
295
|
+
it('should validate property sort', () => {
|
|
296
|
+
const sort = {
|
|
297
|
+
property: 'Name',
|
|
298
|
+
direction: 'ascending',
|
|
299
|
+
};
|
|
300
|
+
expect(SortSchema.safeParse(sort).success).toBe(true);
|
|
301
|
+
});
|
|
302
|
+
it('should validate timestamp sort', () => {
|
|
303
|
+
const sort = {
|
|
304
|
+
timestamp: 'created_time',
|
|
305
|
+
direction: 'descending',
|
|
306
|
+
};
|
|
307
|
+
expect(SortSchema.safeParse(sort).success).toBe(true);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
describe('Page Property Schemas', () => {
|
|
312
|
+
describe('TitlePropertySchema', () => {
|
|
313
|
+
it('should validate title property', () => {
|
|
314
|
+
const title = {
|
|
315
|
+
type: 'title',
|
|
316
|
+
title: [{ type: 'text', text: { content: 'My Page' } }],
|
|
317
|
+
};
|
|
318
|
+
expect(TitlePropertySchema.safeParse(title).success).toBe(true);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
describe('StatusPropertySchema', () => {
|
|
322
|
+
it('should validate status property', () => {
|
|
323
|
+
const status = {
|
|
324
|
+
type: 'status',
|
|
325
|
+
status: {
|
|
326
|
+
name: 'In Progress',
|
|
327
|
+
color: 'blue',
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
expect(StatusPropertySchema.safeParse(status).success).toBe(true);
|
|
331
|
+
});
|
|
332
|
+
it('should validate null status', () => {
|
|
333
|
+
const status = {
|
|
334
|
+
type: 'status',
|
|
335
|
+
status: null,
|
|
336
|
+
};
|
|
337
|
+
expect(StatusPropertySchema.safeParse(status).success).toBe(true);
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
describe('RollupPropertySchema', () => {
|
|
341
|
+
it('should validate number rollup', () => {
|
|
342
|
+
const rollup = {
|
|
343
|
+
type: 'rollup',
|
|
344
|
+
rollup: {
|
|
345
|
+
type: 'number',
|
|
346
|
+
number: 42,
|
|
347
|
+
function: 'sum',
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
expect(RollupPropertySchema.safeParse(rollup).success).toBe(true);
|
|
351
|
+
});
|
|
352
|
+
it('should validate array rollup', () => {
|
|
353
|
+
const rollup = {
|
|
354
|
+
type: 'rollup',
|
|
355
|
+
rollup: {
|
|
356
|
+
type: 'array',
|
|
357
|
+
array: [
|
|
358
|
+
{ type: 'title', title: [{ type: 'text', text: { content: 'Item 1' } }] },
|
|
359
|
+
{ type: 'title', title: [{ type: 'text', text: { content: 'Item 2' } }] },
|
|
360
|
+
],
|
|
361
|
+
function: 'show_original',
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
expect(RollupPropertySchema.safeParse(rollup).success).toBe(true);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
describe('PropertyValueSchema (union)', () => {
|
|
368
|
+
it('should validate various property types', () => {
|
|
369
|
+
const properties = [
|
|
370
|
+
{ type: 'title', title: [] },
|
|
371
|
+
{ type: 'rich_text', rich_text: [] },
|
|
372
|
+
{ type: 'number', number: 123 },
|
|
373
|
+
{ type: 'checkbox', checkbox: true },
|
|
374
|
+
{ type: 'url', url: 'https://example.com' },
|
|
375
|
+
];
|
|
376
|
+
for (const prop of properties) {
|
|
377
|
+
expect(PropertyValueSchema.safeParse(prop).success).toBe(true);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
describe('Database Schemas', () => {
|
|
383
|
+
describe('StatusPropertySchemaSchema', () => {
|
|
384
|
+
it('should validate status property schema', () => {
|
|
385
|
+
// Note: Status options cannot be specified at creation time
|
|
386
|
+
// Notion creates default options automatically
|
|
387
|
+
const statusSchema = { status: {} };
|
|
388
|
+
expect(StatusPropertySchemaSchema.safeParse(statusSchema).success).toBe(true);
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
describe('DatabasePropertiesSchema', () => {
|
|
392
|
+
it('should validate database properties', () => {
|
|
393
|
+
const properties = {
|
|
394
|
+
Name: { title: {} },
|
|
395
|
+
Description: { rich_text: {} },
|
|
396
|
+
Price: { number: { format: 'dollar' } },
|
|
397
|
+
Status: { status: {} }, // Status options created automatically by Notion
|
|
398
|
+
Tags: {
|
|
399
|
+
multi_select: {
|
|
400
|
+
options: [
|
|
401
|
+
{ name: 'Tag1', color: 'blue' },
|
|
402
|
+
{ name: 'Tag2', color: 'red' },
|
|
403
|
+
],
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
Priority: {
|
|
407
|
+
select: {
|
|
408
|
+
options: [
|
|
409
|
+
{ name: 'High', color: 'red' },
|
|
410
|
+
{ name: 'Low', color: 'green' },
|
|
411
|
+
],
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
expect(DatabasePropertiesSchema.safeParse(properties).success).toBe(true);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
});
|
package/dist/src/server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { McpServer } from
|
|
2
|
-
import { StdioServerTransport } from
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
export declare function startServer(): Promise<void>;
|
|
4
4
|
export { McpServer, StdioServerTransport };
|
|
5
5
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/src/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAMhF,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAWjD;AAED,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,CAAA"}
|
package/dist/src/server.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { McpServer } from
|
|
2
|
-
import { StdioServerTransport } from
|
|
3
|
-
import { createNotionClient } from
|
|
4
|
-
import { registerAllTools } from
|
|
5
|
-
const PACKAGE_VERSION =
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { createNotionClient } from './notion-client.js';
|
|
4
|
+
import { registerAllTools } from './tools/index.js';
|
|
5
|
+
const PACKAGE_VERSION = '0.1.0';
|
|
6
6
|
export async function startServer() {
|
|
7
7
|
const server = new McpServer({
|
|
8
|
-
name:
|
|
8
|
+
name: 'notion-mcp-server',
|
|
9
9
|
version: PACKAGE_VERSION,
|
|
10
10
|
});
|
|
11
11
|
const notion = createNotionClient();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { McpServer } from
|
|
2
|
-
import type { NotionClient } from
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { NotionClient } from '../notion-client.js';
|
|
3
3
|
export declare function registerAppendBlockChildren(server: McpServer, notion: NotionClient): void;
|
|
4
4
|
//# sourceMappingURL=append-block-children.d.ts.map
|
|
@@ -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,
|
|
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,23 +1,29 @@
|
|
|
1
|
-
import { z } from
|
|
2
|
-
import {
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BlockChildrenSchema } from '../schemas/block.js';
|
|
3
|
+
import { formatResponse, handleError } from '../utils/index.js';
|
|
3
4
|
const inputSchema = {
|
|
4
|
-
block_id: z.string().describe(
|
|
5
|
-
children:
|
|
6
|
-
.
|
|
7
|
-
.describe("Array of block objects to append. " +
|
|
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
8
|
'Example: [{ "type": "paragraph", "paragraph": { "rich_text": [{ "text": { "content": "Hello" } }] } }]'),
|
|
9
9
|
after: z
|
|
10
10
|
.string()
|
|
11
11
|
.optional()
|
|
12
|
-
.describe(
|
|
13
|
-
|
|
12
|
+
.describe('The ID of a block to insert the new children after. ' +
|
|
13
|
+
'If not provided, children are appended at the end.'),
|
|
14
14
|
};
|
|
15
15
|
export function registerAppendBlockChildren(server, notion) {
|
|
16
|
-
server.
|
|
16
|
+
server.registerTool('append-block-children', {
|
|
17
|
+
description: 'Append new blocks as children to a block or page. ' +
|
|
18
|
+
'Supports all block types: paragraph, headings, lists, code, images, etc. ' +
|
|
19
|
+
'Returns the created blocks with their IDs. ' +
|
|
20
|
+
'Use the "after" parameter to insert blocks at a specific position.',
|
|
21
|
+
inputSchema,
|
|
22
|
+
}, async ({ block_id, children, after }) => {
|
|
17
23
|
try {
|
|
18
24
|
const params = {
|
|
19
25
|
block_id,
|
|
20
|
-
children,
|
|
26
|
+
children: children,
|
|
21
27
|
};
|
|
22
28
|
if (after) {
|
|
23
29
|
params.after = after;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { McpServer } from
|
|
2
|
-
import type { NotionClient } from
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { NotionClient } from '../notion-client.js';
|
|
3
3
|
export declare function registerCreateComment(server: McpServer, notion: NotionClient): void;
|
|
4
4
|
//# sourceMappingURL=create-comment.d.ts.map
|
|
@@ -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,
|
|
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,19 +1,25 @@
|
|
|
1
|
-
import { z } from
|
|
2
|
-
import {
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { RichTextArraySchema } from '../schemas/common.js';
|
|
3
|
+
import { formatResponse, handleError } from '../utils/index.js';
|
|
3
4
|
const inputSchema = {
|
|
4
|
-
page_id: z.string().describe(
|
|
5
|
-
rich_text:
|
|
6
|
-
.
|
|
7
|
-
.describe("The comment content as rich text. " +
|
|
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
8
|
'Example: [{ "type": "text", "text": { "content": "This is a comment" } }]'),
|
|
9
9
|
discussion_id: z
|
|
10
10
|
.string()
|
|
11
11
|
.optional()
|
|
12
|
-
.describe(
|
|
13
|
-
|
|
12
|
+
.describe('Optional ID of an existing discussion to add the comment to. ' +
|
|
13
|
+
'If not provided, a new discussion is created.'),
|
|
14
14
|
};
|
|
15
15
|
export function registerCreateComment(server, notion) {
|
|
16
|
-
server.
|
|
16
|
+
server.registerTool('create-comment', {
|
|
17
|
+
description: 'Add a comment to a Notion page. Creates a new discussion or adds to an existing one. ' +
|
|
18
|
+
'Comments support rich text formatting (bold, italic, links, etc.). ' +
|
|
19
|
+
'Use discussion_id to reply to an existing comment thread. ' +
|
|
20
|
+
'Returns the created comment with its ID.',
|
|
21
|
+
inputSchema,
|
|
22
|
+
}, async ({ page_id, rich_text, discussion_id }) => {
|
|
17
23
|
try {
|
|
18
24
|
// The Notion API requires either parent or discussion_id
|
|
19
25
|
const params = {
|
|
@@ -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 registerCreateDatabase(server: McpServer, notion: NotionClient): void;
|
|
4
|
+
//# sourceMappingURL=create-database.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { CoverSchema, IconSchema, RichTextArraySchema, } from '../schemas/common.js';
|
|
3
|
+
import { DatabasePropertiesSchema } from '../schemas/database.js';
|
|
4
|
+
import { formatResponse, handleError } from '../utils/index.js';
|
|
5
|
+
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.'),
|
|
25
|
+
};
|
|
26
|
+
export function registerCreateDatabase(server, notion) {
|
|
27
|
+
server.registerTool('create-database', {
|
|
28
|
+
description: 'Create a new database as a subpage of an existing Notion page. ' +
|
|
29
|
+
'Requires a parent_page_id and properties object defining the database schema. ' +
|
|
30
|
+
'Each database must have exactly one title property.',
|
|
31
|
+
inputSchema,
|
|
32
|
+
}, async ({ parent_page_id, title, properties, icon, cover, is_inline }) => {
|
|
33
|
+
try {
|
|
34
|
+
const params = {
|
|
35
|
+
parent: { page_id: parent_page_id },
|
|
36
|
+
properties: properties,
|
|
37
|
+
};
|
|
38
|
+
if (title) {
|
|
39
|
+
params.title = title;
|
|
40
|
+
}
|
|
41
|
+
if (icon) {
|
|
42
|
+
params.icon = icon;
|
|
43
|
+
}
|
|
44
|
+
if (cover) {
|
|
45
|
+
params.cover = cover;
|
|
46
|
+
}
|
|
47
|
+
if (is_inline !== undefined) {
|
|
48
|
+
params.is_inline = is_inline;
|
|
49
|
+
}
|
|
50
|
+
const response = await notion.databases.create(params);
|
|
51
|
+
return formatResponse(response);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
return handleError(error);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { McpServer } from
|
|
2
|
-
import type { NotionClient } from
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { NotionClient } from '../notion-client.js';
|
|
3
3
|
export declare function registerCreatePage(server: McpServer, notion: NotionClient): void;
|
|
4
4
|
//# sourceMappingURL=create-page.d.ts.map
|
|
@@ -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,
|
|
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"}
|