@atikk-co-jp/notion-mcp-server 0.7.0 → 0.8.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 (134) hide show
  1. package/README.ja.md +1 -0
  2. package/README.md +1 -0
  3. package/dist/src/converters/__tests__/block-to-markdown.test.js +482 -111
  4. package/dist/src/converters/__tests__/markdown-to-blocks.test.js +116 -13
  5. package/dist/src/converters/__tests__/page-to-markdown.test.js +83 -70
  6. package/dist/src/converters/__tests__/rich-text-to-markdown.test.js +23 -26
  7. package/dist/src/converters/block-to-markdown.d.ts +4 -13
  8. package/dist/src/converters/block-to-markdown.d.ts.map +1 -1
  9. package/dist/src/converters/block-to-markdown.js +117 -120
  10. package/dist/src/converters/index.d.ts +3 -3
  11. package/dist/src/converters/index.d.ts.map +1 -1
  12. package/dist/src/converters/index.js +1 -1
  13. package/dist/src/converters/markdown-to-blocks.d.ts +21 -7
  14. package/dist/src/converters/markdown-to-blocks.d.ts.map +1 -1
  15. package/dist/src/converters/markdown-to-blocks.js +56 -0
  16. package/dist/src/converters/page-to-markdown.d.ts +5 -20
  17. package/dist/src/converters/page-to-markdown.d.ts.map +1 -1
  18. package/dist/src/converters/page-to-markdown.js +47 -37
  19. package/dist/src/converters/rich-text-to-markdown.d.ts +3 -47
  20. package/dist/src/converters/rich-text-to-markdown.d.ts.map +1 -1
  21. package/dist/src/converters/rich-text-to-markdown.js +15 -14
  22. package/dist/src/notion-client.d.ts +5 -177
  23. package/dist/src/notion-client.d.ts.map +1 -1
  24. package/dist/src/notion-client.js +6 -172
  25. package/dist/src/schemas/descriptions/examples.d.ts +14 -0
  26. package/dist/src/schemas/descriptions/examples.d.ts.map +1 -0
  27. package/dist/src/schemas/descriptions/examples.js +87 -0
  28. package/dist/src/schemas/descriptions/fields.d.ts +146 -0
  29. package/dist/src/schemas/descriptions/fields.d.ts.map +1 -0
  30. package/dist/src/schemas/descriptions/fields.js +184 -0
  31. package/dist/src/schemas/descriptions/index.d.ts +3 -0
  32. package/dist/src/schemas/descriptions/index.d.ts.map +1 -0
  33. package/dist/src/schemas/descriptions/index.js +2 -0
  34. package/dist/src/schemas/index.d.ts +1 -5
  35. package/dist/src/schemas/index.d.ts.map +1 -1
  36. package/dist/src/schemas/index.js +2 -10
  37. package/dist/src/tools/__tests__/context-size.test.d.ts +2 -0
  38. package/dist/src/tools/__tests__/context-size.test.d.ts.map +1 -0
  39. package/dist/src/tools/__tests__/context-size.test.js +143 -0
  40. package/dist/src/tools/__tests__/error-handler.test.d.ts +2 -0
  41. package/dist/src/tools/__tests__/error-handler.test.d.ts.map +1 -0
  42. package/dist/src/tools/__tests__/error-handler.test.js +125 -0
  43. package/dist/src/tools/append-block-children.d.ts.map +1 -1
  44. package/dist/src/tools/append-block-children.js +8 -5
  45. package/dist/src/tools/append-blocks-simple.d.ts.map +1 -1
  46. package/dist/src/tools/append-blocks-simple.js +9 -13
  47. package/dist/src/tools/archive-database.d.ts.map +1 -1
  48. package/dist/src/tools/archive-database.js +3 -2
  49. package/dist/src/tools/archive-page.d.ts.map +1 -1
  50. package/dist/src/tools/archive-page.js +2 -1
  51. package/dist/src/tools/create-comment-simple.d.ts.map +1 -1
  52. package/dist/src/tools/create-comment-simple.js +5 -4
  53. package/dist/src/tools/create-comment.d.ts.map +1 -1
  54. package/dist/src/tools/create-comment.js +9 -6
  55. package/dist/src/tools/create-database.d.ts.map +1 -1
  56. package/dist/src/tools/create-database.js +19 -25
  57. package/dist/src/tools/create-page-simple.d.ts +1 -1
  58. package/dist/src/tools/create-page-simple.d.ts.map +1 -1
  59. package/dist/src/tools/create-page-simple.js +26 -27
  60. package/dist/src/tools/create-page.d.ts.map +1 -1
  61. package/dist/src/tools/create-page.js +10 -6
  62. package/dist/src/tools/delete-block.d.ts.map +1 -1
  63. package/dist/src/tools/delete-block.js +2 -1
  64. package/dist/src/tools/get-block-children.d.ts +1 -1
  65. package/dist/src/tools/get-block-children.d.ts.map +1 -1
  66. package/dist/src/tools/get-block-children.js +13 -27
  67. package/dist/src/tools/index.d.ts +2 -2
  68. package/dist/src/tools/index.d.ts.map +1 -1
  69. package/dist/src/tools/index.js +2 -2
  70. package/dist/src/tools/list-comments.d.ts +1 -1
  71. package/dist/src/tools/list-comments.d.ts.map +1 -1
  72. package/dist/src/tools/list-comments.js +11 -10
  73. package/dist/src/tools/list-users.d.ts.map +1 -1
  74. package/dist/src/tools/list-users.js +4 -3
  75. package/dist/src/tools/move-page.d.ts.map +1 -1
  76. package/dist/src/tools/move-page.js +5 -4
  77. package/dist/src/tools/query-data-source.d.ts +1 -1
  78. package/dist/src/tools/query-data-source.d.ts.map +1 -1
  79. package/dist/src/tools/query-data-source.js +23 -30
  80. package/dist/src/tools/retrieve-block.d.ts +1 -1
  81. package/dist/src/tools/retrieve-block.d.ts.map +1 -1
  82. package/dist/src/tools/retrieve-block.js +13 -10
  83. package/dist/src/tools/retrieve-bot-user.js +1 -1
  84. package/dist/src/tools/retrieve-data-source.d.ts +1 -1
  85. package/dist/src/tools/retrieve-data-source.d.ts.map +1 -1
  86. package/dist/src/tools/retrieve-data-source.js +15 -11
  87. package/dist/src/tools/retrieve-database.d.ts +1 -1
  88. package/dist/src/tools/retrieve-database.d.ts.map +1 -1
  89. package/dist/src/tools/retrieve-database.js +11 -6
  90. package/dist/src/tools/retrieve-page-property.d.ts.map +1 -1
  91. package/dist/src/tools/retrieve-page-property.js +6 -5
  92. package/dist/src/tools/retrieve-page.d.ts +1 -1
  93. package/dist/src/tools/retrieve-page.d.ts.map +1 -1
  94. package/dist/src/tools/retrieve-page.js +15 -20
  95. package/dist/src/tools/retrieve-user.d.ts.map +1 -1
  96. package/dist/src/tools/retrieve-user.js +2 -1
  97. package/dist/src/tools/search.d.ts.map +1 -1
  98. package/dist/src/tools/search.js +10 -17
  99. package/dist/src/tools/update-block-simple.d.ts +1 -1
  100. package/dist/src/tools/update-block-simple.d.ts.map +1 -1
  101. package/dist/src/tools/update-block-simple.js +14 -4
  102. package/dist/src/tools/update-block.d.ts.map +1 -1
  103. package/dist/src/tools/update-block.js +8 -5
  104. package/dist/src/tools/update-data-source.d.ts.map +1 -1
  105. package/dist/src/tools/update-data-source.js +7 -7
  106. package/dist/src/tools/update-database.d.ts.map +1 -1
  107. package/dist/src/tools/update-database.js +22 -32
  108. package/dist/src/tools/update-page.d.ts.map +1 -1
  109. package/dist/src/tools/update-page.js +11 -8
  110. package/dist/src/utils/error-handler.d.ts +16 -7
  111. package/dist/src/utils/error-handler.d.ts.map +1 -1
  112. package/dist/src/utils/error-handler.js +44 -17
  113. package/dist/src/utils/index.d.ts +1 -1
  114. package/dist/src/utils/index.d.ts.map +1 -1
  115. package/dist/src/utils/index.js +1 -1
  116. package/package.json +2 -1
  117. package/dist/src/schemas/block.d.ts +0 -3787
  118. package/dist/src/schemas/block.d.ts.map +0 -1
  119. package/dist/src/schemas/block.js +0 -402
  120. package/dist/src/schemas/common.d.ts +0 -638
  121. package/dist/src/schemas/common.d.ts.map +0 -1
  122. package/dist/src/schemas/common.js +0 -163
  123. package/dist/src/schemas/database.d.ts +0 -687
  124. package/dist/src/schemas/database.d.ts.map +0 -1
  125. package/dist/src/schemas/database.js +0 -258
  126. package/dist/src/schemas/filter.d.ts +0 -611
  127. package/dist/src/schemas/filter.d.ts.map +0 -1
  128. package/dist/src/schemas/filter.js +0 -222
  129. package/dist/src/schemas/page.d.ts +0 -2607
  130. package/dist/src/schemas/page.d.ts.map +0 -1
  131. package/dist/src/schemas/page.js +0 -328
  132. package/dist/src/schemas/schemas.test.d.ts +0 -2
  133. package/dist/src/schemas/schemas.test.d.ts.map +0 -1
  134. package/dist/src/schemas/schemas.test.js +0 -418
