outline-mcp-server 4.12.3 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +27 -16
  2. package/build/index.js +53 -51
  3. package/build/tools/archiveDocument.js +7 -13
  4. package/build/tools/askDocuments.js +21 -35
  5. package/build/tools/createCollection.js +14 -29
  6. package/build/tools/createComment.js +13 -25
  7. package/build/tools/createDocument.js +15 -34
  8. package/build/tools/createTemplateFromDocument.js +7 -13
  9. package/build/tools/deleteComment.js +7 -13
  10. package/build/tools/deleteDocument.js +7 -13
  11. package/build/tools/getCollection.js +7 -13
  12. package/build/tools/getDocument.js +9 -13
  13. package/build/tools/listCollections.js +18 -12
  14. package/build/tools/listDocuments.js +34 -55
  15. package/build/tools/listUsers.js +31 -46
  16. package/build/tools/moveDocument.js +15 -21
  17. package/build/tools/searchDocuments.js +14 -21
  18. package/build/tools/updateCollection.js +14 -29
  19. package/build/tools/updateComment.js +9 -21
  20. package/build/tools/updateDocument.js +11 -29
  21. package/build/utils/getMcpServer.js +16 -0
  22. package/build/utils/{importTools.js → loadAllTools.js} +8 -5
  23. package/build/utils/toolRegistry.js +27 -0
  24. package/package.json +18 -21
  25. package/bin/cli.js +0 -57
  26. package/build/e2e/archiveDocument.spec.js +0 -7
  27. package/build/e2e/askDocuments.spec.js +0 -7
  28. package/build/e2e/createCollection.spec.js +0 -7
  29. package/build/e2e/createComment.spec.js +0 -7
  30. package/build/e2e/createDocument.spec.js +0 -7
  31. package/build/e2e/createTemplateFromDocument.spec.js +0 -7
  32. package/build/e2e/deleteComment.spec.js +0 -7
  33. package/build/e2e/deleteDocument.spec.js +0 -7
  34. package/build/e2e/getCollection.spec.js +0 -7
  35. package/build/e2e/getDocument.spec.js +0 -7
  36. package/build/e2e/listCollections.spec.js +0 -7
  37. package/build/e2e/listDocuments.spec.js +0 -7
  38. package/build/e2e/moveDocument.spec.js +0 -7
  39. package/build/e2e/searchDocuments.spec.js +0 -7
  40. package/build/e2e/setup.js +0 -33
  41. package/build/e2e/updateCollection.spec.js +0 -7
  42. package/build/e2e/updateComment.spec.js +0 -7
  43. package/build/e2e/updateDocument.spec.js +0 -7
  44. package/build/e2e/util/smokeTest.js +0 -88
  45. package/build/types.js +0 -1
  46. package/build/utils/listTools.js +0 -10
  47. /package/build/{client.js → outline/outlineClient.js} +0 -0
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "outline-mcp-server",
3
- "version": "4.12.3",
3
+ "version": "5.0.1",
4
4
  "description": "An MCP server for interacting with Outline's API",
5
5
  "type": "module",
6
6
  "bin": {
7
- "outline-mcp-server": "./bin/cli.js",
8
- "outline-mcp-server-stdio": "./build/index.js"
7
+ "outline-mcp-server": "./build/index.js"
9
8
  },
10
9
  "files": [
11
10
  "build",
@@ -20,34 +19,32 @@
20
19
  "url": "https://github.com/mmmeff/outline-mcp-server"
21
20
  },
22
21
  "scripts": {
23
- "build": "tsc && chmod +x build/index.js && chmod +x bin/cli.js",
22
+ "build": "tsc && chmod +x build/index.js",
24
23
  "prepare": "npm run build",
25
- "watch": "npm run build && tsc --watch",
26
- "dev": "concurrently -n 'build,inspector' -c 'blue.bold,green.bold' 'npm run watch' 'npx @modelcontextprotocol/inspector build/index.js'",
27
- "start:stdio": "npm run build && echo 'build done. running...\n' && node build/index.js",
28
- "start": "npx -y supergateway --port 6060 --stdio \"npm run start:stdio\"",
24
+ "watch": "bun --watch src/index.ts",
25
+ "dev": "concurrently -n 'build,inspector' -c 'blue.bold,green.bold' 'npm run watch' 'npm run inspector'",
26
+ "inspector": "npx @modelcontextprotocol/inspector@latest -y",
27
+ "start": "bun build/index.js",
29
28
  "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json}\"",
30
- "semantic-release": "semantic-release",
31
- "test:e2e": "playwright test",
32
- "test:e2e:ui": "playwright test --ui",
33
- "test:e2e:debug": "playwright test --debug"
29
+ "semantic-release": "semantic-release"
34
30
  },
