160 lines
4.6 KiB
JavaScript
160 lines
4.6 KiB
JavaScript
|
|
/**
|
|||
|
|
* 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
|
|||
|
|
};
|