Notion MCP Server
by arre-ankit
- src
#!/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";
import dotenv from "dotenv";
dotenv.config();
// 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;
}
// 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;
}
// Tool definitions
// Blocks
const appendBlockChildrenTool: Tool = {
name: "notion_append_block_children",
description: "Append blocks to a parent block in Notion",
inputSchema: {
type: "object",
properties: {
block_id: {
type: "string",
description: "The ID of the parent block. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).",
},
children: {
type: "array",
description: "Array of block objects to append",
},
},
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. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).",
},
},
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. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).",
},
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. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).",
},
},
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. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).",
},
},
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. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).",
},
properties: {
type: "object",
description: "Properties to update. These correspond to the columns or fields in the database.",
},
},
required: ["page_id", "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.",
},
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. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).",
},
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. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).",
},
},
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. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).",
},
title: {
type: "array",
description: "An array of rich text objects that represents the title of the database that is displayed in the Notion UI.",
},
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. It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-).",
},
properties: {
type: "object",
description: "Properties of the new database item. These should match the database schema.",
},
},
required: ["database_id", "properties"],
},
};
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 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 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_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) }],
};
}
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,
createDatabaseTool,
queryDatabaseTool,
retrieveDatabaseTool,
updateDatabaseTool,
createDatabaseItemTool,
],
};
});
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);
});