35
31
  "dependencies": {
36
- "@modelcontextprotocol/sdk": "0.6.0",
37
- "axios": "^1.6.0",
38
- "dotenv": "16.4.7",
39
- "omit-ts": "^2.0.1"
32
+ "@modelcontextprotocol/sdk": "1.12.1",
33
+ "axios": "1.9.0",
34
+ "bun": "^1.2.15",
35
+ "dotenv": "16.5.0",
36
+ "fastify": "^4.28.1",
37
+ "omit-ts": "^2.0.1",
38
+ "zod": "3.25.61"
40
39
  },
41
40
  "devDependencies": {
42
- "@faker-js/faker": "^9.6.0",
43
- "@playwright/test": "^1.42.1",
44
41
  "@semantic-release/changelog": "^6.0.3",
45
42
  "@semantic-release/git": "^10.0.1",
46
- "@types/node": "^20.11.24",
43
+ "@types/node": "20.x",
47
44
  "concurrently": "^9.1.2",
48
45
  "prettier": "^3.2.5",
49
46
  "semantic-release": "^22.0.12",
50
- "typescript": "^5.3.3"
47
+ "typescript": "5.x"
51
48
  },
52
49
  "keywords": [
53
50
  "outline",
@@ -57,6 +54,6 @@
57
54
  ],
58
55
  "license": "MIT",
59
56
  "engines": {
60
- "node": ">=18"
57
+ "node": ">=20"
61
58
  }
62
59
  }
