systemprompt-mcp-notion
Version:
A specialized Model Context Protocol (MCP) server that integrates Notion into your AI workflows. This server enables seamless access to Notion through MCP, allowing AI agents to interact with pages, databases, and comments.
278 lines (277 loc) • 9.3 kB
JavaScript
import { Client } from "@notionhq/client";
import { isFullPage } from "../utils/notion-utils.js";
export class NotionService {
static instance;
client;
constructor(token) {
this.client = new Client({
auth: token,
});
}
static initialize(token) {
if (!token) {
throw new Error("Notion API token is required");
}
if (!NotionService.instance) {
NotionService.instance = new NotionService(token);
}
}
static getInstance() {
if (!NotionService.instance) {
throw new Error("NotionService must be initialized before use");
}
return NotionService.instance;
}
async listPages(options = {}) {
const response = await this.client.search({
filter: {
property: "object",
value: "page",
},
page_size: options?.pageSize || 50,
sort: options?.sort || {
direction: "ascending",
timestamp: "last_edited_time",
},
start_cursor: options?.startCursor,
});
const items = response.results
.filter((result) => isFullPage(result))
.map((page) => ({
id: page.id,
content: this.extractPageTitle(page),
_message: `Page ${page.id} retrieved successfully`,
metadata: {
lastEditedTime: page.last_edited_time,
createdTime: page.created_time,
type: "page",
},
}));
return {
items,
_message: "List pages operation completed successfully",
pagination: {
hasMore: response.has_more,
nextCursor: response.next_cursor || undefined,
},
};
}
// Helper function to safely extract page title
extractPageTitle(page) {
const titleProperty = Object.values(page.properties).find((prop) => prop.type === "title");
return titleProperty?.type === "title"
? titleProperty.title[0]?.plain_text || ""
: "";
}
async searchPages(query, maxResults = 50) {
const response = await this.client.search({
query,
filter: {
property: "object",
value: "page",
},
page_size: maxResults,
sort: {
direction: "descending",
timestamp: "last_edited_time",
},
});
const items = response.results
.filter((result) => isFullPage(result))
.map((page) => ({
id: page.id,
content: this.extractPageTitle(page),
_message: `Page ${page.id} found in search`,
metadata: {
lastEditedTime: page.last_edited_time,
type: "page",
},
}));
return {
items,
_message: `Search pages operation completed successfully with ${items.length} results`,
pagination: {
hasMore: response.has_more,
nextCursor: response.next_cursor || undefined,
},
};
}
async getPage(pageId) {
const page = (await this.client.pages.retrieve({
page_id: pageId,
}));
return {
id: page.id,
content: this.extractPageTitle(page),
_message: `Page ${pageId} retrieved successfully`,
metadata: {
lastEditedTime: page.last_edited_time,
createdTime: page.created_time,
type: "page",
properties: page.properties,
},
};
}
async createPage(options) {
const page = (await this.client.pages.create(options));
return {
id: page.id,
content: this.extractPageTitle(page),
_message: `Page ${page.id} created successfully`,
metadata: {
lastEditedTime: page.last_edited_time,
createdTime: page.created_time,
type: "page",
},
};
}
async updatePage(options) {
const response = await this.client.pages.update({
page_id: options.pageId,
properties: options.properties,
});
if (!isFullPage(response)) {
throw new Error("Invalid response from Notion API");
}
if (options.children) {
await this.client.blocks.children.append({
block_id: options.pageId,
children: options.children,
});
}
return {
id: response.id,
content: this.extractPageTitle(response),
_message: `Page ${options.pageId} updated successfully`,
metadata: {
lastEditedTime: response.last_edited_time,
type: "page",
updated: true,
},
};
}
async searchPagesByTitle(title, maxResults = 10) {
const response = await this.client.search({
query: title,
filter: {
property: "object",
value: "page",
},
page_size: maxResults,
sort: {
direction: "descending",
timestamp: "last_edited_time",
},
});
const items = response.results
.filter((result) => isFullPage(result))
.map((page) => ({
id: page.id,
content: this.extractPageTitle(page),
_message: `Page ${page.id} matched title search`,
metadata: {
lastEditedTime: page.last_edited_time,
type: "page",
},
}))
.filter((page) => page.content.toLowerCase().includes(title.toLowerCase()));
return {
items,
_message: `Title search completed successfully with ${items.length} matches`,
pagination: {
hasMore: response.has_more,
nextCursor: response.next_cursor || undefined,
},
};
}
async searchDatabases(maxResults = 50) {
const response = await this.client.search({
filter: {
property: "object",
value: "database",
},
page_size: maxResults,
sort: {
direction: "descending",
timestamp: "last_edited_time",
},
});
const items = response.results
.filter((result) => result.object === "database")
.map((database) => ({
id: database.id,
content: database.title[0]?.plain_text || "",
_message: `Database ${database.id} found`,
metadata: {
type: "database",
lastEditedTime: database.last_edited_time,
},
}));
return {
items,
_message: `Database search completed successfully with ${items.length} results`,
pagination: {
hasMore: response.has_more,
nextCursor: response.next_cursor || undefined,
},
};
}
async deletePage(pageId) {
const response = (await this.client.pages.update({
page_id: pageId,
archived: true,
}));
return {
id: pageId,
content: "Page archived",
_message: `Page ${pageId} archived successfully`,
metadata: {
type: "page",
archived: true,
lastEditedTime: response.last_edited_time,
},
};
}
async getPageProperty(pageId, propertyId) {
const response = await this.client.pages.properties.retrieve({
page_id: pageId,
property_id: propertyId,
});
return {
id: propertyId,
content: JSON.stringify(response),
_message: `Property ${propertyId} retrieved successfully from page ${pageId}`,
metadata: {
type: "property",
pageId: pageId,
},
};
}
async getPageBlocks(pageId) {
const response = await this.client.blocks.children.list({
block_id: pageId,
});
const items = response.results.map((block) => {
const blockResponse = block;
return {
id: block.id,
content: blockResponse.type === "paragraph"
? blockResponse.paragraph?.rich_text?.[0]?.plain_text || ""
: "",
_message: `Block ${block.id} retrieved successfully`,
metadata: {
type: blockResponse.type,
hasChildren: blockResponse.has_children || false,
},
};
});
return {
items,
_message: `Page blocks retrieved successfully with ${items.length} blocks`,
pagination: {
hasMore: response.has_more,
nextCursor: response.next_cursor || undefined,
},
};
}
}