@utaba/ucm-mcp-server 4.2.2 → 4.3.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 (27) hide show
  1. package/dist/clients/UcmApiClient.d.ts +49 -4
  2. package/dist/clients/UcmApiClient.js +71 -4
  3. package/dist/clients/UcmLocalApiClient.d.ts +165 -0
  4. package/dist/clients/UcmLocalApiClient.js +460 -0
  5. package/dist/index.js +3 -4
  6. package/dist/server/McpConfig.d.ts +0 -2
  7. package/dist/server/McpConfig.js +1 -5
  8. package/dist/server/McpServer.js +2 -7
  9. package/dist/server/ToolRegistry.js +14 -0
  10. package/dist/tools/repository/CreateRepositoryTool.js +2 -12
  11. package/dist/tools/repository/UpdateRepositoryTool.js +4 -15
  12. package/dist/tools/sharepoint/SharePointListConnectionsTool.d.ts +19 -0
  13. package/dist/tools/sharepoint/SharePointListConnectionsTool.js +84 -0
  14. package/dist/tools/sharepoint/SharePointListFoldersTool.d.ts +21 -0
  15. package/dist/tools/sharepoint/SharePointListFoldersTool.js +140 -0
  16. package/dist/tools/sharepoint/SharePointReadFileTool.d.ts +22 -0
  17. package/dist/tools/sharepoint/SharePointReadFileTool.js +145 -0
  18. package/dist/tools/sharepoint/SharePointReadRelatedFileTool.d.ts +18 -0
  19. package/dist/tools/sharepoint/SharePointReadRelatedFileTool.js +102 -0
  20. package/dist/tools/sharepoint/SharePointSearchTool.d.ts +22 -0
  21. package/dist/tools/sharepoint/SharePointSearchTool.js +134 -0
  22. package/dist/tools/sharepoint/SharePointSignOutTool.d.ts +22 -0
  23. package/dist/tools/sharepoint/SharePointSignOutTool.js +108 -0
  24. package/dist/utils/SharePointErrorHandler.d.ts +28 -0
  25. package/dist/utils/SharePointErrorHandler.js +47 -0
  26. package/package.json +1 -1
  27. package/package.json.backup +1 -1
