@mcp-monorepo/notion-query 1.1.0 → 1.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 (134) hide show
  1. package/dist/index.js +56 -7
  2. package/dist/index.js.map +1 -1
  3. package/dist/lib/client.d.ts +9 -0
  4. package/dist/lib/client.d.ts.map +1 -0
  5. package/dist/lib/client.js +19 -0
  6. package/dist/lib/client.js.map +1 -0
  7. package/dist/lib/config.d.ts +44 -0
  8. package/dist/lib/config.d.ts.map +1 -0
  9. package/dist/lib/config.js +49 -0
  10. package/dist/lib/config.js.map +1 -0
  11. package/dist/lib/id-utils.d.ts +8 -0
  12. package/dist/lib/id-utils.d.ts.map +1 -0
  13. package/dist/lib/id-utils.js +20 -0
  14. package/dist/lib/id-utils.js.map +1 -0
  15. package/dist/lib/markdown-converter.d.ts +8 -0
  16. package/dist/lib/markdown-converter.d.ts.map +1 -0
  17. package/dist/lib/markdown-converter.js +95 -0
  18. package/dist/lib/markdown-converter.js.map +1 -0
  19. package/dist/lib/notion-syncer.d.ts +27 -0
  20. package/dist/lib/notion-syncer.d.ts.map +1 -0
  21. package/dist/lib/notion-syncer.js +212 -0
  22. package/dist/lib/notion-syncer.js.map +1 -0
  23. package/dist/lib/parser.d.ts +13 -0
  24. package/dist/lib/parser.d.ts.map +1 -0
  25. package/dist/lib/parser.js +88 -0
  26. package/dist/lib/parser.js.map +1 -0
  27. package/dist/lib/property-parser.d.ts +23 -0
  28. package/dist/lib/property-parser.d.ts.map +1 -0
  29. package/dist/lib/property-parser.js +200 -0
  30. package/dist/lib/property-parser.js.map +1 -0
  31. package/dist/lib/response-formatter.d.ts +16 -0
  32. package/dist/lib/response-formatter.d.ts.map +1 -0
  33. package/dist/lib/response-formatter.js +173 -0
  34. package/dist/lib/response-formatter.js.map +1 -0
  35. package/dist/lib/sync-state-manager.d.ts +29 -0
  36. package/dist/lib/sync-state-manager.d.ts.map +1 -0
  37. package/dist/lib/sync-state-manager.js +45 -0
  38. package/dist/lib/sync-state-manager.js.map +1 -0
  39. package/dist/local-rag/DEMO.d.ts +22 -0
  40. package/dist/local-rag/DEMO.d.ts.map +1 -0
  41. package/dist/local-rag/DEMO.js +142 -0
  42. package/dist/local-rag/DEMO.js.map +1 -0
  43. package/dist/local-rag/chunker.d.ts +24 -0
  44. package/dist/local-rag/chunker.d.ts.map +1 -0
  45. package/dist/local-rag/chunker.js +58 -0
  46. package/dist/local-rag/chunker.js.map +1 -0
  47. package/dist/local-rag/embedder.d.ts +43 -0
  48. package/dist/local-rag/embedder.d.ts.map +1 -0
  49. package/dist/local-rag/embedder.js +74 -0
  50. package/dist/local-rag/embedder.js.map +1 -0
  51. package/dist/local-rag/embedder.service.d.ts +15 -0
  52. package/dist/local-rag/embedder.service.d.ts.map +1 -0
  53. package/dist/local-rag/embedder.service.js +84 -0
  54. package/dist/local-rag/embedder.service.js.map +1 -0
  55. package/dist/local-rag/embedder.worker.d.ts +2 -0
  56. package/dist/local-rag/embedder.worker.d.ts.map +1 -0
  57. package/dist/local-rag/embedder.worker.js +34 -0
  58. package/dist/local-rag/embedder.worker.js.map +1 -0
  59. package/dist/local-rag/errors.d.ts +31 -0
  60. package/dist/local-rag/errors.d.ts.map +1 -0
  61. package/dist/local-rag/errors.js +47 -0
  62. package/dist/local-rag/errors.js.map +1 -0
  63. package/dist/local-rag/html-parser.d.ts +2 -0
  64. package/dist/local-rag/html-parser.d.ts.map +1 -0
  65. package/dist/local-rag/html-parser.js +32 -0
  66. package/dist/local-rag/html-parser.js.map +1 -0
  67. package/dist/local-rag/index.d.ts +67 -0
  68. package/dist/local-rag/index.d.ts.map +1 -0
  69. package/dist/local-rag/index.js +410 -0
  70. package/dist/local-rag/index.js.map +1 -0
  71. package/dist/local-rag/parser.d.ts +59 -0
  72. package/dist/local-rag/parser.d.ts.map +1 -0
  73. package/dist/local-rag/parser.js +206 -0
  74. package/dist/local-rag/parser.js.map +1 -0
  75. package/dist/local-rag/types.d.ts +209 -0
  76. package/dist/local-rag/types.d.ts.map +1 -0
  77. package/dist/local-rag/types.js +5 -0
  78. package/dist/local-rag/types.js.map +1 -0
  79. package/dist/local-rag/utils/pool.d.ts +60 -0
  80. package/dist/local-rag/utils/pool.d.ts.map +1 -0
  81. package/dist/local-rag/utils/pool.js +140 -0
  82. package/dist/local-rag/utils/pool.js.map +1 -0
  83. package/dist/local-rag/utils/typed-emitter.d.ts +28 -0
  84. package/dist/local-rag/utils/typed-emitter.d.ts.map +1 -0
  85. package/dist/local-rag/utils/typed-emitter.js +44 -0
  86. package/dist/local-rag/utils/typed-emitter.js.map +1 -0
  87. package/dist/local-rag/vectordb/index.d.ts +91 -0
  88. package/dist/local-rag/vectordb/index.d.ts.map +1 -0
  89. package/dist/local-rag/vectordb/index.js +278 -0
  90. package/dist/local-rag/vectordb/index.js.map +1 -0
  91. package/dist/local-rag/vectordb/manager.d.ts +28 -0
  92. package/dist/local-rag/vectordb/manager.d.ts.map +1 -0
  93. package/dist/local-rag/vectordb/manager.js +91 -0
  94. package/dist/local-rag/vectordb/manager.js.map +1 -0
  95. package/dist/local-rag/vectordb/migration.d.ts +27 -0
  96. package/dist/local-rag/vectordb/migration.d.ts.map +1 -0
  97. package/dist/local-rag/vectordb/migration.js +121 -0
  98. package/dist/local-rag/vectordb/migration.js.map +1 -0
  99. package/dist/local-rag/vectordb/retriever.d.ts +51 -0
  100. package/dist/local-rag/vectordb/retriever.d.ts.map +1 -0
  101. package/dist/local-rag/vectordb/retriever.js +157 -0
  102. package/dist/local-rag/vectordb/retriever.js.map +1 -0
  103. package/dist/local-rag/vectordb/schema.d.ts +33 -0
  104. package/dist/local-rag/vectordb/schema.d.ts.map +1 -0
  105. package/dist/local-rag/vectordb/schema.js +102 -0
  106. package/dist/local-rag/vectordb/schema.js.map +1 -0
  107. package/dist/local-rag/watcher.d.ts +48 -0
  108. package/dist/local-rag/watcher.d.ts.map +1 -0
  109. package/dist/local-rag/watcher.js +102 -0
  110. package/dist/local-rag/watcher.js.map +1 -0
  111. package/dist/tools/create-pages.d.ts +4 -0
  112. package/dist/tools/create-pages.d.ts.map +1 -0
  113. package/dist/tools/create-pages.js +184 -0
  114. package/dist/tools/create-pages.js.map +1 -0
  115. package/dist/tools/fetch.d.ts +4 -0
  116. package/dist/tools/fetch.d.ts.map +1 -0
  117. package/dist/tools/fetch.js +90 -0
  118. package/dist/tools/fetch.js.map +1 -0
  119. package/dist/tools/query-datasource.d.ts +4 -0
  120. package/dist/tools/query-datasource.d.ts.map +1 -0
  121. package/dist/tools/{notion-query.js → query-datasource.js} +31 -38
  122. package/dist/tools/query-datasource.js.map +1 -0
  123. package/dist/tools/search.d.ts +12 -0
  124. package/dist/tools/search.d.ts.map +1 -0
  125. package/dist/tools/search.js +75 -0
  126. package/dist/tools/search.js.map +1 -0
  127. package/dist/tools/update-page.d.ts +4 -0
  128. package/dist/tools/update-page.d.ts.map +1 -0
  129. package/dist/tools/update-page.js +135 -0
  130. package/dist/tools/update-page.js.map +1 -0
  131. package/package.json +23 -8
  132. package/dist/tools/notion-query.d.ts +0 -3
  133. package/dist/tools/notion-query.d.ts.map +0 -1
  134. package/dist/tools/notion-query.js.map +0 -1