@@ -1,10 +1,2 @@
1
- // Database schemas
2
- // Block schemas
3
- export { AudioBlockSchema, BlockChildrenSchema, BlockSchema, BookmarkBlockSchema, BreadcrumbBlockSchema, BulletedListItemBlockSchema, CalloutBlockSchema, ChildDatabaseBlockSchema, ChildPageBlockSchema, CodeBlockSchema, ColumnBlockSchema, ColumnListBlockSchema, DividerBlockSchema, EmbedBlockSchema, EmojiIconBlockSchema, EquationBlockSchema, ExternalFileSchema, ExternalIconBlockSchema, FileBlockSchema, FileIconBlockSchema, FileObjectSchema, FileUploadSchema, Heading1BlockSchema, Heading2BlockSchema, Heading3BlockSchema, IconBlockSchema, ImageBlockSchema, LinkPreviewBlockSchema, LinkToPageBlockSchema, NotionFileSchema, NumberedListItemBlockSchema, ParagraphBlockSchema, PdfBlockSchema, QuoteBlockSchema, SyncedBlockDuplicateSchema, SyncedBlockOriginalSchema, SyncedBlockSchema, TableBlockSchema, TableOfContentsBlockSchema, TableRowBlockSchema, TemplateBlockSchema, ToDoBlockSchema, ToggleBlockSchema, UnsupportedBlockSchema, VideoBlockSchema, } from './block.js';
4
- // Common schemas
5
- export { AnnotationsSchema, ColorSchema, CoverSchema, DatabaseMentionSchema, DatabaseParentSchema, DateMentionSchema, EmojiIconSchema, EquationRichTextSchema, ExternalIconSchema, IconSchema, LinkPreviewMentionSchema, MentionRichTextSchema, MentionSchema, PageMentionSchema, PageParentSchema, ParentSchema, RichTextArraySchema, RichTextSchema, SimpleTextSchema, TemplateMentionDateSchema, TemplateMentionSchema, TemplateMentionUserSchema, TextRichTextSchema, UserMentionSchema, } from './common.js';
6
- export { CheckboxPropertySchemaSchema, CreatedByPropertySchemaSchema, CreatedTimePropertySchemaSchema, DatabasePropertiesSchema, DatabasePropertySchemaSchema, DatabaseTitleSchema, DatePropertySchemaSchema, EmailPropertySchemaSchema, FilesPropertySchemaSchema, FormulaPropertySchemaSchema, LastEditedByPropertySchemaSchema, LastEditedTimePropertySchemaSchema, MultiSelectPropertySchemaSchema, NumberPropertySchemaSchema, PeoplePropertySchemaSchema, PhoneNumberPropertySchemaSchema, RelationPropertySchemaSchema, RichTextPropertySchemaSchema, RollupPropertySchemaSchema, SelectOptionSchema, SelectPropertySchemaSchema, TitlePropertySchemaSchema, UrlPropertySchemaSchema, } from './database.js';
7
- // Filter and sort schemas
8
- export { FilterSchema, PropertyFilterSchema, SortSchema, SortsSchema, TimestampFilterSchema, } from './filter.js';
9
- // Page property schemas
10
- export { CheckboxPropertySchema, CreatedByPropertySchema, CreatedTimePropertySchema, DatePropertySchema, EmailPropertySchema, FilesPropertySchema, FormulaPropertySchema, LastEditedByPropertySchema, LastEditedTimePropertySchema, MultiSelectPropertySchema, NumberPropertySchema, PeoplePropertySchema, PhoneNumberPropertySchema, PropertiesSchema, PropertyValueSchema, RelationPropertySchema, RichTextPropertySchema, RollupPropertySchema, SelectPropertySchema, StatusPropertySchema, TitlePropertySchema, UniqueIdPropertySchema, UrlPropertySchema, UserObjectSchema, VerificationPropertySchema, } from './page.js';
1
+ // Field descriptions for MCP tool schemas
2
+ export { F, Fields } from './descriptions/index.js';
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=context-size.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-size.test.d.ts","sourceRoot":"","sources":["../../../../src/tools/__tests__/context-size.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,143 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { toJSONSchema } from 'zod/v4/core';
4
+ import { NotionClient } from '../../notion-client.js';
5
+ import { registerAllTools } from '../index.js';
6
+ /**
7
+ * Test that all tools don't consume excessive context when registered with MCP.
8
+ *
9
+ * Context is consumed by:
10
+ * 1. Tool description - the description string passed to registerTool
11
+ * 2. Input schema - converted to JSON Schema via toJSONSchema (Zod v4)
12
+ *
13
+ * These tests measure the actual JSON Schema that MCP sends to LLMs.
14
+ * Token counts are approximated as chars/4 (rough estimate for JSON).
15
+ */
16
+ // Thresholds in estimated tokens (chars / 4)
17
+ // Based on actual MCP tool registration token counts from user data
18
+ const SINGLE_TOOL_MAX_TOKENS = 1500; // Max tokens for a single tool
19
+ const TOTAL_TOOLS_MAX_TOKENS = 30000; // Max total tokens for all tools combined
20
+ const TOTAL_TOOLS_WARNING_TOKENS = 35000; // Warning threshold
21
+ // Known large tools that need optimization (tracked for future improvement)
22
+ // NOTE: query-data-source, update-database, create-database were optimized using
23
+ // z.any() with FieldDescriptions, reducing ~13,500 tokens. All tools now fit within
24
+ // the standard SINGLE_TOOL_MAX_TOKENS threshold.
25
+ const KNOWN_LARGE_TOOLS = {
26
+ // Currently empty - all tools have been optimized
27
+ };
28
+ // Access internal registered tools from McpServer
29
+ // Using type assertion chain to avoid explicit any
30
+ function getRegisteredTools(server) {
31
+ return server
32
+ ._registeredTools;
33
+ }
34
+ // Create a mock notion client for testing (won't make actual API calls)
35
+ function createMockNotionClient() {
36
+ return new NotionClient({ auth: 'test-token' });
37
+ }
38
+ // Convert Zod schema to JSON Schema (same as MCP does internally)
39
+ function zodToJsonSchema(zodSchema) {
40
+ try {
41
+ // Use Zod v4's toJSONSchema - same as MCP SDK uses
42
+ return toJSONSchema(zodSchema);
43
+ }
44
+ catch {
45
+ // Fallback for non-Zod schemas
46
+ return {};
47
+ }
48
+ }
49
+ // Calculate the context size for a single tool (in estimated tokens)
50
+ function calculateToolContextSize(tool) {
51
+ const descriptionChars = tool.description?.length ?? 0;
52
+ // Convert to JSON Schema (same as MCP does)
53
+ const jsonSchema = tool.inputSchema ? zodToJsonSchema(tool.inputSchema) : {};
54
+ const schemaChars = JSON.stringify(jsonSchema).length;
55
+ const totalChars = descriptionChars + schemaChars;
56
+ // Rough token estimate: ~4 chars per token for JSON
57
+ const estimatedTokens = Math.ceil(totalChars / 4);
58
+ return {
59
+ descriptionChars,
60
+ schemaChars,
61
+ totalChars,
62
+ estimatedTokens,
63
+ };
64
+ }
65
+ describe('Tool context size', () => {
66
+ const server = new McpServer({ name: 'test-server', version: '1.0.0' });
67
+ const notion = createMockNotionClient();
68
+ // Register all tools
69
+ registerAllTools(server, notion);
70
+ const registeredTools = getRegisteredTools(server);
71
+ const toolNames = Object.keys(registeredTools);
72
+ describe('Individual tool sizes', () => {
73
+ it.each(toolNames)('%s should not exceed token limit', (toolName) => {
74
+ const tool = registeredTools[toolName];
75
+ const { estimatedTokens, descriptionChars, schemaChars } = calculateToolContextSize(tool);
76
+ // Check if this is a known large tool with special limits
77
+ const knownLargeTool = KNOWN_LARGE_TOOLS[toolName];
78
+ const maxAllowed = knownLargeTool?.maxAllowed ?? SINGLE_TOOL_MAX_TOKENS;
79
+ // Log sizes for debugging (visible in verbose mode)
80
+ console.log(`${toolName}: ~${estimatedTokens} tokens (desc=${descriptionChars}, schema=${schemaChars} chars)` +
81
+ (knownLargeTool ? ` [KNOWN LARGE: ${knownLargeTool.reason}]` : ''));
82
+ expect(estimatedTokens).toBeLessThanOrEqual(maxAllowed);
83
+ });
84
+ });
85
+ describe('Total context size', () => {
86
+ it('should report total context size and check limits', () => {
87
+ let totalTokens = 0;
88
+ let totalTokensExcludingKnownLarge = 0;
89
+ const toolSizes = [];
90
+ for (const [name, tool] of Object.entries(registeredTools)) {
91
+ const { estimatedTokens } = calculateToolContextSize(tool);
92
+ const isKnownLarge = name in KNOWN_LARGE_TOOLS;
93
+ totalTokens += estimatedTokens;
94
+ if (!isKnownLarge) {
95
+ totalTokensExcludingKnownLarge += estimatedTokens;
96
+ }
97
+ toolSizes.push({ name, tokens: estimatedTokens, isKnownLarge });
98
+ }
99
+ // Sort by size descending for the report
100
+ toolSizes.sort((a, b) => b.tokens - a.tokens);
101
+ console.log('\n=== Tool Context Size Report (Estimated Tokens) ===');
102
+ console.log(`Total tools: ${toolNames.length}`);
103
+ console.log(`Total estimated tokens: ~${totalTokens}`);
104
+ console.log(`Total (excluding known large): ~${totalTokensExcludingKnownLarge}`);
105
+ console.log(`Target limit: ${TOTAL_TOOLS_MAX_TOKENS} tokens`);
106
+ console.log(`Warning limit: ${TOTAL_TOOLS_WARNING_TOKENS} tokens`);
107
+ console.log(`Usage (excluding known large): ${((totalTokensExcludingKnownLarge / TOTAL_TOOLS_MAX_TOKENS) * 100).toFixed(1)}%`);
108
+ console.log('\nTop 10 largest tools:');
109
+ toolSizes.slice(0, 10).forEach(({ name, tokens, isKnownLarge }, i) => {
110
+ const marker = isKnownLarge ? ' [KNOWN LARGE]' : '';
111
+ console.log(` ${i + 1}. ${name}: ~${tokens} tokens${marker}`);
112
+ });
113
+ // Check that tools excluding known large ones are within target
114
+ expect(totalTokensExcludingKnownLarge).toBeLessThanOrEqual(TOTAL_TOOLS_MAX_TOKENS);
115
+ // Warn if total exceeds warning threshold (test still passes but logs warning)
116
+ if (totalTokens > TOTAL_TOOLS_WARNING_TOKENS) {
117
+ console.warn(`\n⚠️ WARNING: Total tokens (~${totalTokens}) exceeds warning threshold (${TOTAL_TOOLS_WARNING_TOKENS})`);
118
+ console.warn(' Consider optimizing known large tools by replacing complex schemas with z.any()');
119
+ }
120
+ });
121
+ });
122
+ describe('Schema complexity checks', () => {
123
+ it.each(toolNames)('%s should use z.any() for complex nested structures', (toolName) => {
124
+ const tool = registeredTools[toolName];
125
+ if (!tool.inputSchema)
126
+ return;
127
+ const schemaStr = JSON.stringify(tool.inputSchema);
128
+ // Check that complex properties don't have deeply nested definitions
129
+ // If they did, the schema would contain many "properties" keys
130
+ const propertiesCount = (schemaStr.match(/"properties":/g) || []).length;
131
+ // Allow max 2 levels of nesting (root + one level of additionalProperties)
132
+ expect(propertiesCount).toBeLessThanOrEqual(3);
133
+ });
134
+ });
135
+ describe('Description length checks', () => {
136
+ const MAX_DESCRIPTION_LENGTH = 500;
137
+ it.each(toolNames)('%s description should be concise', (toolName) => {
138
+ const tool = registeredTools[toolName];
139
+ const descLength = tool.description?.length ?? 0;
140
+ expect(descLength).toBeLessThanOrEqual(MAX_DESCRIPTION_LENGTH);
141
+ });
142
+ });
143
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=error-handler.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-handler.test.d.ts","sourceRoot":"","sources":["../../../../src/tools/__tests__/error-handler.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,125 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { describe, expect, it } from 'vitest';
4
+ const toolsDir = resolve(import.meta.dirname, '..');
5
+ const toolErrorConfigs = [
6
+ // Tools using handleErrorWithContext
7
+ { toolFile: 'append-block-children.ts', handler: 'handleErrorWithContext', exampleType: 'block' },
8
+ {
9
+ toolFile: 'create-comment.ts',
10
+ handler: 'handleErrorWithContext',
11
+ exampleType: 'richTextArray',
12
+ },
13
+ { toolFile: 'create-database.ts', handler: 'handleErrorWithContext', exampleType: 'schema' },
14
+ {
15
+ toolFile: 'create-page.ts',
16
+ handler: 'handleErrorWithContext',
17
+ exampleType: 'page',
18
+ hasDataSourceId: true,
19
+ },
20
+ {
21
+ toolFile: 'create-page-simple.ts',
22
+ handler: 'handleErrorWithContext',
23
+ exampleType: 'page',
24
+ hasDataSourceId: true,
25
+ },
26
+ { toolFile: 'query-data-source.ts', handler: 'handleErrorWithContext', exampleType: 'filter' },
27
+ { toolFile: 'search.ts', handler: 'handleErrorWithContext', exampleType: 'filter' },
28
+ { toolFile: 'update-block.ts', handler: 'handleErrorWithContext', exampleType: 'block' },
29
+ {
30
+ toolFile: 'update-data-source.ts',
31
+ handler: 'handleErrorWithContext',
32
+ exampleType: 'schema',
33
+ hasDataSourceId: true,
34
+ },
35
+ {
36
+ toolFile: 'update-database.ts',
37
+ handler: 'handleErrorWithContext',
38
+ exampleType: 'richTextArray',
39
+ },
40
+ { toolFile: 'update-page.ts', handler: 'handleErrorWithContext', exampleType: 'page' },
41
+ // Tools using handleError (no contextual help needed)
42
+ { toolFile: 'append-blocks-simple.ts', handler: 'handleError' },
43
+ { toolFile: 'archive-database.ts', handler: 'handleError' },
44
+ { toolFile: 'archive-page.ts', handler: 'handleError' },
45
+ { toolFile: 'create-comment-simple.ts', handler: 'handleError' },
46
+ { toolFile: 'delete-block.ts', handler: 'handleError' },
47
+ { toolFile: 'get-block-children.ts', handler: 'handleError' },
48
+ { toolFile: 'list-comments.ts', handler: 'handleError' },
49
+ { toolFile: 'list-users.ts', handler: 'handleError' },
50
+ { toolFile: 'move-page.ts', handler: 'handleError' },
51
+ { toolFile: 'retrieve-block.ts', handler: 'handleError' },
52
+ { toolFile: 'retrieve-bot-user.ts', handler: 'handleError' },
53
+ { toolFile: 'retrieve-data-source.ts', handler: 'handleError' },
54
+ { toolFile: 'retrieve-database.ts', handler: 'handleError' },
55
+ { toolFile: 'retrieve-page.ts', handler: 'handleError' },
56
+ { toolFile: 'retrieve-page-property.ts', handler: 'handleError' },
57
+ { toolFile: 'retrieve-user.ts', handler: 'handleError' },
58
+ { toolFile: 'update-block-simple.ts', handler: 'handleError' },
59
+ ];
60
+ describe('Tool error handlers', () => {
61
+ describe.each(toolErrorConfigs)('$toolFile', ({ toolFile, handler, exampleType, hasDataSourceId, }) => {
62
+ const filePath = resolve(toolsDir, toolFile);
63
+ const content = readFileSync(filePath, 'utf-8');
64
+ it(`should import ${handler}`, () => {
65
+ const importRegex = new RegExp(`import\\s+\\{[^}]*\\b${handler}\\b[^}]*\\}`);
66
+ expect(content).toMatch(importRegex);
67
+ });
68
+ it(`should call ${handler} in catch block`, () => {
69
+ const callRegex = new RegExp(`return\\s+${handler}\\(error`);
70
+ expect(content).toMatch(callRegex);
71
+ });
72
+ if (handler === 'handleErrorWithContext') {
73
+ it(`should use exampleType: '${exampleType}'`, () => {
74
+ const exampleTypeRegex = new RegExp(`exampleType:\\s*['"]${exampleType}['"]`);
75
+ expect(content).toMatch(exampleTypeRegex);
76
+ });
77
+ if (hasDataSourceId) {
78
+ it('should pass dataSourceId option', () => {
79
+ expect(content).toMatch(/dataSourceId:\s*\w+/);
80
+ });
81
+ }
82
+ }
83
+ if (handler === 'handleError') {
84
+ it('should not use handleErrorWithContext', () => {
85
+ expect(content).not.toMatch(/handleErrorWithContext/);
86
+ });
87
+ }
88
+ });
89
+ });
90
+ describe('All tool files are covered', () => {
91
+ it('should have a config for each tool file (excluding index.ts)', () => {
92
+ const expectedToolFiles = [
93
+ 'append-block-children.ts',
94
+ 'append-blocks-simple.ts',
95
+ 'archive-database.ts',
96
+ 'archive-page.ts',
97
+ 'create-comment.ts',
98
+ 'create-comment-simple.ts',
99
+ 'create-database.ts',
100
+ 'create-page.ts',
101
+ 'create-page-simple.ts',
102
+ 'delete-block.ts',
103
+ 'get-block-children.ts',
104
+ 'list-comments.ts',
105
+ 'list-users.ts',
106
+ 'move-page.ts',
107
+ 'query-data-source.ts',
108
+ 'retrieve-block.ts',
109
+ 'retrieve-bot-user.ts',
110
+ 'retrieve-data-source.ts',
111
+ 'retrieve-database.ts',
112
+ 'retrieve-page.ts',
113
+ 'retrieve-page-property.ts',
114
+ 'retrieve-user.ts',
115
+ 'search.ts',
116
+ 'update-block.ts',
117
+ 'update-block-simple.ts',
118
+ 'update-data-source.ts',
119
+ 'update-database.ts',
120
+ 'update-page.ts',
121
+ ];
122
+ const configuredFiles = toolErrorConfigs.map((c) => c.toolFile).sort();
123
+ expect(configuredFiles).toEqual(expectedToolFiles.sort());
124
+ });
125
+ });
@@ -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;AAWvD,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;AAevD,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAmCzF"}
@@ -1,10 +1,11 @@
1
1
  import { z } from 'zod';
2
- import { formatResponse, handleError } from '../utils/index.js';
2
+ import { F } from '../schemas/descriptions/index.js';
3
+ import { formatResponse, handleErrorWithContext } from '../utils/index.js';
3
4
  // Minimal schema for MCP (full validation by Notion API)
4
5
  const inputSchema = {
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'),
6
+ block_id: z.string().describe(F.block_id),
7
+ children: z.array(z.any()).describe(F.children),
8
+ after: z.string().optional().describe(F.after),
8
9
  };
9
10
  export function registerAppendBlockChildren(server, notion) {
10
11
  server.registerTool('append-block-children', {
@@ -26,7 +27,9 @@ export function registerAppendBlockChildren(server, notion) {
26
27
  return formatResponse(response);
27
28
  }
28
29
  catch (error) {
29
- return handleError(error);
30
+ return handleErrorWithContext(error, notion, {
31
+ exampleType: 'block',
32
+ });
30
33
  }
31
34
  });
32
35
  }
@@ -1 +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"}
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;AAIxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAWvD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CA0BxF"}
@@ -1,32 +1,28 @@
1
1
  import { z } from 'zod';
2
2
  import { markdownToBlocks } from '../converters/index.js';
3
+ import { F } from '../schemas/descriptions/index.js';
3
4
  import { formatResponse, handleError } from '../utils/index.js';
4
5
  // Minimal schema for MCP
5
6
  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'),
7
+ block_id: z.string().describe(F.block_id),
8
+ content: z.string().describe(F.content),
9
+ after: z.string().optional().describe(F.after),
9
10
  };
10
11
  export function registerAppendBlocksSimple(server, notion) {
11
12
  server.registerTool('append-blocks-simple', {
12
13
  description: 'Append blocks to a page using Markdown. ' +
13
14
  '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
+ 'Supports: headings (#), lists (- or 1.), checkboxes (- [ ]), code blocks (```), quotes (>), tables (| |), images (![]()), bold (**), italic (*), links ([]()), etc.',
15
16
  inputSchema,
16
17
  }, async ({ block_id, content, after }) => {
17
18
  try {
18
- // Convert markdown to blocks
19
+ // Convert markdown to blocks and cast to SDK type
19
20
  const children = markdownToBlocks(content);
20
- // Build params
21
- const params = {
21
+ const response = await notion.blocks.children.append({
22
22
  block_id,
23
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);
24
+ ...(after && { after }),
25
+ });
30
26
  return formatResponse(response);
31
27
  }
32
28
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"archive-database.d.ts","sourceRoot":"","sources":["../../../src/tools/archive-database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAOvD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAoBrF"}
1
+ {"version":3,"file":"archive-database.d.ts","sourceRoot":"","sources":["../../../src/tools/archive-database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAQvD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAmBrF"}
@@ -1,7 +1,8 @@
1
1
  import { z } from 'zod';
2
+ import { F } from '../schemas/descriptions/index.js';
2
3
  import { formatResponse, handleError } from '../utils/index.js';
3
4
  const inputSchema = {
4
- database_id: z.string().describe('Database ID to archive'),
5
+ database_id: z.string().describe(F.database_id),
5
6
  };
6
7
  export function registerArchiveDatabase(server, notion) {
7
8
  server.registerTool('archive-database', {
@@ -11,7 +12,7 @@ export function registerArchiveDatabase(server, notion) {
11
12
  try {
12
13
  const response = await notion.databases.update({
13
14
  database_id,
14
- archived: true,
15
+ in_trash: true,
15
16
  });
16
17
  return formatResponse(response);
17
18
  }
@@ -1 +1 @@
1
- {"version":3,"file":"archive-page.d.ts","sourceRoot":"","sources":["../../../src/tools/archive-page.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAOvD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAoBjF"}
1
+ {"version":3,"file":"archive-page.d.ts","sourceRoot":"","sources":["../../../src/tools/archive-page.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAQvD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAmBjF"}
@@ -1,7 +1,8 @@
1
1
  import { z } from 'zod';
2
+ import { F } from '../schemas/descriptions/index.js';
2
3
  import { formatResponse, handleError } from '../utils/index.js';
3
4
  const inputSchema = {
4
- page_id: z.string().describe('Page ID to archive'),
5
+ page_id: z.string().describe(F.page_id),
5
6
  };
6
7
  export function registerArchivePage(server, notion) {
7
8
  server.registerTool('archive-page', {
@@ -1 +1 @@
1
- {"version":3,"file":"create-comment-simple.d.ts","sourceRoot":"","sources":["../../../src/tools/create-comment-simple.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAUvD,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAmDzF"}
1
+ {"version":3,"file":"create-comment-simple.d.ts","sourceRoot":"","sources":["../../../src/tools/create-comment-simple.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAWvD,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAmDzF"}
@@ -1,11 +1,12 @@
1
1
  import { z } from 'zod';
2
2
  import { parseInlineMarkdown } from '../converters/index.js';
3
+ import { F } from '../schemas/descriptions/index.js';
3
4
  import { formatResponse, handleError } from '../utils/index.js';
4
5
  const inputSchema = {
5
- page_id: z.string().optional().describe('Page ID (for page comments)'),
6
- block_id: z.string().optional().describe('Block ID (for block comments)'),
7
- discussion_id: z.string().optional().describe('Discussion ID (for replies)'),
8
- content: z.string().describe('Comment in Markdown (**bold**, *italic*, [link](url), `code`)'),
6
+ page_id: z.string().optional().describe(F.page_id),
7
+ block_id: z.string().optional().describe(F.block_id),
8
+ discussion_id: z.string().optional().describe(F.discussion_id),
9
+ content: z.string().describe(F.content),
9
10
  };
10
11
  export function registerCreateCommentSimple(server, notion) {
11
12
  server.registerTool('create-comment-simple', {
@@ -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;AAWvD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAqDnF"}
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;AAYvD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAuDnF"}
@@ -1,11 +1,12 @@
1
1
  import { z } from 'zod';
2
- import { formatResponse, handleError } from '../utils/index.js';
2
+ import { F } from '../schemas/descriptions/index.js';
3
+ import { formatResponse, handleErrorWithContext } from '../utils/index.js';
3
4
  // Minimal schema for MCP (full validation by Notion API)
4
5
  const inputSchema = {
5
- page_id: z.string().optional().describe('Page ID (for page comments)'),
6
- block_id: z.string().optional().describe('Block ID (for block comments)'),
7
- discussion_id: z.string().optional().describe('Discussion ID (for replies)'),
8
- rich_text: z.array(z.any()).describe('Comment content as rich text'),
6
+ page_id: z.string().optional().describe(F.page_id),
7
+ block_id: z.string().optional().describe(F.block_id),
8
+ discussion_id: z.string().optional().describe(F.discussion_id),
9
+ rich_text: z.array(z.any()).describe(F.rich_text),
9
10
  };
10
11
  export function registerCreateComment(server, notion) {
11
12
  server.registerTool('create-comment', {
@@ -46,7 +47,9 @@ export function registerCreateComment(server, notion) {
46
47
  return formatResponse(response);
47
48
  }
48
49
  catch (error) {
49
- return handleError(error);
50
+ return handleErrorWithContext(error, notion, {
51
+ exampleType: 'richTextArray',
52
+ });
50
53
  }
51
54
  });
52
55
  }
@@ -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;AAevD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAgDpF"}
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;AAGxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAevD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CA+BpF"}
@@ -1,15 +1,15 @@
1
1
  import { z } from 'zod';
2
- import { formatResponse, handleError } from '../utils/index.js';
2
+ import { F } from '../schemas/descriptions/index.js';
3
+ import { formatResponse, handleErrorWithContext } from '../utils/index.js';
3
4
  // Minimal schema for MCP (full validation by Notion API)
5
+ // Uses z.any() for title/icon/cover to reduce context size (~2,300 tokens saved)
4
6
  const inputSchema = {
5
- parent_page_id: z.string().describe('Parent page ID'),
6
- title: z.array(z.any()).optional().describe('Database title'),
7
- properties: z
8
- .record(z.string(), z.any())
9
- .describe('Property schema (must include one title property)'),
10
- icon: z.any().optional().describe('Database icon { type: "emoji", emoji: "📝" } or { type: "external", external: { url: "..." } }. Emoji must be an actual emoji character.'),
11
- cover: z.any().optional().describe('Cover image'),
12
- is_inline: z.boolean().optional().describe('Inline database'),
7
+ parent_page_id: z.string().describe(F.parent_page_id),
8
+ title: z.any().optional().describe(F.title),
9
+ properties: z.record(z.string(), z.any()).describe(F.properties_schema),
10
+ icon: z.any().optional().describe(F.icon),
11
+ cover: z.any().optional().describe(F.cover),
12
+ is_inline: z.boolean().optional().describe(F.is_inline),
13
13
  };
14
14
  export function registerCreateDatabase(server, notion) {
15
15
  server.registerTool('create-database', {
@@ -19,28 +19,22 @@ export function registerCreateDatabase(server, notion) {
19
19
  inputSchema,
20
20
  }, async ({ parent_page_id, title, properties, icon, cover, is_inline }) => {
21
21
  try {
22
+ // Build params with only defined values (API validates full structure)
22
23
  const params = {
23
- parent: { page_id: parent_page_id },
24
- initial_data_source: { properties: properties },
24
+ parent: { type: 'page_id', page_id: parent_page_id },
25
+ initial_data_source: { properties },
26
+ ...(title !== undefined && { title }),
27
+ ...(icon !== undefined && { icon }),
28
+ ...(cover !== undefined && { cover }),
29
+ ...(is_inline !== undefined && { is_inline }),
25
30
  };
26
- if (title) {
27
- params.title = title;
28
- }
29
- if (icon) {
30
- params.icon = icon;
31
- }
32
- if (cover) {
33
- params.cover = cover;
34
- }
35
- if (is_inline !== undefined) {
36
- params.is_inline = is_inline;
37
- }
38
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
31
  const response = await notion.databases.create(params);
40
32
  return formatResponse(response);
41
33
  }
42
34
  catch (error) {
43
- return handleError(error);
35
+ return handleErrorWithContext(error, notion, {
36
+ exampleType: 'schema',
37
+ });
44
38
  }
45
39
  });
46
40
  }
@@ -1,4 +1,4 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { NotionClient } from '../notion-client.js';
2
+ import { type NotionClient } from '../notion-client.js';
3
3
  export declare function registerCreatePageSimple(server: McpServer, notion: NotionClient): void;
4
4
  //# sourceMappingURL=create-page-simple.d.ts.map
@@ -1 +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;AAqBvD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CA+EtF"}
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;AAIxE,OAAO,EAAoB,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAiBzE,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAqEtF"}