@@ -0,0 +1,460 @@
1
+ import axios from 'axios';
2
+ export class UcmLocalApiClient {
3
+ baseUrl;
4
+ authToken;
5
+ authorId;
6
+ organizationId;
7
+ client;
8
+ constructor(baseUrl, authToken, timeout = 600000, authorId, organizationId) {
9
+ this.baseUrl = baseUrl;
10
+ this.authToken = authToken;
11
+ this.authorId = authorId;
12
+ this.organizationId = organizationId;
13
+ this.client = axios.create({
14
+ baseURL: this.baseUrl,
15
+ timeout,
16
+ headers: {
17
+ 'Content-Type': 'application/json',
18
+ ...(authToken && { 'Authorization': `Bearer ${authToken}` })
19
+ }
20
+ });
21
+ this.setupInterceptors();
22
+ }
23
+ setupInterceptors() {
24
+ this.client.interceptors.response.use((response) => response, (error) => {
25
+ // For 4xx and 5xx errors, preserve the full error object so calling code
26
+ // can access error.response.data.details (e.g., for loginUrl in auth errors)
27
+ if (error.response && error.response.status >= 400) {
28
+ // Pass through the full axios error - don't wrap it
29
+ return Promise.reject(error);
30
+ }
31
+ // For other errors (network, timeout, etc.), preserve original error
32
+ return Promise.reject(error);
33
+ });
34
+ }
35
+ ensureClient() {
36
+ if (!this.client) {
37
+ throw new Error('UcmApiClient has been cleaned up and is no longer available');
38
+ }
39
+ return this.client;
40
+ }
41
+ async getAuthors() {
42
+ const client = this.ensureClient();
43
+ const response = await client.get('/api/v1/authors');
44
+ return response.data;
45
+ }
46
+ async getAuthor(authorId) {
47
+ // No individual author endpoint - get from authors list
48
+ const authors = await this.getAuthors();
49
+ return authors.find(author => author.id === authorId) || null;
50
+ }
51
+ buildApiPath(api, author, repository, category, subcategory, filename, version) {
52
+ let path = `/api/v1/${api}`;
53
+ if (author) {
54
+ path += `/${author}`;
55
+ }
56
+ else {
57
+ return path;
58
+ }
59
+ if (repository) {
60
+ path += `/${repository}`;
61
+ }
62
+ else {
63
+ return path;
64
+ }
65
+ if (category) {
66
+ path += `/${category}`;
67
+ }
68
+ else {
69
+ return path;
70
+ }
71
+ if (subcategory) {
72
+ path += `/${subcategory}`;
73
+ }
74
+ else {
75
+ return path;
76
+ }
77
+ if (filename) {
78
+ path += `/${filename}`;
79
+ }
80
+ else {
81
+ return path;
82
+ }
83
+ if (version) {
84
+ path += `@${version}`;
85
+ }
86
+ else {
87
+ return path;
88
+ }
89
+ return path;
90
+ }
91
+ async getArtifact(author, repository, category, subcategory, filename, version) {
92
+ // Default repository to 'main' for MVP
93
+ const repo = repository || 'main';
94
+ let metadataApiPath = this.buildApiPath('authors', author, repo, category, subcategory, filename, version);
95
+ // First get metadata from authors endpoint
96
+ const client = this.ensureClient();
97
+ const metadataResponse = await client.get(metadataApiPath);
98
+ const metadata = metadataResponse.data;
99
+ if (filename) {
100
+ let fileApiPath = this.buildApiPath('files', author, repo, category, subcategory, filename, version);
101
+ // Then get actual file content from files endpoint
102
+ let contentResponse = await client.get(fileApiPath, {
103
+ headers: { 'accept': metadataResponse.data }
104
+ });
105
+ return {
106
+ ...metadata.data,
107
+ content: contentResponse.data
108
+ };
109
+ }
110
+ // Combine metadata and content
111
+ return {
112
+ ...metadata.data,
113
+ };
114
+ }
115
+ async getLatestArtifact(author, repository, category, subcategory, filename) {
116
+ // Default repository to 'main' for MVP
117
+ const repo = repository || 'main';
118
+ let metadataApiPath = this.buildApiPath('authors', author, repo, category, subcategory, filename);
119
+ const client = this.ensureClient();
120
+ const response = await client.get(metadataApiPath);
121
+ return response.data;
122
+ }
123
+ async listArtifacts(author, repository, category, subcategory, offset, limit) {
124
+ const params = new URLSearchParams();
125
+ if (offset !== undefined)
126
+ params.append('offset', offset.toString());
127
+ if (limit !== undefined)
128
+ params.append('limit', limit.toString());
129
+ const queryString = params.toString();
130
+ // Build URL based on provided parameters for exploratory browsing
131
+ // Default repository to 'main' for MVP
132
+ const repo = repository;
133
+ let metadataApiPath = this.buildApiPath('authors', author, repo, category, subcategory);
134
+ metadataApiPath += queryString ? `?${queryString}` : '';
135
+ const client = this.ensureClient();
136
+ const response = await client.get(metadataApiPath);
137
+ return response.data; // response.data contains the full structured response with pagination
138
+ }
139
+ async searchArtifacts(filters = {}) {
140
+ const params = new URLSearchParams();
141
+ // Default repository to 'main' for MVP if not specified
142
+ if (!filters.repository) {
143
+ filters.repository = 'main';
144
+ }
145
+ Object.entries(filters).forEach(([key, value]) => {
146
+ if (value !== undefined)
147
+ params.append(key, value.toString());
148
+ });
149
+ const client = this.ensureClient();
150
+ const response = await client.get(`/api/v1/artifacts?${params}`);
151
+ return response.data;
152
+ }
153
+ async searchArtifactsByText(searchParams) {
154
+ const params = new URLSearchParams();
155
+ // Add all parameters to URL
156
+ Object.entries(searchParams).forEach(([key, value]) => {
157
+ if (value !== undefined)
158
+ params.append(key, value.toString());
159
+ });
160
+ const client = this.ensureClient();
161
+ const response = await client.get(`/api/v1/search/artifacts?${params}`);
162
+ return response.data;
163
+ }
164
+ async publishArtifact(author, repository, category, subcategory, data) {
165
+ // Default repository to 'main' for MVP
166
+ const repo = repository || 'main';
167
+ // Build query parameters from the data object
168
+ const params = new URLSearchParams();
169
+ if (data.queryParams) {
170
+ // New API structure with query params
171
+ Object.entries(data.queryParams).forEach(([key, value]) => {
172
+ // Skip if value is undefined, null, empty string, or string "null"/"undefined"
173
+ if (value !== undefined && value !== null && value !== '' && value !== 'null' && value !== 'undefined') {
174
+ // Handle arrays (e.g., tags) by JSON stringifying them
175
+ if (Array.isArray(value)) {
176
+ params.append(key, JSON.stringify(value));
177
+ }
178
+ else {
179
+ params.append(key, String(value));
180
+ }
181
+ }
182
+ });
183
+ // Send raw content as text/plain body
184
+ const client = this.ensureClient();
185
+ const response = await client.post(`/api/v1/authors/${author}/${repo}/${category}/${subcategory}?${params.toString()}`, data.content, {
186
+ headers: {
187
+ 'Content-Type': 'text/plain'
188
+ }
189
+ });
190
+ return response.data;
191
+ }
192
+ else {
193
+ // Legacy API structure (for backward compatibility)
194
+ const client = this.ensureClient();
195
+ const response = await client.post(`/api/v1/authors/${author}/${repo}/${category}/${subcategory}`, data);
196
+ return response.data;
197
+ }
198
+ }
199
+ async updateArtifact(author, repository, category, subcategory, filename, version, data) {
200
+ // Default repository to 'main' for MVP
201
+ const repo = repository || 'main';
202
+ // Build query parameters from the data object
203
+ const params = new URLSearchParams();
204
+ if (data.queryParams) {
205
+ // New API structure with query params
206
+ Object.entries(data.queryParams).forEach(([key, value]) => {
207
+ // Skip if value is undefined, null, empty string, or string "null"/"undefined"
208
+ if (value !== undefined && value !== null && value !== '' && value !== 'null' && value !== 'undefined') {
209
+ // Handle arrays (e.g., tags) by JSON stringifying them
210
+ if (Array.isArray(value)) {
211
+ params.append(key, JSON.stringify(value));
212
+ }
213
+ else {
214
+ params.append(key, String(value));
215
+ }
216
+ }
217
+ });
218
+ // Send raw content as text/plain body
219
+ const client = this.ensureClient();
220
+ const response = await client.put(`/api/v1/authors/${author}/${repo}/${category}/${subcategory}/${filename}@${version}?${params.toString()}`, data.content, {
221
+ headers: {
222
+ 'Content-Type': 'text/plain'
223
+ }
224
+ });
225
+ return response.data;
226
+ }
227
+ else {
228
+ // Legacy API structure (for backward compatibility)
229
+ const client = this.ensureClient();
230
+ const response = await client.put(`/api/v1/authors/${author}/${repo}/${category}/${subcategory}/${filename}@${version}`, data);
231
+ return response.data;
232
+ }
233
+ }
234
+ async deleteArtifact(author, repository, category, subcategory, filename, version) {
235
+ // Default repository to 'main' for MVP
236
+ const repo = repository || 'main';
237
+ const url = version
238
+ ? `/api/v1/authors/${author}/${repo}/${category}/${subcategory}/${filename}@${version}`
239
+ : `/api/v1/authors/${author}/${repo}/${category}/${subcategory}/${filename}`;
240
+ const client = this.ensureClient();
241
+ const response = await client.delete(url);
242
+ return response.data;
243
+ }
244
+ async getArtifactVersions(author, repository, category, subcategory, filename) {
245
+ // Default repository to 'main' for MVP
246
+ const repo = repository || 'main';
247
+ const client = this.ensureClient();
248
+ const response = await client.get(`/api/v1/authors/${author}/${repo}/${category}/${subcategory}/${filename}/versions`);
249
+ return response.data;
250
+ }
251
+ async getCategories() {
252
+ // Categories are derived from artifacts since there's no dedicated endpoint
253
+ // Return the standard UCM categories
254
+ return ['commands', 'services', 'patterns', 'implementations', 'contracts', 'guidance', 'project'];
255
+ }
256
+ async healthCheck() {
257
+ const client = this.ensureClient();
258
+ const response = await client.get('/api/v1/health');
259
+ return response.data;
260
+ }
261
+ async getQuickstart() {
262
+ const client = this.ensureClient();
263
+ const response = await client.get('/api/v1/quickstart', {
264
+ headers: { 'accept': 'text/markdown' }
265
+ });
266
+ return response.data;
267
+ }
268
+ async getAuthorIndex(author, repository) {
269
+ const client = this.ensureClient();
270
+ let url;
271
+ if (repository) {
272
+ // Repository-specific index (future use)
273
+ url = `/api/v1/authors/${author}/${repository}/index`;
274
+ }
275
+ else {
276
+ // Author-level index
277
+ url = `/api/v1/authors/${author}/index`;
278
+ }
279
+ const response = await client.get(url, {
280
+ headers: { 'accept': 'text/markdown' }
281
+ });
282
+ return response.data;
283
+ }
284
+ async getAuthorRecents(author) {
285
+ const client = this.ensureClient();
286
+ // Author-level recent activity (using new resources API structure)
287
+ const url = `/api/v1/resources/author/${author}/recents`;
288
+ const response = await client.get(url, {
289
+ headers: { 'accept': 'text/markdown' }
290
+ });
291
+ return response.data;
292
+ }
293
+ /**
294
+ * Cleanup method to properly dispose of HTTP client resources
295
+ * This helps prevent memory leaks from accumulated AbortSignal listeners
296
+ */
297
+ cleanup() {
298
+ if (this.client) {
299
+ // Clear interceptors to remove event listeners
300
+ this.client.interceptors.request.clear();
301
+ this.client.interceptors.response.clear();
302
+ // Set client to null to let GC handle cleanup
303
+ this.client = null;
304
+ }
305
+ }
306
+ /**
307
+ * Check if the client is still available for use
308
+ */
309
+ isAvailable() {
310
+ return this.client !== null;
311
+ }
312
+ // Repository Management Methods
313
+ async createRepository(author, data) {
314
+ const client = this.ensureClient();
315
+ const response = await client.post(`/api/v1/authors/${author}`, data);
316
+ return response.data;
317
+ }
318
+ async getRepository(author, repository) {
319
+ const client = this.ensureClient();
320
+ const response = await client.get(`/api/v1/authors/${author}/${repository}`);
321
+ return response.data;
322
+ }
323
+ async updateRepository(author, repository, data) {
324
+ const client = this.ensureClient();
325
+ const response = await client.put(`/api/v1/authors/${author}/${repository}`, data);
326
+ return response.data;
327
+ }
328
+ async deleteRepository(author, repository) {
329
+ const client = this.ensureClient();
330
+ const response = await client.delete(`/api/v1/authors/${author}/${repository}`);
331
+ return response.data;
332
+ }
333
+ async listRepositories(author, offset, limit) {
334
+ const params = new URLSearchParams();
335
+ if (offset !== undefined)
336
+ params.append('offset', offset.toString());
337
+ if (limit !== undefined)
338
+ params.append('limit', limit.toString());
339
+ const queryString = params.toString();
340
+ const url = `/api/v1/authors/${author}${queryString ? `?${queryString}` : ''}`;
341
+ const client = this.ensureClient();
342
+ const response = await client.get(url);
343
+ return response.data;
344
+ }
345
+ async submitFeedback(data) {
346
+ const client = this.ensureClient();
347
+ const response = await client.post('/api/v1/feedback', data);
348
+ return response.data;
349
+ }
350
+ async editArtifactMetadata(author, repository, category, subcategory, filename, data) {
351
+ const client = this.ensureClient();
352
+ const url = this.buildApiPath('authors', author, repository, category, subcategory, filename) + '/edit';
353
+ // Transform updateTags from comma-separated string to array for API
354
+ const requestData = { ...data };
355
+ if (data.updateTags && typeof data.updateTags === 'string') {
356
+ // Parse comma-separated tags into array, trim whitespace, filter empty strings
357
+ requestData.updateTags = data.updateTags
358
+ .split(',')
359
+ .map(tag => tag.trim())
360
+ .filter(tag => tag.length > 0);
361
+ }
362
+ const response = await client.post(url, requestData);
363
+ return response.data;
364
+ }
365
+ // SharePoint API methods
366
+ /**
367
+ * List External Connections including SharePoint
368
+ * Returns markdown with a table of connections available
369
+ */
370
+ async listExternalConnections(params) {
371
+ const client = this.ensureClient();
372
+ const url = `/api/v1/connections`;
373
+ const response = await client.get(url, { params });
374
+ return response.data;
375
+ }
376
+ /**
377
+ * Search SharePoint documents using Microsoft Search API
378
+ * Uses V1 API - organizationId is auto-detected from auth token
379
+ */
380
+ async sharePointSearch(connectionId, params) {
381
+ const client = this.ensureClient();
382
+ const response = await client.post(`/api/v1/connections/sharepoint/search`, {
383
+ connectionId,
384
+ ...params
385
+ });
386
+ return response.data;
387
+ }
388
+ /**
389
+ * List folders and files in SharePoint with pagination
390
+ * Uses V1 API - organizationId is auto-detected from auth token
391
+ */
392
+ async sharePointListFolders(connectionId, params) {
393
+ const client = this.ensureClient();
394
+ const response = await client.post(`/api/v1/connections/sharepoint/folders`, {
395
+ connectionId,
396
+ ...params
397
+ });
398
+ return response.data;
399
+ }
400
+ /**
401
+ * Read SharePoint file content with support for both fileUrl and legacy fileId
402
+ * Uses V1 API - organizationId is auto-detected from auth token
403
+ */
404
+ async sharePointReadFile(connectionId, params) {
405
+ const client = this.ensureClient();
406
+ const response = await client.post(`/api/v1/connections/sharepoint/read`, {
407
+ connectionId,
408
+ ...params
409
+ });
410
+ return response.data;
411
+ }
412
+ /**
413
+ * Read SharePoint related file (image/embedded file extracted from document)
414
+ * Uses V1 API - organizationId is auto-detected from auth token
415
+ * Accepts full URI from markdown (with or without protocol prefix)
416
+ * Returns complete file content (no pagination support)
417
+ */
418
+ async sharePointReadRelatedFile(uri) {
419
+ const client = this.ensureClient();
420
+ const response = await client.post(`/api/v1/connections/sharepoint/related-file`, { uri }, // Pass URI in JSON body
421
+ {
422
+ responseType: 'arraybuffer' // Binary data
423
+ });
424
+ // Convert binary response to base64 for MCP transport
425
+ const base64Content = Buffer.from(response.data).toString('base64');
426
+ const contentType = response.headers['content-type'] || 'application/octet-stream';
427
+ const contentLength = parseInt(response.headers['content-length'] || '0', 10);
428
+ // Extract fileName from URI for response metadata
429
+ const fileName = this.extractFileNameFromUri(uri);
430
+ return {
431
+ fileName,
432
+ mimeType: contentType,
433
+ size: contentLength,
434
+ content: base64Content
435
+ };
436
+ }
437
+ /**
438
+ * Extract fileName from SharePoint related file URI
439
+ * Helper method for metadata purposes
440
+ */
441
+ extractFileNameFromUri(uri) {
442
+ // Strip protocol prefix if present
443
+ const cleaned = uri.replace(/^ucm_sharepoint_read_relatedfile:\/\//, '');
444
+ // Extract path portion before query string
445
+ const pathPart = cleaned.split('?')[0];
446
+ // URL-decode the fileName
447
+ return decodeURIComponent(pathPart);
448
+ }
449
+ /**
450
+ * Revoke SharePoint OnBehalfOf authorization for a connection
451
+ * Deletes user's access tokens from Key Vault and database
452
+ * Uses V1 API - organizationId is auto-detected from auth token
453
+ */
454
+ async sharePointSignOut(connectionId) {
455
+ const client = this.ensureClient();
456
+ const response = await client.post(`/api/v1/connections/sharepoint/signout`, { connectionId });
457
+ return response.data;
458
+ }
459
+ }
460
+ //# sourceMappingURL=UcmLocalApiClient.js.map
package/dist/index.js CHANGED
@@ -12,12 +12,12 @@ async function main() {
12
12
  program
13
13
  .name('ucm-mcp-server')
14
14
  .description('Universal Context Manager - Read the ucm_connect tool first to avoid mistakes')
15
- .version('4.1.0')
15
+ .version('1.0.5')
16
16
  .option('-u, --ucm-url <url>', 'UCM API base URL (defaults to https://ucm.utaba.ai)')
17
17
  .option('-p, --port <port>', 'Server port', '3001')
18
18
  .option('--log-level <level>', 'Log level', 'ERROR')
19
19
  .option('--auth-token <token>', 'Authentication token for UCM API format is {auhtorid}:{apikey}. Get one from https://ucm.utaba.ai')
20
- .option('--ca-cert <path>', 'Path to CA certificate file (PEM format) for self-signed certificates')
20
+ .option('--trusted-authors <authors>', 'Comma-separated list of trusted authors (e.g., "utaba,{anotherauthor}")', "utaba")
21
21
  .parse();
22
22
  const options = program.opts();
23
23
  const config = new McpConfig({
@@ -25,8 +25,7 @@ async function main() {
25
25
  port: parseInt(options.port),
26
26
  logLevel: options.logLevel,
27
27
  authToken: options.authToken,
28
- trustedAuthors: options.trustedAuthors ? options.trustedAuthors.split(',').map((a) => a.trim()) : ["utaba"],
29
- caCertPath: options.caCert
28
+ trustedAuthors: options.trustedAuthors ? options.trustedAuthors.split(',').map((a) => a.trim()) : ["utaba"]
30
29
  });
31
30
  // Set the log level in the factory before creating any loggers
32
31
  LoggerFactory.setLogLevel(config.logLevel);
@@ -6,7 +6,6 @@ export interface McpServerConfig {
6
6
  logLevel: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';
7
7
  requestTimeout: number;
8
8
  trustedAuthors: string[];
9
- caCertPath?: string;
10
9
  }
11
10
  export declare class McpConfig {
12
11
  private config;
@@ -22,7 +21,6 @@ export declare class McpConfig {
22
21
  get requestTimeout(): number;
23
22
  get trustedAuthors(): string[];
24
23
  get authorId(): string | undefined;
25
- get caCertPath(): string | undefined;
26
24
  getFullConfig(): McpServerConfig;
27
25
  }
28
26
  //# sourceMappingURL=McpConfig.d.ts.map
@@ -12,8 +12,7 @@ export class McpConfig {
12
12
  authorId: options.authorId || authorId,
13
13
  logLevel: options.logLevel || this.getEnvVar('MCP_LOG_LEVEL', 'ERROR'),
14
14
  requestTimeout: options.requestTimeout || parseInt(this.getEnvVar('MCP_REQUEST_TIMEOUT', '30000')),
15
- trustedAuthors: options.trustedAuthors || this.parseTrustedAuthors(this.getEnvVar('MCP_TRUSTED_AUTHORS', '')),
16
- caCertPath: options.caCertPath
15
+ trustedAuthors: options.trustedAuthors || this.parseTrustedAuthors(this.getEnvVar('MCP_TRUSTED_AUTHORS', ''))
17
16
  };
18
17
  this.validateConfig();
19
18
  }
@@ -83,9 +82,6 @@ export class McpConfig {
83
82
  get authorId() {
84
83
  return this.config.authorId;
85
84
  }
86
- get caCertPath() {
87
- return this.config.caCertPath;
88
- }
89
85
  // Get full configuration object (for testing and debugging)
90
86
  getFullConfig() {
91
87
  return { ...this.config };
@@ -2,9 +2,8 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
4
4
  import { McpHandler } from './McpHandler.js';
5
- import { UcmLocalApiClient } from '../clients/UcmApiClient.js';
5
+ import { UcmLocalApiClient } from '../clients/UcmLocalApiClient.js';
6
6
  import { ToolRegistry } from './ToolRegistry.js';
7
- import { HttpsAgentFactory } from '../utils/HttpsAgentFactory.js';
8
7
  export class McpServer {
9
8
  config;
10
9
  logger;
@@ -15,11 +14,7 @@ export class McpServer {
15
14
  constructor(config, logger) {
16
15
  this.config = config;
17
16
  this.logger = logger;
18
- // Create HTTPS agent with custom CA certificate if provided
19
- const httpsAgent = config.caCertPath
20
- ? HttpsAgentFactory.createAgent({ caCertPath: config.caCertPath }, logger)
21
- : undefined;
22
- this.ucmClient = new UcmLocalApiClient(config.ucmApiUrl, config.authToken, config.requestTimeout, config.authorId, httpsAgent);
17
+ this.ucmClient = new UcmLocalApiClient(config.ucmApiUrl, config.authToken, config.requestTimeout, config.authorId);
23
18
  this.toolRegistry = new ToolRegistry(this.ucmClient, this.logger, config.trustedAuthors, config.authorId, config.ucmApiUrl);
24
19
  this.handler = new McpHandler(this.toolRegistry, this.logger, config.authorId);
25
20
  this.server = new Server({
@@ -21,6 +21,13 @@ import { CreateRepositoryTool } from '../tools/repository/CreateRepositoryTool.j
21
21
  import { GetRepositoryTool } from '../tools/repository/GetRepositoryTool.js';
22
22
  import { UpdateRepositoryTool } from '../tools/repository/UpdateRepositoryTool.js';
23
23
  import { DeleteRepositoryGuidanceTool } from '../tools/repository/DeleteRepositoryGuidanceTool.js';
24
+ // Import SharePoint tools
25
+ import { SharePointListConnectionsTool as ListConnectionsTool } from '../tools/sharepoint/SharePointListConnectionsTool.js';
26
+ import { SharePointSearchTool } from '../tools/sharepoint/SharePointSearchTool.js';
27
+ import { SharePointListFoldersTool } from '../tools/sharepoint/SharePointListFoldersTool.js';
28
+ import { SharePointReadFileTool } from '../tools/sharepoint/SharePointReadFileTool.js';
29
+ import { SharePointReadRelatedFileTool } from '../tools/sharepoint/SharePointReadRelatedFileTool.js';
30
+ import { SharePointSignOutTool } from '../tools/sharepoint/SharePointSignOutTool.js';
24
31
  export class ToolRegistry {
25
32
  ucmClient;
26
33
  logger;
@@ -59,6 +66,13 @@ export class ToolRegistry {
59
66
  this.registerTool(new GetRepositoryTool(this.ucmClient, this.logger, this.authorId));
60
67
  this.registerTool(new UpdateRepositoryTool(this.ucmClient, this.logger, this.authorId));
61
68
  this.registerTool(new DeleteRepositoryGuidanceTool(this.ucmClient, this.logger, this.authorId, this.baseUrl));
69
+ // Register SharePoint tools
70
+ this.registerTool(new ListConnectionsTool(this.ucmClient, this.logger, this.authorId));
71
+ this.registerTool(new SharePointSearchTool(this.ucmClient, this.logger, this.authorId));
72
+ this.registerTool(new SharePointListFoldersTool(this.ucmClient, this.logger, this.authorId));
73
+ this.registerTool(new SharePointReadFileTool(this.ucmClient, this.logger, this.authorId));
74
+ this.registerTool(new SharePointReadRelatedFileTool(this.ucmClient, this.logger, this.authorId));
75
+ this.registerTool(new SharePointSignOutTool(this.ucmClient, this.logger, this.authorId));
62
76
  this.logger.info('ToolRegistry', `Registered ${this.tools.size} MCP tools`);
63
77
  }
64
78
  registerTool(tool) {
@@ -8,7 +8,7 @@ export class CreateRepositoryTool extends BaseToolController {
8
8
  return 'ucm_create_repository';
9
9
  }
10
10
  get description() {
11
- const authorInfo = this.publishingAuthorId ? ` Your author id is '${this.publishingAuthorId}'.` : '';
11
+ const authorInfo = this.publishingAuthorId ? ` Your author default author id is '${this.publishingAuthorId}'.` : '';
12
12
  return `Create a new repository for a specific author. Repository names must be unique within the author namespace and follow UCM naming conventions. (3-200 characters, start with letter, alphanumeric and hyphens only, no consecutive hyphens).${authorInfo}`;
13
13
  }
14
14
  get inputSchema() {
@@ -37,11 +37,6 @@ export class CreateRepositoryTool extends BaseToolController {
37
37
  type: 'string',
38
38
  description: 'Repository description (optional)',
39
39
  maxLength: 1000
40
- },
41
- isPublic: {
42
- type: 'boolean',
43
- description: 'Whether the repository is public (defaults to false, public repositories not yet supported)',
44
- default: false
45
40
  }
46
41
  },
47
42
  required: ['author', 'repositoryName'],
@@ -74,10 +69,6 @@ export class CreateRepositoryTool extends BaseToolController {
74
69
  if (params.description && params.description.length > 1000) {
75
70
  throw new McpError(McpErrorCode.InvalidParams, 'Description cannot exceed 1,000 characters');
76
71
  }
77
- // Validate isPublic - public repositories are not yet supported
78
- if (params.isPublic === true) {
79
- throw new McpError(McpErrorCode.InvalidParams, 'Public repositories are not yet supported. Set isPublic to false or omit the parameter.');
80
- }
81
72
  }
82
73
  async handleExecute(params) {
83
74
  this.logger.info('CreateRepositoryTool', `Creating repository ${params.repositoryName} for author ${params.author}`);
@@ -85,8 +76,7 @@ export class CreateRepositoryTool extends BaseToolController {
85
76
  const repositoryData = {
86
77
  repositoryName: params.repositoryName,
87
78
  displayName: params.displayName,
88
- description: params.description,
89
- isPublic: params.isPublic || false
79
+ description: params.description
90
80
  };
91
81
  const result = await this.ucmClient.createRepository(params.author, repositoryData);
92
82
  this.logger.info('CreateRepositoryTool', `Repository created successfully: ${params.repositoryName}`);
@@ -8,8 +8,8 @@ export class UpdateRepositoryTool extends BaseToolController {
8
8
  return 'ucm_update_repository';
9
9
  }
10
10
  get description() {
11
- const authorInfo = this.publishingAuthorId ? ` Your author id is '${this.publishingAuthorId}'.` : '';
12
- return `Update repository metadata including display name, description, and visibility settings. Note: Repository names cannot be changed after creation as they serve as permanent identifiers in the namespace path.${authorInfo}`;
11
+ const authorInfo = this.publishingAuthorId ? ` Your default author id is '${this.publishingAuthorId}'.` : '';
12
+ return `Update repository metadata including display name and description.${authorInfo}`;
13
13
  }
14
14
  get inputSchema() {
15
15
  return {
@@ -36,10 +36,6 @@ export class UpdateRepositoryTool extends BaseToolController {
36
36
  type: 'string',
37
37
  description: 'New repository description (optional)',
38
38
  maxLength: 1000
39
- },
40
- isPublic: {
41
- type: 'boolean',
42
- description: 'Whether the repository should be public (currently not supported, must be false)'
43
39
  }
44
40
  },
45
41
  required: ['author', 'repository'],
@@ -55,10 +51,10 @@ export class UpdateRepositoryTool extends BaseToolController {
55
51
  throw new McpError(McpErrorCode.InvalidParams, 'Repository name is required and cannot be empty');
56
52
  }
57
53
  // Validate at least one field to update is provided
58
- const updateFields = ['displayName', 'description', 'isPublic'];
54
+ const updateFields = ['displayName', 'description'];
59
55
  const hasUpdateField = updateFields.some(field => params[field] !== undefined);
60
56
  if (!hasUpdateField) {
61
- throw new McpError(McpErrorCode.InvalidParams, 'At least one field to update must be provided (displayName, description, or isPublic)');
57
+ throw new McpError(McpErrorCode.InvalidParams, 'At least one field to update must be provided (displayName or description)');
62
58
  }
63
59
  // Validate displayName length if provided
64
60
  if (params.displayName !== undefined && params.displayName.length > 255) {
@@ -68,10 +64,6 @@ export class UpdateRepositoryTool extends BaseToolController {
68
64
  if (params.description !== undefined && params.description.length > 1000) {
69
65
  throw new McpError(McpErrorCode.InvalidParams, 'Description cannot exceed 1,000 characters');
70
66
  }
71
- // Validate isPublic - public repositories are not yet supported
72
- if (params.isPublic === true) {
73
- throw new McpError(McpErrorCode.InvalidParams, 'Public repositories are not yet supported. Set isPublic to false or omit the parameter.');
74
- }
75
67
  }
76
68
  async handleExecute(params) {
77
69
  this.logger.info('UpdateRepositoryTool', `Updating repository ${params.repository} for author ${params.author}`);
@@ -84,9 +76,6 @@ export class UpdateRepositoryTool extends BaseToolController {
84
76
  if (params.description !== undefined) {
85
77
  updateData.description = params.description;
86
78
  }
87
- if (params.isPublic !== undefined) {
88
- updateData.isPublic = params.isPublic;
89
- }
90
79
  const result = await this.ucmClient.updateRepository(params.author, params.repository, updateData);
91
80
  this.logger.info('UpdateRepositoryTool', `Repository updated successfully: ${params.repository}`);
92
81
  return {