@@ -0,0 +1,102 @@
1
+ import { basename, dirname, extname } from 'node:path';
2
+ import { logger } from '@mcp-monorepo/shared';
3
+ import { FSWatcher } from 'chokidar';
4
+ import { TypedEventEmitter } from './utils/typed-emitter.js';
5
+ /**
6
+ * A robust file system watcher that abstracts away the complexities of chokidar.
7
+ * It debounces change events and only reports on supported file types, emitting clean,
8
+ * actionable events. It handles both initial discovery of existing files and
9
+ * subsequent changes.
10
+ */
11
+ export class DirectoryWatcher extends TypedEventEmitter {
12
+ watcher;
13
+ debounceTimers = new Map();
14
+ debounceMs;
15
+ supportedExtensions;
16
+ constructor(debounceMs, supportedExtensions) {
17
+ super();
18
+ this.debounceMs = debounceMs;
19
+ this.supportedExtensions = new Set(supportedExtensions);
20
+ this.watcher = new FSWatcher({
21
+ ignored: /(^|[/\\])\../, // ignore dotfiles and dot folders
22
+ persistent: true,
23
+ awaitWriteFinish: true,
24
+ });
25
+ this.setupListeners();
26
+ }
27
+ setupListeners() {
28
+ this.watcher
29
+ .on('add', (path) => this.handleFileEvent(path, 'file-added'))
30
+ .on('change', (path) => this.handleFileEvent(path, 'file-changed'))
31
+ .on('unlink', (path) => this.handleFileEvent(path, 'file-deleted'))
32
+ .on('error', (error) => logger.error(`Watcher error: ${error}`));
33
+ }
34
+ isSupported(filePath) {
35
+ const ext = extname(filePath).toLowerCase();
36
+ return this.supportedExtensions.has(ext);
37
+ }
38
+ handleFileEvent(filePath, event) {
39
+ if (!this.isSupported(filePath)) {
40
+ return; // Ignore unsupported file types
41
+ }
42
+ // Clear any existing timer to debounce rapid changes
43
+ if (this.debounceTimers.has(filePath)) {
44
+ clearTimeout(this.debounceTimers.get(filePath));
45
+ this.debounceTimers.delete(filePath);
46
+ }
47
+ // For deletions, we want to act immediately.
48
+ if (event === 'file-deleted') {
49
+ logger.info(`Watcher: Detected deletion for: ${filePath}`);
50
+ this.emit('file-deleted', filePath);
51
+ return;
52
+ }
53
+ // For adds and changes, debounce the event to avoid churn.
54
+ const timer = setTimeout(() => {
55
+ logger.info(`Watcher: Detected ${event === 'file-added' ? 'addition' : 'change'} for: ${filePath}`);
56
+ this.emit(event, filePath);
57
+ this.debounceTimers.delete(filePath);
58
+ }, this.debounceMs);
59
+ this.debounceTimers.set(filePath, timer);
60
+ }
61
+ /**
62
+ * Checks if a given path is already being watched by chokidar.
63
+ * @param path The absolute path to check.
64
+ * @returns True if the path is being watched, false otherwise.
65
+ */
66
+ isWatching(path) {
67
+ const watched = this.watcher.getWatched();
68
+ // getWatched() returns an object like: { '/path/to/dir': ['file1.txt', 'file2.txt'], '/path/to/other/file.md': [] }
69
+ const dir = dirname(path);
70
+ const base = basename(path);
71
+ return !!watched[path] || watched[dir]?.includes(base);
72
+ }
73
+ /**
74
+ * Starts watching a given path (file or directory).
75
+ * @param path - The path to watch.
76
+ */
77
+ watch(path) {
78
+ if (this.isWatching(path)) {
79
+ logger.debug(`Watcher: Already watching ${path}`);
80
+ return;
81
+ }
82
+ this.watcher.add(path);
83
+ }
84
+ /**
85
+ * Stops watching a given path.
86
+ * @param path - The path to unwatch.
87
+ */
88
+ unwatch(path) {
89
+ this.watcher.unwatch(path);
90
+ }
91
+ /**
92
+ * Gracefully shuts down the watcher, releasing all file system handles.
93
+ */
94
+ async close() {
95
+ for (const timer of this.debounceTimers.values()) {
96
+ clearTimeout(timer);
97
+ }
98
+ this.debounceTimers.clear();
99
+ await this.watcher.close();
100
+ }
101
+ }
102
+ //# sourceMappingURL=watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../src/local-rag/watcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { NotionSyncer } from '../lib/notion-syncer.js';
3
+ export declare const registerCreatePagesTool: (server: McpServer, notionSyncer: NotionSyncer) => void;
4
+ //# sourceMappingURL=create-pages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-pages.d.ts","sourceRoot":"","sources":["../../src/tools/create-pages.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAcxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAyD3D,eAAO,MAAM,uBAAuB,GAAI,QAAQ,SAAS,EAAE,cAAc,YAAY,SAyIjF,CAAA"}
@@ -0,0 +1,184 @@
1
+ import { registerTool } from '@mcp-monorepo/shared';
2
+ import { markdownToBlocks } from '@tryfabric/martian';
3
+ import { z } from 'zod';
4
+ import { getNotionClient } from '../lib/client.js';
5
+ import { normalizeId } from '../lib/id-utils.js';
6
+ import { parsePropertiesForCreate } from '../lib/property-parser.js';
7
+ // The description is an exact copy of the provided tool definition [1].
8
+ const description = `Creates one or more Notion pages with specified properties and content.
9
+ Use "create-pages" when you need to create one or more new pages that don't exist yet.
10
+ Always include a title property under \`properties\` in each entry of the \`pages\` array.
11
+ Otherwise, the page title will appear blank even if the page content is populated. Don't
12
+ duplicate the page title at the top of the page's \`content\`.
13
+ When creating pages under a Notion database, the property names must match the database's
14
+ schema. Use the "fetch" tool with a Notion database URL to get the database schema. Or, look
15
+ for existing pages under the database using the "search" tool then use the "fetch" tool to see
16
+ the names of the property keys. One exception is the "title" property, which all pages have,
17
+ but can be named differently in the schema of a database. For convenience, you can use the
18
+ generic property name "title" in the "properties" object, and it will automatically be
19
+ re-mapped to the actual name of the title property in the database schema when creating the
20
+ page.
21
+ All pages created with a single call to this tool will have the same parent.
22
+ The parent can be a Notion page or database. If the parent is omitted, the pages will be
23
+ created as standalone, workspace-level private pages and the person that created them
24
+ can organize them as they see fit later.
25
+ IMPORTANT: When specifying a parent database, use the appropriate ID format:
26
+ - Use "data_source_id" when you have a collection:// URL from the fetch tool (this is the most common case)
27
+ - Use "database_id" when you have a page URL for a database view (less common)
28
+ - Use "page_id" when the parent is a regular page
29
+ Examples of creating pages:
30
+ 1. Create a standalone page with a title and content:
31
+ {
32
+ "pages": [
33
+ {
34
+ "properties": {"title":"Page title"},
35
+ "content": "# Section 1\\nSection 1 content\\n# Section 2\\nSection 2 content"
36
+ }
37
+ ]
38
+ }
39
+ 2. Create a page under a database's data source (collection), e.g. using an ID from a collection:// URL provided by the fetch tool:
40
+ {
41
+ "parent": {"data_source_id": "f336d0bc-b841-465b-8045-024475c079dd"},
42
+ "pages": [
43
+ {
44
+ "properties": {
45
+ "Task Name": "Task 123",
46
+ "Status": "In Progress",
47
+ },
48
+ },
49
+ ],
50
+ }
51
+ 3. Create a page with an existing page as a parent:
52
+ {
53
+ "parent": {"page_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"},
54
+ "pages": [
55
+ {
56
+ "properties": {"title": "Page title"},
57
+ "content": "# Section 1\\nSection 1 content\\n# Section 2\\nSection 2 content"
58
+ }
59
+ ]
60
+ }`;
61
+ export const registerCreatePagesTool = (server, notionSyncer) => registerTool(server, {
62
+ name: 'create-pages',
63
+ title: 'Create pages in Markdown',
64
+ description,
65
+ inputSchema: {
66
+ pages: z
67
+ .array(z
68
+ .object({
69
+ content: z.string().describe('The content of the new page, using Notion Markdown.').optional(),
70
+ properties: z
71
+ .record(z.union([z.string(), z.number(), z.null()]))
72
+ .describe('The properties of the new page, which is a JSON map of property names to SQLite values.\nFor pages in a database, use the SQLite schema definition shown in <database>.\nFor pages outside of a database, the only allowed property is "title", which is the title of the page and is automatically shown at the top of the page as a large heading.'),
73
+ })
74
+ .strict())
75
+ .max(100)
76
+ .describe('The pages to create.'),
77
+ parent: z
78
+ .union([
79
+ z
80
+ .object({
81
+ type: z.literal('page_id').optional(),
82
+ page_id: z
83
+ .string()
84
+ .describe('The ID of the parent page (with or without dashes), for example, 195de9221179449fab8075a27c979105'),
85
+ })
86
+ .strict(),
87
+ z
88
+ .object({
89
+ type: z.literal('database_id').optional(),
90
+ database_id: z
91
+ .string()
92
+ .describe('The ID of the parent database (with or without dashes), for example, 195de9221179449fab8075a27c979105'),
93
+ })
94
+ .strict(),
95
+ z
96
+ .object({
97
+ type: z.literal('data_source_id').optional(),
98
+ data_source_id: z
99
+ .string()
100
+ .describe('The ID of the parent data source (collection), with or without dashes. For example, f336d0bc-b841-465b-8045-024475c079dd'),
101
+ })
102
+ .strict(),
103
+ ])
104
+ .optional()
105
+ .describe('The parent under which the new pages will be created. This can be a page (page_id), a database page (database_id), or a data source/collection under a database (data_source_id). If omitted, the new pages will be created as private pages at the workspace level. Use data_source_id when you have a collection:// URL from the fetch tool.'),
106
+ },
107
+ outputSchema: {
108
+ created_pages: z
109
+ .array(z.object({
110
+ page_id: z.string(),
111
+ url: z.string(),
112
+ title: z.string().optional(),
113
+ }))
114
+ .describe('A list of the pages that were successfully created.'),
115
+ },
116
+ async fetcher(args) {
117
+ const notion = getNotionClient();
118
+ const { pages, parent } = args;
119
+ let apiParent;
120
+ let dataSourceSchema;
121
+ if (!parent) {
122
+ // To create a page at the workspace level, the parent must be `{ workspace: true }` [2, 1].
123
+ apiParent = { workspace: true };
124
+ }
125
+ else if ('data_source_id' in parent && parent.data_source_id) {
126
+ const id = normalizeId(parent.data_source_id);
127
+ if (!id)
128
+ throw new Error(`Invalid data_source_id: ${parent.data_source_id}`);
129
+ const dataSource = (await notion.dataSources.retrieve({ data_source_id: id }));
130
+ apiParent = { database_id: dataSource.parent.database_id };
131
+ dataSourceSchema = dataSource.properties;
132
+ }
133
+ else if ('database_id' in parent && parent.database_id) {
134
+ const id = normalizeId(parent.database_id);
135
+ if (!id)
136
+ throw new Error(`Invalid database_id: ${parent.database_id}`);
137
+ const database = (await notion.databases.retrieve({ database_id: id }));
138
+ const firstDataSourceId = database.data_sources[0]?.id;
139
+ if (!firstDataSourceId)
140
+ throw new Error(`Database ${parent.database_id} has no data sources.`);
141
+ const dataSource = (await notion.dataSources.retrieve({
142
+ data_source_id: firstDataSourceId,
143
+ }));
144
+ apiParent = { database_id: dataSource.parent.database_id };
145
+ dataSourceSchema = dataSource.properties;
146
+ }
147
+ else if ('page_id' in parent && parent.page_id) {
148
+ const id = normalizeId(parent.page_id);
149
+ if (!id)
150
+ throw new Error(`Invalid page_id: ${parent.page_id}`);
151
+ apiParent = { page_id: id };
152
+ }
153
+ else {
154
+ throw new Error('Invalid parent object provided.');
155
+ }
156
+ const createPagePromises = pages.map(async (page) => {
157
+ const children = page.content ? markdownToBlocks(page.content) : undefined;
158
+ const notionProperties = parsePropertiesForCreate(page.properties, dataSourceSchema);
159
+ const newPage = await notion.pages.create({
160
+ parent: apiParent,
161
+ properties: notionProperties,
162
+ children,
163
+ });
164
+ return newPage;
165
+ });
166
+ return Promise.all(createPagePromises);
167
+ },
168
+ async formatter(createdPages) {
169
+ await Promise.allSettled(createdPages.map((page) => notionSyncer.triggerImmediateSync(page.id)));
170
+ const results = createdPages.map((page) => {
171
+ const titleProperty = Object.values(page.properties).find((p) => p.type === 'title');
172
+ const title = titleProperty && 'title' in titleProperty && titleProperty.title.length > 0
173
+ ? titleProperty.title[0].plain_text
174
+ : 'Untitled';
175
+ return {
176
+ page_id: page.id,
177
+ url: page.url,
178
+ title,
179
+ };
180
+ });
181
+ return { created_pages: results };
182
+ },
183
+ });
184
+ //# sourceMappingURL=create-pages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-pages.js","sourceRoot":"","sources":["../../src/tools/create-pages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAQnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAA;AAIpE,wEAAwE;AACxE,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoDlB,CAAA;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,MAAiB,EAAE,YAA0B,EAAE,EAAE,CACvF,YAAY,CAAC,MAAM,EAAE;IACnB,IAAI,EAAE,cAAc;IACpB,KAAK,EAAE,0BAA0B;IACjC,WAAW;IACX,WAAW,EAAE;QACX,KAAK,EAAE,CAAC;aACL,KAAK,CACJ,CAAC;aACE,MAAM,CAAC;YACN,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qDAAqD,CAAC,CAAC,QAAQ,EAAE;YAC9F,UAAU,EAAE,CAAC;iBACV,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;iBACnD,QAAQ,CACP,sVAAsV,CACvV;SACJ,CAAC;aACD,MAAM,EAAE,CACZ;aACA,GAAG,CAAC,GAAG,CAAC;aACR,QAAQ,CAAC,sBAAsB,CAAC;QACnC,MAAM,EAAE,CAAC;aACN,KAAK,CAAC;YACL,CAAC;iBACE,MAAM,CAAC;gBACN,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE;gBACrC,OAAO,EAAE,CAAC;qBACP,MAAM,EAAE;qBACR,QAAQ,CACP,mGAAmG,CACpG;aACJ,CAAC;iBACD,MAAM,EAAE;YACX,CAAC;iBACE,MAAM,CAAC;gBACN,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,QAAQ,EAAE;gBACzC,WAAW,EAAE,CAAC;qBACX,MAAM,EAAE;qBACR,QAAQ,CACP,uGAAuG,CACxG;aACJ,CAAC;iBACD,MAAM,EAAE;YACX,CAAC;iBACE,MAAM,CAAC;gBACN,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,QAAQ,EAAE;gBAC5C,cAAc,EAAE,CAAC;qBACd,MAAM,EAAE;qBACR,QAAQ,CACP,0HAA0H,CAC3H;aACJ,CAAC;iBACD,MAAM,EAAE;SACZ,CAAC;aACD,QAAQ,EAAE;aACV,QAAQ,CACP,gVAAgV,CACjV;KACJ;IACD,YAAY,EAAE;QACZ,aAAa,EAAE,CAAC;aACb,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;YACnB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;YACf,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SAC7B,CAAC,CACH;aACA,QAAQ,CAAC,qDAAqD,CAAC;KACnE;IACD,KAAK,CAAC,OAAO,CAAC,IAAI;QAChB,MAAM,MAAM,GAAG,eAAe,EAAE,CAAA;QAChC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;QAE9B,IAAI,SAAyC,CAAA;QAC7C,IAAI,gBAAoE,CAAA;QAExE,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,4FAA4F;YAC5F,SAAS,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAA;QACjC,CAAC;aAAM,IAAI,gBAAgB,IAAI,MAAM,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;YAC/D,MAAM,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,cAAc,CAAC,CAAA;YAC7C,IAAI,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,MAAM,CAAC,cAAc,EAAE,CAAC,CAAA;YAC5E,MAAM,UAAU,GAAG,CAAC,MAAM,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC,CAA6B,CAAA;YAC1G,SAAS,GAAG,EAAE,WAAW,EAAE,UAAU,CAAC,MAAM,CAAC,WAAW,EAAE,CAAA;YAC1D,gBAAgB,GAAG,UAAU,CAAC,UAAU,CAAA;QAC1C,CAAC;aAAM,IAAI,aAAa,IAAI,MAAM,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;YACzD,MAAM,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;YAC1C,IAAI,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,MAAM,CAAC,WAAW,EAAE,CAAC,CAAA;YACtE,MAAM,QAAQ,GAAG,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAA2B,CAAA;YACjG,MAAM,iBAAiB,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;YACtD,IAAI,CAAC,iBAAiB;gBAAE,MAAM,IAAI,KAAK,CAAC,YAAY,MAAM,CAAC,WAAW,uBAAuB,CAAC,CAAA;YAC9F,MAAM,UAAU,GAAG,CAAC,MAAM,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC;gBACpD,cAAc,EAAE,iBAAiB;aAClC,CAAC,CAA6B,CAAA;YAC/B,SAAS,GAAG,EAAE,WAAW,EAAE,UAAU,CAAC,MAAM,CAAC,WAAW,EAAE,CAAA;YAC1D,gBAAgB,GAAG,UAAU,CAAC,UAAU,CAAA;QAC1C,CAAC;aAAM,IAAI,SAAS,IAAI,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACjD,MAAM,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YACtC,IAAI,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;YAC9D,SAAS,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;QAC7B,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;QACpD,CAAC;QAED,MAAM,kBAAkB,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;YAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAE,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAA0B,CAAC,CAAC,CAAC,SAAS,CAAA;YACpG,MAAM,gBAAgB,GAAG,wBAAwB,CAAC,IAAI,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAA;YAEpF,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;gBACxC,MAAM,EAAE,SAAS;gBACjB,UAAU,EAAE,gBAAgB;gBAC5B,QAAQ;aACT,CAAC,CAAA;YACF,OAAO,OAA6B,CAAA;QACtC,CAAC,CAAC,CAAA;QAEF,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;IACxC,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,YAAY;QAC1B,MAAM,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QAChG,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YACxC,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAA;YACpF,MAAM,KAAK,GACT,aAAa,IAAI,OAAO,IAAI,aAAa,IAAI,aAAa,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;gBACzE,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU;gBACnC,CAAC,CAAC,UAAU,CAAA;YAEhB,OAAO;gBACL,OAAO,EAAE,IAAI,CAAC,EAAE;gBAChB,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,KAAK;aACN,CAAA;QACH,CAAC,CAAC,CAAA;QACF,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,CAAA;IACnC,CAAC;CACF,CAAC,CAAA"}
@@ -0,0 +1,4 @@
1
+ import { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { NotionSyncer } from '../lib/notion-syncer.js';
3
+ export declare const registerFetchTool: (server: McpServer, notionSyncer: NotionSyncer) => void;
4
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/tools/fetch.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAmBxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAM3D,eAAO,MAAM,iBAAiB,GAAI,QAAQ,SAAS,EAAE,cAAc,YAAY,SA6F3E,CAAA"}
@@ -0,0 +1,90 @@
1
+ import { registerTool } from '@mcp-monorepo/shared';
2
+ import { z } from 'zod';
3
+ import { getNotionClient } from '../lib/client.js';
4
+ import { normalizeId } from '../lib/id-utils.js';
5
+ import { formatDatabaseToMarkdown, formatPageToMarkdown } from '../lib/response-formatter.js';
6
+ export const registerFetchTool = (server, notionSyncer) => registerTool(server, {
7
+ name: 'fetch',
8
+ title: 'Fetch Notion entities',
9
+ description: `Retrieves details about a Notion entity by its URL or ID.
10
+ You can fetch the following types of entities:
11
+ - Page, i.e. from a <page> block or a <mention-page> mention
12
+ - Database, i.e. from a <database> block or a <mention-database> mention
13
+ Use the "fetch" tool when you need to see the details of a Notion entity you already know
14
+ exists and have its URL or ID.
15
+ Provide the Notion entity's URL or ID in the \`id\` parameter. You must make multiple calls
16
+ to the "fetch" tool if you want to fetch multiple entities.
17
+ Content for pages that are returned use the enhanced Markdown format, which is a superset of
18
+ the standard Markdown syntax. See the full spec in the description of the "create-pages"
19
+ tool.
20
+ Databases can have multiple data sources, which are collections of pages with the same schema.
21
+ When fetching a database, the tool will return information about all its data sources.
22
+ Examples of fetching entities:
23
+ 1. Fetch a page by URL:
24
+ {
25
+ "id": "https://www.notion.so/workspace/Product-Requirements-1234567890abcdef"
26
+ }
27
+ 2. Fetch a page by ID (UUIDv4 with dashes):
28
+ {
29
+ "id": "12345678-90ab-cdef-1234-567890abcdef"
30
+ }
31
+ 3. Fetch a page by ID (UUIDv4 without dashes):
32
+ {
33
+ "id": "1234567890abcdef1234567890abcdef"
34
+ }
35
+ 4. Fetch a database:
36
+ {
37
+ "id": "https://www.notion.so/workspace/Projects-Database-abcdef1234567890"
38
+ }
39
+ Common use cases:
40
+ - "What are the product requirements still need to be implemented from this ticket
41
+ https://notion.so/page-url?"
42
+ - "Show me the details of the project database at this URL"
43
+ - "Get the content of page 12345678-90ab-cdef-1234-567890abcdef"`,
44
+ isReadOnly: true,
45
+ inputSchema: {
46
+ id: z.string().describe('The ID or URL of the Notion page or database to fetch.'),
47
+ },
48
+ outputSchema: {
49
+ markdown: z.string(),
50
+ },
51
+ async fetcher(args) {
52
+ const notion = getNotionClient();
53
+ const entityId = normalizeId(args.id);
54
+ if (!entityId) {
55
+ throw new Error(`Invalid Notion ID or URL: ${args.id}`);
56
+ }
57
+ try {
58
+ const page = await notion.pages.retrieve({ page_id: entityId });
59
+ const blocks = await notion.blocks.children.list({ block_id: entityId });
60
+ return { type: 'page', page, blocks };
61
+ }
62
+ catch (pageError) {
63
+ try {
64
+ const databaseResponse = (await notion.databases.retrieve({
65
+ database_id: entityId,
66
+ }));
67
+ // A database contains references to its data sources. We need to fetch each one to get the schema.
68
+ const dataSourcePromises = (databaseResponse.data_sources || []).map((ds) => notion.dataSources.retrieve({ data_source_id: ds.id }));
69
+ const data_sources = await Promise.all(dataSourcePromises);
70
+ return { type: 'database', database: databaseResponse, data_sources };
71
+ }
72
+ catch (dbError) {
73
+ throw new Error(`Could not fetch '${entityId}' as a page or database.`);
74
+ }
75
+ }
76
+ },
77
+ async formatter(data) {
78
+ if (data.type === 'page') {
79
+ await notionSyncer.triggerImmediateSync(data.page.id);
80
+ return {
81
+ markdown: await formatPageToMarkdown(data.page, data.blocks.results),
82
+ };
83
+ }
84
+ // Pass both the database and its full data_sources to the formatter.
85
+ return {
86
+ markdown: await formatDatabaseToMarkdown(data.database, data.data_sources),
87
+ };
88
+ },
89
+ });
90
+ //# sourceMappingURL=fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/tools/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAcnD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAA;AAQ7F,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,MAAiB,EAAE,YAA0B,EAAE,EAAE,CACjF,YAAY,CAAC,MAAM,EAAE;IACnB,IAAI,EAAE,OAAO;IACb,KAAK,EAAE,uBAAuB;IAC9B,WAAW,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iEAkCgD;IAC7D,UAAU,EAAE,IAAI;IAChB,WAAW,EAAE;QACX,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,wDAAwD,CAAC;KAClF;IACD,YAAY,EAAE;QACZ,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;KACrB;IACD,KAAK,CAAC,OAAO,CAAC,IAAI;QAChB,MAAM,MAAM,GAAG,eAAe,EAAE,CAAA;QAChC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACrC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,CAAC,EAAE,EAAE,CAAC,CAAA;QACzD,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAA;YAC/D,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAA;YACxE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;QACvC,CAAC;QAAC,OAAO,SAAS,EAAE,CAAC;YACnB,IAAI,CAAC;gBACH,MAAM,gBAAgB,GAAG,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC;oBACxD,WAAW,EAAE,QAAQ;iBACtB,CAAC,CAA2B,CAAA;gBAE7B,mGAAmG;gBACnG,MAAM,kBAAkB,GAAG,CAAC,gBAAgB,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAC1E,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,cAAc,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CACvD,CAAA;gBACD,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;gBAE1D,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,gBAAgB,EAAE,YAAY,EAAE,CAAA;YACvE,CAAC;YAAC,OAAO,OAAO,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,oBAAoB,QAAQ,0BAA0B,CAAC,CAAA;YACzE,CAAC;QACH,CAAC;IACH,CAAC;IACD,KAAK,CAAC,SAAS,CAAC,IAAI;QAClB,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACzB,MAAM,YAAY,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YACrD,OAAO;gBACL,QAAQ,EAAE,MAAM,oBAAoB,CAClC,IAAI,CAAC,IAA0B,EAC/B,IAAI,CAAC,MAAM,CAAC,OAAgC,CAC7C;aACF,CAAA;QACH,CAAC;QACD,qEAAqE;QACrE,OAAO;YACL,QAAQ,EAAE,MAAM,wBAAwB,CACtC,IAAI,CAAC,QAAkC,EACvC,IAAI,CAAC,YAA0C,CAChD;SACF,CAAA;IACH,CAAC;CACF,CAAC,CAAA"}
@@ -0,0 +1,4 @@
1
+ import type { NotionSyncer } from '../lib/notion-syncer.js';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ export declare const registerQueryDatasourceTool: (server: McpServer, notionSyncer: NotionSyncer) => void;
4
+ //# sourceMappingURL=query-datasource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-datasource.d.ts","sourceRoot":"","sources":["../../src/tools/query-datasource.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,eAAO,MAAM,2BAA2B,GAAI,QAAQ,SAAS,EAAE,cAAc,YAAY,SA+ErF,CAAA"}
@@ -1,9 +1,11 @@
1
1
  import { registerTool } from '@mcp-monorepo/shared';
2
2
  import { z } from 'zod';
3
- export const registerNotionQueryTool = (server) => registerTool(server, {
4
- name: 'notion-query',
3
+ import { getNotionClient } from '../lib/client.js';
4
+ import { simplifyNotionPages } from '../lib/parser.js';
5
+ export const registerQueryDatasourceTool = (server, notionSyncer) => registerTool(server, {
6
+ name: 'query-datasource',
5
7
  title: 'Query a Notion Data Source',
6
- description: `Gets a list of pages from a data source, identified by its URL (e.g., "collection://<uuid>"). Supports filtering and sorting. The response is paginated; use 'start_cursor' for subsequent requests if 'next_cursor' is returned. Filters operate on properties and can be simple or compound (using "and"/"or"). Sorts operate on properties or timestamps. For performance, use 'filter_properties' to retrieve only necessary page properties. Refer to Notion API documentation for the exact filter and sort object structures.`,
8
+ description: `Gets a list of pages from a data source, identified by its URL (e.g., "collection://<uuid>"). Supports filtering and sorting. The response is paginated; use 'start_cursor' for subsequent requests if 'next_cursor' is returned. Filters operate on properties and can be simple or compound (using "and"/"or"). Sorts operate on properties or timestamps. For performance, you can use 'filter_properties' to retrieve only necessary page properties. Refer to Notion API documentation for the exact filter and sort object structures.`,
7
9
  inputSchema: {
8
10
  data_source_url: z
9
11
  .string()
@@ -13,7 +15,11 @@ export const registerNotionQueryTool = (server) => registerTool(server, {
13
15
  .optional()
14
16
  .describe('A JSON filter object to apply. Example: {"property": "Done", "checkbox": {"equals": true}} or {"and": [...]}.'),
15
17
  sorts: z
16
- .array(z.record(z.string(), z.unknown()))
18
+ .array(z.object({
19
+ property: z.string().optional(),
20
+ timestamp: z.string().optional(),
21
+ direction: z.enum(['ascending', 'descending']),
22
+ }))
17
23
  .optional()
18
24
  .describe('An array of JSON sort objects. Example: [{"property": "Name", "direction": "ascending"}].'),
19
25
  start_cursor: z.string().optional().describe('If provided, starts the query at the specified cursor.'),
@@ -24,7 +30,9 @@ export const registerNotionQueryTool = (server) => registerTool(server, {
24
30
  .describe('An array of property IDs or names to return. If not provided, all properties are returned.'),
25
31
  },
26
32
  outputSchema: {
27
- results: z.array(z.any()).describe('An array of page objects matching the query.'),
33
+ results: z
34
+ .array(z.record(z.string(), z.any()))
35
+ .describe('An array of simplified page objects. Each object is a flat key-value map of its properties.'),
28
36
  next_cursor: z
29
37
  .string()
30
38
  .nullable()
@@ -37,47 +45,32 @@ export const registerNotionQueryTool = (server) => registerTool(server, {
37
45
  if (!data_source_url?.startsWith('collection://')) {
38
46
  throw new Error('Invalid data_source_url format. Must start with "collection://".');
39
47
  }
48
+ const notion = getNotionClient();
40
49
  const dataSourceId = data_source_url.substring('collection://'.length);
41
- const NOTION_API_KEY = process.env.NOTION_API_KEY;
42
- if (!NOTION_API_KEY) {
43
- throw new Error('NOTION_API_KEY environment variable is not set.');
44
- }
45
- const apiUrl = new URL(`https://api.notion.com/v1/data_sources/${dataSourceId}/query`);
46
- if (filter_properties && filter_properties.length > 0) {
47
- filter_properties.forEach((prop) => {
48
- apiUrl.searchParams.append('filter_properties', prop);
50
+ try {
51
+ return await notion.dataSources.query({
52
+ data_source_id: dataSourceId,
53
+ filter: filter,
54
+ sorts: sorts,
55
+ start_cursor,
56
+ page_size,
57
+ filter_properties,
49
58
  });
50
59
  }
51
- const body = {};
52
- if (filter)
53
- body.filter = filter;
54
- if (sorts)
55
- body.sorts = sorts;
56
- if (start_cursor)
57
- body.start_cursor = start_cursor;
58
- if (page_size)
59
- body.page_size = page_size;
60
- const response = await fetch(apiUrl.toString(), {
61
- method: 'POST',
62
- headers: {
63
- Authorization: `Bearer ${NOTION_API_KEY}`,
64
- 'Notion-Version': '2025-09-03',
65
- 'Content-Type': 'application/json',
66
- },
67
- body: Object.keys(body).length > 0 ? JSON.stringify(body) : undefined,
68
- });
69
- if (!response.ok) {
70
- const errorText = await response.text();
71
- throw new Error(`Notion API request failed with status ${response.status}: ${errorText}`);
60
+ catch (error) {
61
+ if (error instanceof Error) {
62
+ throw new Error(`Notion API request failed: ${error.message}`);
63
+ }
64
+ throw new Error('An unknown Notion API error occurred.');
72
65
  }
73
- return (await response.json());
74
66
  },
75
- formatter(data) {
67
+ async formatter(data) {
68
+ await Promise.allSettled(data.results.map((pageOrDatasource) => notionSyncer.triggerImmediateSync(pageOrDatasource.id)));
76
69
  return {
77
- results: data.results,
70
+ results: simplifyNotionPages(data.results),
78
71
  next_cursor: data.next_cursor,
79
72
  has_more: data.has_more,
80
73
  };
81
74
  },
82
75
  });
83
- //# sourceMappingURL=notion-query.js.map
76
+ //# sourceMappingURL=query-datasource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-datasource.js","sourceRoot":"","sources":["../../src/tools/query-datasource.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { NotionSyncer } from '../lib/notion-syncer.js';
2
+ import type { LocalRAG } from '../local-rag/index.js';
3
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ /**
5
+ * Registers the Notion search tool with the MCP server.
6
+ * The tool uses a LocalRAG instance for searching and a NotionSyncer for metadata.
7
+ * @param server - The McpServer instance.
8
+ * @param localRag - The initialized LocalRAG instance.
9
+ * @param notionSyncer - The initialized NotionSyncer instance.
10
+ */
11
+ export declare const registerSearchTool: (server: McpServer, localRag: LocalRAG, notionSyncer: NotionSyncer) => void;
12
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAYxE;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,SAAS,EAAE,UAAU,QAAQ,EAAE,cAAc,YAAY,SA6EnG,CAAA"}
@@ -0,0 +1,75 @@
1
+ import { registerTool } from '@mcp-monorepo/shared';
2
+ import { z } from 'zod';
3
+ /**
4
+ * Registers the Notion search tool with the MCP server.
5
+ * The tool uses a LocalRAG instance for searching and a NotionSyncer for metadata.
6
+ * @param server - The McpServer instance.
7
+ * @param localRag - The initialized LocalRAG instance.
8
+ * @param notionSyncer - The initialized NotionSyncer instance.
9
+ */
10
+ export const registerSearchTool = (server, localRag, notionSyncer) => {
11
+ return registerTool(server, {
12
+ name: 'search',
13
+ title: 'Search Notion workspace',
14
+ description: 'Perform a semantic search over your entire Notion workspace.\nYou can use search when you need to find information which is not already available via other tools, and you don\'t know where it\'s located.\nIf initial results do not contain all the information you need, you can try more specific queries.\nAfter obtaining search results, if the user asks for the full contents of a page or database, use the "fetch" tool. This tool only shows some details like a highlight and the URL and title of each search result.',
15
+ inputSchema: {
16
+ query: z
17
+ .string()
18
+ .min(1)
19
+ .describe("Semantic search query over your entire Notion workspace. For best results, don't provide more than one question per tool call."),
20
+ page_url: z
21
+ .string()
22
+ .optional()
23
+ .describe('Optionally, provide the URL or ID of a page to search within. This will perform a semantic search over the content within and under the specified page.'),
24
+ teamspace_id: z
25
+ .string()
26
+ .optional()
27
+ .describe('Optionally, provide the ID of a teamspace to restrict search results to. This will perform a search over content within the specified teamspace only.'),
28
+ data_source_url: z
29
+ .string()
30
+ .optional()
31
+ .describe('Optionally, provide the URL of a Data source to search. This will perform a semantic search over the pages in the Data Source.'),
32
+ },
33
+ outputSchema: {
34
+ results: z
35
+ .array(z.object({
36
+ title: z.string(),
37
+ url: z.string().url(),
38
+ highlight: z.string(),
39
+ score: z.number(),
40
+ }))
41
+ .describe('An array of search results, ordered by relevance (lower score is better).'),
42
+ },
43
+ isReadOnly: true,
44
+ async fetcher(args) {
45
+ // The core logic is just to query the LocalRAG instance.
46
+ // We don't need the syncer state here, we'll use it in the formatter.
47
+ return localRag.query({
48
+ query: args.query,
49
+ limit: 5, // Return top 5 results
50
+ });
51
+ },
52
+ async formatter(ragResults) {
53
+ const syncState = notionSyncer.getSyncState();
54
+ if (!syncState) {
55
+ return { results: [] };
56
+ }
57
+ const searchResults = [];
58
+ for (const result of ragResults) {
59
+ // Extract the page ID from the file path (e.g., '.../content/page-id.md')
60
+ const pageId = result.filePath.split(/[/\\]/).pop()?.replace('.md', '');
61
+ if (pageId && syncState.pages[pageId]) {
62
+ const pageInfo = syncState.pages[pageId];
63
+ searchResults.push({
64
+ title: pageInfo.title ?? '<N/A>',
65
+ url: pageInfo.url ?? '<N/A>',
66
+ highlight: result.text,
67
+ score: result.score,
68
+ });
69
+ }
70
+ }
71
+ return { results: searchResults };
72
+ },
73
+ });
74
+ };
75
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAkBvB;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,MAAiB,EAAE,QAAkB,EAAE,YAA0B,EAAE,EAAE;IACtG,OAAO,YAAY,CAAC,MAAM,EAAE;QAC1B,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,yBAAyB;QAChC,WAAW,EACT,sgBAAsgB;QACxgB,WAAW,EAAE;YACX,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,CACP,gIAAgI,CACjI;YACH,QAAQ,EAAE,CAAC;iBACR,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CACP,yJAAyJ,CAC1J;YACH,YAAY,EAAE,CAAC;iBACZ,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CACP,uJAAuJ,CACxJ;YACH,eAAe,EAAE,CAAC;iBACf,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CACP,gIAAgI,CACjI;SACJ;QACD,YAAY,EAAE;YACZ,OAAO,EAAE,CAAC;iBACP,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;gBACP,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;gBACjB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;gBACrB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;gBACrB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;aAClB,CAAC,CACH;iBACA,QAAQ,CAAC,2EAA2E,CAAC;SACzF;QACD,UAAU,EAAE,IAAI;QAChB,KAAK,CAAC,OAAO,CAAC,IAAI;YAChB,yDAAyD;YACzD,sEAAsE;YACtE,OAAO,QAAQ,CAAC,KAAK,CAAC;gBACpB,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,KAAK,EAAE,CAAC,EAAE,uBAAuB;aAClC,CAAC,CAAA;QACJ,CAAC;QACD,KAAK,CAAC,SAAS,CAAC,UAAyB;YACvC,MAAM,SAAS,GAAG,YAAY,CAAC,YAAY,EAAE,CAAA;YAC7C,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;YACxB,CAAC;YAED,MAAM,aAAa,GAAyB,EAAE,CAAA;YAC9C,KAAK,MAAM,MAAM,IAAI,UAAU,EAAE,CAAC;gBAChC,0EAA0E;gBAC1E,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;gBAEvE,IAAI,MAAM,IAAI,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;oBACtC,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;oBACxC,aAAa,CAAC,IAAI,CAAC;wBACjB,KAAK,EAAE,QAAQ,CAAC,KAAK,IAAI,OAAO;wBAChC,GAAG,EAAE,QAAQ,CAAC,GAAG,IAAI,OAAO;wBAC5B,SAAS,EAAE,MAAM,CAAC,IAAI;wBACtB,KAAK,EAAE,MAAM,CAAC,KAAK;qBACpB,CAAC,CAAA;gBACJ,CAAC;YACH,CAAC;YACD,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,CAAA;QACnC,CAAC;KACF,CAAC,CAAA;AACJ,CAAC,CAAA"}