adoptai-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -0
- package/bin/adoptai-mcp.js +2 -0
- package/dist/apps/canva.js +1 -0
- package/dist/apps/figma.js +1 -0
- package/dist/apps/github.js +2 -0
- package/dist/apps/notion.js +1 -0
- package/dist/apps/registry.js +20 -0
- package/dist/apps/salesforce.js +1 -0
- package/dist/cli/add.js +532 -0
- package/dist/cli/index.js +39 -0
- package/dist/cli/list.js +19 -0
- package/dist/cli/remove.js +37 -0
- package/dist/cli/serve.js +27 -0
- package/dist/cli/status.js +24 -0
- package/dist/config/clients.js +118 -0
- package/dist/config/credentials.js +34 -0
- package/dist/core/auth-manager.js +237 -0
- package/dist/core/config-writer.js +161 -0
- package/dist/core/doctor.js +199 -0
- package/dist/core/package.json +3 -0
- package/dist/core/server-base.js +81 -0
- package/dist/integrations/canva/.env +3 -0
- package/dist/integrations/canva/auth.js +287 -0
- package/dist/integrations/canva/env.js +9 -0
- package/dist/integrations/canva/index.js +12 -0
- package/dist/integrations/canva/package.json +31 -0
- package/dist/integrations/canva/publish-to-adoptai.js +365 -0
- package/dist/integrations/canva/setup.js +90 -0
- package/dist/integrations/canva/tools.js +1315 -0
- package/dist/integrations/canva/tools.original.js +1315 -0
- package/dist/integrations/figma/auth.js +48 -0
- package/dist/integrations/figma/index.js +11 -0
- package/dist/integrations/figma/package.json +27 -0
- package/dist/integrations/figma/publish-to-adoptai.js +384 -0
- package/dist/integrations/figma/setup.js +90 -0
- package/dist/integrations/figma/tools.js +1137 -0
- package/dist/integrations/github/auth.js +53 -0
- package/dist/integrations/github/index.js +11 -0
- package/dist/integrations/github/package.json +28 -0
- package/dist/integrations/github/publish-to-adoptai.js +240 -0
- package/dist/integrations/github/setup.js +103 -0
- package/dist/integrations/github/tools.js +78 -0
- package/dist/integrations/github-actions/auth.js +53 -0
- package/dist/integrations/github-actions/index.js +11 -0
- package/dist/integrations/github-actions/package.json +27 -0
- package/dist/integrations/github-actions/setup.js +103 -0
- package/dist/integrations/github-actions/tools.js +5642 -0
- package/dist/integrations/github-activity/auth.js +53 -0
- package/dist/integrations/github-activity/index.js +11 -0
- package/dist/integrations/github-activity/package.json +27 -0
- package/dist/integrations/github-activity/setup.js +103 -0
- package/dist/integrations/github-activity/tools.js +925 -0
- package/dist/integrations/github-apps/auth.js +53 -0
- package/dist/integrations/github-apps/index.js +11 -0
- package/dist/integrations/github-apps/package.json +27 -0
- package/dist/integrations/github-apps/setup.js +103 -0
- package/dist/integrations/github-apps/tools.js +791 -0
- package/dist/integrations/github-billing/auth.js +53 -0
- package/dist/integrations/github-billing/index.js +11 -0
- package/dist/integrations/github-billing/package.json +27 -0
- package/dist/integrations/github-billing/setup.js +103 -0
- package/dist/integrations/github-billing/tools.js +438 -0
- package/dist/integrations/github-checks/auth.js +53 -0
- package/dist/integrations/github-checks/index.js +11 -0
- package/dist/integrations/github-checks/package.json +27 -0
- package/dist/integrations/github-checks/setup.js +103 -0
- package/dist/integrations/github-checks/tools.js +607 -0
- package/dist/integrations/github-code-scanning/auth.js +53 -0
- package/dist/integrations/github-code-scanning/index.js +11 -0
- package/dist/integrations/github-code-scanning/package.json +27 -0
- package/dist/integrations/github-code-scanning/setup.js +103 -0
- package/dist/integrations/github-code-scanning/tools.js +987 -0
- package/dist/integrations/github-dependabot/auth.js +53 -0
- package/dist/integrations/github-dependabot/index.js +11 -0
- package/dist/integrations/github-dependabot/package.json +27 -0
- package/dist/integrations/github-dependabot/setup.js +103 -0
- package/dist/integrations/github-dependabot/tools.js +915 -0
- package/dist/integrations/github-gists/auth.js +53 -0
- package/dist/integrations/github-gists/index.js +11 -0
- package/dist/integrations/github-gists/package.json +27 -0
- package/dist/integrations/github-gists/setup.js +103 -0
- package/dist/integrations/github-gists/tools.js +545 -0
- package/dist/integrations/github-git/auth.js +53 -0
- package/dist/integrations/github-git/index.js +11 -0
- package/dist/integrations/github-git/package.json +27 -0
- package/dist/integrations/github-git/setup.js +103 -0
- package/dist/integrations/github-git/tools.js +513 -0
- package/dist/integrations/github-issues/auth.js +53 -0
- package/dist/integrations/github-issues/index.js +11 -0
- package/dist/integrations/github-issues/package.json +27 -0
- package/dist/integrations/github-issues/setup.js +103 -0
- package/dist/integrations/github-issues/tools.js +2232 -0
- package/dist/integrations/github-orgs/auth.js +53 -0
- package/dist/integrations/github-orgs/index.js +11 -0
- package/dist/integrations/github-orgs/package.json +27 -0
- package/dist/integrations/github-orgs/setup.js +103 -0
- package/dist/integrations/github-orgs/tools.js +3512 -0
- package/dist/integrations/github-packages/auth.js +53 -0
- package/dist/integrations/github-packages/index.js +11 -0
- package/dist/integrations/github-packages/package.json +27 -0
- package/dist/integrations/github-packages/setup.js +103 -0
- package/dist/integrations/github-packages/tools.js +1088 -0
- package/dist/integrations/github-pulls/auth.js +53 -0
- package/dist/integrations/github-pulls/index.js +11 -0
- package/dist/integrations/github-pulls/package.json +27 -0
- package/dist/integrations/github-pulls/setup.js +103 -0
- package/dist/integrations/github-pulls/tools.js +1252 -0
- package/dist/integrations/github-reactions/auth.js +53 -0
- package/dist/integrations/github-reactions/index.js +11 -0
- package/dist/integrations/github-reactions/package.json +27 -0
- package/dist/integrations/github-reactions/setup.js +103 -0
- package/dist/integrations/github-reactions/tools.js +706 -0
- package/dist/integrations/github-repos/auth.js +53 -0
- package/dist/integrations/github-repos/index.js +11 -0
- package/dist/integrations/github-repos/package.json +27 -0
- package/dist/integrations/github-repos/setup.js +103 -0
- package/dist/integrations/github-repos/tools.js +7286 -0
- package/dist/integrations/github-search/auth.js +53 -0
- package/dist/integrations/github-search/index.js +11 -0
- package/dist/integrations/github-search/package.json +27 -0
- package/dist/integrations/github-search/setup.js +103 -0
- package/dist/integrations/github-search/tools.js +370 -0
- package/dist/integrations/github-teams/auth.js +53 -0
- package/dist/integrations/github-teams/index.js +11 -0
- package/dist/integrations/github-teams/package.json +27 -0
- package/dist/integrations/github-teams/setup.js +103 -0
- package/dist/integrations/github-teams/tools.js +633 -0
- package/dist/integrations/github-users/auth.js +53 -0
- package/dist/integrations/github-users/index.js +11 -0
- package/dist/integrations/github-users/package.json +27 -0
- package/dist/integrations/github-users/setup.js +103 -0
- package/dist/integrations/github-users/tools.js +1118 -0
- package/dist/integrations/notion/api.js +108 -0
- package/dist/integrations/notion/auth.js +59 -0
- package/dist/integrations/notion/endpoints.json +630 -0
- package/dist/integrations/notion/index.js +11 -0
- package/dist/integrations/notion/package.json +33 -0
- package/dist/integrations/notion/publish-to-adoptai.js +271 -0
- package/dist/integrations/notion/scripts/generate-endpoints.mjs +306 -0
- package/dist/integrations/notion/setup.js +89 -0
- package/dist/integrations/notion/tools.js +586 -0
- package/dist/integrations/notion/tools.original.js +568 -0
- package/dist/integrations/salesforce/.env +8 -0
- package/dist/integrations/salesforce/.env.example +15 -0
- package/dist/integrations/salesforce/auth.js +311 -0
- package/dist/integrations/salesforce/endpoints.json +1359 -0
- package/dist/integrations/salesforce/env.js +9 -0
- package/dist/integrations/salesforce/index.js +12 -0
- package/dist/integrations/salesforce/package.json +42 -0
- package/dist/integrations/salesforce/publish-smart-specs.js +890 -0
- package/dist/integrations/salesforce/publish-to-adoptai.js +386 -0
- package/dist/integrations/salesforce/scripts/extract-postman.mjs +222 -0
- package/dist/integrations/salesforce/setup.js +112 -0
- package/dist/integrations/salesforce/tools.js +4544 -0
- package/dist/integrations/salesforce/tools.original.js +4487 -0
- package/dist/server/mcp-server.js +50 -0
- package/dist/server/tool-loader.js +47 -0
- package/dist/specs/figma-api.json +13621 -0
- package/dist/specs/split/salesforce-auth.json +3931 -0
- package/dist/specs/split/salesforce-bulk-v1.json +1489 -0
- package/dist/specs/split/salesforce-bulk-v2.json +1951 -0
- package/dist/specs/split/salesforce-composite.json +1246 -0
- package/dist/specs/split/salesforce-connect.json +11639 -0
- package/dist/specs/split/salesforce-einstein-prediction-service.json +576 -0
- package/dist/specs/split/salesforce-event-platform.json +2682 -0
- package/dist/specs/split/salesforce-graphql.json +1754 -0
- package/dist/specs/split/salesforce-industries.json +4115 -0
- package/dist/specs/split/salesforce-metadata.json +555 -0
- package/dist/specs/split/salesforce-rest.json +4798 -0
- package/dist/specs/split/salesforce-soap.json +210 -0
- package/dist/specs/split/salesforce-subscription-management.json +1299 -0
- package/dist/specs/split/salesforce-tooling.json +2026 -0
- package/dist/specs/split/salesforce-ui.json +7426 -0
- package/package.json +47 -0
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { notionRequest } from './api.js';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
const manifest = JSON.parse(readFileSync(join(__dirname, 'endpoints.json'), 'utf8'));
|
|
9
|
+
const BASE_URL = manifest.baseUrl;
|
|
10
|
+
|
|
11
|
+
function text(payload) {
|
|
12
|
+
return {
|
|
13
|
+
content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function enrichSchema(schema, method) {
|
|
18
|
+
const s = structuredClone(schema);
|
|
19
|
+
s.properties = s.properties || {};
|
|
20
|
+
if (['POST', 'PUT', 'PATCH'].includes(method)) {
|
|
21
|
+
s.properties._rawBody = {
|
|
22
|
+
type: 'object',
|
|
23
|
+
description:
|
|
24
|
+
'Optional full JSON body for nested structures (parent, properties, rich_text, etc.). When set, replaces other body fields.',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return s;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const baseTools = manifest.endpoints.map((endpoint) => ({
|
|
31
|
+
name: endpoint.name,
|
|
32
|
+
description: endpoint.description,
|
|
33
|
+
inputSchema: enrichSchema(endpoint.inputSchema, endpoint.method),
|
|
34
|
+
handler: async (params) => text(await notionRequest(endpoint, BASE_URL, params)),
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
function call(method, path, params = {}) {
|
|
38
|
+
return notionRequest({ method, path }, BASE_URL, params);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function richText(content) {
|
|
42
|
+
return [{ type: 'text', text: { content: String(content ?? '') } }];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function blockText(block) {
|
|
46
|
+
const t = block?.type;
|
|
47
|
+
const value = block?.[t];
|
|
48
|
+
if (!value) return '';
|
|
49
|
+
if (Array.isArray(value.rich_text)) {
|
|
50
|
+
return value.rich_text.map((x) => x?.plain_text || x?.text?.content || '').join('');
|
|
51
|
+
}
|
|
52
|
+
if (Array.isArray(value.title)) {
|
|
53
|
+
return value.title.map((x) => x?.plain_text || x?.text?.content || '').join('');
|
|
54
|
+
}
|
|
55
|
+
return '';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function pageTitle(page) {
|
|
59
|
+
const props = page?.properties || {};
|
|
60
|
+
for (const p of Object.values(props)) {
|
|
61
|
+
if (p?.type === 'title' && Array.isArray(p.title)) {
|
|
62
|
+
const t = p.title.map((x) => x?.plain_text || x?.text?.content || '').join('');
|
|
63
|
+
if (t) return t;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return '';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function listAllChildren(block_id, max_depth = 5, depth = 1) {
|
|
70
|
+
let cursor;
|
|
71
|
+
const items = [];
|
|
72
|
+
do {
|
|
73
|
+
const page = await call('GET', '/v1/blocks/{block_id}/children', {
|
|
74
|
+
block_id,
|
|
75
|
+
start_cursor: cursor,
|
|
76
|
+
page_size: 100,
|
|
77
|
+
});
|
|
78
|
+
const results = page?.results || [];
|
|
79
|
+
for (const b of results) {
|
|
80
|
+
const out = { ...b };
|
|
81
|
+
if (b.has_children && depth < max_depth) {
|
|
82
|
+
out.children = await listAllChildren(b.id, max_depth, depth + 1);
|
|
83
|
+
}
|
|
84
|
+
items.push(out);
|
|
85
|
+
}
|
|
86
|
+
cursor = page?.has_more ? page?.next_cursor : null;
|
|
87
|
+
} while (cursor);
|
|
88
|
+
return items;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function markdownToBlocks(content, defaultType = 'paragraph') {
|
|
92
|
+
const lines = String(content || '')
|
|
93
|
+
.split('\n')
|
|
94
|
+
.map((x) => x.replace(/\r$/, ''));
|
|
95
|
+
const blocks = [];
|
|
96
|
+
for (const line of lines) {
|
|
97
|
+
if (!line.trim()) continue;
|
|
98
|
+
let type = defaultType;
|
|
99
|
+
let textValue = line;
|
|
100
|
+
let checked = false;
|
|
101
|
+
if (line.startsWith('### ')) {
|
|
102
|
+
type = 'heading_3';
|
|
103
|
+
textValue = line.slice(4);
|
|
104
|
+
} else if (line.startsWith('## ')) {
|
|
105
|
+
type = 'heading_2';
|
|
106
|
+
textValue = line.slice(3);
|
|
107
|
+
} else if (line.startsWith('# ')) {
|
|
108
|
+
type = 'heading_1';
|
|
109
|
+
textValue = line.slice(2);
|
|
110
|
+
} else if (line.startsWith('- [ ] ')) {
|
|
111
|
+
type = 'to_do';
|
|
112
|
+
textValue = line.slice(6);
|
|
113
|
+
checked = false;
|
|
114
|
+
} else if (line.startsWith('- [x] ') || line.startsWith('- [X] ')) {
|
|
115
|
+
type = 'to_do';
|
|
116
|
+
textValue = line.slice(6);
|
|
117
|
+
checked = true;
|
|
118
|
+
} else if (/^\d+\.\s+/.test(line)) {
|
|
119
|
+
type = 'numbered_list_item';
|
|
120
|
+
textValue = line.replace(/^\d+\.\s+/, '');
|
|
121
|
+
} else if (line.startsWith('- ')) {
|
|
122
|
+
type = 'bulleted_list_item';
|
|
123
|
+
textValue = line.slice(2);
|
|
124
|
+
} else if (line.startsWith('> ')) {
|
|
125
|
+
type = 'quote';
|
|
126
|
+
textValue = line.slice(2);
|
|
127
|
+
} else if (line.startsWith('!! ')) {
|
|
128
|
+
type = 'callout';
|
|
129
|
+
textValue = line.slice(3);
|
|
130
|
+
}
|
|
131
|
+
const block = { object: 'block', type, [type]: { rich_text: richText(textValue) } };
|
|
132
|
+
if (type === 'to_do') block[type].checked = checked;
|
|
133
|
+
if (type === 'callout') block[type].icon = { emoji: '💡' };
|
|
134
|
+
blocks.push(block);
|
|
135
|
+
}
|
|
136
|
+
return blocks;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function buildDatabaseFilter(filter_property, filter_type, filter_value) {
|
|
140
|
+
const property = String(filter_property || '').trim();
|
|
141
|
+
const kind = String(filter_type || 'text').trim().toLowerCase();
|
|
142
|
+
if (!property || filter_value === undefined || filter_value === null || filter_value === '') return null;
|
|
143
|
+
if (kind === 'number') return { property, number: { equals: Number(filter_value) } };
|
|
144
|
+
if (kind === 'checkbox') return { property, checkbox: { equals: String(filter_value) === 'true' } };
|
|
145
|
+
if (kind === 'select') return { property, select: { equals: String(filter_value) } };
|
|
146
|
+
if (kind === 'date') return { property, date: { equals: String(filter_value) } };
|
|
147
|
+
if (kind === 'relation') return { property, relation: { contains: String(filter_value) } };
|
|
148
|
+
return { property, rich_text: { contains: String(filter_value) } };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function propertyToCell(prop) {
|
|
152
|
+
if (!prop || !prop.type) return '';
|
|
153
|
+
if (prop.type === 'title') return (prop.title || []).map((x) => x?.plain_text || '').join('');
|
|
154
|
+
if (prop.type === 'rich_text') return (prop.rich_text || []).map((x) => x?.plain_text || '').join('');
|
|
155
|
+
if (prop.type === 'number') return prop.number;
|
|
156
|
+
if (prop.type === 'checkbox') return prop.checkbox;
|
|
157
|
+
if (prop.type === 'select') return prop.select?.name || '';
|
|
158
|
+
if (prop.type === 'multi_select') return (prop.multi_select || []).map((x) => x.name).join(', ');
|
|
159
|
+
if (prop.type === 'date') return prop.date?.start || '';
|
|
160
|
+
if (prop.type === 'status') return prop.status?.name || '';
|
|
161
|
+
if (prop.type === 'url') return prop.url || '';
|
|
162
|
+
if (prop.type === 'email') return prop.email || '';
|
|
163
|
+
if (prop.type === 'phone_number') return prop.phone_number || '';
|
|
164
|
+
if (prop.type === 'relation') return (prop.relation || []).map((x) => x.id).join(', ');
|
|
165
|
+
return '';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Smart tools; `publish` is HTTP routing metadata for adopt.ai catalog (see publish-to-adoptai.js). */
|
|
169
|
+
export const smartTools = [
|
|
170
|
+
{
|
|
171
|
+
name: 'get_page_full_content',
|
|
172
|
+
publish: { method: 'GET', path: '/v1/pages/{page_id}' },
|
|
173
|
+
description:
|
|
174
|
+
'Retrieves a complete Notion page including all its properties, metadata, and deeply nested block content in a single call by automatically traversing child blocks. Use this instead of get_page when you need the full text content and structure, not just page properties.',
|
|
175
|
+
inputSchema: {
|
|
176
|
+
type: 'object',
|
|
177
|
+
properties: {
|
|
178
|
+
page_id: { type: 'string', description: 'Notion page ID.' },
|
|
179
|
+
max_depth: { type: 'number', description: 'Maximum recursion depth. Defaults to 3.' },
|
|
180
|
+
},
|
|
181
|
+
required: ['page_id'],
|
|
182
|
+
},
|
|
183
|
+
handler: async ({ page_id, max_depth = 3 }) => {
|
|
184
|
+
const page = await call('GET', '/v1/pages/{page_id}', { page_id });
|
|
185
|
+
const blocks = await listAllChildren(page_id, max_depth);
|
|
186
|
+
return text({ page, content_blocks: blocks });
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: 'create_page_with_content',
|
|
191
|
+
publish: { method: 'POST', path: '/v1/pages' },
|
|
192
|
+
description:
|
|
193
|
+
'Creates a new Notion page with initial content blocks already populated in one operation, accepting both page properties and an array of content blocks. Use this when creating pages that need content immediately rather than creating empty pages and appending content separately.',
|
|
194
|
+
inputSchema: {
|
|
195
|
+
type: 'object',
|
|
196
|
+
properties: {
|
|
197
|
+
parent_page_id: { type: 'string', description: 'Parent page ID.' },
|
|
198
|
+
parent_database_id: { type: 'string', description: 'Parent database ID.' },
|
|
199
|
+
title: { type: 'string', description: 'Page title.' },
|
|
200
|
+
content: { type: 'string', description: 'Markdown-like content text.' },
|
|
201
|
+
},
|
|
202
|
+
required: ['title', 'content'],
|
|
203
|
+
},
|
|
204
|
+
handler: async ({ parent_page_id, parent_database_id, title, content }) => {
|
|
205
|
+
if (!parent_page_id && !parent_database_id) {
|
|
206
|
+
throw new Error('Provide parent_page_id or parent_database_id.');
|
|
207
|
+
}
|
|
208
|
+
const children = markdownToBlocks(content, 'paragraph');
|
|
209
|
+
const parent = parent_database_id ? { database_id: parent_database_id } : { page_id: parent_page_id };
|
|
210
|
+
const properties = parent_database_id
|
|
211
|
+
? { Name: { title: richText(title) } }
|
|
212
|
+
: { title: { title: richText(title) } };
|
|
213
|
+
const created = await call('POST', '/v1/pages', { _rawBody: { parent, properties, children } });
|
|
214
|
+
return text(created);
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: 'duplicate_page',
|
|
219
|
+
publish: { method: 'POST', path: '/v1/pages' },
|
|
220
|
+
description:
|
|
221
|
+
'Creates an exact copy of an existing Notion page including all properties and top-level content blocks under a specified parent page or database. Use when you need to replicate a page as a template, but note it only copies top-level blocks, not deeply nested content.',
|
|
222
|
+
inputSchema: {
|
|
223
|
+
type: 'object',
|
|
224
|
+
properties: {
|
|
225
|
+
page_id: { type: 'string', description: 'Source page ID.' },
|
|
226
|
+
parent_id: { type: 'string', description: 'Destination parent ID.' },
|
|
227
|
+
},
|
|
228
|
+
required: ['page_id', 'parent_id'],
|
|
229
|
+
},
|
|
230
|
+
handler: async ({ page_id, parent_id }) => {
|
|
231
|
+
const sourcePage = await call('GET', '/v1/pages/{page_id}', { page_id });
|
|
232
|
+
const childrenResp = await call('GET', '/v1/blocks/{block_id}/children', { block_id: page_id, page_size: 100 });
|
|
233
|
+
const children = childrenResp?.results || [];
|
|
234
|
+
const payload = {
|
|
235
|
+
parent: { page_id: parent_id },
|
|
236
|
+
properties: sourcePage.properties || {},
|
|
237
|
+
children,
|
|
238
|
+
};
|
|
239
|
+
const duplicated = await call('POST', '/v1/pages', { _rawBody: payload });
|
|
240
|
+
return text({ source_page_id: page_id, duplicated_page: duplicated });
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: 'query_database_filtered',
|
|
245
|
+
publish: { method: 'POST', path: '/v1/databases/{database_id}/query' },
|
|
246
|
+
description:
|
|
247
|
+
'Queries a Notion database with specific property-based filters supporting text contains/equals, number comparisons, checkbox states, select values, date ranges, and relation matching. Use this for targeted searches when you know what criteria you\'re looking for, rather than retrieving all database rows.',
|
|
248
|
+
inputSchema: {
|
|
249
|
+
type: 'object',
|
|
250
|
+
properties: {
|
|
251
|
+
database_id: { type: 'string', description: 'Database ID.' },
|
|
252
|
+
filter_property: { type: 'string', description: 'Property name to filter on.' },
|
|
253
|
+
filter_type: { type: 'string', description: 'text|number|checkbox|select|date|relation' },
|
|
254
|
+
filter_value: { type: 'string', description: 'Filter value.' },
|
|
255
|
+
page_size: { type: 'number', description: 'Page size for query.' },
|
|
256
|
+
},
|
|
257
|
+
required: ['database_id'],
|
|
258
|
+
},
|
|
259
|
+
handler: async ({ database_id, filter_property, filter_type, filter_value, page_size = 20 }) => {
|
|
260
|
+
const filter = buildDatabaseFilter(filter_property, filter_type, filter_value);
|
|
261
|
+
const data = await call('POST', '/v1/databases/{id}/query', {
|
|
262
|
+
id: database_id,
|
|
263
|
+
_rawBody: { page_size, ...(filter ? { filter } : {}) },
|
|
264
|
+
});
|
|
265
|
+
return text(data);
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: 'query_database_all',
|
|
270
|
+
publish: { method: 'POST', path: '/v1/databases/{database_id}/query' },
|
|
271
|
+
description:
|
|
272
|
+
'Retrieves every single row from a Notion database by automatically handling all pagination behind the scenes until the complete dataset is fetched. Use when you need comprehensive database contents, but be cautious with large databases as this can be slow and return massive amounts of data.',
|
|
273
|
+
inputSchema: {
|
|
274
|
+
type: 'object',
|
|
275
|
+
properties: {
|
|
276
|
+
database_id: { type: 'string', description: 'Database ID.' },
|
|
277
|
+
max_pages: { type: 'number', description: 'Safety cap for pagination pages. Defaults to 10.' },
|
|
278
|
+
},
|
|
279
|
+
required: ['database_id'],
|
|
280
|
+
},
|
|
281
|
+
handler: async ({ database_id, max_pages = 10 }) => {
|
|
282
|
+
const all = [];
|
|
283
|
+
let start_cursor;
|
|
284
|
+
let page = 0;
|
|
285
|
+
do {
|
|
286
|
+
const resp = await call('POST', '/v1/databases/{id}/query', {
|
|
287
|
+
id: database_id,
|
|
288
|
+
_rawBody: { start_cursor, page_size: 100 },
|
|
289
|
+
});
|
|
290
|
+
all.push(...(resp?.results || []));
|
|
291
|
+
start_cursor = resp?.has_more ? resp?.next_cursor : null;
|
|
292
|
+
page += 1;
|
|
293
|
+
} while (start_cursor && page < max_pages);
|
|
294
|
+
return text({ database_id, total_rows: all.length, results: all, truncated: !!start_cursor });
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: 'query_database_sorted',
|
|
299
|
+
publish: { method: 'POST', path: '/v1/databases/{database_id}/query' },
|
|
300
|
+
description:
|
|
301
|
+
'Queries a Notion database with results sorted by any property in ascending or descending order, useful for getting recent entries, alphabetical lists, or priority-ordered items. Use when the order of results matters more than filtering by specific criteria.',
|
|
302
|
+
inputSchema: {
|
|
303
|
+
type: 'object',
|
|
304
|
+
properties: {
|
|
305
|
+
database_id: { type: 'string', description: 'Database ID.' },
|
|
306
|
+
sort_property: { type: 'string', description: 'Property used for sorting.' },
|
|
307
|
+
sort_direction: { type: 'string', description: 'ascending or descending' },
|
|
308
|
+
page_size: { type: 'number', description: 'Page size for query.' },
|
|
309
|
+
},
|
|
310
|
+
required: ['database_id', 'sort_property'],
|
|
311
|
+
},
|
|
312
|
+
handler: async ({ database_id, sort_property, sort_direction = 'ascending', page_size = 20 }) => {
|
|
313
|
+
const resp = await call('POST', '/v1/databases/{id}/query', {
|
|
314
|
+
id: database_id,
|
|
315
|
+
_rawBody: { page_size, sorts: [{ property: sort_property, direction: sort_direction }] },
|
|
316
|
+
});
|
|
317
|
+
return text(resp);
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
name: 'get_database_rows_as_table',
|
|
322
|
+
publish: { method: 'POST', path: '/v1/databases/{database_id}/query' },
|
|
323
|
+
description:
|
|
324
|
+
'Queries a Notion database and transforms the raw API response into a clean, readable table format with extracted column headers and cell values. Use this when you need to present database contents to users in a human-readable format rather than processing raw Notion API objects.',
|
|
325
|
+
inputSchema: {
|
|
326
|
+
type: 'object',
|
|
327
|
+
properties: {
|
|
328
|
+
database_id: { type: 'string', description: 'Database ID.' },
|
|
329
|
+
page_size: { type: 'number', description: 'Number of rows to retrieve. Defaults to 20.' },
|
|
330
|
+
},
|
|
331
|
+
required: ['database_id'],
|
|
332
|
+
},
|
|
333
|
+
handler: async ({ database_id, page_size = 20 }) => {
|
|
334
|
+
const resp = await call('POST', '/v1/databases/{id}/query', {
|
|
335
|
+
id: database_id,
|
|
336
|
+
_rawBody: { page_size },
|
|
337
|
+
});
|
|
338
|
+
const rows = resp?.results || [];
|
|
339
|
+
const columns = [...new Set(rows.flatMap((r) => Object.keys(r.properties || {})))];
|
|
340
|
+
const table = rows.map((row) =>
|
|
341
|
+
Object.fromEntries(columns.map((c) => [c, propertyToCell(row.properties?.[c])]))
|
|
342
|
+
);
|
|
343
|
+
return text({ database_id, columns, rows: table });
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: 'append_text_content',
|
|
348
|
+
publish: { method: 'PATCH', path: '/v1/blocks/{block_id}/children' },
|
|
349
|
+
description:
|
|
350
|
+
'Appends simple text content to any Notion page or block, automatically converting plain text or basic markdown into properly formatted Notion paragraph blocks. Use for straightforward text additions; for complex formatting or nested block structures, use append_complex_blocks instead.',
|
|
351
|
+
inputSchema: {
|
|
352
|
+
type: 'object',
|
|
353
|
+
properties: {
|
|
354
|
+
block_id: { type: 'string', description: 'Parent block/page ID.' },
|
|
355
|
+
content: { type: 'string', description: 'Plain text or markdown content.' },
|
|
356
|
+
block_type: {
|
|
357
|
+
type: 'string',
|
|
358
|
+
description:
|
|
359
|
+
'paragraph|heading_1|heading_2|heading_3|bulleted_list_item|numbered_list_item|to_do|quote|callout',
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
required: ['block_id', 'content'],
|
|
363
|
+
},
|
|
364
|
+
handler: async ({ block_id, content, block_type = 'paragraph' }) => {
|
|
365
|
+
const children = markdownToBlocks(content, block_type);
|
|
366
|
+
const resp = await call('PATCH', '/v1/blocks/{block_id}/children', { block_id, _rawBody: { children } });
|
|
367
|
+
return text(resp);
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: 'append_code_block',
|
|
372
|
+
publish: { method: 'PATCH', path: '/v1/blocks/{block_id}/children' },
|
|
373
|
+
description:
|
|
374
|
+
'Appends a syntax-highlighted code block to a Notion page with support for all major programming languages and proper code formatting. Use specifically when adding code snippets or technical content that requires syntax highlighting, not for regular text content.',
|
|
375
|
+
inputSchema: {
|
|
376
|
+
type: 'object',
|
|
377
|
+
properties: {
|
|
378
|
+
block_id: { type: 'string', description: 'Parent block/page ID.' },
|
|
379
|
+
code: { type: 'string', description: 'Code content.' },
|
|
380
|
+
language: { type: 'string', description: 'Code language. Defaults to plain text.' },
|
|
381
|
+
caption: { type: 'string', description: 'Optional caption.' },
|
|
382
|
+
},
|
|
383
|
+
required: ['block_id', 'code'],
|
|
384
|
+
},
|
|
385
|
+
handler: async ({ block_id, code, language = 'plain text', caption }) => {
|
|
386
|
+
const child = {
|
|
387
|
+
object: 'block',
|
|
388
|
+
type: 'code',
|
|
389
|
+
code: {
|
|
390
|
+
rich_text: richText(code),
|
|
391
|
+
language,
|
|
392
|
+
...(caption ? { caption: richText(caption) } : {}),
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
const resp = await call('PATCH', '/v1/blocks/{block_id}/children', {
|
|
396
|
+
block_id,
|
|
397
|
+
_rawBody: { children: [child] },
|
|
398
|
+
});
|
|
399
|
+
return text(resp);
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
name: 'append_table_block',
|
|
404
|
+
publish: { method: 'PATCH', path: '/v1/blocks/{block_id}/children' },
|
|
405
|
+
description:
|
|
406
|
+
'Appends a structured table to a Notion page by converting a 2D array of data into proper Notion table blocks with rows and cells. Use when you need to add tabular data with rows and columns, automatically handling the creation of table_row child blocks.',
|
|
407
|
+
inputSchema: {
|
|
408
|
+
type: 'object',
|
|
409
|
+
properties: {
|
|
410
|
+
block_id: { type: 'string', description: 'Parent block/page ID.' },
|
|
411
|
+
headers: { type: 'array', description: 'Array of column headers.' },
|
|
412
|
+
rows: { type: 'array', description: '2D array of row values.' },
|
|
413
|
+
},
|
|
414
|
+
required: ['block_id', 'headers', 'rows'],
|
|
415
|
+
},
|
|
416
|
+
handler: async ({ block_id, headers, rows }) => {
|
|
417
|
+
const head = Array.isArray(headers) ? headers : [];
|
|
418
|
+
const bodyRows = Array.isArray(rows) ? rows : [];
|
|
419
|
+
const toCell = (v) => richText(v == null ? '' : String(v));
|
|
420
|
+
const tableChildren = [
|
|
421
|
+
{ object: 'block', type: 'table_row', table_row: { cells: head.map((h) => toCell(h)) } },
|
|
422
|
+
...bodyRows.map((r) => ({
|
|
423
|
+
object: 'block',
|
|
424
|
+
type: 'table_row',
|
|
425
|
+
table_row: { cells: (Array.isArray(r) ? r : []).map((v) => toCell(v)) },
|
|
426
|
+
})),
|
|
427
|
+
];
|
|
428
|
+
const table = {
|
|
429
|
+
object: 'block',
|
|
430
|
+
type: 'table',
|
|
431
|
+
table: {
|
|
432
|
+
table_width: head.length,
|
|
433
|
+
has_column_header: true,
|
|
434
|
+
has_row_header: false,
|
|
435
|
+
children: tableChildren,
|
|
436
|
+
},
|
|
437
|
+
};
|
|
438
|
+
const resp = await call('PATCH', '/v1/blocks/{block_id}/children', {
|
|
439
|
+
block_id,
|
|
440
|
+
_rawBody: { children: [table] },
|
|
441
|
+
});
|
|
442
|
+
return text(resp);
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
name: 'get_all_block_children_recursive',
|
|
447
|
+
publish: { method: 'GET', path: '/v1/blocks/{block_id}/children' },
|
|
448
|
+
description:
|
|
449
|
+
'Retrieves the complete nested structure of all child blocks within a page or block, automatically traversing the entire content hierarchy to return a full block tree. Use this when you need to access all content within a page rather than just the top-level blocks, as it handles nested blocks like toggle lists, columns, and indented items that have has_children=true.',
|
|
450
|
+
inputSchema: {
|
|
451
|
+
type: 'object',
|
|
452
|
+
properties: {
|
|
453
|
+
block_id: { type: 'string', description: 'Block or page ID.' },
|
|
454
|
+
max_depth: { type: 'number', description: 'Maximum recursion depth. Defaults to 5.' },
|
|
455
|
+
},
|
|
456
|
+
required: ['block_id'],
|
|
457
|
+
},
|
|
458
|
+
handler: async ({ block_id, max_depth = 5 }) => text({ block_id, children: await listAllChildren(block_id, max_depth) }),
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: 'find_page_by_title',
|
|
462
|
+
publish: { method: 'POST', path: '/v1/search' },
|
|
463
|
+
description:
|
|
464
|
+
'Searches your Notion workspace to find pages by matching title text and returns their page IDs, titles, and parent information. Use this as your first step before reading or modifying any page content, and note that for finding databases you should use find_database_by_title instead.',
|
|
465
|
+
inputSchema: {
|
|
466
|
+
type: 'object',
|
|
467
|
+
properties: {
|
|
468
|
+
title_query: { type: 'string', description: 'Title query text.' },
|
|
469
|
+
page_size: { type: 'number', description: 'Page size. Defaults to 10.' },
|
|
470
|
+
},
|
|
471
|
+
required: ['title_query'],
|
|
472
|
+
},
|
|
473
|
+
handler: async ({ title_query, page_size = 10 }) => {
|
|
474
|
+
const resp = await call('POST', '/v1/search', {
|
|
475
|
+
_rawBody: { query: title_query, page_size, filter: { property: 'object', value: 'page' } },
|
|
476
|
+
});
|
|
477
|
+
const out = (resp?.results || []).map((x) => ({ id: x.id, title: pageTitle(x), parent: x.parent }));
|
|
478
|
+
return text({ results: out, has_more: resp?.has_more, next_cursor: resp?.next_cursor });
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
name: 'find_database_by_title',
|
|
483
|
+
publish: { method: 'POST', path: '/v1/search' },
|
|
484
|
+
description:
|
|
485
|
+
'Searches your Notion workspace to find databases by matching name text and returns their database IDs along with property schema information. Use this to locate database IDs before querying database records, and note that for finding regular pages you should use find_page_by_title instead.',
|
|
486
|
+
inputSchema: {
|
|
487
|
+
type: 'object',
|
|
488
|
+
properties: {
|
|
489
|
+
title_query: { type: 'string', description: 'Database name query.' },
|
|
490
|
+
page_size: { type: 'number', description: 'Page size. Defaults to 10.' },
|
|
491
|
+
},
|
|
492
|
+
required: ['title_query'],
|
|
493
|
+
},
|
|
494
|
+
handler: async ({ title_query, page_size = 10 }) => {
|
|
495
|
+
const resp = await call('POST', '/v1/search', {
|
|
496
|
+
_rawBody: { query: title_query, page_size, filter: { property: 'object', value: 'database' } },
|
|
497
|
+
});
|
|
498
|
+
const out = (resp?.results || []).map((x) => ({ id: x.id, title: (x.title || []).map((t) => t.plain_text).join(''), properties: x.properties }));
|
|
499
|
+
return text({ results: out, has_more: resp?.has_more, next_cursor: resp?.next_cursor });
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
name: 'list_all_workspace_pages',
|
|
504
|
+
publish: { method: 'POST', path: '/v1/search' },
|
|
505
|
+
description:
|
|
506
|
+
"Lists all pages accessible to this integration in the Notion workspace. Returns page IDs, titles, and parent hierarchy. Use as a discovery tool when you don't know which pages exist.",
|
|
507
|
+
inputSchema: {
|
|
508
|
+
type: 'object',
|
|
509
|
+
properties: {
|
|
510
|
+
page_size: { type: 'number', description: 'Page size. Defaults to 50.' },
|
|
511
|
+
start_cursor: { type: 'string', description: 'Pagination cursor.' },
|
|
512
|
+
},
|
|
513
|
+
required: [],
|
|
514
|
+
},
|
|
515
|
+
handler: async ({ page_size = 50, start_cursor }) => {
|
|
516
|
+
const resp = await call('POST', '/v1/search', {
|
|
517
|
+
_rawBody: { page_size, start_cursor, filter: { property: 'object', value: 'page' } },
|
|
518
|
+
});
|
|
519
|
+
const out = (resp?.results || []).map((x) => ({ id: x.id, title: pageTitle(x), parent: x.parent }));
|
|
520
|
+
return text({ results: out, has_more: resp?.has_more, next_cursor: resp?.next_cursor });
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
name: 'list_all_workspace_databases',
|
|
525
|
+
publish: { method: 'POST', path: '/v1/search' },
|
|
526
|
+
description:
|
|
527
|
+
'Returns a comprehensive list of all databases accessible to the integration across your entire Notion workspace, including database IDs, names, and complete property schemas. Use this to discover what databases are available before querying their records or when you need to understand the structure of databases in the workspace.',
|
|
528
|
+
inputSchema: {
|
|
529
|
+
type: 'object',
|
|
530
|
+
properties: {
|
|
531
|
+
page_size: { type: 'number', description: 'Page size. Defaults to 50.' },
|
|
532
|
+
start_cursor: { type: 'string', description: 'Pagination cursor.' },
|
|
533
|
+
},
|
|
534
|
+
required: [],
|
|
535
|
+
},
|
|
536
|
+
handler: async ({ page_size = 50, start_cursor }) => {
|
|
537
|
+
const resp = await call('POST', '/v1/search', {
|
|
538
|
+
_rawBody: { page_size, start_cursor, filter: { property: 'object', value: 'database' } },
|
|
539
|
+
});
|
|
540
|
+
const out = (resp?.results || []).map((x) => ({ id: x.id, title: (x.title || []).map((t) => t.plain_text).join(''), properties: x.properties }));
|
|
541
|
+
return text({ results: out, has_more: resp?.has_more, next_cursor: resp?.next_cursor });
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
name: 'add_comment_to_page',
|
|
546
|
+
publish: { method: 'POST', path: '/v1/comments' },
|
|
547
|
+
description:
|
|
548
|
+
'Creates a new top-level comment on a Notion page, ideal for adding review feedback, status updates, or general notes to a page. Use this for starting new comment threads, but if you need to respond to an existing discussion use reply_to_comment instead.',
|
|
549
|
+
inputSchema: {
|
|
550
|
+
type: 'object',
|
|
551
|
+
properties: {
|
|
552
|
+
page_id: { type: 'string', description: 'Target page ID.' },
|
|
553
|
+
comment_text: { type: 'string', description: 'Comment text.' },
|
|
554
|
+
},
|
|
555
|
+
required: ['page_id', 'comment_text'],
|
|
556
|
+
},
|
|
557
|
+
handler: async ({ page_id, comment_text }) =>
|
|
558
|
+
text(
|
|
559
|
+
await call('POST', '/v1/comments', {
|
|
560
|
+
_rawBody: { parent: { page_id }, rich_text: richText(comment_text) },
|
|
561
|
+
})
|
|
562
|
+
),
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
name: 'reply_to_comment',
|
|
566
|
+
publish: { method: 'POST', path: '/v1/comments' },
|
|
567
|
+
description:
|
|
568
|
+
'Adds a reply to an existing comment thread on a Notion page and requires the discussion_id parameter from the original comment you\'re replying to. Use this to continue conversations in existing comment threads rather than creating new top-level comments.',
|
|
569
|
+
inputSchema: {
|
|
570
|
+
type: 'object',
|
|
571
|
+
properties: {
|
|
572
|
+
discussion_id: { type: 'string', description: 'Discussion thread ID.' },
|
|
573
|
+
reply_text: { type: 'string', description: 'Reply text.' },
|
|
574
|
+
},
|
|
575
|
+
required: ['discussion_id', 'reply_text'],
|
|
576
|
+
},
|
|
577
|
+
handler: async ({ discussion_id, reply_text }) =>
|
|
578
|
+
text(
|
|
579
|
+
await call('POST', '/v1/comments', {
|
|
580
|
+
_rawBody: { discussion_id, rich_text: richText(reply_text) },
|
|
581
|
+
})
|
|
582
|
+
),
|
|
583
|
+
},
|
|
584
|
+
];
|
|
585
|
+
|
|
586
|
+
export const tools = [...baseTools, ...smartTools];
|