@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.
- package/dist/clients/UcmApiClient.d.ts +49 -4
- package/dist/clients/UcmApiClient.js +71 -4
- package/dist/clients/UcmLocalApiClient.d.ts +165 -0
- package/dist/clients/UcmLocalApiClient.js +460 -0
- package/dist/index.js +3 -4
- package/dist/server/McpConfig.d.ts +0 -2
- package/dist/server/McpConfig.js +1 -5
- package/dist/server/McpServer.js +2 -7
- package/dist/server/ToolRegistry.js +14 -0
- package/dist/tools/repository/CreateRepositoryTool.js +2 -12
- package/dist/tools/repository/UpdateRepositoryTool.js +4 -15
- package/dist/tools/sharepoint/SharePointListConnectionsTool.d.ts +19 -0
- package/dist/tools/sharepoint/SharePointListConnectionsTool.js +84 -0
- package/dist/tools/sharepoint/SharePointListFoldersTool.d.ts +21 -0
- package/dist/tools/sharepoint/SharePointListFoldersTool.js +140 -0
- package/dist/tools/sharepoint/SharePointReadFileTool.d.ts +22 -0
- package/dist/tools/sharepoint/SharePointReadFileTool.js +145 -0
- package/dist/tools/sharepoint/SharePointReadRelatedFileTool.d.ts +18 -0
- package/dist/tools/sharepoint/SharePointReadRelatedFileTool.js +102 -0
- package/dist/tools/sharepoint/SharePointSearchTool.d.ts +22 -0
- package/dist/tools/sharepoint/SharePointSearchTool.js +134 -0
- package/dist/tools/sharepoint/SharePointSignOutTool.d.ts +22 -0
- package/dist/tools/sharepoint/SharePointSignOutTool.js +108 -0
- package/dist/utils/SharePointErrorHandler.d.ts +28 -0
- package/dist/utils/SharePointErrorHandler.js +47 -0
- package/package.json +1 -1
- package/package.json.backup +1 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharePointSearchTool
|
|
3
|
+
* Search SharePoint documents with text query
|
|
4
|
+
*/
|
|
5
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
6
|
+
import { McpError, McpErrorCode } from '../../utils/McpErrorHandler.js';
|
|
7
|
+
import { SharePointErrorHandler } from '../../utils/SharePointErrorHandler.js';
|
|
8
|
+
export class SharePointSearchTool extends BaseToolController {
|
|
9
|
+
constructor(ucmClient, logger, publishingAuthorId) {
|
|
10
|
+
super(ucmClient, logger, publishingAuthorId);
|
|
11
|
+
}
|
|
12
|
+
get name() {
|
|
13
|
+
return 'ucm_sharepoint_search_documents';
|
|
14
|
+
}
|
|
15
|
+
get description() {
|
|
16
|
+
return 'Search SharePoint documents with text query using Microsoft Search API. Returns document metadata with snippets. Supports pagination and file type filtering via KQL.';
|
|
17
|
+
}
|
|
18
|
+
get inputSchema() {
|
|
19
|
+
return {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
connectionId: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'SharePoint connection ID to use for searching (get this from list_connections)',
|
|
25
|
+
minLength: 36,
|
|
26
|
+
maxLength: 36
|
|
27
|
+
},
|
|
28
|
+
query: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'Search query text (optional, defaults to * for all documents)',
|
|
31
|
+
maxLength: 500
|
|
32
|
+
},
|
|
33
|
+
limit: {
|
|
34
|
+
type: 'number',
|
|
35
|
+
description: 'Maximum number of results to return (default: 20, max: 100)',
|
|
36
|
+
minimum: 1,
|
|
37
|
+
maximum: 100
|
|
38
|
+
},
|
|
39
|
+
offset: {
|
|
40
|
+
type: 'number',
|
|
41
|
+
description: 'Number of results to skip for pagination (default: 0)',
|
|
42
|
+
minimum: 0
|
|
43
|
+
},
|
|
44
|
+
fileType: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
description: 'Filter by file extension (e.g., "docx", "pdf", "xlsx") - uses Microsoft Search KQL filtering',
|
|
47
|
+
maxLength: 10
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
required: ['connectionId'],
|
|
51
|
+
additionalProperties: false
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
validateParams(params) {
|
|
55
|
+
super.validateParams(params);
|
|
56
|
+
// Validate connectionId is UUID format
|
|
57
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
58
|
+
if (!uuidPattern.test(params.connectionId)) {
|
|
59
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Connection ID must be a valid UUID');
|
|
60
|
+
}
|
|
61
|
+
// Validate query
|
|
62
|
+
if (!params.query || typeof params.query !== 'string' || params.query.trim() === '') {
|
|
63
|
+
params.query = '*';
|
|
64
|
+
}
|
|
65
|
+
// Validate and set default limit
|
|
66
|
+
if (params.limit !== undefined) {
|
|
67
|
+
if (typeof params.limit !== 'number' || params.limit < 1 || params.limit > 100) {
|
|
68
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Limit must be between 1 and 100');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
params.limit = 20;
|
|
73
|
+
}
|
|
74
|
+
// Validate and set default offset
|
|
75
|
+
if (params.offset !== undefined) {
|
|
76
|
+
if (typeof params.offset !== 'number' || params.offset < 0) {
|
|
77
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Offset must be 0 or greater');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
params.offset = 0;
|
|
82
|
+
}
|
|
83
|
+
// Validate file type if provided (now supported via Microsoft Search API KQL)
|
|
84
|
+
if (params.fileType && !/^[a-zA-Z0-9]{1,10}$/.test(params.fileType)) {
|
|
85
|
+
throw new McpError(McpErrorCode.InvalidParams, 'File type must be alphanumeric and no more than 10 characters');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async handleExecute(params) {
|
|
89
|
+
this.logger.info('SharePointSearchTool', `Searching SharePoint with query: "${params.query}"`);
|
|
90
|
+
try {
|
|
91
|
+
// Call the API to search SharePoint documents
|
|
92
|
+
const searchParams = {
|
|
93
|
+
query: params.query,
|
|
94
|
+
limit: params.limit,
|
|
95
|
+
offset: params.offset,
|
|
96
|
+
fileType: params.fileType
|
|
97
|
+
};
|
|
98
|
+
// Make API call via UCM client
|
|
99
|
+
const result = await this.ucmClient.sharePointSearch(params.connectionId, searchParams);
|
|
100
|
+
this.logger.info('SharePointSearchTool', `Search completed, found ${result.totalCount} results`);
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: 'text',
|
|
105
|
+
text: JSON.stringify({
|
|
106
|
+
success: true,
|
|
107
|
+
query: params.query,
|
|
108
|
+
totalCount: result.totalCount,
|
|
109
|
+
returnedCount: result.files?.length || 0,
|
|
110
|
+
hasMore: result.hasMore,
|
|
111
|
+
limit: params.limit,
|
|
112
|
+
offset: params.offset,
|
|
113
|
+
fileType: params.fileType,
|
|
114
|
+
files: result.files?.map((file) => ({
|
|
115
|
+
id: file.id,
|
|
116
|
+
name: file.name,
|
|
117
|
+
webUrl: file.webUrl,
|
|
118
|
+
size: file.size,
|
|
119
|
+
mimeType: file.mimeType,
|
|
120
|
+
createdDateTime: file.createdDateTime,
|
|
121
|
+
lastModifiedDateTime: file.lastModifiedDateTime,
|
|
122
|
+
folder: file.folder
|
|
123
|
+
})) || []
|
|
124
|
+
}, null, 2)
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return SharePointErrorHandler.handle(error, this.logger, 'SharePointSearchTool');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=SharePointSearchTool.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharePointSignOutTool
|
|
3
|
+
* Revoke user's OnBehalfOf authorization for a SharePoint connection
|
|
4
|
+
*/
|
|
5
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
6
|
+
import { UcmLocalApiClient } from '../../clients/UcmLocalApiClient.js';
|
|
7
|
+
import { ILogger } from '../../interfaces/ILogger.js';
|
|
8
|
+
export declare class SharePointSignOutTool extends BaseToolController {
|
|
9
|
+
constructor(ucmClient: UcmLocalApiClient, logger: ILogger, publishingAuthorId?: string);
|
|
10
|
+
get name(): string;
|
|
11
|
+
get description(): string;
|
|
12
|
+
get inputSchema(): any;
|
|
13
|
+
protected validateParams(params: any): void;
|
|
14
|
+
protected handleExecute(params: {
|
|
15
|
+
connectionId: string;
|
|
16
|
+
}): Promise<any>;
|
|
17
|
+
/**
|
|
18
|
+
* Format sign out response as markdown
|
|
19
|
+
*/
|
|
20
|
+
private formatSignOutResponse;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=SharePointSignOutTool.d.ts.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharePointSignOutTool
|
|
3
|
+
* Revoke user's OnBehalfOf authorization for a SharePoint connection
|
|
4
|
+
*/
|
|
5
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
6
|
+
import { McpError, McpErrorCode } from '../../utils/McpErrorHandler.js';
|
|
7
|
+
export class SharePointSignOutTool extends BaseToolController {
|
|
8
|
+
constructor(ucmClient, logger, publishingAuthorId) {
|
|
9
|
+
super(ucmClient, logger, publishingAuthorId);
|
|
10
|
+
}
|
|
11
|
+
get name() {
|
|
12
|
+
return 'ucm_sharepoint_signout';
|
|
13
|
+
}
|
|
14
|
+
get description() {
|
|
15
|
+
return 'This signs the user out only for connections with user-delegated permissions.';
|
|
16
|
+
}
|
|
17
|
+
get inputSchema() {
|
|
18
|
+
return {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
connectionId: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'SharePoint connection ID to revoke authorization for (get this from list_connections)',
|
|
24
|
+
minLength: 36,
|
|
25
|
+
maxLength: 36
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
required: ['connectionId']
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
validateParams(params) {
|
|
32
|
+
super.validateParams(params);
|
|
33
|
+
// Validate connectionId
|
|
34
|
+
if (!params.connectionId || typeof params.connectionId !== 'string') {
|
|
35
|
+
throw new McpError(McpErrorCode.InvalidParams, 'connectionId is required and must be a string');
|
|
36
|
+
}
|
|
37
|
+
if (params.connectionId.length !== 36) {
|
|
38
|
+
throw new McpError(McpErrorCode.InvalidParams, 'connectionId must be a valid UUID (36 characters)');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async handleExecute(params) {
|
|
42
|
+
this.logger.info('SharePointSignOutTool', `Revoking SharePoint authorization for connection ${params.connectionId}`);
|
|
43
|
+
try {
|
|
44
|
+
// Call API to revoke authorization
|
|
45
|
+
const result = await this.ucmClient.sharePointSignOut(params.connectionId);
|
|
46
|
+
this.logger.info('SharePointSignOutTool', 'Authorization revoked successfully', '', {
|
|
47
|
+
connectionId: params.connectionId,
|
|
48
|
+
success: result.success
|
|
49
|
+
});
|
|
50
|
+
// Return structured markdown response
|
|
51
|
+
const markdown = this.formatSignOutResponse(result);
|
|
52
|
+
return markdown;
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
this.logger.error('SharePointSignOutTool: Failed to revoke authorization', error);
|
|
56
|
+
if (error instanceof McpError) {
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
// Handle API errors
|
|
60
|
+
if (error.response) {
|
|
61
|
+
const status = error.response.status;
|
|
62
|
+
const errorData = error.response.data;
|
|
63
|
+
if (status === 401 || status === 403) {
|
|
64
|
+
throw new McpError(McpErrorCode.InvalidRequest, `Access denied: ${errorData?.message || 'Not authorized to revoke SharePoint authorization'}`);
|
|
65
|
+
}
|
|
66
|
+
if (status === 404) {
|
|
67
|
+
throw new McpError(McpErrorCode.InvalidRequest, `Connection not found: ${errorData?.message || 'SharePoint connection does not exist'}`);
|
|
68
|
+
}
|
|
69
|
+
if (status === 400 && errorData?.error === 'INVALID_AUTH_TYPE') {
|
|
70
|
+
throw new McpError(McpErrorCode.InvalidRequest, `Invalid operation: Only OnBehalfOf (user-delegated) connections support user authorization. This connection uses ServicePrincipal authentication.`);
|
|
71
|
+
}
|
|
72
|
+
throw new McpError(McpErrorCode.InternalError, `API error: ${errorData?.message || error.message}`);
|
|
73
|
+
}
|
|
74
|
+
throw new McpError(McpErrorCode.InternalError, `Failed to revoke SharePoint authorization: ${error.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Format sign out response as markdown
|
|
79
|
+
*/
|
|
80
|
+
formatSignOutResponse(result) {
|
|
81
|
+
const lines = [];
|
|
82
|
+
lines.push('# SharePoint Authorization Revoked\n');
|
|
83
|
+
if (result.success) {
|
|
84
|
+
lines.push('✅ **Status**: Authorization successfully revoked\n');
|
|
85
|
+
lines.push(`**Connection ID**: ${result.connectionId}\n`);
|
|
86
|
+
if (result.revokedAt) {
|
|
87
|
+
lines.push(`**Revoked At**: ${new Date(result.revokedAt).toLocaleString()}\n`);
|
|
88
|
+
}
|
|
89
|
+
lines.push('\n## What This Means\n');
|
|
90
|
+
lines.push('- Your access tokens have been deleted from the system');
|
|
91
|
+
lines.push('- You are now signed out of this SharePoint connection');
|
|
92
|
+
lines.push('- To access SharePoint files again, you must re-authorize\n');
|
|
93
|
+
if (result._links?.authorize) {
|
|
94
|
+
lines.push('\n## Re-authorize Access\n');
|
|
95
|
+
lines.push(`To grant access again, visit: ${result._links.authorize.href}\n`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
lines.push('⚠️ **Status**: Revocation failed or not needed\n');
|
|
100
|
+
lines.push(`**Message**: ${result.message}\n`);
|
|
101
|
+
if (result.details?.reason) {
|
|
102
|
+
lines.push(`**Reason**: ${result.details.reason}\n`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return lines.join('\n');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=SharePointSignOutTool.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharePointErrorHandler
|
|
3
|
+
* Centralized error handling for SharePoint MCP tools
|
|
4
|
+
*/
|
|
5
|
+
import { ILogger } from '../interfaces/ILogger.js';
|
|
6
|
+
/**
|
|
7
|
+
* Standard MCP tool result with content array
|
|
8
|
+
*/
|
|
9
|
+
interface ToolResult {
|
|
10
|
+
content: Array<{
|
|
11
|
+
type: string;
|
|
12
|
+
text?: string;
|
|
13
|
+
uri?: string;
|
|
14
|
+
mimeType?: string;
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
export declare class SharePointErrorHandler {
|
|
18
|
+
/**
|
|
19
|
+
* Handle SharePoint API errors and return appropriate MCP responses
|
|
20
|
+
* @param error - The error from axios/API call
|
|
21
|
+
* @param logger - Logger instance for error tracking
|
|
22
|
+
* @param toolName - Name of the tool for logging context
|
|
23
|
+
* @returns ToolResult for authentication errors, throws McpError for others
|
|
24
|
+
*/
|
|
25
|
+
static handle(error: any, logger: ILogger, toolName: string): ToolResult | never;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=SharePointErrorHandler.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharePointErrorHandler
|
|
3
|
+
* Centralized error handling for SharePoint MCP tools
|
|
4
|
+
*/
|
|
5
|
+
import { McpError, McpErrorCode } from './McpErrorHandler.js';
|
|
6
|
+
export class SharePointErrorHandler {
|
|
7
|
+
/**
|
|
8
|
+
* Handle SharePoint API errors and return appropriate MCP responses
|
|
9
|
+
* @param error - The error from axios/API call
|
|
10
|
+
* @param logger - Logger instance for error tracking
|
|
11
|
+
* @param toolName - Name of the tool for logging context
|
|
12
|
+
* @returns ToolResult for authentication errors, throws McpError for others
|
|
13
|
+
*/
|
|
14
|
+
static handle(error, logger, toolName) {
|
|
15
|
+
logger.error(toolName, 'SharePoint operation failed', '', error);
|
|
16
|
+
const serverError = error?.response?.data;
|
|
17
|
+
// Handle SharePoint authentication required (OnBehalfOf flow)
|
|
18
|
+
if (error?.response?.status === 401 && serverError?.error === 'SHAREPOINT_LOGIN_REQUIRED') {
|
|
19
|
+
const userMessage = serverError.message || 'To access SharePoint, please login then continue your conversation.';
|
|
20
|
+
const loginUrl = serverError.details?.loginUrl;
|
|
21
|
+
return {
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: 'text',
|
|
25
|
+
text: userMessage
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: 'resource',
|
|
29
|
+
uri: loginUrl,
|
|
30
|
+
mimeType: 'text/html'
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// Handle not found errors
|
|
36
|
+
if (error?.response?.status === 404) {
|
|
37
|
+
throw new McpError(McpErrorCode.InvalidParams, 'SharePoint resource not found');
|
|
38
|
+
}
|
|
39
|
+
// Handle authorization errors (403 or other 401s)
|
|
40
|
+
if (error?.response?.status === 401 || error?.response?.status === 403) {
|
|
41
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Not authorized to access this SharePoint resource');
|
|
42
|
+
}
|
|
43
|
+
// Handle all other errors
|
|
44
|
+
throw new McpError(McpErrorCode.InternalError, `SharePoint operation failed: ${error?.message || 'Unknown error'}`, { originalError: error?.message });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=SharePointErrorHandler.js.map
|
package/package.json
CHANGED