outline-mcp-server 4.12.2 → 5.0.0

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 +28 -17
  2. package/build/index.js +52 -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
@@ -1,24 +1,20 @@
1
1
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
- import { outlineClient } from '../client.js';
3
- import { registerTool } from '../utils/listTools.js';
2
+ import { outlineClient } from '../outline/outlineClient.js';
3
+ import toolRegistry from '../utils/toolRegistry.js';
4
+ import z from 'zod';
4
5
  // Register this tool
5
- registerTool({
6
+ toolRegistry.register('get_document', {
6
7
  name: 'get_document',
7
8
  description: 'Get details about a specific document. At least id XOR shareId are required.',
8
9
  inputSchema: {
9
- properties: {
10
- id: {
11
- type: 'string',
12
- description: 'Unique identifier for the document. Either the UUID or the urlId is acceptable',
13
- },
14
- },
15
- required: ['id'],
16
- type: 'object',
10
+ id: z
11
+ .string()
12
+ .describe('Unique identifier for the document. Either the UUID or the urlId is acceptable'),
17
13
  },
18
- handler: async function handleGetDocument(args) {
14
+ async callback(args) {
19
15
  try {
20
16
  const response = await outlineClient.post('/documents.info', { id: args.id });
21
- return response.data.data;
17
+ return { content: [{ type: 'text', text: JSON.stringify(response.data.data) }] };
22
18
  }
23
19
  catch (error) {
24
20
  console.error('Error getting document:', error.message);
@@ -1,27 +1,33 @@
1
1
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
- import { outlineClient } from '../client.js';
3
- import { registerTool } from '../utils/listTools.js';
2
+ import { outlineClient } from '../outline/outlineClient.js';
3
+ import toolRegistry from '../utils/toolRegistry.js';
4
+ import z from 'zod';
4
5
  // Register this tool
5
- registerTool({
6
+ toolRegistry.register('list_collections', {
6
7
  name: 'list_collections',
7
8
  description: 'List all collections in the Outline workspace',
8
9
  inputSchema: {
9
- properties: {
10
- limit: {
11
- type: 'number',
12
- description: 'Maximum number of collections to return (optional)',
13
- },
14
- },
15
- type: 'object',
10
+ limit: z.number().describe('Maximum number of collections to return (optional)').optional(),
16
11
  },
17
- handler: async function handleListCollections(args) {
12
+ async callback(args) {
18
13
  try {
19
14
  const payload = {};
20
15
  if (args.limit) {
21
16
  payload.limit = args.limit;
22
17
  }
23
18
  const response = await outlineClient.post('/collections.list', payload);
24
- return response.data.data;
19
+ return {
20
+ content: [
21
+ {
22
+ type: 'text',
23
+ text: `collections: ${JSON.stringify(response.data.data)}`,
24
+ },
25
+ {
26
+ type: 'text',
27
+ text: `pagination: ${JSON.stringify(response.data.pagination)}`,
28
+ },
29
+ ],
30
+ };
25
31
  }
26
32
  catch (error) {
27
33
  console.error('Error listing collections:', error.message);
@@ -1,57 +1,33 @@
1
1
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
- import { outlineClient } from '../client.js';
3
- import { registerTool } from '../utils/listTools.js';
2
+ import { outlineClient } from '../outline/outlineClient.js';
3
+ import toolRegistry from '../utils/toolRegistry.js';
4
+ import z from 'zod';
4
5
  // Register this tool
5
- registerTool({
6
+ toolRegistry.register('list_documents', {
6
7
  name: 'list_documents',
7
8
  description: 'List documents in the Outline workspace with optional filters',
8
9
  inputSchema: {
9
- properties: {
10
- collectionId: {
11
- type: 'string',
12
- description: 'Filter by collection ID (optional)',
13
- },
14
- query: {
15
- type: 'string',
16
- description: 'Search query to filter documents (optional)',
17
- },
18
- limit: {
19
- type: 'number',
20
- description: 'Maximum number of documents to return (optional)',
21
- },
22
- offset: {
23
- type: 'number',
24
- description: 'Pagination offset (optional)',
25
- },
26
- sort: {
27
- type: 'string',
28
- description: 'Field to sort by (e.g. "updatedAt") (optional)',
29
- },
30
- direction: {
31
- type: 'string',
32
- description: 'Sort direction, either "ASC" or "DESC" (optional)',
33
- enum: ['ASC', 'DESC'],
34
- },
35
- template: {
36
- type: 'boolean',
37
- description: 'Optionally filter to only templates (optional)',
38
- },
39
- userId: {
40
- type: 'string',
41
- description: 'Optionally filter by user ID (optional)',
42
- },
43
- parentDocumentId: {
44
- type: 'string',
45
- description: 'Optionally filter by parent document ID (optional)',
46
- },
47
- backlinkDocumentId: {
48
- type: 'string',
49
- description: 'Optionally filter by backlink document ID (optional)',
50
- },
51
- },
52
- type: 'object',
10
+ collectionId: z.string().describe('Filter by collection ID (optional)').optional(),
11
+ query: z.string().describe('Search query to filter documents (optional)').optional(),
12
+ limit: z.number().describe('Maximum number of documents to return (optional)').optional(),
13
+ offset: z.number().describe('Pagination offset (optional)').optional(),
14
+ sort: z.string().describe('Field to sort by (e.g. "updatedAt") (optional)').optional(),
15
+ direction: z
16
+ .enum(['ASC', 'DESC'])
17
+ .describe('Sort direction, either "ASC" or "DESC" (optional)')
18
+ .optional(),
19
+ template: z.boolean().describe('Optionally filter to only templates (optional)').optional(),
20
+ userId: z.string().describe('Optionally filter by user ID (optional)').optional(),
21
+ parentDocumentId: z
22
+ .string()
23
+ .describe('Optionally filter by parent document ID (optional)')
24
+ .optional(),
25
+ backlinkDocumentId: z
26
+ .string()
27
+ .describe('Optionally filter by backlink document ID (optional)')
28
+ .optional(),
53
29
  },
54
- handler: async function handleListDocuments(args) {
30
+ async callback(args) {
55
31
  try {
56
32
  // Create the payload object
57
33
  const payload = {
@@ -78,13 +54,16 @@ registerTool({
78
54
  const documents = response.data.data;
79
55
  // Return the documents with additional metadata
80
56
  return {
81
- documents,
82
- pagination: {
83
- offset: response.data.pagination.offset,
84
- limit: response.data.pagination.limit,
85
- nextPath: response.data.pagination.nextPath,
86
- totalCount: response.data.pagination.totalCount,
87
- },
57
+ content: [
58
+ {
59
+ type: 'text',
60
+ text: `documents: ${JSON.stringify(documents)}`,
61
+ },
62
+ {
63
+ type: 'text',
64
+ text: `pagination: ${JSON.stringify(response.data.pagination)}`,
65
+ },
66
+ ],
88
67
  };
89
68
  }
90
69
  catch (error) {
@@ -1,54 +1,34 @@
1
1
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
- import { outlineClient } from '../client.js';
3
- import { registerTool } from '../utils/listTools.js';
2
+ import { outlineClient } from '../outline/outlineClient.js';
3
+ import toolRegistry from '../utils/toolRegistry.js';
4
+ import z from 'zod';
4
5
  // Register this tool
5
- registerTool({
6
+ toolRegistry.register('list_users', {
6
7
  name: 'list_users',
7
8
  description: 'List all users in the Outline workspace',
8
9
  inputSchema: {
9
- properties: {
10
- offset: {
11
- type: 'number',
12
- description: 'Pagination offset (optional)',
13
- },
14
- limit: {
15
- type: 'number',
16
- description: 'Maximum number of users to return (optional)',
17
- },
18
- sort: {
19
- type: 'string',
20
- description: 'Field to sort by (e.g. "name", "email", "createdAt") (optional)',
21
- },
22
- direction: {
23
- type: 'string',
24
- description: 'Sort direction, either "ASC" or "DESC" (optional)',
25
- enum: ['ASC', 'DESC'],
26
- },
27
- query: {
28
- type: 'string',
29
- description: 'Search query to filter users (optional)',
30
- },
31
- emails: {
32
- type: 'array',
33
- items: {
34
- type: 'string',
35
- },
36
- description: 'Filter by email addresses (optional)',
37
- },
38
- filter: {
39
- type: 'string',
40
- description: 'Filter by user status (optional)',
41
- enum: ['all', 'invited', 'active', 'suspended'],
42
- },
43
- role: {
44
- type: 'string',
45
- description: 'Filter by user role (optional)',
46
- enum: ['admin', 'member', 'viewer', 'guest'],
47
- },
48
- },
49
- type: 'object',
10
+ offset: z.number().describe('Pagination offset (optional)').optional(),
11
+ limit: z.number().describe('Maximum number of users to return (optional)').optional(),
12
+ sort: z
13
+ .string()
14
+ .describe('Field to sort by (e.g. "name", "email", "createdAt") (optional)')
15
+ .optional(),
16
+ direction: z
17
+ .enum(['ASC', 'DESC'])
18
+ .describe('Sort direction, either "ASC" or "DESC" (optional)')
19
+ .optional(),
20
+ query: z.string().describe('Search query to filter users (optional)').optional(),
21
+ emails: z.array(z.string()).describe('Filter by email addresses (optional)').optional(),
22
+ filter: z
23
+ .enum(['all', 'invited', 'active', 'suspended'])
24
+ .describe('Filter by user status (optional)')
25
+ .optional(),
26
+ role: z
27
+ .enum(['admin', 'member', 'viewer', 'guest'])
28
+ .describe('Filter by user role (optional)')
29
+ .optional(),
50
30
  },
51
- handler: async function handleListUsers(args) {
31
+ async callback(args) {
52
32
  try {
53
33
  const payload = {};
54
34
  if (args.offset !== undefined) {
@@ -76,7 +56,12 @@ registerTool({
76
56
  payload.role = args.role;
77
57
  }
78
58
  const response = await outlineClient.post('/users.list', payload);
79
- return response.data.data;
59
+ return {
60
+ content: [
61
+ { type: 'text', text: `users: ${JSON.stringify(response.data.data)}` },
62
+ { type: 'text', text: `pagination: ${JSON.stringify(response.data.pagination)}` },
63
+ ],
64
+ };
80
65
  }
81
66
  catch (error) {
82
67
  console.error('Error listing users:', error.message);
@@ -1,29 +1,23 @@
1
1
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
- import { outlineClient } from '../client.js';
3
- import { registerTool } from '../utils/listTools.js';
2
+ import { outlineClient } from '../outline/outlineClient.js';
3
+ import toolRegistry from '../utils/toolRegistry.js';
4
+ import z from 'zod';
4
5
  // Register this tool
5
- registerTool({
6
+ toolRegistry.register('move_document', {
6
7
  name: 'move_document',
7
8
  description: 'Move a document to a different collection or parent document',
8
9
  inputSchema: {
9
- properties: {
10
- id: {
11
- type: 'string',
12
- description: 'ID of the document to move',
13
- },
14
- collectionId: {
15
- type: 'string',
16
- description: 'ID of the collection to move the document to (optional)',
17
- },
18
- parentDocumentId: {
19
- type: 'string',
20
- description: 'ID of the parent document to move under (optional)',
21
- },
22
- },
23
- required: ['id'],
24
- type: 'object',
10
+ id: z.string().describe('ID of the document to move'),
11
+ collectionId: z
12
+ .string()
13
+ .describe('ID of the collection to move the document to (optional)')
14
+ .optional(),
15
+ parentDocumentId: z
16
+ .string()
17
+ .describe('ID of the parent document to move under (optional)')
18
+ .optional(),
25
19
  },
26
- handler: async function handleMoveDocument(args) {
20
+ async callback(args) {
27
21
  try {
28
22
  const payload = {
29
23
  id: args.id,
@@ -35,7 +29,7 @@ registerTool({
35
29
  payload.parentDocumentId = args.parentDocumentId;
36
30
  }
37
31
  const response = await outlineClient.post('/documents.move', payload);
38
- return response.data.data;
32
+ return { content: [{ type: 'text', text: JSON.stringify(response.data.data) }] };
39
33
  }
40
34
  catch (error) {
41
35
  console.error('Error moving document:', error.message);
@@ -1,29 +1,17 @@
1
1
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
- import { outlineClient } from '../client.js';
3
- import { registerTool } from '../utils/listTools.js';
2
+ import { outlineClient } from '../outline/outlineClient.js';
3
+ import toolRegistry from '../utils/toolRegistry.js';
4
+ import z from 'zod';
4
5
  // Register this tool
5
- registerTool({
6
+ toolRegistry.register('search_documents', {
6
7
  name: 'search_documents',
7
8
  description: 'Search for documents in the Outline workspace',
8
9
  inputSchema: {
9
- properties: {
10
- query: {
11
- type: 'string',
12
- description: 'Search query to filter documents',
13
- },
14
- collectionId: {
15
- type: 'string',
16
- description: 'Filter by collection ID (optional)',
17
- },
18
- limit: {
19
- type: 'number',
20
- description: 'Maximum number of documents to return (optional)',
21
- },
22
- },
23
- required: ['query'],
24
- type: 'object',
10
+ query: z.string().describe('Search query to filter documents'),
11
+ collectionId: z.string().describe('Filter by collection ID (optional)').optional(),
12
+ limit: z.number().describe('Maximum number of documents to return (optional)').optional(),
25
13
  },
26
- handler: async function handleSearchDocuments(args) {
14
+ async callback(args) {
27
15
  try {
28
16
  const payload = {
29
17
  query: args.query,
@@ -35,7 +23,12 @@ registerTool({
35
23
  payload.limit = args.limit;
36
24
  }
37
25
  const response = await outlineClient.post('/documents.search', payload);
38
- return response.data.data;
26
+ return {
27
+ content: [
28
+ { type: 'text', text: `documents: ${JSON.stringify(response.data.data)}` },
29
+ { type: 'text', text: `pagination: ${JSON.stringify(response.data.pagination)}` },
30
+ ],
31
+ };
39
32
  }
40
33
  catch (error) {
41
34
  console.error('Error searching documents:', error.message);
@@ -1,37 +1,22 @@
1
1
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
- import { outlineClient } from '../client.js';
3
- import { registerTool } from '../utils/listTools.js';
2
+ import { outlineClient } from '../outline/outlineClient.js';
3
+ import toolRegistry from '../utils/toolRegistry.js';
4
+ import z from 'zod';
4
5
  // Register this tool
5
- registerTool({
6
+ toolRegistry.register('update_collection', {
6
7
  name: 'update_collection',
7
8
  description: 'Update an existing collection',
8
9
  inputSchema: {
9
- properties: {
10
- id: {
11
- type: 'string',
12
- description: 'ID of the collection to update',
13
- },
14
- name: {
15
- type: 'string',
16
- description: 'New name for the collection (optional)',
17
- },
18
- description: {
19
- type: 'string',
20
- description: 'New description for the collection (optional)',
21
- },
22
- permission: {
23
- type: 'string',
24
- description: 'New permission setting for the collection (optional)',
25
- },
26
- color: {
27
- type: 'string',
28
- description: 'New color for the collection (optional)',
29
- },
30
- },
31
- required: ['id'],
32
- type: 'object',
10
+ id: z.string().describe('ID of the collection to update'),
11
+ name: z.string().describe('New name for the collection (optional)').optional(),
12
+ description: z.string().describe('New description for the collection (optional)').optional(),
13
+ permission: z
14
+ .string()
15
+ .describe('New permission setting for the collection (optional)')
16
+ .optional(),
17
+ color: z.string().describe('New color for the collection (optional)').optional(),
33
18
  },
34
- handler: async function handleUpdateCollection(args) {
19
+ async callback(args) {
35
20
  try {
36
21
  const payload = {
37
22
  id: args.id,
@@ -49,7 +34,7 @@ registerTool({
49
34
  payload.color = args.color;
50
35
  }
51
36
  const response = await outlineClient.post('/collections.update', payload);
52
- return response.data.data;
37
+ return { content: [{ type: 'text', text: JSON.stringify(response.data.data) }] };
53
38
  }
54
39
  catch (error) {
55
40
  console.error('Error updating collection:', error.message);
@@ -1,29 +1,17 @@
1
1
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
- import { outlineClient } from '../client.js';
3
- import { registerTool } from '../utils/listTools.js';
2
+ import { outlineClient } from '../outline/outlineClient.js';
3
+ import toolRegistry from '../utils/toolRegistry.js';
4
+ import z from 'zod';
4
5
  // Register this tool
5
- registerTool({
6
+ toolRegistry.register('update_comment', {
6
7
  name: 'update_comment',
7
8
  description: 'Update an existing comment',
8
9
  inputSchema: {
9
- properties: {
10
- id: {
11
- type: 'string',
12
- description: 'ID of the comment to update',
13
- },
14
- text: {
15
- type: 'string',
16
- description: 'New content for the comment in markdown format',
17
- },
18
- data: {
19
- type: 'object',
20
- description: 'Additional data for the comment (optional)',
21
- },
22
- },
23
- required: ['id'],
24
- type: 'object',
10
+ id: z.string().describe('ID of the comment to update'),
11
+ text: z.string().describe('New content for the comment in markdown format').optional(),
12
+ data: z.object({}).describe('Additional data for the comment (optional)').optional(),
25
13
  },
26
- handler: async function handleUpdateComment(args) {
14
+ async callback(args) {
27
15
  try {
28
16
  const payload = {
29
17
  id: args.id,
@@ -35,7 +23,7 @@ registerTool({
35
23
  payload.data = args.data;
36
24
  }
37
25
  const response = await outlineClient.post('/comments.update', payload);
38
- return response.data.data;
26
+ return { content: [{ type: 'text', text: JSON.stringify(response.data.data) }] };
39
27
  }
40
28
  catch (error) {
41
29
  console.error('Error updating comment:', error.message);
@@ -1,37 +1,19 @@
1
1
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
- import { outlineClient } from '../client.js';
3
- import { registerTool } from '../utils/listTools.js';
2
+ import { outlineClient } from '../outline/outlineClient.js';
3
+ import toolRegistry from '../utils/toolRegistry.js';
4
+ import z from 'zod';
4
5
  // Register this tool
5
- registerTool({
6
+ toolRegistry.register('update_document', {
6
7
  name: 'update_document',
7
8
  description: 'Update an existing document',
8
9
  inputSchema: {
9
- properties: {
10
- documentId: {
11
- type: 'string',
12
- description: 'ID of the document to update',
13
- },
14
- title: {
15
- type: 'string',
16
- description: 'New title for the document',
17
- },
18
- text: {
19
- type: 'string',
20
- description: 'New content for the document in markdown format',
21
- },
22
- publish: {
23
- type: 'boolean',
24
- description: 'Whether to publish the document',
25
- },
26
- done: {
27
- type: 'boolean',
28
- description: 'Whether the document is marked as done',
29
- },
30
- },
31
- required: ['documentId'],
32
- type: 'object',
10
+ documentId: z.string().describe('ID of the document to update'),
11
+ title: z.string().describe('New title for the document').optional(),
12
+ text: z.string().describe('New content for the document in markdown format').optional(),
13
+ publish: z.boolean().describe('Whether to publish the document').optional(),
14
+ done: z.boolean().describe('Whether the document is marked as done').optional(),
33
15
  },
34
- handler: async function handleUpdateDocument(args) {
16
+ async callback(args) {
35
17
  try {
36
18
  const payload = {
37
19
  id: args.documentId,
@@ -49,7 +31,7 @@ registerTool({
49
31
  payload.done = args.done;
50
32
  }
51
33
  const response = await outlineClient.post('/documents.update', payload);
52
- return response.data.data;
34
+ return { content: [{ type: 'text', text: JSON.stringify(response.data.data) }] };
53
35
  }
54
36
  catch (error) {
55
37
  console.error('Error updating document:', error.message);
@@ -0,0 +1,16 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { loadAllTools } from './loadAllTools.js';
3
+ // Helper to create a new MCP server instance with all tools registered
4
+ export async function getMcpServer() {
5
+ const server = new McpServer({
6
+ name: 'outline-mcp',
7
+ version: process.env.npm_package_version || 'unknown',
8
+ description: 'Outline Model Context Protocol server',
9
+ });
10
+ await loadAllTools(tool => server.registerTool(tool.name, {
11
+ description: tool.description,
12
+ inputSchema: tool.inputSchema,
13
+ outputSchema: tool.outputSchema,
14
+ }, tool.callback));
15
+ return server;
16
+ }
@@ -1,23 +1,26 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
- import { getToolDefinitions } from './listTools.js';
4
+ import toolRegistry from './toolRegistry.js';
5
5
  /**
6
6
  * Dynamically imports all tool files from the tools directory
7
7
  */
8
- export async function registerTools() {
8
+ export async function loadAllTools(onToolLoaded) {
9
9
  // Get the directory path
10
10
  const __filename = fileURLToPath(import.meta.url);
11
11
  const __dirname = path.dirname(__filename);
12
12
  const toolsDir = path.join(__dirname, '..', 'tools');
13
- // Import all tool files
13
+ // List tool files
14
14
  const toolFiles = fs
15
15
  .readdirSync(toolsDir)
16
16
  .filter(file => file.endsWith('.ts') || file.endsWith('.js'));
17
+ // Import all tool files, causing them to be restered via `registerTool`
17
18
  for (const file of toolFiles) {
18
19
  const resolved = path.resolve(toolsDir, file);
19
20
  await import(resolved);
20
21
  }
21
- // return tool definitions
22
- return getToolDefinitions();
22
+ // configure McpServer with all definitions
23
+ for (const tool of toolRegistry.tools) {
24
+ await onToolLoaded?.(tool);
25
+ }
23
26
  }
@@ -0,0 +1,27 @@
1
+ class ToolRegistry {
2
+ constructor() {
3
+ this.registry = new Map();
4
+ this.registry = new Map();
5
+ }
6
+ get tools() {
7
+ return Array.from(this.registry.values());
8
+ }
9
+ has(name) {
10
+ return this.registry.has(name);
11
+ }
12
+ get(name) {
13
+ return this.registry.get(name);
14
+ }
15
+ /**
16
+ * Registers a tool with the global registry
17
+ */
18
+ register(name, definition) {
19
+ if (this.has(name)) {
20
+ throw new Error(`Attempted to register duplicate tool: "${name}"`);
21
+ }
22
+ this.registry.set(name, definition);
23
+ }
24
+ }
25
+ // We'll collect all tool definitions here, keyed by name
26
+ const toolRegistry = new ToolRegistry();
27
+ export default toolRegistry;