diff --git a/README.md b/README.md index fe7e44d..aacdb02 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,15 @@ or - `NOTION_API_TOKEN` (required): Your Notion API integration token. - `NOTION_MARKDOWN_CONVERSION`: Set to "true" to enable experimental Markdown conversion. This can significantly reduce token consumption when viewing content, but may cause issues when trying to edit page content. +## Command Line Arguments + +- `--enabledTools`: Comma-separated list of tools to enable (e.g. "notion_retrieve_page,notion_query_database"). When specified, only the listed tools will be available. If not specified, all tools are enabled. + +Read-only tools example (copy-paste friendly): +```bash +node build/index.js --enabledTools=notion_retrieve_block,notion_retrieve_block_children,notion_retrieve_page,notion_query_database,notion_retrieve_database,notion_search,notion_list_all_users,notion_retrieve_user,notion_retrieve_bot_user,notion_retrieve_comments +``` + ## Advanced Configuration ### Markdown Conversion diff --git a/notion/package-lock.json b/notion/package-lock.json index 89b5933..6447c9c 100644 --- a/notion/package-lock.json +++ b/notion/package-lock.json @@ -17,7 +17,9 @@ }, "devDependencies": { "@types/node": "^20.11.24", - "typescript": "^5.3.3" + "@types/yargs": "^17.0.33", + "typescript": "^5.3.3", + "yargs": "^17.7.2" } }, "node_modules/@esbuild/aix-ppc64": { @@ -674,6 +676,23 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@vitest/expect": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.9.tgz", @@ -785,6 +804,32 @@ "node": ">= 0.6" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -904,6 +949,41 @@ "node": ">= 16" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", @@ -1012,6 +1092,13 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -1091,6 +1178,16 @@ "@esbuild/win32-x64": "0.25.1" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1269,6 +1366,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -1379,6 +1486,16 @@ "node": ">= 0.10" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", @@ -1637,6 +1754,16 @@ "node": ">= 0.8" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rollup": { "version": "4.37.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.37.0.tgz", @@ -1887,6 +2014,34 @@ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.1.tgz", "integrity": "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==" }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -2163,11 +2318,68 @@ "node": ">=8" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/zod": { "version": "3.24.2", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", diff --git a/notion/package.json b/notion/package.json index 3b684c7..8a32b85 100644 --- a/notion/package.json +++ b/notion/package.json @@ -28,6 +28,8 @@ }, "devDependencies": { "@types/node": "^20.11.24", - "typescript": "^5.3.3" + "@types/yargs": "^17.0.33", + "typescript": "^5.3.3", + "yargs": "^17.7.2" } } diff --git a/notion/src/client.test.ts b/notion/src/client.test.ts index 2e46793..d37c7c8 100644 --- a/notion/src/client.test.ts +++ b/notion/src/client.test.ts @@ -1,11 +1,28 @@ import { expect, test, describe, vi, beforeEach } from "vitest"; import { NotionClientWrapper } from "./index.js"; import { PageResponse } from "./types/index.js"; +import { filterTools } from "./index.js"; vi.mock("./markdown/index.js", () => ({ convertToMarkdown: vi.fn().mockReturnValue("# Test"), })); +// Mock tool list +const mockInputSchema = { type: "object" as const } +const mockTools = [ + { + name: "notion_retrieve_block", + inputSchema: mockInputSchema + }, + { + name: "notion_retrieve_page", + inputSchema: mockInputSchema + }, + { + name: "notion_query_database", + inputSchema: mockInputSchema + } +]; global.fetch = vi.fn(); describe("NotionClientWrapper", () => { @@ -162,4 +179,26 @@ describe("NotionClientWrapper", () => { expect(convertToMarkdown).toHaveBeenCalledWith(response); }); + + describe("filterTools", () => { + test("should return all tools when no filter specified", () => { + const result = filterTools(mockTools); + expect(result).toEqual(mockTools); + }); + + test("should filter tools based on enabledTools", () => { + const enabledToolsSet = new Set(["notion_retrieve_block", "notion_query_database"]); + const result = filterTools(mockTools, enabledToolsSet); + expect(result).toEqual([ + { name: "notion_retrieve_block", inputSchema: mockInputSchema }, + { name: "notion_query_database", inputSchema: mockInputSchema } + ]); + }); + + test("should return empty array when no tools match", () => { + const enabledToolsSet = new Set(["non_existent_tool"]); + const result = filterTools(mockTools, enabledToolsSet); + expect(result).toEqual([]); + }); + }); }); diff --git a/notion/src/index.ts b/notion/src/index.ts index 27d20e8..b680576 100644 --- a/notion/src/index.ts +++ b/notion/src/index.ts @@ -4,7 +4,10 @@ * Set the "format" parameter to "json" or "markdown" (default is "markdown"). * - Use "markdown" for human-readable output when only reading content * - Use "json" when you need to process or modify the data programmatically - * + * + * Command-line Arguments: + * --enabledTools: Comma-separated list of tools to enable (e.g. "notion_retrieve_page,notion_query_database") + * * Environment Variables: * - NOTION_API_TOKEN: Required. Your Notion API integration token. * - NOTION_MARKDOWN_CONVERSION: Optional. Set to "true" to enable @@ -13,6 +16,8 @@ */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; import { CallToolRequest, CallToolRequestSchema, @@ -20,6 +25,19 @@ import { Tool, } from "@modelcontextprotocol/sdk/types.js"; import { convertToMarkdown } from "./markdown/index.js"; + +// Parse command line arguments +const argv = yargs(hideBin(process.argv)) + .option('enabledTools', { + type: 'string', + description: 'Comma-separated list of tools to enable', + }) + .parseSync(); + +const enabledToolsSet = new Set( + argv.enabledTools ? argv.enabledTools.split(',') : [] +); + import { NotionResponse, BlockResponse, @@ -160,6 +178,12 @@ interface SearchArgs { } +// Filter tools based on enabledTools parameter +export function filterTools(tools: Tool[], enabledToolsSet: Set = new Set()): Tool[] { + if (enabledToolsSet.size === 0) return tools; + return tools.filter(tool => enabledToolsSet.has(tool.name)); +} + // TODO: Define Type-safe request/response handling using Zod schemas const commonIdDescription = "It should be a 32-character string (excluding hyphens) formatted as 8-4-4-4-12 with hyphens (-)."; @@ -1461,7 +1485,7 @@ async function main() { // Check format parameter and return appropriate response const requestedFormat = (request.params.arguments as any)?.format || "markdown"; - + // Only convert to markdown if both conditions are met: // 1. The requested format is markdown // 2. The experimental markdown conversion is enabled via environment variable @@ -1492,27 +1516,28 @@ async function main() { ); server.setRequestHandler(ListToolsRequestSchema, async () => { + const allTools = [ + appendBlockChildrenTool, + retrieveBlockTool, + retrieveBlockChildrenTool, + deleteBlockTool, + updateBlockTool, + retrievePageTool, + updatePagePropertiesTool, + listAllUsersTool, + retrieveUserTool, + retrieveBotUserTool, + createDatabaseTool, + queryDatabaseTool, + retrieveDatabaseTool, + updateDatabaseTool, + createDatabaseItemTool, + createCommentTool, + retrieveCommentsTool, + searchTool, + ]; return { - tools: [ - appendBlockChildrenTool, - retrieveBlockTool, - retrieveBlockChildrenTool, - deleteBlockTool, - updateBlockTool, - retrievePageTool, - updatePagePropertiesTool, - listAllUsersTool, - retrieveUserTool, - retrieveBotUserTool, - createDatabaseTool, - queryDatabaseTool, - retrieveDatabaseTool, - updateDatabaseTool, - createDatabaseItemTool, - createCommentTool, - retrieveCommentsTool, - searchTool, - ], + tools: filterTools(allTools, enabledToolsSet), }; });