@m2ai-mcp/notion-mcp 0.1.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/.env.example +7 -0
- package/LICENSE +21 -0
- package/README.md +228 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +374 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/blocks.d.ts +43 -0
- package/dist/tools/blocks.d.ts.map +1 -0
- package/dist/tools/blocks.js +124 -0
- package/dist/tools/blocks.js.map +1 -0
- package/dist/tools/databases.d.ts +71 -0
- package/dist/tools/databases.d.ts.map +1 -0
- package/dist/tools/databases.js +121 -0
- package/dist/tools/databases.js.map +1 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +9 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/pages.d.ts +72 -0
- package/dist/tools/pages.d.ts.map +1 -0
- package/dist/tools/pages.js +153 -0
- package/dist/tools/pages.js.map +1 -0
- package/dist/tools/search.d.ts +28 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +62 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/users.d.ts +33 -0
- package/dist/tools/users.d.ts.map +1 -0
- package/dist/tools/users.js +51 -0
- package/dist/tools/users.js.map +1 -0
- package/dist/utils/markdown-converter.d.ts +31 -0
- package/dist/utils/markdown-converter.d.ts.map +1 -0
- package/dist/utils/markdown-converter.js +355 -0
- package/dist/utils/markdown-converter.js.map +1 -0
- package/dist/utils/notion-client.d.ts +32 -0
- package/dist/utils/notion-client.d.ts.map +1 -0
- package/dist/utils/notion-client.js +111 -0
- package/dist/utils/notion-client.js.map +1 -0
- package/dist/utils/types.d.ts +212 -0
- package/dist/utils/types.d.ts.map +1 -0
- package/dist/utils/types.js +18 -0
- package/dist/utils/types.js.map +1 -0
- package/jest.config.cjs +33 -0
- package/package.json +53 -0
- package/server.json +92 -0
- package/src/index.ts +435 -0
- package/src/tools/blocks.ts +184 -0
- package/src/tools/databases.ts +216 -0
- package/src/tools/index.ts +9 -0
- package/src/tools/pages.ts +253 -0
- package/src/tools/search.ts +96 -0
- package/src/tools/users.ts +93 -0
- package/src/utils/markdown-converter.ts +408 -0
- package/src/utils/notion-client.ts +159 -0
- package/src/utils/types.ts +237 -0
- package/tests/markdown-converter.test.ts +252 -0
- package/tests/notion-client.test.ts +67 -0
- package/tests/tools.test.ts +448 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Tools
|
|
3
|
+
* get_database, query_database, create_database
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { NotionClient, extractNotionId } from '../utils/notion-client.js';
|
|
7
|
+
import { NotionDatabase, NotionPage, PaginatedResponse, getPageTitle, getDatabaseTitle, NotionFilter, NotionSort } from '../utils/types.js';
|
|
8
|
+
|
|
9
|
+
// Get Database
|
|
10
|
+
export interface GetDatabaseParams {
|
|
11
|
+
database_id: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface GetDatabaseResult {
|
|
15
|
+
success: boolean;
|
|
16
|
+
database?: {
|
|
17
|
+
id: string;
|
|
18
|
+
title: string;
|
|
19
|
+
description: string;
|
|
20
|
+
url: string;
|
|
21
|
+
created_time: string;
|
|
22
|
+
last_edited_time: string;
|
|
23
|
+
properties: Record<string, {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
type: string;
|
|
27
|
+
}>;
|
|
28
|
+
is_inline: boolean;
|
|
29
|
+
archived: boolean;
|
|
30
|
+
};
|
|
31
|
+
error?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function getDatabase(client: NotionClient, params: GetDatabaseParams): Promise<GetDatabaseResult> {
|
|
35
|
+
const databaseId = extractNotionId(params.database_id);
|
|
36
|
+
const response = await client.get<NotionDatabase>(`/databases/${databaseId}`);
|
|
37
|
+
|
|
38
|
+
if (!response.success || !response.data) {
|
|
39
|
+
return {
|
|
40
|
+
success: false,
|
|
41
|
+
error: response.error || 'Failed to get database'
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const db = response.data;
|
|
46
|
+
|
|
47
|
+
// Simplify properties schema
|
|
48
|
+
const properties: Record<string, { id: string; name: string; type: string }> = {};
|
|
49
|
+
for (const [name, prop] of Object.entries(db.properties)) {
|
|
50
|
+
properties[name] = {
|
|
51
|
+
id: prop.id,
|
|
52
|
+
name: prop.name,
|
|
53
|
+
type: prop.type
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
success: true,
|
|
59
|
+
database: {
|
|
60
|
+
id: db.id,
|
|
61
|
+
title: getDatabaseTitle(db),
|
|
62
|
+
description: db.description.map(d => d.plain_text || d.text?.content || '').join(''),
|
|
63
|
+
url: db.url,
|
|
64
|
+
created_time: db.created_time,
|
|
65
|
+
last_edited_time: db.last_edited_time,
|
|
66
|
+
properties,
|
|
67
|
+
is_inline: db.is_inline,
|
|
68
|
+
archived: db.archived
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Query Database
|
|
74
|
+
export interface QueryDatabaseParams {
|
|
75
|
+
database_id: string;
|
|
76
|
+
filter?: NotionFilter;
|
|
77
|
+
sorts?: NotionSort[];
|
|
78
|
+
page_size?: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface QueryDatabaseResult {
|
|
82
|
+
success: boolean;
|
|
83
|
+
pages?: {
|
|
84
|
+
id: string;
|
|
85
|
+
title: string;
|
|
86
|
+
url: string;
|
|
87
|
+
created_time: string;
|
|
88
|
+
last_edited_time: string;
|
|
89
|
+
properties: Record<string, unknown>;
|
|
90
|
+
}[];
|
|
91
|
+
total_count?: number;
|
|
92
|
+
has_more?: boolean;
|
|
93
|
+
next_cursor?: string | null;
|
|
94
|
+
error?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function queryDatabase(client: NotionClient, params: QueryDatabaseParams): Promise<QueryDatabaseResult> {
|
|
98
|
+
const { database_id, filter, sorts, page_size = 100 } = params;
|
|
99
|
+
const databaseId = extractNotionId(database_id);
|
|
100
|
+
|
|
101
|
+
const requestBody: Record<string, unknown> = {
|
|
102
|
+
page_size: Math.min(Math.max(page_size, 1), 100)
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
if (filter) {
|
|
106
|
+
requestBody.filter = filter;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (sorts && sorts.length > 0) {
|
|
110
|
+
requestBody.sorts = sorts;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const response = await client.post<PaginatedResponse<NotionPage>>(
|
|
114
|
+
`/databases/${databaseId}/query`,
|
|
115
|
+
requestBody
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (!response.success || !response.data) {
|
|
119
|
+
return {
|
|
120
|
+
success: false,
|
|
121
|
+
error: response.error || 'Failed to query database'
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const pages = response.data.results.map(page => ({
|
|
126
|
+
id: page.id,
|
|
127
|
+
title: getPageTitle(page),
|
|
128
|
+
url: page.url,
|
|
129
|
+
created_time: page.created_time,
|
|
130
|
+
last_edited_time: page.last_edited_time,
|
|
131
|
+
properties: page.properties
|
|
132
|
+
}));
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
success: true,
|
|
136
|
+
pages,
|
|
137
|
+
total_count: pages.length,
|
|
138
|
+
has_more: response.data.has_more,
|
|
139
|
+
next_cursor: response.data.next_cursor
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Create Database
|
|
144
|
+
export interface CreateDatabaseParams {
|
|
145
|
+
parent_page_id: string;
|
|
146
|
+
title: string;
|
|
147
|
+
properties: Record<string, DatabasePropertySchema>;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface DatabasePropertySchema {
|
|
151
|
+
type: string;
|
|
152
|
+
[key: string]: unknown;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface CreateDatabaseResult {
|
|
156
|
+
success: boolean;
|
|
157
|
+
database?: {
|
|
158
|
+
id: string;
|
|
159
|
+
title: string;
|
|
160
|
+
url: string;
|
|
161
|
+
};
|
|
162
|
+
error?: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function createDatabase(client: NotionClient, params: CreateDatabaseParams): Promise<CreateDatabaseResult> {
|
|
166
|
+
const { parent_page_id, title, properties } = params;
|
|
167
|
+
const pageId = extractNotionId(parent_page_id);
|
|
168
|
+
|
|
169
|
+
// Ensure there's at least a title property
|
|
170
|
+
const dbProperties: Record<string, unknown> = { ...properties };
|
|
171
|
+
|
|
172
|
+
// Every database needs a title property
|
|
173
|
+
let hasTitleProperty = false;
|
|
174
|
+
for (const prop of Object.values(dbProperties)) {
|
|
175
|
+
if ((prop as Record<string, unknown>).type === 'title') {
|
|
176
|
+
hasTitleProperty = true;
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!hasTitleProperty) {
|
|
182
|
+
dbProperties['Name'] = { title: {} };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const requestBody = {
|
|
186
|
+
parent: {
|
|
187
|
+
type: 'page_id',
|
|
188
|
+
page_id: pageId
|
|
189
|
+
},
|
|
190
|
+
title: [
|
|
191
|
+
{
|
|
192
|
+
type: 'text',
|
|
193
|
+
text: { content: title }
|
|
194
|
+
}
|
|
195
|
+
],
|
|
196
|
+
properties: dbProperties
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const response = await client.post<NotionDatabase>('/databases', requestBody);
|
|
200
|
+
|
|
201
|
+
if (!response.success || !response.data) {
|
|
202
|
+
return {
|
|
203
|
+
success: false,
|
|
204
|
+
error: response.error || 'Failed to create database'
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
success: true,
|
|
210
|
+
database: {
|
|
211
|
+
id: response.data.id,
|
|
212
|
+
title: getDatabaseTitle(response.data),
|
|
213
|
+
url: response.data.url
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Tools
|
|
3
|
+
* get_page, create_page, update_page, get_page_content
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { NotionClient, extractNotionId } from '../utils/notion-client.js';
|
|
7
|
+
import { NotionPage, NotionBlock, PaginatedResponse, getPageTitle } from '../utils/types.js';
|
|
8
|
+
import { markdownToBlocks, blocksToMarkdown, blocksToPlainText } from '../utils/markdown-converter.js';
|
|
9
|
+
|
|
10
|
+
// Get Page
|
|
11
|
+
export interface GetPageParams {
|
|
12
|
+
page_id: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface GetPageResult {
|
|
16
|
+
success: boolean;
|
|
17
|
+
page?: {
|
|
18
|
+
id: string;
|
|
19
|
+
title: string;
|
|
20
|
+
url: string;
|
|
21
|
+
created_time: string;
|
|
22
|
+
last_edited_time: string;
|
|
23
|
+
archived: boolean;
|
|
24
|
+
properties: Record<string, unknown>;
|
|
25
|
+
parent: {
|
|
26
|
+
type: string;
|
|
27
|
+
id?: string;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
error?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function getPage(client: NotionClient, params: GetPageParams): Promise<GetPageResult> {
|
|
34
|
+
const pageId = extractNotionId(params.page_id);
|
|
35
|
+
const response = await client.get<NotionPage>(`/pages/${pageId}`);
|
|
36
|
+
|
|
37
|
+
if (!response.success || !response.data) {
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
error: response.error || 'Failed to get page'
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const page = response.data;
|
|
45
|
+
return {
|
|
46
|
+
success: true,
|
|
47
|
+
page: {
|
|
48
|
+
id: page.id,
|
|
49
|
+
title: getPageTitle(page),
|
|
50
|
+
url: page.url,
|
|
51
|
+
created_time: page.created_time,
|
|
52
|
+
last_edited_time: page.last_edited_time,
|
|
53
|
+
archived: page.archived,
|
|
54
|
+
properties: page.properties,
|
|
55
|
+
parent: {
|
|
56
|
+
type: page.parent.type,
|
|
57
|
+
id: page.parent.database_id || page.parent.page_id
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Create Page
|
|
64
|
+
export interface CreatePageParams {
|
|
65
|
+
parent_id: string;
|
|
66
|
+
parent_type: 'database_id' | 'page_id';
|
|
67
|
+
title: string;
|
|
68
|
+
properties?: Record<string, unknown>;
|
|
69
|
+
content?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface CreatePageResult {
|
|
73
|
+
success: boolean;
|
|
74
|
+
page?: {
|
|
75
|
+
id: string;
|
|
76
|
+
url: string;
|
|
77
|
+
title: string;
|
|
78
|
+
};
|
|
79
|
+
error?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function createPage(client: NotionClient, params: CreatePageParams): Promise<CreatePageResult> {
|
|
83
|
+
const { parent_id, parent_type, title, properties = {}, content } = params;
|
|
84
|
+
const parentId = extractNotionId(parent_id);
|
|
85
|
+
|
|
86
|
+
// Build parent object
|
|
87
|
+
const parent: Record<string, string> = {};
|
|
88
|
+
parent[parent_type] = parentId;
|
|
89
|
+
|
|
90
|
+
// Build properties - always include title
|
|
91
|
+
const pageProperties: Record<string, unknown> = { ...properties };
|
|
92
|
+
|
|
93
|
+
// For database parents, title is usually a property called "Name" or "Title"
|
|
94
|
+
// For page parents, we need to set the title via page properties
|
|
95
|
+
if (parent_type === 'database_id') {
|
|
96
|
+
// If no title property is set, try "Name" first, then "Title"
|
|
97
|
+
if (!pageProperties['Name'] && !pageProperties['Title'] && !pageProperties['title']) {
|
|
98
|
+
pageProperties['Name'] = {
|
|
99
|
+
title: [{ type: 'text', text: { content: title } }]
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
// For page parents, set title property
|
|
104
|
+
pageProperties['title'] = {
|
|
105
|
+
title: [{ type: 'text', text: { content: title } }]
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Build request body
|
|
110
|
+
const requestBody: Record<string, unknown> = {
|
|
111
|
+
parent,
|
|
112
|
+
properties: pageProperties
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Add content as children blocks if provided
|
|
116
|
+
if (content) {
|
|
117
|
+
requestBody.children = markdownToBlocks(content);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const response = await client.post<NotionPage>('/pages', requestBody);
|
|
121
|
+
|
|
122
|
+
if (!response.success || !response.data) {
|
|
123
|
+
return {
|
|
124
|
+
success: false,
|
|
125
|
+
error: response.error || 'Failed to create page'
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
success: true,
|
|
131
|
+
page: {
|
|
132
|
+
id: response.data.id,
|
|
133
|
+
url: response.data.url,
|
|
134
|
+
title: getPageTitle(response.data)
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Update Page
|
|
140
|
+
export interface UpdatePageParams {
|
|
141
|
+
page_id: string;
|
|
142
|
+
properties: Record<string, unknown>;
|
|
143
|
+
archived?: boolean;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface UpdatePageResult {
|
|
147
|
+
success: boolean;
|
|
148
|
+
page?: {
|
|
149
|
+
id: string;
|
|
150
|
+
url: string;
|
|
151
|
+
title: string;
|
|
152
|
+
last_edited_time: string;
|
|
153
|
+
};
|
|
154
|
+
error?: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function updatePage(client: NotionClient, params: UpdatePageParams): Promise<UpdatePageResult> {
|
|
158
|
+
const { page_id, properties, archived } = params;
|
|
159
|
+
const pageId = extractNotionId(page_id);
|
|
160
|
+
|
|
161
|
+
const requestBody: Record<string, unknown> = {
|
|
162
|
+
properties
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
if (archived !== undefined) {
|
|
166
|
+
requestBody.archived = archived;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const response = await client.patch<NotionPage>(`/pages/${pageId}`, requestBody);
|
|
170
|
+
|
|
171
|
+
if (!response.success || !response.data) {
|
|
172
|
+
return {
|
|
173
|
+
success: false,
|
|
174
|
+
error: response.error || 'Failed to update page'
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
success: true,
|
|
180
|
+
page: {
|
|
181
|
+
id: response.data.id,
|
|
182
|
+
url: response.data.url,
|
|
183
|
+
title: getPageTitle(response.data),
|
|
184
|
+
last_edited_time: response.data.last_edited_time
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Get Page Content
|
|
190
|
+
export interface GetPageContentParams {
|
|
191
|
+
page_id: string;
|
|
192
|
+
format?: 'markdown' | 'blocks' | 'plain_text';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export interface GetPageContentResult {
|
|
196
|
+
success: boolean;
|
|
197
|
+
content?: string | unknown[];
|
|
198
|
+
format?: string;
|
|
199
|
+
block_count?: number;
|
|
200
|
+
error?: string;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export async function getPageContent(client: NotionClient, params: GetPageContentParams): Promise<GetPageContentResult> {
|
|
204
|
+
const { page_id, format = 'markdown' } = params;
|
|
205
|
+
const pageId = extractNotionId(page_id);
|
|
206
|
+
|
|
207
|
+
// Fetch all blocks (with pagination)
|
|
208
|
+
const allBlocks: NotionBlock[] = [];
|
|
209
|
+
let cursor: string | undefined;
|
|
210
|
+
let hasMore = true;
|
|
211
|
+
|
|
212
|
+
while (hasMore) {
|
|
213
|
+
const endpoint = cursor
|
|
214
|
+
? `/blocks/${pageId}/children?start_cursor=${cursor}&page_size=100`
|
|
215
|
+
: `/blocks/${pageId}/children?page_size=100`;
|
|
216
|
+
|
|
217
|
+
const response = await client.get<PaginatedResponse<NotionBlock>>(endpoint);
|
|
218
|
+
|
|
219
|
+
if (!response.success || !response.data) {
|
|
220
|
+
return {
|
|
221
|
+
success: false,
|
|
222
|
+
error: response.error || 'Failed to get page content'
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
allBlocks.push(...response.data.results);
|
|
227
|
+
hasMore = response.data.has_more;
|
|
228
|
+
cursor = response.data.next_cursor || undefined;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Convert blocks to requested format
|
|
232
|
+
let content: string | unknown[];
|
|
233
|
+
|
|
234
|
+
switch (format) {
|
|
235
|
+
case 'blocks':
|
|
236
|
+
content = allBlocks;
|
|
237
|
+
break;
|
|
238
|
+
case 'plain_text':
|
|
239
|
+
content = blocksToPlainText(allBlocks);
|
|
240
|
+
break;
|
|
241
|
+
case 'markdown':
|
|
242
|
+
default:
|
|
243
|
+
content = blocksToMarkdown(allBlocks);
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
success: true,
|
|
249
|
+
content,
|
|
250
|
+
format,
|
|
251
|
+
block_count: allBlocks.length
|
|
252
|
+
};
|
|
253
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search Tool
|
|
3
|
+
* Search across all pages and databases in the workspace
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { NotionClient } from '../utils/notion-client.js';
|
|
7
|
+
import { SearchResponse, getPageTitle, getDatabaseTitle, NotionPage, NotionDatabase } from '../utils/types.js';
|
|
8
|
+
|
|
9
|
+
export interface SearchParams {
|
|
10
|
+
query: string;
|
|
11
|
+
filter_type?: 'page' | 'database';
|
|
12
|
+
sort_direction?: 'ascending' | 'descending';
|
|
13
|
+
page_size?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SearchResult {
|
|
17
|
+
id: string;
|
|
18
|
+
type: 'page' | 'database';
|
|
19
|
+
title: string;
|
|
20
|
+
url: string;
|
|
21
|
+
last_edited_time: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SearchToolResult {
|
|
25
|
+
success: boolean;
|
|
26
|
+
results?: SearchResult[];
|
|
27
|
+
total_count?: number;
|
|
28
|
+
has_more?: boolean;
|
|
29
|
+
error?: string;
|
|
30
|
+
suggestion?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function search(client: NotionClient, params: SearchParams): Promise<SearchToolResult> {
|
|
34
|
+
const { query, filter_type, sort_direction = 'descending', page_size = 10 } = params;
|
|
35
|
+
|
|
36
|
+
const requestBody: Record<string, unknown> = {
|
|
37
|
+
query,
|
|
38
|
+
page_size: Math.min(Math.max(page_size, 1), 100)
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
if (filter_type) {
|
|
42
|
+
requestBody.filter = {
|
|
43
|
+
value: filter_type,
|
|
44
|
+
property: 'object'
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
requestBody.sort = {
|
|
49
|
+
direction: sort_direction,
|
|
50
|
+
timestamp: 'last_edited_time'
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const response = await client.post<SearchResponse>('/search', requestBody);
|
|
54
|
+
|
|
55
|
+
if (!response.success || !response.data) {
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
error: response.error || 'Failed to search'
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const results: SearchResult[] = response.data.results.map(item => {
|
|
63
|
+
if (item.object === 'page') {
|
|
64
|
+
const page = item as NotionPage;
|
|
65
|
+
return {
|
|
66
|
+
id: page.id,
|
|
67
|
+
type: 'page' as const,
|
|
68
|
+
title: getPageTitle(page),
|
|
69
|
+
url: page.url,
|
|
70
|
+
last_edited_time: page.last_edited_time
|
|
71
|
+
};
|
|
72
|
+
} else {
|
|
73
|
+
const database = item as NotionDatabase;
|
|
74
|
+
return {
|
|
75
|
+
id: database.id,
|
|
76
|
+
type: 'database' as const,
|
|
77
|
+
title: getDatabaseTitle(database),
|
|
78
|
+
url: database.url,
|
|
79
|
+
last_edited_time: database.last_edited_time
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const result: SearchToolResult = {
|
|
85
|
+
success: true,
|
|
86
|
+
results,
|
|
87
|
+
total_count: results.length,
|
|
88
|
+
has_more: response.data.has_more
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
if (results.length === 0) {
|
|
92
|
+
result.suggestion = `No results found for "${query}". Try alternative search terms or check that the content is shared with the integration.`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Tools
|
|
3
|
+
* list_users, get_user
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { NotionClient, extractNotionId } from '../utils/notion-client.js';
|
|
7
|
+
import { NotionUser, PaginatedResponse } from '../utils/types.js';
|
|
8
|
+
|
|
9
|
+
// List Users
|
|
10
|
+
export interface ListUsersParams {
|
|
11
|
+
page_size?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface UserInfo {
|
|
15
|
+
id: string;
|
|
16
|
+
type: 'person' | 'bot' | undefined;
|
|
17
|
+
name: string | undefined;
|
|
18
|
+
avatar_url: string | null | undefined;
|
|
19
|
+
email?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ListUsersResult {
|
|
23
|
+
success: boolean;
|
|
24
|
+
users?: UserInfo[];
|
|
25
|
+
total_count?: number;
|
|
26
|
+
has_more?: boolean;
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function listUsers(client: NotionClient, params: ListUsersParams = {}): Promise<ListUsersResult> {
|
|
31
|
+
const { page_size = 100 } = params;
|
|
32
|
+
|
|
33
|
+
const endpoint = `/users?page_size=${Math.min(Math.max(page_size, 1), 100)}`;
|
|
34
|
+
const response = await client.get<PaginatedResponse<NotionUser>>(endpoint);
|
|
35
|
+
|
|
36
|
+
if (!response.success || !response.data) {
|
|
37
|
+
return {
|
|
38
|
+
success: false,
|
|
39
|
+
error: response.error || 'Failed to list users'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const users: UserInfo[] = response.data.results.map(user => ({
|
|
44
|
+
id: user.id,
|
|
45
|
+
type: user.type,
|
|
46
|
+
name: user.name,
|
|
47
|
+
avatar_url: user.avatar_url,
|
|
48
|
+
email: user.person?.email
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
success: true,
|
|
53
|
+
users,
|
|
54
|
+
total_count: users.length,
|
|
55
|
+
has_more: response.data.has_more
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Get User
|
|
60
|
+
export interface GetUserParams {
|
|
61
|
+
user_id: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface GetUserResult {
|
|
65
|
+
success: boolean;
|
|
66
|
+
user?: UserInfo;
|
|
67
|
+
error?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function getUser(client: NotionClient, params: GetUserParams): Promise<GetUserResult> {
|
|
71
|
+
const userId = extractNotionId(params.user_id);
|
|
72
|
+
const response = await client.get<NotionUser>(`/users/${userId}`);
|
|
73
|
+
|
|
74
|
+
if (!response.success || !response.data) {
|
|
75
|
+
return {
|
|
76
|
+
success: false,
|
|
77
|
+
error: response.error || 'Failed to get user'
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const user = response.data;
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
success: true,
|
|
85
|
+
user: {
|
|
86
|
+
id: user.id,
|
|
87
|
+
type: user.type,
|
|
88
|
+
name: user.name,
|
|
89
|
+
avatar_url: user.avatar_url,
|
|
90
|
+
email: user.person?.email
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|