@utaba/ucm-mcp-server 1.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.
- package/LICENSE +29 -0
- package/README.md +79 -0
- package/dist/clients/UcmApiClient.d.ts +53 -0
- package/dist/clients/UcmApiClient.js +297 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +68 -0
- package/dist/interfaces/ILogger.d.ts +8 -0
- package/dist/interfaces/ILogger.js +4 -0
- package/dist/interfaces/IMcpTool.d.ts +7 -0
- package/dist/interfaces/IMcpTool.js +3 -0
- package/dist/logging/ConsoleLogger.d.ts +16 -0
- package/dist/logging/ConsoleLogger.js +45 -0
- package/dist/logging/LoggerFactory.d.ts +9 -0
- package/dist/logging/LoggerFactory.js +12 -0
- package/dist/server/McpConfig.d.ts +26 -0
- package/dist/server/McpConfig.js +93 -0
- package/dist/server/McpHandler.d.ts +12 -0
- package/dist/server/McpHandler.js +69 -0
- package/dist/server/McpServer.d.ts +15 -0
- package/dist/server/McpServer.js +49 -0
- package/dist/server/ToolRegistry.d.ts +22 -0
- package/dist/server/ToolRegistry.js +85 -0
- package/dist/tools/artifacts/GetArtifactController.d.ts +34 -0
- package/dist/tools/artifacts/GetArtifactController.js +397 -0
- package/dist/tools/artifacts/GetLatestController.d.ts +39 -0
- package/dist/tools/artifacts/GetLatestController.js +469 -0
- package/dist/tools/artifacts/ListVersionsController.d.ts +43 -0
- package/dist/tools/artifacts/ListVersionsController.js +530 -0
- package/dist/tools/artifacts/PublishArtifactController.d.ts +37 -0
- package/dist/tools/artifacts/PublishArtifactController.js +605 -0
- package/dist/tools/base/BaseToolController.d.ts +16 -0
- package/dist/tools/base/BaseToolController.js +32 -0
- package/dist/tools/core/DeleteArtifactTool.d.ts +11 -0
- package/dist/tools/core/DeleteArtifactTool.js +82 -0
- package/dist/tools/core/GetArtifactTool.d.ts +13 -0
- package/dist/tools/core/GetArtifactTool.js +125 -0
- package/dist/tools/core/GetArtifactVersionsTool.d.ts +11 -0
- package/dist/tools/core/GetArtifactVersionsTool.js +63 -0
- package/dist/tools/core/GetChunkTool.d.ts +11 -0
- package/dist/tools/core/GetChunkTool.js +56 -0
- package/dist/tools/core/ListArtifactsTool.d.ts +11 -0
- package/dist/tools/core/ListArtifactsTool.js +84 -0
- package/dist/tools/core/PublishArtifactFromFileTool.d.ts +15 -0
- package/dist/tools/core/PublishArtifactFromFileTool.js +256 -0
- package/dist/tools/core/PublishArtifactTool.d.ts +13 -0
- package/dist/tools/core/PublishArtifactTool.js +197 -0
- package/dist/tools/discovery/BrowseCategoriesController.d.ts +25 -0
- package/dist/tools/discovery/BrowseCategoriesController.js +400 -0
- package/dist/tools/discovery/FindByPurposeController.d.ts +12 -0
- package/dist/tools/discovery/FindByPurposeController.js +131 -0
- package/dist/tools/discovery/ListAuthorsController.d.ts +20 -0
- package/dist/tools/discovery/ListAuthorsController.js +274 -0
- package/dist/tools/discovery/SearchArtifactsController.d.ts +14 -0
- package/dist/tools/discovery/SearchArtifactsController.js +226 -0
- package/dist/tools/list/ListNamespaceController.d.ts +1 -0
- package/dist/tools/list/ListNamespaceController.js +8 -0
- package/dist/tools/navigation/ExploreNamespaceController.d.ts +35 -0
- package/dist/tools/navigation/ExploreNamespaceController.js +548 -0
- package/dist/tools/utility/AuthorIndexTool.d.ts +11 -0
- package/dist/tools/utility/AuthorIndexTool.js +48 -0
- package/dist/tools/utility/HealthCheckController.d.ts +11 -0
- package/dist/tools/utility/HealthCheckController.js +56 -0
- package/dist/tools/utility/ListRepositoriesTool.d.ts +11 -0
- package/dist/tools/utility/ListRepositoriesTool.js +70 -0
- package/dist/tools/utility/QuickstartTool.d.ts +11 -0
- package/dist/tools/utility/QuickstartTool.js +36 -0
- package/dist/tools/utility/ValidatePathController.d.ts +30 -0
- package/dist/tools/utility/ValidatePathController.js +465 -0
- package/dist/types/UcmApiTypes.d.ts +40 -0
- package/dist/types/UcmApiTypes.js +4 -0
- package/dist/utils/McpErrorHandler.d.ts +25 -0
- package/dist/utils/McpErrorHandler.js +67 -0
- package/dist/utils/PathUtils.d.ts +61 -0
- package/dist/utils/PathUtils.js +178 -0
- package/dist/utils/ResponseChunker.d.ts +25 -0
- package/dist/utils/ResponseChunker.js +79 -0
- package/dist/utils/ValidationUtils.d.ts +10 -0
- package/dist/utils/ValidationUtils.js +50 -0
- package/package.json +37 -0
- package/package.json.backup +37 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// MCP-specific error handling following MCP standards
|
|
2
|
+
export var McpErrorCode;
|
|
3
|
+
(function (McpErrorCode) {
|
|
4
|
+
McpErrorCode[McpErrorCode["ParseError"] = -32700] = "ParseError";
|
|
5
|
+
McpErrorCode[McpErrorCode["InvalidRequest"] = -32600] = "InvalidRequest";
|
|
6
|
+
McpErrorCode[McpErrorCode["MethodNotFound"] = -32601] = "MethodNotFound";
|
|
7
|
+
McpErrorCode[McpErrorCode["InvalidParams"] = -32602] = "InvalidParams";
|
|
8
|
+
McpErrorCode[McpErrorCode["InternalError"] = -32603] = "InternalError";
|
|
9
|
+
McpErrorCode[McpErrorCode["ExternalServiceUnavailable"] = -32001] = "ExternalServiceUnavailable"; // External resource offline/unavailable
|
|
10
|
+
})(McpErrorCode || (McpErrorCode = {}));
|
|
11
|
+
export class McpError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
data;
|
|
14
|
+
constructor(code, message, data) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.data = data;
|
|
18
|
+
this.name = 'McpError';
|
|
19
|
+
}
|
|
20
|
+
toResponse() {
|
|
21
|
+
return {
|
|
22
|
+
error: {
|
|
23
|
+
code: this.code,
|
|
24
|
+
message: this.message,
|
|
25
|
+
data: this.data
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export class McpErrorHandler {
|
|
31
|
+
static formatError(error) {
|
|
32
|
+
if (error instanceof McpError) {
|
|
33
|
+
return error;
|
|
34
|
+
}
|
|
35
|
+
if (error.error) {
|
|
36
|
+
error = error.error;
|
|
37
|
+
}
|
|
38
|
+
if (error instanceof McpError) {
|
|
39
|
+
return error;
|
|
40
|
+
}
|
|
41
|
+
// Extract error message from various sources
|
|
42
|
+
let message = error.message || error.toString();
|
|
43
|
+
if (!message) {
|
|
44
|
+
message = JSON.stringify(error);
|
|
45
|
+
}
|
|
46
|
+
// Map common errors to MCP error codes
|
|
47
|
+
if (message?.includes('not found')) {
|
|
48
|
+
return new McpError(McpErrorCode.InvalidParams, message, { originalError: error });
|
|
49
|
+
}
|
|
50
|
+
if (message?.includes('validation')) {
|
|
51
|
+
return new McpError(McpErrorCode.InvalidParams, 'Parameter validation failed', { originalError: error });
|
|
52
|
+
}
|
|
53
|
+
if (message?.includes('ECONNREFUSED')) {
|
|
54
|
+
return new McpError(McpErrorCode.ExternalServiceUnavailable, 'External API is unavailable, try the mcp_ucm_health_check tool', { originalError: error });
|
|
55
|
+
}
|
|
56
|
+
if (message?.includes('validation') || message?.includes('invalid') ||
|
|
57
|
+
message?.includes('required') || message?.includes('must be')) {
|
|
58
|
+
return new McpError(McpErrorCode.InvalidParams, message, { originalError: error });
|
|
59
|
+
}
|
|
60
|
+
if (message?.includes('UCM API server error')) {
|
|
61
|
+
return new McpError(McpErrorCode.InternalError, message, { originalError: error });
|
|
62
|
+
}
|
|
63
|
+
// Don't leak internal errors
|
|
64
|
+
return new McpError(McpErrorCode.InternalError, message || 'An internal error occurred');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=McpErrorHandler.js.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for parsing UCM paths into component parts
|
|
3
|
+
*/
|
|
4
|
+
export interface ParsedPath {
|
|
5
|
+
author?: string | undefined;
|
|
6
|
+
repository?: string | undefined;
|
|
7
|
+
category?: string | undefined;
|
|
8
|
+
subcategory?: string | undefined;
|
|
9
|
+
filename?: string;
|
|
10
|
+
version?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Parse a UCM path into its component parts
|
|
14
|
+
* Supports formats like:
|
|
15
|
+
* - "utaba/main/commands/user" (namespace only)
|
|
16
|
+
* - "utaba/main/commands/user/CreateUserCommand.ts" (with filename)
|
|
17
|
+
* - "utaba/main/commands/user/CreateUserCommand.ts@1.0.0" (with version)
|
|
18
|
+
*/
|
|
19
|
+
export declare function parsePath(path: string): ParsedPath;
|
|
20
|
+
/**
|
|
21
|
+
* Build a namespace path from components (author/repository/category/subcategory)
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildNamespacePath(author: string, repository: string, category: string, subcategory: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Build a full path with filename and optional version
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildFullPath(author: string, repository: string, category: string, subcategory: string, filename?: string, version?: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Extract namespace from a full path
|
|
30
|
+
*/
|
|
31
|
+
export declare function extractNamespace(path: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Validate if a path follows UCM naming conventions
|
|
34
|
+
*/
|
|
35
|
+
export declare function validatePath(path: string): {
|
|
36
|
+
isValid: boolean;
|
|
37
|
+
errors: string[];
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Compare two semantic versions
|
|
41
|
+
* Returns: -1 if version1 < version2, 0 if equal, 1 if version1 > version2
|
|
42
|
+
*/
|
|
43
|
+
export declare function compareVersions(version1: string, version2: string): number;
|
|
44
|
+
/**
|
|
45
|
+
* Check if a version is greater than another version
|
|
46
|
+
*/
|
|
47
|
+
export declare function isVersionGreater(version1: string, version2: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Check if a version is less than another version
|
|
50
|
+
*/
|
|
51
|
+
export declare function isVersionLess(version1: string, version2: string): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Check if two versions are equal
|
|
54
|
+
*/
|
|
55
|
+
export declare function isVersionEqual(version1: string, version2: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Increment the patch version of a semantic version
|
|
58
|
+
* Examples: 1.0.0 -> 1.0.1, 2.1.5 -> 2.1.6
|
|
59
|
+
*/
|
|
60
|
+
export declare function incrementPatchVersion(version: string): string;
|
|
61
|
+
//# sourceMappingURL=PathUtils.d.ts.map
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for parsing UCM paths into component parts
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Parse a UCM path into its component parts
|
|
6
|
+
* Supports formats like:
|
|
7
|
+
* - "utaba/main/commands/user" (namespace only)
|
|
8
|
+
* - "utaba/main/commands/user/CreateUserCommand.ts" (with filename)
|
|
9
|
+
* - "utaba/main/commands/user/CreateUserCommand.ts@1.0.0" (with version)
|
|
10
|
+
*/
|
|
11
|
+
export function parsePath(path) {
|
|
12
|
+
if (!path)
|
|
13
|
+
path = "";
|
|
14
|
+
// Remove leading/trailing slashes
|
|
15
|
+
const cleanPath = path.replace(/^\/+|\/+$/g, '');
|
|
16
|
+
// Split into parts
|
|
17
|
+
const parts = cleanPath.split('/');
|
|
18
|
+
const result = {
|
|
19
|
+
author: parts.length > 0 ? parts[0] : undefined,
|
|
20
|
+
repository: parts.length > 1 ? parts[1] : undefined,
|
|
21
|
+
category: parts.length > 2 ? parts[2] : undefined,
|
|
22
|
+
subcategory: parts.length > 3 ? parts[3] : undefined,
|
|
23
|
+
};
|
|
24
|
+
if (parts.length > 4) {
|
|
25
|
+
let filenameParts = parts[4].split('@');
|
|
26
|
+
//filename
|
|
27
|
+
result.filename = filenameParts[0];
|
|
28
|
+
if (filenameParts.length > 1) {
|
|
29
|
+
result.version = filenameParts[1];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build a namespace path from components (author/repository/category/subcategory)
|
|
36
|
+
*/
|
|
37
|
+
export function buildNamespacePath(author, repository, category, subcategory) {
|
|
38
|
+
return `${author}/${repository}/${category}/${subcategory}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Build a full path with filename and optional version
|
|
42
|
+
*/
|
|
43
|
+
export function buildFullPath(author, repository, category, subcategory, filename, version) {
|
|
44
|
+
let path = buildNamespacePath(author, repository, category, subcategory);
|
|
45
|
+
if (filename) {
|
|
46
|
+
path += `/${filename}`;
|
|
47
|
+
if (version) {
|
|
48
|
+
path += `@${version}`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return path;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Extract namespace from a full path
|
|
55
|
+
*/
|
|
56
|
+
export function extractNamespace(path) {
|
|
57
|
+
const parsed = parsePath(path);
|
|
58
|
+
return buildNamespacePath(parsed.author || '', parsed.repository || '', parsed.category || '', parsed.subcategory || '');
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Validate if a path follows UCM naming conventions
|
|
62
|
+
*/
|
|
63
|
+
export function validatePath(path) {
|
|
64
|
+
const errors = [];
|
|
65
|
+
try {
|
|
66
|
+
const parsed = parsePath(path);
|
|
67
|
+
// Validate author (lowercase alphanumeric and hyphens)
|
|
68
|
+
if (!/^[a-z0-9-]+$/.test(parsed.author || '')) {
|
|
69
|
+
errors.push('Author must contain only lowercase letters, numbers, and hyphens');
|
|
70
|
+
}
|
|
71
|
+
// Validate repository (lowercase alphanumeric and hyphens)
|
|
72
|
+
if (!/^[a-z0-9-]+$/.test(parsed.repository || '')) {
|
|
73
|
+
errors.push('Repository must contain only lowercase letters, numbers, and hyphens');
|
|
74
|
+
}
|
|
75
|
+
// Validate category (must be valid UCM category)
|
|
76
|
+
const validCategories = ['commands', 'services', 'patterns', 'implementations', 'contracts', 'guidance', 'project'];
|
|
77
|
+
if (!validCategories.includes(parsed.category || '')) {
|
|
78
|
+
errors.push(`Category must be one of: ${validCategories.join(', ')}`);
|
|
79
|
+
}
|
|
80
|
+
// Validate subcategory (lowercase alphanumeric and hyphens)
|
|
81
|
+
if (!/^[a-z0-9-]+$/.test(parsed.subcategory || '')) {
|
|
82
|
+
errors.push('Subcategory must contain only lowercase letters, numbers, and hyphens');
|
|
83
|
+
}
|
|
84
|
+
// Validate filename if present
|
|
85
|
+
if (parsed.filename) {
|
|
86
|
+
if (!/^[a-zA-Z0-9._-]+\.[a-z]+$/.test(parsed.filename)) {
|
|
87
|
+
errors.push('Filename must be valid with an extension');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Validate version if present
|
|
91
|
+
if (parsed.version) {
|
|
92
|
+
if (!/^v?\d+\.\d+\.\d+(-[a-z0-9-]+)?(\+[a-z0-9-]+)?$/.test(parsed.version)) {
|
|
93
|
+
errors.push('Version must follow semantic versioning (e.g., 1.0.0, v2.1.0)');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
errors.push(error instanceof Error ? error.message : 'Invalid path format');
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
isValid: errors.length === 0,
|
|
102
|
+
errors
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Compare two semantic versions
|
|
107
|
+
* Returns: -1 if version1 < version2, 0 if equal, 1 if version1 > version2
|
|
108
|
+
*/
|
|
109
|
+
export function compareVersions(version1, version2) {
|
|
110
|
+
// Normalize versions by removing 'v' prefix if present
|
|
111
|
+
const v1 = version1.replace(/^v/, '');
|
|
112
|
+
const v2 = version2.replace(/^v/, '');
|
|
113
|
+
// Split versions into parts
|
|
114
|
+
const parts1 = v1.split(/[.-]/);
|
|
115
|
+
const parts2 = v2.split(/[.-]/);
|
|
116
|
+
// Compare major, minor, patch
|
|
117
|
+
for (let i = 0; i < 3; i++) {
|
|
118
|
+
const num1 = parseInt(parts1[i] || '0', 10);
|
|
119
|
+
const num2 = parseInt(parts2[i] || '0', 10);
|
|
120
|
+
if (num1 > num2)
|
|
121
|
+
return 1;
|
|
122
|
+
if (num1 < num2)
|
|
123
|
+
return -1;
|
|
124
|
+
}
|
|
125
|
+
// If major.minor.patch are equal, compare pre-release versions
|
|
126
|
+
const preRelease1 = parts1.slice(3).join('.');
|
|
127
|
+
const preRelease2 = parts2.slice(3).join('.');
|
|
128
|
+
// No pre-release is greater than pre-release
|
|
129
|
+
if (!preRelease1 && preRelease2)
|
|
130
|
+
return 1;
|
|
131
|
+
if (preRelease1 && !preRelease2)
|
|
132
|
+
return -1;
|
|
133
|
+
if (!preRelease1 && !preRelease2)
|
|
134
|
+
return 0;
|
|
135
|
+
// Compare pre-release versions lexically
|
|
136
|
+
return preRelease1.localeCompare(preRelease2);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Check if a version is greater than another version
|
|
140
|
+
*/
|
|
141
|
+
export function isVersionGreater(version1, version2) {
|
|
142
|
+
return compareVersions(version1, version2) > 0;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Check if a version is less than another version
|
|
146
|
+
*/
|
|
147
|
+
export function isVersionLess(version1, version2) {
|
|
148
|
+
return compareVersions(version1, version2) < 0;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Check if two versions are equal
|
|
152
|
+
*/
|
|
153
|
+
export function isVersionEqual(version1, version2) {
|
|
154
|
+
return compareVersions(version1, version2) === 0;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Increment the patch version of a semantic version
|
|
158
|
+
* Examples: 1.0.0 -> 1.0.1, 2.1.5 -> 2.1.6
|
|
159
|
+
*/
|
|
160
|
+
export function incrementPatchVersion(version) {
|
|
161
|
+
// Normalize version by removing 'v' prefix if present
|
|
162
|
+
const normalized = version.replace(/^v/, '');
|
|
163
|
+
// Split version into parts
|
|
164
|
+
const parts = normalized.split('.');
|
|
165
|
+
if (parts.length < 3) {
|
|
166
|
+
throw new Error(`Invalid semantic version format: ${version}`);
|
|
167
|
+
}
|
|
168
|
+
// Parse major, minor, patch
|
|
169
|
+
const major = parseInt(parts[0], 10);
|
|
170
|
+
const minor = parseInt(parts[1], 10);
|
|
171
|
+
const patch = parseInt(parts[2].split('-')[0], 10); // Handle pre-release versions
|
|
172
|
+
if (isNaN(major) || isNaN(minor) || isNaN(patch)) {
|
|
173
|
+
throw new Error(`Invalid semantic version format: ${version}`);
|
|
174
|
+
}
|
|
175
|
+
// Increment patch version
|
|
176
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=PathUtils.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface ChunkedResponse {
|
|
2
|
+
chunk: {
|
|
3
|
+
id: string;
|
|
4
|
+
sequence: number;
|
|
5
|
+
total: number;
|
|
6
|
+
data: string;
|
|
7
|
+
};
|
|
8
|
+
hasMore: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare class ResponseChunker {
|
|
11
|
+
private static readonly MAX_CHUNK_SIZE;
|
|
12
|
+
private static chunks;
|
|
13
|
+
private static readonly CHUNK_TTL;
|
|
14
|
+
private static chunkTimestamps;
|
|
15
|
+
static chunk(data: any, chunkId?: string): ChunkedResponse | any;
|
|
16
|
+
static getNextChunk(chunkId: string, sequence: number): ChunkedResponse | null;
|
|
17
|
+
static cleanup(chunkId: string): void;
|
|
18
|
+
static cleanupExpired(): void;
|
|
19
|
+
static getChunkInfo(chunkId: string): {
|
|
20
|
+
exists: boolean;
|
|
21
|
+
total?: number;
|
|
22
|
+
expires?: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=ResponseChunker.d.ts.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export class ResponseChunker {
|
|
2
|
+
static MAX_CHUNK_SIZE = 20000; // ~20k characters to stay under token limit
|
|
3
|
+
static chunks = new Map();
|
|
4
|
+
static CHUNK_TTL = 5 * 60 * 1000; // 5 minutes
|
|
5
|
+
static chunkTimestamps = new Map();
|
|
6
|
+
static chunk(data, chunkId) {
|
|
7
|
+
const serialized = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
|
|
8
|
+
// If small enough, return as-is
|
|
9
|
+
if (serialized.length < this.MAX_CHUNK_SIZE) {
|
|
10
|
+
return data;
|
|
11
|
+
}
|
|
12
|
+
// Generate chunk ID if not provided
|
|
13
|
+
const id = chunkId || `ucm_chunk_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
14
|
+
// Split into chunks
|
|
15
|
+
const chunks = [];
|
|
16
|
+
for (let i = 0; i < serialized.length; i += this.MAX_CHUNK_SIZE) {
|
|
17
|
+
chunks.push(serialized.slice(i, i + this.MAX_CHUNK_SIZE));
|
|
18
|
+
}
|
|
19
|
+
// Store chunks for retrieval with timestamp
|
|
20
|
+
this.chunks.set(id, chunks);
|
|
21
|
+
this.chunkTimestamps.set(id, Date.now());
|
|
22
|
+
// Schedule cleanup after TTL
|
|
23
|
+
setTimeout(() => {
|
|
24
|
+
this.cleanup(id);
|
|
25
|
+
}, this.CHUNK_TTL);
|
|
26
|
+
// Return first chunk with metadata
|
|
27
|
+
return {
|
|
28
|
+
chunk: {
|
|
29
|
+
id,
|
|
30
|
+
sequence: 0,
|
|
31
|
+
total: chunks.length,
|
|
32
|
+
data: chunks[0]
|
|
33
|
+
},
|
|
34
|
+
hasMore: chunks.length > 1
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
static getNextChunk(chunkId, sequence) {
|
|
38
|
+
const chunks = this.chunks.get(chunkId);
|
|
39
|
+
if (!chunks || sequence >= chunks.length || sequence < 0) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
// Update timestamp to extend TTL
|
|
43
|
+
this.chunkTimestamps.set(chunkId, Date.now());
|
|
44
|
+
return {
|
|
45
|
+
chunk: {
|
|
46
|
+
id: chunkId,
|
|
47
|
+
sequence,
|
|
48
|
+
total: chunks.length,
|
|
49
|
+
data: chunks[sequence]
|
|
50
|
+
},
|
|
51
|
+
hasMore: sequence < chunks.length - 1
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
static cleanup(chunkId) {
|
|
55
|
+
this.chunks.delete(chunkId);
|
|
56
|
+
this.chunkTimestamps.delete(chunkId);
|
|
57
|
+
}
|
|
58
|
+
static cleanupExpired() {
|
|
59
|
+
const now = Date.now();
|
|
60
|
+
for (const [id, timestamp] of this.chunkTimestamps.entries()) {
|
|
61
|
+
if (now - timestamp > this.CHUNK_TTL) {
|
|
62
|
+
this.cleanup(id);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
static getChunkInfo(chunkId) {
|
|
67
|
+
const chunks = this.chunks.get(chunkId);
|
|
68
|
+
const timestamp = this.chunkTimestamps.get(chunkId);
|
|
69
|
+
if (!chunks || !timestamp) {
|
|
70
|
+
return { exists: false };
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
exists: true,
|
|
74
|
+
total: chunks.length,
|
|
75
|
+
expires: timestamp + this.CHUNK_TTL
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=ResponseChunker.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class ValidationUtils {
|
|
2
|
+
static validateArtifactPath(path: string): void;
|
|
3
|
+
static sanitizeSearchQuery(query: string): string;
|
|
4
|
+
static validateContentSize(content: string, maxSize?: number): void;
|
|
5
|
+
static validatePageParams(offset?: number, limit?: number): void;
|
|
6
|
+
static validateAuthorId(authorId: string): void;
|
|
7
|
+
static validateCategory(category: string): void;
|
|
8
|
+
static validateVersion(version: string): void;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=ValidationUtils.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { McpError, McpErrorCode } from './McpErrorHandler.js';
|
|
2
|
+
export class ValidationUtils {
|
|
3
|
+
static validateArtifactPath(path) {
|
|
4
|
+
// Prevent path traversal
|
|
5
|
+
if (path.includes('..') || path.includes('//')) {
|
|
6
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Invalid artifact path');
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
static sanitizeSearchQuery(query) {
|
|
10
|
+
// Remove potentially dangerous characters
|
|
11
|
+
return query.replace(/[<>\"'&]/g, '').trim();
|
|
12
|
+
}
|
|
13
|
+
static validateContentSize(content, maxSize = 1024 * 1024) {
|
|
14
|
+
if (content.length > maxSize) {
|
|
15
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Content size exceeds maximum allowed');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
static validatePageParams(offset = 0, limit = 20) {
|
|
19
|
+
if (offset < 0) {
|
|
20
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Offset must be non-negative');
|
|
21
|
+
}
|
|
22
|
+
if (limit < 1 || limit > 100) {
|
|
23
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Limit must be between 1 and 100');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
static validateAuthorId(authorId) {
|
|
27
|
+
if (!authorId || typeof authorId !== 'string') {
|
|
28
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Author ID is required and must be a string');
|
|
29
|
+
}
|
|
30
|
+
// Basic validation - alphanumeric, hyphens, underscores
|
|
31
|
+
const authorRegex = /^[a-zA-Z0-9\-_]+$/;
|
|
32
|
+
if (!authorRegex.test(authorId)) {
|
|
33
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Author ID contains invalid characters');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
static validateCategory(category) {
|
|
37
|
+
const validCategories = ['commands', 'services', 'patterns', 'implementations', 'contracts', 'guidance'];
|
|
38
|
+
if (!validCategories.includes(category)) {
|
|
39
|
+
throw new McpError(McpErrorCode.InvalidParams, `Invalid category. Must be one of: ${validCategories.join(', ')}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
static validateVersion(version) {
|
|
43
|
+
// Semantic version validation
|
|
44
|
+
const versionRegex = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9\-_]+)?$/;
|
|
45
|
+
if (!versionRegex.test(version)) {
|
|
46
|
+
throw new McpError(McpErrorCode.InvalidParams, 'Version must follow semantic versioning format');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=ValidationUtils.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@utaba/ucm-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Universal Context Manager MCP Server - AI-native artifact management",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ucm-mcp-server": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"mcp",
|
|
12
|
+
"model-context-protocol",
|
|
13
|
+
"ucm",
|
|
14
|
+
"universal-context-manager",
|
|
15
|
+
"ai",
|
|
16
|
+
"context-manager",
|
|
17
|
+
"micro-blocks",
|
|
18
|
+
"utaba"
|
|
19
|
+
],
|
|
20
|
+
"author": {
|
|
21
|
+
"name": "Utaba AI",
|
|
22
|
+
"url": "https://utaba.ai"
|
|
23
|
+
},
|
|
24
|
+
"license": "BSD-3-Clause",
|
|
25
|
+
"homepage": "https://ucm.utaba.ai",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
28
|
+
"commander": "^14.0.0",
|
|
29
|
+
"axios": "^1.10.0"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18.0.0"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ucm-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Universal Context Manager MCP Server - AI-native artifact management",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ucm-mcp-server": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"mcp",
|
|
12
|
+
"model-context-protocol",
|
|
13
|
+
"ucm",
|
|
14
|
+
"universal-context-manager",
|
|
15
|
+
"ai",
|
|
16
|
+
"context-manager",
|
|
17
|
+
"micro-blocks",
|
|
18
|
+
"utaba"
|
|
19
|
+
],
|
|
20
|
+
"author": {
|
|
21
|
+
"name": "Utaba AI",
|
|
22
|
+
"url": "https://utaba.ai"
|
|
23
|
+
},
|
|
24
|
+
"license": "BSD-3-Clause",
|
|
25
|
+
"homepage": "https://ucm.utaba.ai",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
28
|
+
"commander": "^14.0.0",
|
|
29
|
+
"axios": "^1.10.0"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18.0.0"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
}
|
|
37
|
+
}
|