@utaba/ucm-mcp-server 1.0.4 → 1.0.6
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 +22 -0
- package/dist/clients/UcmApiClient.js +34 -1
- package/dist/index.js +2 -2
- package/dist/server/ToolRegistry.js +10 -0
- package/dist/tools/core/ListArtifactsTool.js +4 -4
- package/dist/tools/core/PublishArtifactFromFileTool.js +6 -3
- package/dist/tools/core/PublishArtifactTool.js +6 -3
- package/dist/tools/repository/CreateRepositoryTool.d.ts +12 -0
- package/dist/tools/repository/CreateRepositoryTool.js +125 -0
- package/dist/tools/repository/DeleteRepositoryGuidanceTool.d.ts +12 -0
- package/dist/tools/repository/DeleteRepositoryGuidanceTool.js +92 -0
- package/dist/tools/repository/GetRepositoryTool.d.ts +12 -0
- package/dist/tools/repository/GetRepositoryTool.js +85 -0
- package/dist/tools/repository/UpdateRepositoryTool.d.ts +12 -0
- package/dist/tools/repository/UpdateRepositoryTool.js +125 -0
- package/dist/tools/utility/ListRepositoriesTool.js +2 -5
- package/dist/tools/utility/QuickstartTool.js +1 -1
- package/package.json +2 -2
- package/package.json.backup +2 -2
|
@@ -49,5 +49,27 @@ export declare class UcmApiClient {
|
|
|
49
49
|
* Check if the client is still available for use
|
|
50
50
|
*/
|
|
51
51
|
isAvailable(): boolean;
|
|
52
|
+
createRepository(author: string, data: {
|
|
53
|
+
repositoryName: string;
|
|
54
|
+
displayName?: string;
|
|
55
|
+
description?: string;
|
|
56
|
+
isPublic?: boolean;
|
|
57
|
+
}): Promise<any>;
|
|
58
|
+
getRepository(author: string, repository: string): Promise<any>;
|
|
59
|
+
updateRepository(author: string, repository: string, data: {
|
|
60
|
+
displayName?: string;
|
|
61
|
+
description?: string;
|
|
62
|
+
isPublic?: boolean;
|
|
63
|
+
}): Promise<any>;
|
|
64
|
+
deleteRepository(author: string, repository: string): Promise<any>;
|
|
65
|
+
listRepositories(author: string, offset?: number, limit?: number): Promise<{
|
|
66
|
+
data: any[];
|
|
67
|
+
pagination: {
|
|
68
|
+
offset: number;
|
|
69
|
+
limit: number;
|
|
70
|
+
total: number;
|
|
71
|
+
};
|
|
72
|
+
_links?: any;
|
|
73
|
+
}>;
|
|
52
74
|
}
|
|
53
75
|
//# sourceMappingURL=UcmApiClient.d.ts.map
|
|
@@ -147,7 +147,7 @@ export class UcmApiClient {
|
|
|
147
147
|
const queryString = params.toString();
|
|
148
148
|
// Build URL based on provided parameters for exploratory browsing
|
|
149
149
|
// Default repository to 'main' for MVP
|
|
150
|
-
const repo = repository
|
|
150
|
+
const repo = repository;
|
|
151
151
|
let metadataApiPath = this.buildApiPath('authors', author, repo, category, subcategory);
|
|
152
152
|
metadataApiPath += queryString ? `?${queryString}` : '';
|
|
153
153
|
const client = this.ensureClient();
|
|
@@ -293,5 +293,38 @@ export class UcmApiClient {
|
|
|
293
293
|
isAvailable() {
|
|
294
294
|
return this.client !== null;
|
|
295
295
|
}
|
|
296
|
+
// Repository Management Methods
|
|
297
|
+
async createRepository(author, data) {
|
|
298
|
+
const client = this.ensureClient();
|
|
299
|
+
const response = await client.post(`/api/v1/authors/${author}`, data);
|
|
300
|
+
return response.data;
|
|
301
|
+
}
|
|
302
|
+
async getRepository(author, repository) {
|
|
303
|
+
const client = this.ensureClient();
|
|
304
|
+
const response = await client.get(`/api/v1/authors/${author}/${repository}`);
|
|
305
|
+
return response.data;
|
|
306
|
+
}
|
|
307
|
+
async updateRepository(author, repository, data) {
|
|
308
|
+
const client = this.ensureClient();
|
|
309
|
+
const response = await client.put(`/api/v1/authors/${author}/${repository}`, data);
|
|
310
|
+
return response.data;
|
|
311
|
+
}
|
|
312
|
+
async deleteRepository(author, repository) {
|
|
313
|
+
const client = this.ensureClient();
|
|
314
|
+
const response = await client.delete(`/api/v1/authors/${author}/${repository}`);
|
|
315
|
+
return response.data;
|
|
316
|
+
}
|
|
317
|
+
async listRepositories(author, offset, limit) {
|
|
318
|
+
const params = new URLSearchParams();
|
|
319
|
+
if (offset !== undefined)
|
|
320
|
+
params.append('offset', offset.toString());
|
|
321
|
+
if (limit !== undefined)
|
|
322
|
+
params.append('limit', limit.toString());
|
|
323
|
+
const queryString = params.toString();
|
|
324
|
+
const url = `/api/v1/authors/${author}${queryString ? `?${queryString}` : ''}`;
|
|
325
|
+
const client = this.ensureClient();
|
|
326
|
+
const response = await client.get(url);
|
|
327
|
+
return response.data;
|
|
328
|
+
}
|
|
296
329
|
}
|
|
297
330
|
//# sourceMappingURL=UcmApiClient.js.map
|
package/dist/index.js
CHANGED
|
@@ -6,13 +6,13 @@ import { McpConfig } from './server/McpConfig.js';
|
|
|
6
6
|
import { LoggerFactory } from './logging/LoggerFactory.js';
|
|
7
7
|
// Increase max listeners to prevent AbortSignal memory leak warnings
|
|
8
8
|
// This is a safety net while we also implement proper cleanup
|
|
9
|
-
EventEmitter.defaultMaxListeners =
|
|
9
|
+
EventEmitter.defaultMaxListeners = 40;
|
|
10
10
|
async function main() {
|
|
11
11
|
const program = new Command();
|
|
12
12
|
program
|
|
13
13
|
.name('ucm-mcp-server')
|
|
14
14
|
.description('Universal Context Manager - Read the mcp_ucm_quickstart first to avoid mistakes')
|
|
15
|
-
.version('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')
|
|
@@ -12,6 +12,11 @@ import { PublishArtifactFromFileTool } from '../tools/core/PublishArtifactFromFi
|
|
|
12
12
|
import { ListArtifactsTool } from '../tools/core/ListArtifactsTool.js';
|
|
13
13
|
import { DeleteArtifactTool } from '../tools/core/DeleteArtifactTool.js';
|
|
14
14
|
import { GetArtifactVersionsTool } from '../tools/core/GetArtifactVersionsTool.js';
|
|
15
|
+
// Import repository tools
|
|
16
|
+
import { CreateRepositoryTool } from '../tools/repository/CreateRepositoryTool.js';
|
|
17
|
+
import { GetRepositoryTool } from '../tools/repository/GetRepositoryTool.js';
|
|
18
|
+
import { UpdateRepositoryTool } from '../tools/repository/UpdateRepositoryTool.js';
|
|
19
|
+
import { DeleteRepositoryGuidanceTool } from '../tools/repository/DeleteRepositoryGuidanceTool.js';
|
|
15
20
|
export class ToolRegistry {
|
|
16
21
|
ucmClient;
|
|
17
22
|
logger;
|
|
@@ -39,6 +44,11 @@ export class ToolRegistry {
|
|
|
39
44
|
this.registerTool(new ListArtifactsTool(this.ucmClient, this.logger, this.authorId));
|
|
40
45
|
this.registerTool(new DeleteArtifactTool(this.ucmClient, this.logger, this.authorId));
|
|
41
46
|
this.registerTool(new GetArtifactVersionsTool(this.ucmClient, this.logger, this.authorId));
|
|
47
|
+
// Register repository tools
|
|
48
|
+
this.registerTool(new CreateRepositoryTool(this.ucmClient, this.logger, this.authorId));
|
|
49
|
+
this.registerTool(new GetRepositoryTool(this.ucmClient, this.logger, this.authorId));
|
|
50
|
+
this.registerTool(new UpdateRepositoryTool(this.ucmClient, this.logger, this.authorId));
|
|
51
|
+
this.registerTool(new DeleteRepositoryGuidanceTool(this.ucmClient, this.logger, this.authorId));
|
|
42
52
|
this.logger.info('ToolRegistry', `Registered ${this.tools.size} MCP tools`);
|
|
43
53
|
}
|
|
44
54
|
registerTool(tool) {
|
|
@@ -8,7 +8,7 @@ export class ListArtifactsTool extends BaseToolController {
|
|
|
8
8
|
return 'mcp_ucm_list_artifacts';
|
|
9
9
|
}
|
|
10
10
|
get description() {
|
|
11
|
-
return `List artifacts with exploratory browsing support. Can list
|
|
11
|
+
return `List artifacts with exploratory browsing support. Can list authors, repositories, categories, subcategories, or artifacts depending on path depth. ${this.publishingAuthorId ? "Your author value is '" + this.publishingAuthorId + "'" : ''}`;
|
|
12
12
|
}
|
|
13
13
|
get inputSchema() {
|
|
14
14
|
return {
|
|
@@ -61,9 +61,9 @@ export class ListArtifactsTool extends BaseToolController {
|
|
|
61
61
|
listingType = 'subcategories';
|
|
62
62
|
}
|
|
63
63
|
// For MVP, validate repository is 'main' if provided
|
|
64
|
-
if (pathSegments.length >= 2 && pathComponents.repository && pathComponents.repository !== 'main') {
|
|
65
|
-
|
|
66
|
-
}
|
|
64
|
+
// if (pathSegments.length >= 2 && pathComponents.repository && pathComponents.repository !== 'main') {
|
|
65
|
+
// throw new Error(`Repository must be 'main' for MVP. Received: "${pathComponents.repository}"`);
|
|
66
|
+
// }
|
|
67
67
|
this.logger.info('ListArtifactsTool', `Listed ${response.data.length} ${listingType} in: ${path}`);
|
|
68
68
|
// Return the full response with pagination metadata and context
|
|
69
69
|
return {
|
|
@@ -75,9 +75,12 @@ export class PublishArtifactFromFileTool extends BaseToolController {
|
|
|
75
75
|
throw new McpError(McpErrorCode.InvalidParams, `Path must contain author, repository, category, and subcategory (e.g., "${this.publishingAuthorId || '1234567890'}/main/commands/user")`);
|
|
76
76
|
}
|
|
77
77
|
// Validate repository is 'main' for MVP
|
|
78
|
-
if (pathComponents.repository !== 'main') {
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
// if (pathComponents.repository !== 'main') {
|
|
79
|
+
// throw new McpError(
|
|
80
|
+
// McpErrorCode.InvalidParams,
|
|
81
|
+
// `Repository must be 'main' for MVP. Received: "${pathComponents.repository}"`
|
|
82
|
+
// );
|
|
83
|
+
// }
|
|
81
84
|
// Prepare the publish data - new API expects content in body and metadata in query params
|
|
82
85
|
const publishData = {
|
|
83
86
|
content, // Raw text content from file
|
|
@@ -66,9 +66,12 @@ export class PublishArtifactTool extends BaseToolController {
|
|
|
66
66
|
throw new McpError(McpErrorCode.InvalidParams, 'Path must contain author, repository, category, and subcategory (e.g., "utaba/main/commands/user")');
|
|
67
67
|
}
|
|
68
68
|
// Validate repository is 'main' for MVP
|
|
69
|
-
if (pathComponents.repository !== 'main') {
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
// if (pathComponents.repository !== 'main') {
|
|
70
|
+
// throw new McpError(
|
|
71
|
+
// McpErrorCode.InvalidParams,
|
|
72
|
+
// `Repository must be 'main' for MVP. Received: "${pathComponents.repository}"`
|
|
73
|
+
// );
|
|
74
|
+
// }
|
|
72
75
|
// Prepare the publish data - new API expects content in body and metadata in query params
|
|
73
76
|
const publishData = {
|
|
74
77
|
content, // Raw text content
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
2
|
+
import { UcmApiClient } from '../../clients/UcmApiClient.js';
|
|
3
|
+
import { ILogger } from '../../interfaces/ILogger.js';
|
|
4
|
+
export declare class CreateRepositoryTool extends BaseToolController {
|
|
5
|
+
constructor(ucmClient: UcmApiClient, logger: ILogger, publishingAuthorId?: string);
|
|
6
|
+
get name(): string;
|
|
7
|
+
get description(): string;
|
|
8
|
+
get inputSchema(): any;
|
|
9
|
+
protected validateParams(params: any): void;
|
|
10
|
+
protected handleExecute(params: any): Promise<any>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=CreateRepositoryTool.d.ts.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
2
|
+
import { McpError, McpErrorCode } from '../../utils/McpErrorHandler.js';
|
|
3
|
+
export class CreateRepositoryTool extends BaseToolController {
|
|
4
|
+
constructor(ucmClient, logger, publishingAuthorId) {
|
|
5
|
+
super(ucmClient, logger, publishingAuthorId);
|
|
6
|
+
}
|
|
7
|
+
get name() {
|
|
8
|
+
return 'mcp_ucm_create_repository';
|
|
9
|
+
}
|
|
10
|
+
get description() {
|
|
11
|
+
const authorInfo = this.publishingAuthorId ? ` Your author id is '${this.publishingAuthorId}'.` : '';
|
|
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
|
+
}
|
|
14
|
+
get inputSchema() {
|
|
15
|
+
return {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
author: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: this.publishingAuthorId ? `Author identifier: ${this.publishingAuthorId}` : 'Author identifier (e.g., "utaba", "1064600359")',
|
|
21
|
+
minLength: 1,
|
|
22
|
+
maxLength: 50
|
|
23
|
+
},
|
|
24
|
+
repositoryName: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Unique repository name (3-200 characters, start with letter, alphanumeric and hyphens only)',
|
|
27
|
+
minLength: 3,
|
|
28
|
+
maxLength: 200,
|
|
29
|
+
pattern: '^[a-z][a-z0-9-]{2,199}$'
|
|
30
|
+
},
|
|
31
|
+
displayName: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'Human-readable display name for the repository (optional)',
|
|
34
|
+
maxLength: 255
|
|
35
|
+
},
|
|
36
|
+
description: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
description: 'Repository description (optional)',
|
|
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
|
+
}
|
|
46
|
+
},
|
|
47
|
+
required: ['author', 'repositoryName'],
|
|
48
|
+
additionalProperties: false
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
validateParams(params) {
|
|
52
|
+
super.validateParams(params);
|
|
53
|
+
// Additional validation
|
|
54
|
+
if (!params.author || typeof params.author !== 'string' || params.author.trim() === '') {
|
|
55
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Author identifier is required and cannot be empty');
|
|
56
|
+
}
|
|
57
|
+
if (!params.repositoryName || typeof params.repositoryName !== 'string' || params.repositoryName.trim() === '') {
|
|
58
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Repository name is required and cannot be empty');
|
|
59
|
+
}
|
|
60
|
+
// Validate repository name format
|
|
61
|
+
const namePattern = /^[a-z][a-z0-9-]{2,199}$/;
|
|
62
|
+
if (!namePattern.test(params.repositoryName)) {
|
|
63
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Repository name must be 3-200 characters, start with a lowercase letter, and contain only lowercase letters, numbers, and hyphens (no consecutive hyphens)');
|
|
64
|
+
}
|
|
65
|
+
// Check for consecutive hyphens
|
|
66
|
+
if (params.repositoryName.includes('--')) {
|
|
67
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Repository name cannot contain consecutive hyphens');
|
|
68
|
+
}
|
|
69
|
+
// Validate displayName length if provided
|
|
70
|
+
if (params.displayName && params.displayName.length > 255) {
|
|
71
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Display name cannot exceed 255 characters');
|
|
72
|
+
}
|
|
73
|
+
// Validate description length if provided
|
|
74
|
+
if (params.description && params.description.length > 1000) {
|
|
75
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Description cannot exceed 1,000 characters');
|
|
76
|
+
}
|
|
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
|
+
}
|
|
82
|
+
async handleExecute(params) {
|
|
83
|
+
this.logger.info('CreateRepositoryTool', `Creating repository ${params.repositoryName} for author ${params.author}`);
|
|
84
|
+
try {
|
|
85
|
+
const repositoryData = {
|
|
86
|
+
repositoryName: params.repositoryName,
|
|
87
|
+
displayName: params.displayName,
|
|
88
|
+
description: params.description,
|
|
89
|
+
isPublic: params.isPublic || false
|
|
90
|
+
};
|
|
91
|
+
const result = await this.ucmClient.createRepository(params.author, repositoryData);
|
|
92
|
+
this.logger.info('CreateRepositoryTool', `Repository created successfully: ${params.repositoryName}`);
|
|
93
|
+
return {
|
|
94
|
+
content: [
|
|
95
|
+
{
|
|
96
|
+
type: 'text',
|
|
97
|
+
text: JSON.stringify({
|
|
98
|
+
success: true,
|
|
99
|
+
message: `Repository '${params.repositoryName}' created successfully for author '${params.author}'`,
|
|
100
|
+
repository: result,
|
|
101
|
+
webUrl: `${process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'}/repository/${result.id || params.repositoryName}`
|
|
102
|
+
}, null, 2)
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
this.logger.error('CreateRepositoryTool', 'Failed to create repository', '', error);
|
|
109
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
110
|
+
// Handle specific error cases
|
|
111
|
+
if (errorMessage.includes('already exists') || errorMessage.includes('NAME_EXISTS')) {
|
|
112
|
+
throw new McpError(McpErrorCode.InvalidParams, `Repository name '${params.repositoryName}' already exists for author '${params.author}'. Please choose a different name.`);
|
|
113
|
+
}
|
|
114
|
+
if (errorMessage.includes('not found') || errorMessage.includes('404')) {
|
|
115
|
+
throw new McpError(McpErrorCode.InvalidParams, `Author '${params.author}' not found. Please verify the author identifier.`);
|
|
116
|
+
}
|
|
117
|
+
if (errorMessage.includes('validation') || errorMessage.includes('invalid')) {
|
|
118
|
+
throw new McpError(McpErrorCode.InvalidParams, `Repository creation failed due to validation error: ${errorMessage}`);
|
|
119
|
+
}
|
|
120
|
+
// Generic error
|
|
121
|
+
throw new McpError(McpErrorCode.InternalError, `Failed to create repository: ${errorMessage}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=CreateRepositoryTool.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
2
|
+
import { UcmApiClient } from '../../clients/UcmApiClient.js';
|
|
3
|
+
import { ILogger } from '../../interfaces/ILogger.js';
|
|
4
|
+
export declare class DeleteRepositoryGuidanceTool extends BaseToolController {
|
|
5
|
+
constructor(ucmClient: UcmApiClient, logger: ILogger, publishingAuthorId?: string);
|
|
6
|
+
get name(): string;
|
|
7
|
+
get description(): string;
|
|
8
|
+
get inputSchema(): any;
|
|
9
|
+
protected validateParams(params: any): void;
|
|
10
|
+
protected handleExecute(params: any): Promise<any>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=DeleteRepositoryGuidanceTool.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
2
|
+
import { McpError, McpErrorCode } from '../../utils/McpErrorHandler.js';
|
|
3
|
+
export class DeleteRepositoryGuidanceTool extends BaseToolController {
|
|
4
|
+
constructor(ucmClient, logger, publishingAuthorId) {
|
|
5
|
+
super(ucmClient, logger, publishingAuthorId);
|
|
6
|
+
}
|
|
7
|
+
get name() {
|
|
8
|
+
return 'mcp_ucm_delete_repository_guidance';
|
|
9
|
+
}
|
|
10
|
+
get description() {
|
|
11
|
+
const authorInfo = this.publishingAuthorId ? ` Your author id is '${this.publishingAuthorId}'.` : '';
|
|
12
|
+
return `Provides guidance for deleting a repository via the web UI. Repository deletion is intentionally not available through the MCP tools to prevent accidental deletion. This tool returns the web URL where the user can safely delete the repository with proper confirmation dialogs.${authorInfo}`;
|
|
13
|
+
}
|
|
14
|
+
get inputSchema() {
|
|
15
|
+
return {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
author: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: this.publishingAuthorId ? `Author identifier: ${this.publishingAuthorId}` : 'Author identifier (e.g., "utaba", "1064600359")',
|
|
21
|
+
minLength: 1,
|
|
22
|
+
maxLength: 50
|
|
23
|
+
},
|
|
24
|
+
repository: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Repository name to delete (e.g., "main", "project-alpha")',
|
|
27
|
+
minLength: 1,
|
|
28
|
+
maxLength: 200
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
required: ['author', 'repository'],
|
|
32
|
+
additionalProperties: false
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
validateParams(params) {
|
|
36
|
+
super.validateParams(params);
|
|
37
|
+
if (!params.author || typeof params.author !== 'string' || params.author.trim() === '') {
|
|
38
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Author identifier is required and cannot be empty');
|
|
39
|
+
}
|
|
40
|
+
if (!params.repository || typeof params.repository !== 'string' || params.repository.trim() === '') {
|
|
41
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Repository name is required and cannot be empty');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async handleExecute(params) {
|
|
45
|
+
this.logger.info('DeleteRepositoryGuidanceTool', `Providing delete guidance for repository ${params.repository} for author ${params.author}`);
|
|
46
|
+
// Get the base URL for the web interface
|
|
47
|
+
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000';
|
|
48
|
+
const repositorySettingsUrl = `${baseUrl}/repository/${params.repository}/settings`;
|
|
49
|
+
const browseUrl = `${baseUrl}/browse/${params.author}`;
|
|
50
|
+
const guidance = {
|
|
51
|
+
action: 'delete_repository_guidance',
|
|
52
|
+
repository: {
|
|
53
|
+
author: params.author,
|
|
54
|
+
name: params.repository
|
|
55
|
+
},
|
|
56
|
+
message: `Repository deletion must be performed through the web interface for safety.`,
|
|
57
|
+
reason: 'Repository deletion is intentionally not available through MCP tools to prevent accidental deletion of valuable content.',
|
|
58
|
+
instructions: [
|
|
59
|
+
`1. Open the repository settings page: ${repositorySettingsUrl}`,
|
|
60
|
+
`2. Scroll to the "Danger Zone" section`,
|
|
61
|
+
`3. Click "Delete Repository" button`,
|
|
62
|
+
`4. Confirm the deletion by typing the repository name`,
|
|
63
|
+
`5. Click "Delete" to permanently remove the repository`
|
|
64
|
+
],
|
|
65
|
+
urls: {
|
|
66
|
+
repositorySettings: repositorySettingsUrl,
|
|
67
|
+
authorRepositories: browseUrl
|
|
68
|
+
},
|
|
69
|
+
warnings: [
|
|
70
|
+
'Repository deletion is permanent and cannot be undone',
|
|
71
|
+
'All artifacts and content in the repository will be permanently lost',
|
|
72
|
+
'Any links or references to this repository will become invalid',
|
|
73
|
+
'Make sure to backup any important content before deletion'
|
|
74
|
+
],
|
|
75
|
+
alternatives: [
|
|
76
|
+
'Consider renaming the repository instead of deleting it',
|
|
77
|
+
'Archive the repository by updating its description to indicate it is archived',
|
|
78
|
+
'Move important artifacts to another repository before deletion'
|
|
79
|
+
]
|
|
80
|
+
};
|
|
81
|
+
this.logger.info('DeleteRepositoryGuidanceTool', `Delete guidance provided for repository: ${params.repository}`);
|
|
82
|
+
return {
|
|
83
|
+
content: [
|
|
84
|
+
{
|
|
85
|
+
type: 'text',
|
|
86
|
+
text: JSON.stringify(guidance, null, 2)
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=DeleteRepositoryGuidanceTool.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
2
|
+
import { UcmApiClient } from '../../clients/UcmApiClient.js';
|
|
3
|
+
import { ILogger } from '../../interfaces/ILogger.js';
|
|
4
|
+
export declare class GetRepositoryTool extends BaseToolController {
|
|
5
|
+
constructor(ucmClient: UcmApiClient, logger: ILogger, publishingAuthorId?: string);
|
|
6
|
+
get name(): string;
|
|
7
|
+
get description(): string;
|
|
8
|
+
get inputSchema(): any;
|
|
9
|
+
protected validateParams(params: any): void;
|
|
10
|
+
protected handleExecute(params: any): Promise<any>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=GetRepositoryTool.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
2
|
+
import { McpError, McpErrorCode } from '../../utils/McpErrorHandler.js';
|
|
3
|
+
export class GetRepositoryTool extends BaseToolController {
|
|
4
|
+
constructor(ucmClient, logger, publishingAuthorId) {
|
|
5
|
+
super(ucmClient, logger, publishingAuthorId);
|
|
6
|
+
}
|
|
7
|
+
get name() {
|
|
8
|
+
return 'mcp_ucm_get_repository';
|
|
9
|
+
}
|
|
10
|
+
get description() {
|
|
11
|
+
const authorInfo = this.publishingAuthorId ? ` Your author id is '${this.publishingAuthorId}'.` : '';
|
|
12
|
+
return `Get detailed information about a specific repository, including metadata, statistics, and available categories. Returns repository details along with its content structure.${authorInfo}`;
|
|
13
|
+
}
|
|
14
|
+
get inputSchema() {
|
|
15
|
+
return {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
author: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: this.publishingAuthorId ? `Author identifier: ${this.publishingAuthorId}` : 'Author identifier (e.g., "utaba", "1064600359")',
|
|
21
|
+
minLength: 1,
|
|
22
|
+
maxLength: 50
|
|
23
|
+
},
|
|
24
|
+
repository: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Repository name (e.g., "main", "project-alpha")',
|
|
27
|
+
minLength: 1,
|
|
28
|
+
maxLength: 200
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
required: ['author', 'repository'],
|
|
32
|
+
additionalProperties: false
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
validateParams(params) {
|
|
36
|
+
super.validateParams(params);
|
|
37
|
+
if (!params.author || typeof params.author !== 'string' || params.author.trim() === '') {
|
|
38
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Author identifier is required and cannot be empty');
|
|
39
|
+
}
|
|
40
|
+
if (!params.repository || typeof params.repository !== 'string' || params.repository.trim() === '') {
|
|
41
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Repository name is required and cannot be empty');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async handleExecute(params) {
|
|
45
|
+
this.logger.info('GetRepositoryTool', `Getting repository ${params.repository} for author ${params.author}`);
|
|
46
|
+
try {
|
|
47
|
+
const result = await this.ucmClient.getRepository(params.author, params.repository);
|
|
48
|
+
this.logger.info('GetRepositoryTool', `Repository retrieved successfully: ${params.repository}`);
|
|
49
|
+
// Enhance the response with additional helpful information
|
|
50
|
+
const enhancedResult = {
|
|
51
|
+
success: true,
|
|
52
|
+
message: `Repository '${params.repository}' retrieved successfully for author '${params.author}'`,
|
|
53
|
+
repository: result,
|
|
54
|
+
webUrl: `${process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'}/browse/${params.author}/${params.repository}`,
|
|
55
|
+
apiEndpoints: {
|
|
56
|
+
browse: `/api/v1/authors/${params.author}/${params.repository}`,
|
|
57
|
+
artifacts: `/api/v1/authors/${params.author}/${params.repository}/{category}/{subcategory}`,
|
|
58
|
+
files: `/api/v1/files/${params.author}/${params.repository}/{category}/{subcategory}/{filename}`
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
content: [
|
|
63
|
+
{
|
|
64
|
+
type: 'text',
|
|
65
|
+
text: JSON.stringify(enhancedResult, null, 2)
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
this.logger.error('GetRepositoryTool', 'Failed to get repository', '', error);
|
|
72
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
73
|
+
// Handle specific error cases
|
|
74
|
+
if (errorMessage.includes('not found') || errorMessage.includes('404')) {
|
|
75
|
+
throw new McpError(McpErrorCode.InvalidParams, `Repository '${params.repository}' not found for author '${params.author}'. Please verify the repository name and author identifier.`);
|
|
76
|
+
}
|
|
77
|
+
if (errorMessage.includes('403') || errorMessage.includes('Forbidden')) {
|
|
78
|
+
throw new McpError(McpErrorCode.InvalidParams, `Access denied to repository '${params.repository}' for author '${params.author}'. You may not have permission to view this repository.`);
|
|
79
|
+
}
|
|
80
|
+
// Generic error
|
|
81
|
+
throw new McpError(McpErrorCode.InternalError, `Failed to retrieve repository: ${errorMessage}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=GetRepositoryTool.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
2
|
+
import { UcmApiClient } from '../../clients/UcmApiClient.js';
|
|
3
|
+
import { ILogger } from '../../interfaces/ILogger.js';
|
|
4
|
+
export declare class UpdateRepositoryTool extends BaseToolController {
|
|
5
|
+
constructor(ucmClient: UcmApiClient, logger: ILogger, publishingAuthorId?: string);
|
|
6
|
+
get name(): string;
|
|
7
|
+
get description(): string;
|
|
8
|
+
get inputSchema(): any;
|
|
9
|
+
protected validateParams(params: any): void;
|
|
10
|
+
protected handleExecute(params: any): Promise<any>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=UpdateRepositoryTool.d.ts.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
2
|
+
import { McpError, McpErrorCode } from '../../utils/McpErrorHandler.js';
|
|
3
|
+
export class UpdateRepositoryTool extends BaseToolController {
|
|
4
|
+
constructor(ucmClient, logger, publishingAuthorId) {
|
|
5
|
+
super(ucmClient, logger, publishingAuthorId);
|
|
6
|
+
}
|
|
7
|
+
get name() {
|
|
8
|
+
return 'mcp_ucm_update_repository';
|
|
9
|
+
}
|
|
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}`;
|
|
13
|
+
}
|
|
14
|
+
get inputSchema() {
|
|
15
|
+
return {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
author: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: this.publishingAuthorId ? `Author identifier: ${this.publishingAuthorId}` : 'Author identifier (e.g., "utaba", "1064600359")',
|
|
21
|
+
minLength: 1,
|
|
22
|
+
maxLength: 50
|
|
23
|
+
},
|
|
24
|
+
repository: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Repository to update (e.g., "main", "project-alpha") however the name will not change. Only the displayname can be updated after creation.',
|
|
27
|
+
minLength: 1,
|
|
28
|
+
maxLength: 200
|
|
29
|
+
},
|
|
30
|
+
displayName: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
description: 'New human-readable display name for the repository (optional)',
|
|
33
|
+
maxLength: 255
|
|
34
|
+
},
|
|
35
|
+
description: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: 'New repository description (optional)',
|
|
38
|
+
maxLength: 1000
|
|
39
|
+
},
|
|
40
|
+
isPublic: {
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
description: 'Whether the repository should be public (currently not supported, must be false)'
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
required: ['author', 'repository'],
|
|
46
|
+
additionalProperties: false
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
validateParams(params) {
|
|
50
|
+
super.validateParams(params);
|
|
51
|
+
if (!params.author || typeof params.author !== 'string' || params.author.trim() === '') {
|
|
52
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Author identifier is required and cannot be empty');
|
|
53
|
+
}
|
|
54
|
+
if (!params.repository || typeof params.repository !== 'string' || params.repository.trim() === '') {
|
|
55
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Repository name is required and cannot be empty');
|
|
56
|
+
}
|
|
57
|
+
// Validate at least one field to update is provided
|
|
58
|
+
const updateFields = ['displayName', 'description', 'isPublic'];
|
|
59
|
+
const hasUpdateField = updateFields.some(field => params[field] !== undefined);
|
|
60
|
+
if (!hasUpdateField) {
|
|
61
|
+
throw new McpError(McpErrorCode.InvalidParams, 'At least one field to update must be provided (displayName, description, or isPublic)');
|
|
62
|
+
}
|
|
63
|
+
// Validate displayName length if provided
|
|
64
|
+
if (params.displayName !== undefined && params.displayName.length > 255) {
|
|
65
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Display name cannot exceed 255 characters');
|
|
66
|
+
}
|
|
67
|
+
// Validate description length if provided
|
|
68
|
+
if (params.description !== undefined && params.description.length > 1000) {
|
|
69
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Description cannot exceed 1,000 characters');
|
|
70
|
+
}
|
|
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
|
+
}
|
|
76
|
+
async handleExecute(params) {
|
|
77
|
+
this.logger.info('UpdateRepositoryTool', `Updating repository ${params.repository} for author ${params.author}`);
|
|
78
|
+
try {
|
|
79
|
+
// Build update data from provided parameters
|
|
80
|
+
const updateData = {};
|
|
81
|
+
if (params.displayName !== undefined) {
|
|
82
|
+
updateData.displayName = params.displayName;
|
|
83
|
+
}
|
|
84
|
+
if (params.description !== undefined) {
|
|
85
|
+
updateData.description = params.description;
|
|
86
|
+
}
|
|
87
|
+
if (params.isPublic !== undefined) {
|
|
88
|
+
updateData.isPublic = params.isPublic;
|
|
89
|
+
}
|
|
90
|
+
const result = await this.ucmClient.updateRepository(params.author, params.repository, updateData);
|
|
91
|
+
this.logger.info('UpdateRepositoryTool', `Repository updated successfully: ${params.repository}`);
|
|
92
|
+
return {
|
|
93
|
+
content: [
|
|
94
|
+
{
|
|
95
|
+
type: 'text',
|
|
96
|
+
text: JSON.stringify({
|
|
97
|
+
success: true,
|
|
98
|
+
message: `Repository '${params.repository}' updated successfully for author '${params.author}'`,
|
|
99
|
+
repository: result,
|
|
100
|
+
updatedFields: Object.keys(updateData),
|
|
101
|
+
webUrl: `${process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'}/repository/${result.id || params.repository}/settings`
|
|
102
|
+
}, null, 2)
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
this.logger.error('UpdateRepositoryTool', 'Failed to update repository', '', error);
|
|
109
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
110
|
+
// Handle specific error cases
|
|
111
|
+
if (errorMessage.includes('not found') || errorMessage.includes('404')) {
|
|
112
|
+
throw new McpError(McpErrorCode.InvalidParams, `Repository '${params.repository}' not found for author '${params.author}'. Please verify the repository name and author identifier.`);
|
|
113
|
+
}
|
|
114
|
+
if (errorMessage.includes('403') || errorMessage.includes('Forbidden')) {
|
|
115
|
+
throw new McpError(McpErrorCode.InvalidParams, `Access denied to update repository '${params.repository}' for author '${params.author}'. You may not have permission to modify this repository.`);
|
|
116
|
+
}
|
|
117
|
+
if (errorMessage.includes('validation') || errorMessage.includes('invalid')) {
|
|
118
|
+
throw new McpError(McpErrorCode.InvalidParams, `Repository update failed due to validation error: ${errorMessage}`);
|
|
119
|
+
}
|
|
120
|
+
// Generic error
|
|
121
|
+
throw new McpError(McpErrorCode.InternalError, `Failed to update repository: ${errorMessage}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=UpdateRepositoryTool.js.map
|
|
@@ -41,11 +41,8 @@ export class ListRepositoriesTool extends BaseToolController {
|
|
|
41
41
|
limit: limit || 'default'
|
|
42
42
|
});
|
|
43
43
|
try {
|
|
44
|
-
// Use
|
|
45
|
-
const response = await this.ucmClient.
|
|
46
|
-
undefined, // no category
|
|
47
|
-
undefined, // no subcategory
|
|
48
|
-
offset, limit);
|
|
44
|
+
// Use listRepositories to get repositories for the author
|
|
45
|
+
const response = await this.ucmClient.listRepositories(author, offset, limit);
|
|
49
46
|
this.logger.info('ListRepositoriesTool', `Listed ${response.data.length} repositories for author: ${author}`, '', {
|
|
50
47
|
total: response.pagination?.total,
|
|
51
48
|
offset: response.pagination?.offset,
|
|
@@ -7,7 +7,7 @@ export class QuickstartTool extends BaseToolController {
|
|
|
7
7
|
return 'mcp_ucm_quickstart';
|
|
8
8
|
}
|
|
9
9
|
get description() {
|
|
10
|
-
return '
|
|
10
|
+
return 'IMPORTANT: READ THIS FIRST before using any other tools as it will guide you to understand how to use the UCM system and MCP tools effectively';
|
|
11
11
|
}
|
|
12
12
|
get inputSchema() {
|
|
13
13
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@utaba/ucm-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Universal Context Manager MCP Server - AI-native artifact management",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"model-context-protocol",
|
|
13
13
|
"ucm",
|
|
14
14
|
"universal-context-manager",
|
|
15
|
+
"context-engineering",
|
|
15
16
|
"ai",
|
|
16
17
|
"context-manager",
|
|
17
|
-
"micro-blocks",
|
|
18
18
|
"utaba"
|
|
19
19
|
],
|
|
20
20
|
"author": {
|
package/package.json.backup
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucm-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Universal Context Manager MCP Server - AI-native artifact management",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"model-context-protocol",
|
|
13
13
|
"ucm",
|
|
14
14
|
"universal-context-manager",
|
|
15
|
+
"context-engineering",
|
|
15
16
|
"ai",
|
|
16
17
|
"context-manager",
|
|
17
|
-
"micro-blocks",
|
|
18
18
|
"utaba"
|
|
19
19
|
],
|
|
20
20
|
"author": {
|