guanghulab/mcp-servers/notion-server.js

160 lines
4.6 KiB
JavaScript
Raw Permalink Normal View History

/**
* MCP Server: Notion 工具集
*
* 提供 Notion 数据库操作工具 AGE OS 对话界面调用
* 权限由开发者编号决定只能操作自己频道下的内容
*
* 指令ZY-AGEOS-TOWER-2026-0326-001
* 版权国作登字-2026-A-00037559
*/
'use strict';
/**
* Notion MCP Server tool definitions
*/
var tools = [
{
name: 'notion_query_database',
description: '查询 Notion 数据库',
inputSchema: {
type: 'object',
properties: {
database_id: { type: 'string', description: 'Notion 数据库 ID' },
filter: { type: 'object', description: 'Notion filter 对象' }
},
required: ['database_id']
}
},
{
name: 'notion_update_page',
description: '更新 Notion 页面属性',
inputSchema: {
type: 'object',
properties: {
page_id: { type: 'string', description: 'Notion 页面 ID' },
properties: { type: 'object', description: '要更新的属性' }
},
required: ['page_id', 'properties']
}
},
{
name: 'notion_create_page',
description: '在 Notion 数据库中创建页面',
inputSchema: {
type: 'object',
properties: {
database_id: { type: 'string', description: 'Notion 数据库 ID' },
properties: { type: 'object', description: '页面属性' },
content: { type: 'string', description: '页面内容Markdown' }
},
required: ['database_id', 'properties']
}
},
{
name: 'notion_search',
description: '搜索 Notion 工作区',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: '搜索关键词' }
},
required: ['query']
}
}
];
// Notion API base URL
var NOTION_API_BASE = 'https://api.notion.com/v1';
var NOTION_VERSION = '2022-06-28';
/**
* Permission check for Notion tools
* @param {string} devId - Developer ID
* @param {string} toolName - Tool being called
* @returns {{ allowed: boolean, readOnly?: boolean, reason?: string }}
*/
function checkPermission(devId, toolName) {
// TCS-0002 (冰朔) has full access
if (devId === 'TCS-0002' || devId === 'DEV-000') {
return { allowed: true, readOnly: false };
}
// Guests have no access
if (!devId || !devId.startsWith('DEV-')) {
return { allowed: false, reason: '访客无 Notion 工具权限' };
}
// Other developers: read operations allowed, write operations limited to own channel
var writeTools = ['notion_update_page', 'notion_create_page'];
if (writeTools.indexOf(toolName) >= 0) {
return { allowed: true, readOnly: false, channelRestricted: true };
}
return { allowed: true, readOnly: true };
}
/**
* Execute a Notion MCP tool
* @param {string} toolName
* @param {object} params
* @param {object} context - { devId, notionToken }
* @returns {Promise<object>}
*/
async function executeTool(toolName, params, context) {
var devId = context.devId;
var notionToken = context.notionToken || process.env.NOTION_TOKEN;
if (!notionToken) {
return { error: true, code: 'NO_TOKEN', message: 'Notion token 未配置' };
}
// Permission check
var permResult = checkPermission(devId, toolName);
if (!permResult.allowed) {
return { error: true, code: 'PERMISSION_DENIED', message: permResult.reason };
}
var headers = {
'Authorization': 'Bearer ' + notionToken,
'Notion-Version': NOTION_VERSION,
'Content-Type': 'application/json'
};
switch (toolName) {
case 'notion_query_database': {
var url = NOTION_API_BASE + '/databases/' + params.database_id + '/query';
var body = params.filter ? { filter: params.filter } : {};
var res = await fetch(url, {
method: 'POST',
headers: headers,
body: JSON.stringify(body)
});
if (!res.ok) return { error: true, status: res.status, message: 'Failed to query database' };
var data = await res.json();
return { results: data.results, has_more: data.has_more };
}
case 'notion_search': {
var searchUrl = NOTION_API_BASE + '/search';
var searchRes = await fetch(searchUrl, {
method: 'POST',
headers: headers,
body: JSON.stringify({ query: params.query })
});
if (!searchRes.ok) return { error: true, status: searchRes.status, message: 'Failed to search' };
var searchData = await searchRes.json();
return { results: searchData.results, has_more: searchData.has_more };
}
default:
return { error: true, code: 'NOT_IMPLEMENTED', message: toolName + ' 尚未实现,待后续开发' };
}
}
module.exports = {
tools: tools,
checkPermission: checkPermission,
executeTool: executeTool
};