Dify MCP Server
by AI-FE
- docs
Directory structure:
└── suekou-mcp-notion-server/
├── README.md
├── LICENSE
└── notion/
├── package-lock.json
├── package.json
├── tsconfig.json
├── .gitignore
└── src/
└── index.ts
================================================
File: README.md
================================================
# Notion MCP Server
MCP Server for the Notion API, enabling Claude to interact with Notion workspaces.
## Setup
Here is a detailed explanation of the steps mentioned above in the following articles:
- English Version: https://dev.to/suekou/operating-notion-via-claude-desktop-using-mcp-c0h
- Japanese Version: https://qiita.com/suekou/items/44c864583f5e3e6325d9
1. **Create a Notion Integration**:
- Visit the [Notion Your Integrations page](https://www.notion.so/profile/integrations).
- Click "New Integration".
- Name your integration and select appropriate permissions (e.g., "Read content", "Update content").
2. **Retrieve the Secret Key**:
- Copy the "Internal Integration Token" from your integration.
- This token will be used for authentication.
3. **Add the Integration to Your Workspace**:
- Open the page or database you want the integration to access in Notion.
- Click the navigation button in the top right corner.
- Click "Connect to" button and select your integration.
4. **Configure Claude Desktop**:
Add the following to your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"notion": {
"command": "npx",
"args": ["-y", "@suekou/mcp-notion-server"],
"env": {
"NOTION_API_TOKEN": "your-integration-token"
}
}
}
}
```
or
```json
{
"mcpServers": {
"notion": {
"command": "node",
"args": ["your-built-file-path"],
"env": {
"NOTION_API_TOKEN": "your-integration-token"
}
}
}
}
```
## Troubleshooting
If you encounter permission errors:
1. Ensure the integration has the required permissions.
2. Verify that the integration is invited to the relevant pages or databases.
3. Confirm the token and configuration are correctly set in `claude_desktop_config.json`.
## Tools
1. `notion_append_block_children`
- Append child blocks to a parent block.
- Required inputs:
- `block_id` (string): The ID of the parent block.
- `children` (array): Array of block objects to append.
- Returns: Information about the appended blocks.
2. `notion_retrieve_block`
- Retrieve information about a specific block.
- Required inputs:
- `block_id` (string): The ID of the block to retrieve.
- Returns: Detailed information about the block.
3. `notion_retrieve_block_children`
- Retrieve the children of a specific block.
- Required inputs:
- `block_id` (string): The ID of the parent block.
- Optional inputs:
- `start_cursor` (string): Cursor for the next page of results.
- `page_size` (number, default: 100, max: 100): Number of blocks to retrieve.
- Returns: List of child blocks.
4. `notion_delete_block`
- Delete a specific block.
- Required inputs:
- `block_id` (string): The ID of the block to delete.
- Returns: Confirmation of the deletion.
5. `notion_retrieve_page`
- Retrieve information about a specific page.
- Required inputs:
- `page_id` (string): The ID of the page to retrieve.
- Returns: Detailed information about the page.
6. `notion_update_page_properties`
- Update properties of a page.
- Required inputs:
- `page_id` (string): The ID of the page to update.
- `properties` (object): Properties to update.
- Returns: Information about the updated page.
7. `notion_create_database`
- Create a new database.
- Required inputs:
- `parent` (object): Parent object of the database.
- `title` (array): Title of the database as a rich text array.
- `properties` (object): Property schema of the database.
- Returns: Information about the created database.
8. `notion_query_database`
- Query a database.
- Required inputs:
- `database_id` (string): The ID of the database to query.
- Optional inputs:
- `filter` (object): Filter conditions.
- `sorts` (array): Sorting conditions.
- `start_cursor` (string): Cursor for the next page of results.
- `page_size` (number, default: 100, max: 100): Number of results to retrieve.
- Returns: List of results from the query.
9. `notion_retrieve_database`
- Retrieve information about a specific database.
- Required inputs:
- `database_id` (string): The ID of the database to retrieve.
- Returns: Detailed information about the database.
10. `notion_update_database`
- Update information about a database.
- Required inputs:
- `database_id` (string): The ID of the database to update.
- Optional inputs:
- `title` (array): New title for the database.
- `description` (array): New description for the database.
- `properties` (object): Updated property schema.
- Returns: Information about the updated database.
11. `notion_create_database_item`
- Create a new item in a Notion database.
- Required inputs:
- `database_id` (string): The ID of the database to add the item to.
- `properties` (object): The properties of the new item. These should match the database schema.
- Returns: Information about the newly created item.
12. `notion_search`
- Search pages or databases by title.
- Optional inputs:
- `query` (string): Text to search for in page or database titles.
- `filter` (object): Criteria to limit results to either only pages or only databases.
- `sort` (object): Criteria to sort the results
- `start_cursor` (string): Pagination start cursor.
- `page_size` (number, default: 100, max: 100): Number of results to retrieve.
- Returns: List of matching pages or databases.
13. `notion_list_all_users`
- List all users in the Notion workspace.
- Note: This function requires upgrading to the Notion Enterprise plan and using an Organization API key to avoid permission errors.
- Optional inputs:
- start_cursor (string): Pagination start cursor for listing users.
- page_size (number, max: 100): Number of users to retrieve.
- Returns: A paginated list of all users in the workspace.
14. `notion_retrieve_user`
- Retrieve a specific user by user_id in Notion.
- Note: This function requires upgrading to the Notion Enterprise plan and using an Organization API key to avoid permission errors.
- Required inputs:
- user_id (string): The ID of the user to retrieve.
- Returns: Detailed information about the specified user.
15. `notion_retrieve_bot_user`
- Retrieve the bot user associated with the current token in Notion.
- Returns: Information about the bot user, including details of the person who authorized the integration.
16. `notion_create_comment`
- Create a comment in Notion.
- Requires the integration to have 'insert comment' capabilities.
- Either specify a `parent` object with a `page_id` or a `discussion_id`, but not both.
- Required inputs:
- `rich_text` (array): Array of rich text objects representing the comment content.
- Optional inputs:
- `parent` (object): Must include `page_id` if used.
- `discussion_id` (string): An existing discussion thread ID.
- Returns: Information about the created comment.
17. `notion_retrieve_comments`
- Retrieve a list of unresolved comments from a Notion page or block.
- Requires the integration to have 'read comment' capabilities.
- Required inputs:
- `block_id` (string): The ID of the block or page whose comments you want to retrieve.
- Optional inputs:
- `start_cursor` (string): Pagination start cursor.
- `page_size` (number, max: 100): Number of comments to retrieve.
- Returns: A paginated list of comments associated with the specified block or page.
## License
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
================================================
File: LICENSE
================================================
MIT License
Copyright (c) 2024 suekou
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
File: notion/package-lock.json
================================================
{
"name": "notion",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "notion",
"version": "0.1.0",
"dependencies": {
"@modelcontextprotocol/sdk": "0.6.0"
},
"bin": {
"notion": "build/index.js"
},
"devDependencies": {
"@types/node": "^20.11.24",
"typescript": "^5.3.3"
}
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.6.0.tgz",
"integrity": "sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"raw-body": "^3.0.0",
"zod": "^3.23.8"
}
},
"node_modules/@types/node": {
"version": "20.17.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.9.tgz",
"integrity": "sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.19.2"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"license": "MIT",
"dependencies": {
"depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/raw-body": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.6.3",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/typescript": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
"integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true,
"license": "MIT"
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/zod": {
"version": "3.23.8",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}
================================================
File: notion/package.json
================================================
{
"name": "notion",
"version": "0.1.0",
"description": "A Model Context Protocol server",
"private": true,
"type": "module",
"bin": {
"notion": "./build/index.js"
},
"files": [
"build"
],
"scripts": {
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
"prepare": "npm run build",
"watch": "tsc --watch",
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "0.6.0"
},
"devDependencies": {
"@types/node": "^20.11.24",
"typescript": "^5.3.3"
}
}
================================================
File: notion/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
================================================
File: notion/.gitignore
================================================
node_modules/
build/
*.log
.env*
================================================
File: notion/src/index.ts
================================================
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequest,
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
// Type definitions for tool arguments
// Blocks
interface AppendBlockChildrenArgs {
block_id: string;
children: any[];
}
interface RetrieveBlockArgs {
block_id: string;
}
interface RetrieveBlockChildrenArgs {
block_id: string;
start_cursor?: string;
page_size?: number;
}
interface DeleteBlockArgs {
block_id: string;
}
// Pages
interface RetrievePageArgs {
page_id: string;
}
interface UpdatePagePropertiesArgs {
page_id: string;
properties: any;
}
// Users
interface ListAllUsersArgs {
start_cursor?: string;
page_size?: number;
}
interface RetrieveUserArgs {
user_id: string;
}
// Databases
interface CreateDatabaseArgs {
parent: any;
title: any[];
properties: any;
}
interface QueryDatabaseArgs {
database_id: string;
filter?: any;
sorts?: any;
start_cursor?: string;
page_size?: number;
}
interface RetrieveDatabaseArgs {
database_id: string;
}
interface UpdateDatabaseArgs {
database_id: string;
title?: any[];
description?: any[];
properties?: any;
}
interface CreateDatabaseItemArgs {
database_id: string;
properties: any;
}
// Comments
interface CreateCommentArgs {
parent?: { page_id: string };
discussion_id?: string;
rich_text: any[];
}
interface RetrieveCommentsArgs {
block_id: string;
start_cursor?: string;
page_size?: number;
}
// Search
interface SearchArgs {
query?: string;
filter?: { property: string; value: string };
sort?: {
direction: "ascending" | "descending";
timestamp: "last_edited_time";
};
start_cursor?: string;
page_size?: number;
}
const commonIdDescription = "It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).";
// common object schema
const richTextObjectSchema = {
type: "object",
description: "A rich text object.",
properties: {
type: {
type: "string",
description:
"The type of this rich text object. Possible values: text, mention, equation.",
enum: ["text", "mention", "equation"],
},
text: {
type: "object",
description:
"Object containing text content and optional link info. Required if type is 'text'.",
properties: {
content: {
type: "string",
description: "The actual text content.",
},
link: {
type: "object",
description: "Optional link object with a 'url' field.",
properties: {
url: {
type: "string",
description: "The URL the text links to.",
},
},
},
},
},
mention: {
type: "object",
description:
"Mention object if type is 'mention'. Represents an inline mention of a database, date, link preview, page, template mention, or user.",
properties: {
type: {
type: "string",
description: "The type of the mention.",
enum: [
"database",
"date",
"link_preview",
"page",
"template_mention",
"user",
],
},
database: {
type: "object",
description:
"Database mention object. Contains a database reference with an 'id' field.",
properties: {
id: {
type: "string",
description:
"The ID of the mentioned database." + commonIdDescription,
},
},
required: ["id"],
},
date: {
type: "object",
description:
"Date mention object, containing a date property value object.",
properties: {
start: {
type: "string",
description: "An ISO 8601 formatted start date or date-time.",
},
end: {
type: ["string", "null"],
description:
"An ISO 8601 formatted end date or date-time, or null if not a range.",
},
time_zone: {
type: ["string", "null"],
description:
"Time zone information for start and end. If null, times are in UTC.",
},
},
required: ["start"],
},
link_preview: {
type: "object",
description:
"Link Preview mention object, containing a URL for the link preview.",
properties: {
url: {
type: "string",
description: "The URL for the link preview.",
},
},
required: ["url"],
},
page: {
type: "object",
description:
"Page mention object, containing a page reference with an 'id' field.",
properties: {
id: {
type: "string",
description:
"The ID of the mentioned page." + commonIdDescription,
},
},
required: ["id"],
},
template_mention: {
type: "object",
description:
"Template mention object, can be a template_mention_date or template_mention_user.",
properties: {
type: {
type: "string",
enum: ["template_mention_date", "template_mention_user"],
description: "The template mention type.",
},
template_mention_date: {
type: "string",
enum: ["today", "now"],
description: "For template_mention_date type, the date keyword.",
},
template_mention_user: {
type: "string",
enum: ["me"],
description: "For template_mention_user type, the user keyword.",
},
},
},
user: {
type: "object",
description: "User mention object, contains a user reference.",
properties: {
object: {
type: "string",
description: "Should be 'user'.",
enum: ["user"],
},
id: {
type: "string",
description:
"The ID of the user." + commonIdDescription,
},
},
required: ["object", "id"],
},
},
required: ["type"],
oneOf: [
{ required: ["database"] },
{ required: ["date"] },
{ required: ["link_preview"] },
{ required: ["page"] },
{ required: ["template_mention"] },
{ required: ["user"] },
],
},
equation: {
type: "object",
description:
"Equation object if type is 'equation'. Represents an inline LaTeX equation.",
properties: {
expression: {
type: "string",
description: "LaTeX string representing the inline equation.",
},
},
required: ["expression"],
},
annotations: {
type: "object",
description: "Styling information for the text.",
properties: {
bold: { type: "boolean" },
italic: { type: "boolean" },
strikethrough: { type: "boolean" },
underline: { type: "boolean" },
code: { type: "boolean" },
color: {
type: "string",
description: "Color for the text.",
enum: [
"default",
"blue",
"blue_background",
"brown",
"brown_background",
"gray",
"gray_background",
"green",
"green_background",
"orange",
"orange_background",
"pink",
"pink_background",
"purple",
"purple_background",
"red",
"red_background",
"yellow",
"yellow_background",
],
},
},
},
href: {
type: "string",
description: "The URL of any link or mention in this text, if any.",
},
plain_text: {
type: "string",
description: "The plain text without annotations.",
},
},
required: ["type"],
};
const blockObjectSchema = {
type: "object",
description: "A Notion block object.",
properties: {
object: {
type: "string",
description: "Should be 'block'.",
enum: ["block"],
},
type: {
type: "string",
description:
"Type of the block. Possible values include 'paragraph', 'heading_1', 'heading_2', 'heading_3', 'bulleted_list_item', 'numbered_list_item', 'to_do', 'toggle', 'child_page', 'child_database', 'embed', 'callout', 'quote', 'equation', 'divider', 'table_of_contents', 'column', 'column_list', 'link_preview', 'synced_block', 'template', 'link_to_page', 'audio', 'bookmark', 'breadcrumb', 'code', 'file', 'image', 'pdf', 'video'. Not all types are supported for creation via API.",
},
paragraph: {
type: "object",
description: "Paragraph block object.",
properties: {
rich_text: richTextObjectSchema,
color: {
type: "string",
description: "The color of the block.",
enum: [
"default",
"blue",
"blue_background",
"brown",
"brown_background",
"gray",
"gray_background",
"green",
"green_background",
"orange",
"orange_background",
"pink",
"pink_background",
"purple",
"purple_background",
"red",
"red_background",
"yellow",
"yellow_background",
],
},
children: {
type: "array",
description: "Nested child blocks.",
items: {
type: "object",
description: "A nested block object.",
},
},
},
},
},
required: ["object", "type"],
}
// Tool definitions
// Blocks
const appendBlockChildrenTool: Tool = {
name: "notion_append_block_children",
description:
"Append new children blocks to a specified parent block in Notion. Requires insert content capabilities. You can optionally specify the 'after' parameter to append after a certain block.",
inputSchema: {
type: "object",
properties: {
block_id: {
type: "string",
description:
"The ID of the parent block." + commonIdDescription,
},
children: {
type: "array",
description:
"Array of block objects to append. Each block must follow the Notion block schema.",
items: blockObjectSchema,
},
after: {
type: "string",
description:
"The ID of the existing block that the new block should be appended after." + commonIdDescription,
},
},
required: ["block_id", "children"],
},
};
const retrieveBlockTool: Tool = {
name: "notion_retrieve_block",
description: "Retrieve a block from Notion",
inputSchema: {
type: "object",
properties: {
block_id: {
type: "string",
description:
"The ID of the block to retrieve." + commonIdDescription,
},
},
required: ["block_id"],
},
};
const retrieveBlockChildrenTool: Tool = {
name: "notion_retrieve_block_children",
description: "Retrieve the children of a block",
inputSchema: {
type: "object",
properties: {
block_id: {
type: "string",
description:
"The ID of the block." + commonIdDescription,
},
start_cursor: {
type: "string",
description: "Pagination cursor for next page of results",
},
page_size: {
type: "number",
description: "Number of results per page (max 100)",
},
},
required: ["block_id"],
},
};
const deleteBlockTool: Tool = {
name: "notion_delete_block",
description: "Delete a block in Notion",
inputSchema: {
type: "object",
properties: {
block_id: {
type: "string",
description:
"The ID of the block to delete." + commonIdDescription,
},
},
required: ["block_id"],
},
};
// Pages
const retrievePageTool: Tool = {
name: "notion_retrieve_page",
description: "Retrieve a page from Notion",
inputSchema: {
type: "object",
properties: {
page_id: {
type: "string",
description:
"The ID of the page to retrieve." + commonIdDescription,
},
},
required: ["page_id"],
},
};
const updatePagePropertiesTool: Tool = {
name: "notion_update_page_properties",
description: "Update properties of a page or an item in a Notion database",
inputSchema: {
type: "object",
properties: {
page_id: {
type: "string",
description:
"The ID of the page or database item to update." + commonIdDescription,
},
properties: {
type: "object",
description:
"Properties to update. These correspond to the columns or fields in the database.",
},
},
required: ["page_id", "properties"],
},
};
// Users
const listAllUsersTool: Tool = {
name: "notion_list_all_users",
description:
"List all users in the Notion workspace. **Note:** This function requires upgrading to the Notion Enterprise plan and using an Organization API key to avoid permission errors.",
inputSchema: {
type: "object",
properties: {
start_cursor: {
type: "string",
description: "Pagination start cursor for listing users",
},
page_size: {
type: "number",
description: "Number of users to retrieve (max 100)",
},
},
},
};
const retrieveUserTool: Tool = {
name: "notion_retrieve_user",
description:
"Retrieve a specific user by user_id in Notion. **Note:** This function requires upgrading to the Notion Enterprise plan and using an Organization API key to avoid permission errors.",
inputSchema: {
type: "object",
properties: {
user_id: {
type: "string",
description:
"The ID of the user to retrieve." + commonIdDescription,
},
},
required: ["user_id"],
},
};
const retrieveBotUserTool: Tool = {
name: "notion_retrieve_bot_user",
description:
"Retrieve the bot user associated with the current token in Notion",
inputSchema: {
type: "object",
properties: {},
},
};
// Databases
const createDatabaseTool: Tool = {
name: "notion_create_database",
description: "Create a database in Notion",
inputSchema: {
type: "object",
properties: {
parent: {
type: "object",
description: "Parent object of the database",
},
title: {
type: "array",
description:
"Title of database as it appears in Notion. An array of rich text objects.",
items: richTextObjectSchema,
},
properties: {
type: "object",
description:
"Property schema of database. The keys are the names of properties as they appear in Notion and the values are property schema objects.",
},
},
required: ["parent", "properties"],
},
};
const queryDatabaseTool: Tool = {
name: "notion_query_database",
description: "Query a database in Notion",
inputSchema: {
type: "object",
properties: {
database_id: {
type: "string",
description:
"The ID of the database to query." + commonIdDescription,
},
filter: {
type: "object",
description: "Filter conditions",
},
sorts: {
type: "array",
description: "Sort conditions",
},
start_cursor: {
type: "string",
description: "Pagination cursor for next page of results",
},
page_size: {
type: "number",
description: "Number of results per page (max 100)",
},
},
required: ["database_id"],
},
};
const retrieveDatabaseTool: Tool = {
name: "notion_retrieve_database",
description: "Retrieve a database in Notion",
inputSchema: {
type: "object",
properties: {
database_id: {
type: "string",
description:
"The ID of the database to retrieve." + commonIdDescription,
},
},
required: ["database_id"],
},
};
const updateDatabaseTool: Tool = {
name: "notion_update_database",
description: "Update a database in Notion",
inputSchema: {
type: "object",
properties: {
database_id: {
type: "string",
description:
"The ID of the database to update." + commonIdDescription,
},
title: {
type: "array",
description:
"An array of rich text objects that represents the title of the database that is displayed in the Notion UI.",
items: richTextObjectSchema,
},
description: {
type: "array",
description:
"An array of rich text objects that represents the description of the database that is displayed in the Notion UI.",
},
properties: {
type: "object",
description:
"The properties of a database to be changed in the request, in the form of a JSON object.",
},
},
required: ["database_id"],
},
};
const createDatabaseItemTool: Tool = {
name: "notion_create_database_item",
description: "Create a new item (page) in a Notion database",
inputSchema: {
type: "object",
properties: {
database_id: {
type: "string",
description:
"The ID of the database to add the item to." + commonIdDescription,
},
properties: {
type: "object",
description:
"Properties of the new database item. These should match the database schema.",
},
},
required: ["database_id", "properties"],
},
};
// Comments
const createCommentTool: Tool = {
name: "notion_create_comment",
description:
"Create a comment in Notion. This requires the integration to have 'insert comment' capabilities. You can either specify a page parent or a discussion_id, but not both.",
inputSchema: {
type: "object",
properties: {
parent: {
type: "object",
description:
"Parent object that specifies the page to comment on. Must include a page_id if used.",
properties: {
page_id: {
type: "string",
description:
"The ID of the page to comment on." + commonIdDescription,
},
},
},
discussion_id: {
type: "string",
description:
"The ID of an existing discussion thread to add a comment to." + commonIdDescription,
},
rich_text: {
type: "array",
description:
"Array of rich text objects representing the comment content.",
items: richTextObjectSchema,
},
},
required: ["rich_text"],
},
};
const retrieveCommentsTool: Tool = {
name: "notion_retrieve_comments",
description:
"Retrieve a list of unresolved comments from a Notion page or block. Requires the integration to have 'read comment' capabilities.",
inputSchema: {
type: "object",
properties: {
block_id: {
type: "string",
description:
"The ID of the block or page whose comments you want to retrieve." + commonIdDescription,
},
start_cursor: {
type: "string",
description:
"If supplied, returns a page of results starting after the cursor.",
},
page_size: {
type: "number",
description: "Number of comments to retrieve (max 100).",
},
},
required: ["block_id"],
},
};
// Search
const searchTool: Tool = {
name: "notion_search",
description: "Search pages or databases by title in Notion",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Text to search for in page or database titles",
},
filter: {
type: "object",
description: "Filter results by object type (page or database)",
properties: {
property: {
type: "string",
description: "Must be 'object'",
},
value: {
type: "string",
description: "Either 'page' or 'database'",
},
},
},
sort: {
type: "object",
description: "Sort order of results",
properties: {
direction: {
type: "string",
enum: ["ascending", "descending"],
},
timestamp: {
type: "string",
enum: ["last_edited_time"],
},
},
},
start_cursor: {
type: "string",
description: "Pagination start cursor",
},
page_size: {
type: "number",
description: "Number of results to return (max 100)",
},
},
},
};
class NotionClientWrapper {
private notionToken: string;
private baseUrl: string = "https://api.notion.com/v1";
private headers: { [key: string]: string };
constructor(token: string) {
this.notionToken = token;
this.headers = {
Authorization: `Bearer ${this.notionToken}`,
"Content-Type": "application/json",
"Notion-Version": "2022-06-28",
};
}
async appendBlockChildren(block_id: string, children: any[]): Promise<any> {
const body = { children };
const response = await fetch(
`${this.baseUrl}/blocks/${block_id}/children`,
{
method: "PATCH",
headers: this.headers,
body: JSON.stringify(body),
}
);
return response.json();
}
async retrieveBlock(block_id: string): Promise<any> {
const response = await fetch(`${this.baseUrl}/blocks/${block_id}`, {
method: "GET",
headers: this.headers,
});
return response.json();
}
async retrieveBlockChildren(
block_id: string,
start_cursor?: string,
page_size?: number
): Promise<any> {
const params = new URLSearchParams();
if (start_cursor) params.append("start_cursor", start_cursor);
if (page_size) params.append("page_size", page_size.toString());
const response = await fetch(
`${this.baseUrl}/blocks/${block_id}/children?${params}`,
{
method: "GET",
headers: this.headers,
}
);
return response.json();
}
async deleteBlock(block_id: string): Promise<any> {
const response = await fetch(`${this.baseUrl}/blocks/${block_id}`, {
method: "DELETE",
headers: this.headers,
});
return response.json();
}
async retrievePage(page_id: string): Promise<any> {
const response = await fetch(`${this.baseUrl}/pages/${page_id}`, {
method: "GET",
headers: this.headers,
});
return response.json();
}
async updatePageProperties(page_id: string, properties: any): Promise<any> {
const body = { properties };
const response = await fetch(`${this.baseUrl}/pages/${page_id}`, {
method: "PATCH",
headers: this.headers,
body: JSON.stringify(body),
});
return response.json();
}
async listAllUsers(start_cursor?: string, page_size?: number): Promise<any> {
const params = new URLSearchParams();
if (start_cursor) params.append("start_cursor", start_cursor);
if (page_size) params.append("page_size", page_size.toString());
const response = await fetch(`${this.baseUrl}/users?${params.toString()}`, {
method: "GET",
headers: this.headers,
});
return response.json();
}
async retrieveUser(user_id: string): Promise<any> {
const response = await fetch(`${this.baseUrl}/users/${user_id}`, {
method: "GET",
headers: this.headers,
});
return response.json();
}
async retrieveBotUser(): Promise<any> {
const response = await fetch(`${this.baseUrl}/users/me`, {
method: "GET",
headers: this.headers,
});
return response.json();
}
async createDatabase(
parent: any,
title: any[],
properties: any
): Promise<any> {
const body = { parent, title, properties };
const response = await fetch(`${this.baseUrl}/databases`, {
method: "POST",
headers: this.headers,
body: JSON.stringify(body),
});
return response.json();
}
async queryDatabase(
database_id: string,
filter?: any,
sorts?: any,
start_cursor?: string,
page_size?: number
): Promise<any> {
const body: any = {};
if (filter) body.filter = filter;
if (sorts) body.sorts = sorts;
if (start_cursor) body.start_cursor = start_cursor;
if (page_size) body.page_size = page_size;
const response = await fetch(
`${this.baseUrl}/databases/${database_id}/query`,
{
method: "POST",
headers: this.headers,
body: JSON.stringify(body),
}
);
return response.json();
}
async retrieveDatabase(database_id: string): Promise<any> {
const response = await fetch(`${this.baseUrl}/databases/${database_id}`, {
method: "GET",
headers: this.headers,
});
return response.json();
}
async updateDatabase(
database_id: string,
title?: any[],
description?: any[],
properties?: any
): Promise<any> {
const body: any = {};
if (title) body.title = title;
if (description) body.description = description;
if (properties) body.properties = properties;
const response = await fetch(`${this.baseUrl}/databases/${database_id}`, {
method: "PATCH",
headers: this.headers,
body: JSON.stringify(body),
});
return response.json();
}
async createDatabaseItem(database_id: string, properties: any): Promise<any> {
const body = {
parent: { database_id },
properties,
};
const response = await fetch(`${this.baseUrl}/pages`, {
method: "POST",
headers: this.headers,
body: JSON.stringify(body),
});
return response.json();
}
async createComment(
parent?: { page_id: string },
discussion_id?: string,
rich_text?: any[]
): Promise<any> {
const body: any = { rich_text };
if (parent) {
body.parent = parent;
}
if (discussion_id) {
body.discussion_id = discussion_id;
}
const response = await fetch(`${this.baseUrl}/comments`, {
method: "POST",
headers: this.headers,
body: JSON.stringify(body),
});
return response.json();
}
async retrieveComments(
block_id: string,
start_cursor?: string,
page_size?: number
): Promise<any> {
const params = new URLSearchParams();
params.append("block_id", block_id);
if (start_cursor) params.append("start_cursor", start_cursor);
if (page_size) params.append("page_size", page_size.toString());
const response = await fetch(
`${this.baseUrl}/comments?${params.toString()}`,
{
method: "GET",
headers: this.headers,
}
);
return response.json();
}
async search(
query?: string,
filter?: { property: string; value: string },
sort?: {
direction: "ascending" | "descending";
timestamp: "last_edited_time";
},
start_cursor?: string,
page_size?: number
): Promise<any> {
const body: any = {};
if (query) body.query = query;
if (filter) body.filter = filter;
if (sort) body.sort = sort;
if (start_cursor) body.start_cursor = start_cursor;
if (page_size) body.page_size = page_size;
const response = await fetch(`${this.baseUrl}/search`, {
method: "POST",
headers: this.headers,
body: JSON.stringify(body),
});
return response.json();
}
}
async function main() {
const notionToken = process.env.NOTION_API_TOKEN;
if (!notionToken) {
console.error("Please set NOTION_API_TOKEN environment variable");
process.exit(1);
}
console.error("Starting Notion MCP Server...");
const server = new Server(
{
name: "Notion MCP Server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
const notionClient = new NotionClientWrapper(notionToken);
server.setRequestHandler(
CallToolRequestSchema,
async (request: CallToolRequest) => {
console.error("Received CallToolRequest:", request);
try {
if (!request.params.arguments) {
throw new Error("No arguments provided");
}
switch (request.params.name) {
case "notion_append_block_children": {
const args = request.params
.arguments as unknown as AppendBlockChildrenArgs;
if (!args.block_id || !args.children) {
throw new Error(
"Missing required arguments: block_id and children"
);
}
const response = await notionClient.appendBlockChildren(
args.block_id,
args.children
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "notion_retrieve_block": {
const args = request.params
.arguments as unknown as RetrieveBlockArgs;
if (!args.block_id) {
throw new Error("Missing required argument: block_id");
}
const response = await notionClient.retrieveBlock(args.block_id);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "notion_retrieve_block_children": {
const args = request.params
.arguments as unknown as RetrieveBlockChildrenArgs;
if (!args.block_id) {
throw new Error("Missing required argument: block_id");
}
const response = await notionClient.retrieveBlockChildren(
args.block_id,
args.start_cursor,
args.page_size
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "notion_delete_block": {
const args = request.params.arguments as unknown as DeleteBlockArgs;
if (!args.block_id) {
throw new Error("Missing required argument: block_id");
}
const response = await notionClient.deleteBlock(args.block_id);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "notion_retrieve_page": {
const args = request.params
.arguments as unknown as RetrievePageArgs;
if (!args.page_id) {
throw new Error("Missing required argument: page_id");
}
const response = await notionClient.retrievePage(args.page_id);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "notion_update_page_properties": {
const args = request.params
.arguments as unknown as UpdatePagePropertiesArgs;
if (!args.page_id || !args.properties) {
throw new Error(
"Missing required arguments: page_id and properties"
);
}
const response = await notionClient.updatePageProperties(
args.page_id,
args.properties
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "notion_list_all_users": {
const args = request.params
.arguments as unknown as ListAllUsersArgs;
const response = await notionClient.listAllUsers(
args.start_cursor,
args.page_size
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "notion_retrieve_user": {
const args = request.params
.arguments as unknown as RetrieveUserArgs;
if (!args.user_id) {
throw new Error("Missing required argument: user_id");
}
const response = await notionClient.retrieveUser(args.user_id);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "notion_retrieve_bot_user": {
const response = await notionClient.retrieveBotUser();
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "notion_query_database": {
const args = request.params
.arguments as unknown as QueryDatabaseArgs;
if (!args.database_id) {
throw new Error("Missing required argument: database_id");
}
const response = await notionClient.queryDatabase(
args.database_id,
args.filter,
args.sorts,
args.start_cursor,
args.page_size
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "notion_create_database": {
const args = request.params
.arguments as unknown as CreateDatabaseArgs;
const response = await notionClient.createDatabase(
args.parent,
args.title,
args.properties
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "notion_retrieve_database": {
const args = request.params
.arguments as unknown as RetrieveDatabaseArgs;
const response = await notionClient.retrieveDatabase(
args.database_id
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "notion_update_database": {
const args = request.params
.arguments as unknown as UpdateDatabaseArgs;
const response = await notionClient.updateDatabase(
args.database_id,
args.title,
args.description,
args.properties
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "notion_create_database_item": {
const args = request.params
.arguments as unknown as CreateDatabaseItemArgs;
const response = await notionClient.createDatabaseItem(
args.database_id,
args.properties
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "notion_create_comment": {
const args = request.params
.arguments as unknown as CreateCommentArgs;
if (!args.parent && !args.discussion_id) {
throw new Error(
"Either parent.page_id or discussion_id must be provided"
);
}
const response = await notionClient.createComment(
args.parent,
args.discussion_id,
args.rich_text
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "notion_retrieve_comments": {
const args = request.params
.arguments as unknown as RetrieveCommentsArgs;
if (!args.block_id) {
throw new Error("Missing required argument: block_id");
}
const response = await notionClient.retrieveComments(
args.block_id,
args.start_cursor,
args.page_size
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
case "notion_search": {
const args = request.params.arguments as unknown as SearchArgs;
const response = await notionClient.search(
args.query,
args.filter,
args.sort,
args.start_cursor,
args.page_size
);
return {
content: [{ type: "text", text: JSON.stringify(response) }],
};
}
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
} catch (error) {
console.error("Error executing tool:", error);
return {
content: [
{
type: "text",
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
}),
},
],
};
}
}
);
server.setRequestHandler(ListToolsRequestSchema, async () => {
console.error("Received ListToolsRequest");
return {
tools: [
appendBlockChildrenTool,
retrieveBlockTool,
retrieveBlockChildrenTool,
deleteBlockTool,
retrievePageTool,
updatePagePropertiesTool,
listAllUsersTool,
retrieveUserTool,
retrieveBotUserTool,
createDatabaseTool,
queryDatabaseTool,
retrieveDatabaseTool,
updateDatabaseTool,
createDatabaseItemTool,
createCommentTool,
retrieveCommentsTool,
searchTool,
],
};
});
const transport = new StdioServerTransport();
console.error("Connecting server to transport...");
await server.connect(transport);
console.error("Notion MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});