package/bin/cli.js DELETED
@@ -1,57 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { spawn } from 'child_process';
4
- import { fileURLToPath } from 'url';
5
- import { dirname, resolve } from 'path';
6
-
7
- // Get the directory of the current script
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = dirname(__filename);
10
-
11
- // Parse command line arguments
12
- const args = process.argv.slice(2);
13
- let port = 6060; // Default port
14
-
15
- // Parse --port argument
16
- for (let i = 0; i < args.length; i++) {
17
- if (args[i] === '--port' && i + 1 < args.length) {
18
- port = parseInt(args[i + 1], 10);
19
- // Remove these arguments so they don't get passed to supergateway
20
- args.splice(i, 2);
21
- i--;
22
- }
23
- }
24
-
25
- // Check for required environment variable
26
- if (!process.env.OUTLINE_API_KEY) {
27
- console.error('\x1b[31mError: OUTLINE_API_KEY environment variable is required but not set.\x1b[0m');
28
- console.error('Please set this environment variable before running the server.');
29
- process.exit(1);
30
- }
31
-
32
- // Path to the built index.js file
33
- const serverPath = resolve(__dirname, '../build/index.js');
34
-
35
- // Spawn the supergateway process
36
- const gateway = spawn('npx', [
37
- '-y',
38
- 'supergateway',
39
- '--port',
40
- port.toString(),
41
- '--stdio',
42
- `node ${serverPath}`
43
- ], {
44
- stdio: 'inherit',
45
- shell: true
46
- });
47
-
48
- // Handle process exit
49
- process.on('SIGINT', () => {
50
- console.log('SIGINT received, killing outline-mcp-server');
51
- gateway.kill('SIGINT');
52
- });
53
-
54
- gateway.on('close', (code) => {
55
- console.log('outline-mcp-server exited with code', code);
56
- process.exit(code);
57
- });
@@ -1,7 +0,0 @@
1
- import test from '@playwright/test';
2
- import runSmokeTestForTool from './util/smokeTest';
3
- test.describe('archive_document', () => {
4
- test('passes a smoke test', async ({ page }) => {
5
- await runSmokeTestForTool(page, 'archive_document');
6
- });
7
- });
@@ -1,7 +0,0 @@
1
- import test from '@playwright/test';
2
- import runSmokeTestForTool from './util/smokeTest';
3
- test.describe('ask_documents', () => {
4
- test('passes a smoke test', async ({ page }) => {
5
- await runSmokeTestForTool(page, 'ask_documents');
6
- });
7
- });
@@ -1,7 +0,0 @@
1
- import test from '@playwright/test';
2
- import runSmokeTestForTool from './util/smokeTest';
3
- test.describe('create_collection', () => {
4
- test('passes a smoke test', async ({ page }) => {
5
- await runSmokeTestForTool(page, 'create_collection');
6
- });
7
- });
@@ -1,7 +0,0 @@
1
- import test from '@playwright/test';
2
- import runSmokeTestForTool from './util/smokeTest';
3
- test.describe('create_comment', () => {
4
- test('passes a smoke test', async ({ page }) => {
5
- await runSmokeTestForTool(page, 'create_comment');
6
- });
7
- });
@@ -1,7 +0,0 @@
1
- import test from '@playwright/test';
2
- import runSmokeTestForTool from './util/smokeTest';
3
- test.describe('create_document', () => {
4
- test('passes a smoke test', async ({ page }) => {
5
- await runSmokeTestForTool(page, 'create_document');
6
- });
7
- });
@@ -1,7 +0,0 @@
1
- import test from '@playwright/test';
2
- import runSmokeTestForTool from './util/smokeTest';
3
- test.describe('create_template_from_document', () => {
4
- test('passes a smoke test', async ({ page }) => {
5
- await runSmokeTestForTool(page, 'create_template_from_document');
6
- });
7
- });
@@ -1,7 +0,0 @@
1
- import test from '@playwright/test';
2
- import runSmokeTestForTool from './util/smokeTest';
3
- test.describe('delete_comment', () => {
4
- test('passes a smoke test', async ({ page }) => {
5
- await runSmokeTestForTool(page, 'delete_comment');
6
- });
7
- });
@@ -1,7 +0,0 @@
1
- import test from '@playwright/test';
2
- import runSmokeTestForTool from './util/smokeTest';
3
- test.describe('delete_document', () => {
4
- test('passes a smoke test', async ({ page }) => {
5
- await runSmokeTestForTool(page, 'delete_document');
6
- });
7
- });
@@ -1,7 +0,0 @@
1
- import test from '@playwright/test';
2
- import runSmokeTestForTool from './util/smokeTest';
3
- test.describe('get_collection', () => {
4
- test('passes a smoke test', async ({ page }) => {
5
- await runSmokeTestForTool(page, 'get_collection');
6
- });
7
- });
@@ -1,7 +0,0 @@
1
- import test from '@playwright/test';
2
- import runSmokeTestForTool from './util/smokeTest';
3
- test.describe('get_document', () => {
4
- test('passes a smoke test', async ({ page }) => {
5
- await runSmokeTestForTool(page, 'get_document');
6
- });
7
- });
@@ -1,7 +0,0 @@
1
- import test from '@playwright/test';
2
- import runSmokeTestForTool from './util/smokeTest';
3
- test.describe('list_collections', () => {
4
- test('passes a smoke test', async ({ page }) => {
5
- await runSmokeTestForTool(page, 'list_collections');
6
- });
7
- });
@@ -1,7 +0,0 @@
1
- import test from '@playwright/test';
2
- import runSmokeTestForTool from './util/smokeTest';
3
- test.describe('list_documents', () => {
4
- test('passes a smoke test', async ({ page }) => {
5
- await runSmokeTestForTool(page, 'list_documents');
6
- });
7
- });
@@ -1,7 +0,0 @@
1
- import test from '@playwright/test';
2
- import runSmokeTestForTool from './util/smokeTest';
3
- test.describe('move_document', () => {
4
- test('passes a smoke test', async ({ page }) => {
5
- await runSmokeTestForTool(page, 'move_document');
6
- });
7
- });
@@ -1,7 +0,0 @@
1
- import test from '@playwright/test';
2
- import runSmokeTestForTool from './util/smokeTest';
3
- test.describe('search_documents', () => {
4
- test('passes a smoke test', async ({ page }) => {
5
- await runSmokeTestForTool(page, 'search_documents');
6
- });
7
- });
@@ -1,33 +0,0 @@
1
- import { test } from '@playwright/test';
2
- import { registerTools } from '../utils/importTools.js';
3
- /**
4
- * Helper function to mock API responses
5
- */
6
- export async function mockApiResponse(page, url, response) {
7
- await page.route(url, route => {
8
- route.fulfill({
9
- status: 200,
10
- contentType: 'application/json',
11
- body: JSON.stringify(response),
12
- });
13
- });
14
- }
15
- /**
16
- * Helper function to wait for API calls
17
- */
18
- export async function loadTool(page, toolName) {
19
- return await test.step(`Loading tool: ${toolName}`, async () => {
20
- const toolDefinitions = await registerTools();
21
- const tool = toolDefinitions[toolName];
22
- if (!tool) {
23
- // TODO: Make this actually type-safe instead of a runtime error
24
- throw new Error(`Tool not found for name: ${toolName}`);
25
- }
26
- await page.goto('/');
27
- await page.getByRole('button', { name: 'Connect' }).click();
28
- await page.getByRole('button', { name: 'List Tools' }).click();
29
- await page.getByText(toolName).click();
30
- await page.waitForTimeout(100);
31
- return tool;
32
- });
33
- }
@@ -1,7 +0,0 @@
1
- import test from '@playwright/test';
2
- import runSmokeTestForTool from './util/smokeTest';
3
- test.describe('update_collection', () => {
4
- test('passes a smoke test', async ({ page }) => {
5
- await runSmokeTestForTool(page, 'update_collection');
6
- });
7
- });
@@ -1,7 +0,0 @@
1
- import test from '@playwright/test';
2
- import runSmokeTestForTool from './util/smokeTest';
3
- test.describe('update_comment', () => {
4
- test('passes a smoke test', async ({ page }) => {
5
- await runSmokeTestForTool(page, 'update_comment');
6
- });
7
- });
@@ -1,7 +0,0 @@
1
- import test from '@playwright/test';
2
- import runSmokeTestForTool from './util/smokeTest';
3
- test.describe('update_document', () => {
4
- test('passes a smoke test', async ({ page }) => {
5
- await runSmokeTestForTool(page, 'update_document');
6
- });
7
- });
@@ -1,88 +0,0 @@
1
- import { faker } from '@faker-js/faker';
2
- import test from '@playwright/test';
3
- import { loadTool } from '../setup.js';
4
- export default async function runSmokeTestForTool(page, toolName) {
5
- const tool = await loadTool(page, toolName);
6
- await test.step('Fill in required fields', async () => {
7
- const requiredProperties = tool.inputSchema.required ?? [];
8
- console.log('requiredProperties', requiredProperties);
9
- for (let property of Object.entries(tool.inputSchema.properties ?? {}).filter(([name]) => requiredProperties.includes(name))) {
10
- const [name, config] = property;
11
- const input = page.getByPlaceholder(config.description);
12
- switch (config.type) {
13
- case 'string':
14
- if (config.enum) {
15
- // If it's an enum, select a random value from the enum array
16
- await input.fill(faker.helpers.arrayElement(config.enum));
17
- }
18
- else if (name.toLowerCase().includes('id')) {
19
- // Generate a realistic-looking ID
20
- await input.fill(faker.string.alphanumeric(10));
21
- }
22
- else if (name.toLowerCase().includes('email')) {
23
- // Generate email if the field name suggests it's an email
24
- await input.fill(faker.internet.email());
25
- }
26
- else if (name.toLowerCase().includes('date') || name.toLowerCase().includes('time')) {
27
- // Generate date string if the field name suggests it's a date
28
- await input.fill(faker.date.recent().toISOString());
29
- }
30
- else if (name.toLowerCase().includes('color')) {
31
- // Generate color hex code
32
- await input.fill(faker.color.rgb({ format: 'hex' }));
33
- }
34
- else if (name.toLowerCase().includes('url') || name.toLowerCase().includes('link')) {
35
- // Generate URL
36
- await input.fill(faker.internet.url());
37
- }
38
- else {
39
- // For general string fields
40
- await input.fill(faker.lorem.sentence());
41
- }
42
- break;
43
- case 'number':
44
- await input.fill(faker.number.int({ min: 1, max: 100 }).toString());
45
- break;
46
- case 'boolean':
47
- // For boolean inputs, let's use random true/false
48
- await input.fill(faker.datatype.boolean().toString());
49
- break;
50
- case 'array':
51
- // For array inputs, we'll generate a JSON string with a few items
52
- const arrayItems = [];
53
- // Look at array item type and generate appropriate fake data
54
- if (config.items?.type === 'string') {
55
- for (let i = 0; i < 3; i++) {
56
- arrayItems.push(faker.lorem.word());
57
- }
58
- }
59
- else if (config.items?.type === 'number') {
60
- for (let i = 0; i < 3; i++) {
61
- arrayItems.push(faker.number.int(100));
62
- }
63
- }
64
- await input.fill(JSON.stringify(arrayItems));
65
- break;
66
- case 'object':
67
- // For object inputs, generate a simple object with some properties
68
- const fakeObject = {
69
- prop1: faker.lorem.word(),
70
- prop2: faker.number.int(100),
71
- prop3: faker.datatype.boolean(),
72
- };
73
- await input.fill(JSON.stringify(fakeObject));
74
- break;
75
- default:
76
- // For unhandled types, generate a basic string
77
- await input.fill(faker.lorem.word());
78
- break;
79
- }
80
- }
81
- });
82
- await test.step('Execute the tool', async () => {
83
- await page.getByRole('button', { name: 'Run Tool' }).click();
84
- await page.getByRole('heading', { name: 'Tool Result: Success' }).isVisible();
85
- await page.getByRole('heading', { name: 'Error output from MCP server' }).isHidden();
86
- await page.waitForTimeout(100);
87
- });
88
- }
package/build/types.js DELETED
@@ -1 +0,0 @@
1
- export {};
@@ -1,10 +0,0 @@
1
- // We'll collect all tool definitions here, keyed by name
2
- const toolDefinitions = {};
3
- // Function to register a tool definition
4
- export function registerTool(definition) {
5
- toolDefinitions[definition.name] = definition;
6
- }
7
- // Function to get all registered tool definitions
8
- export function getToolDefinitions() {
9
- return toolDefinitions;
10
- }
File without changes