@yinuo-ngm/mcp-server 0.1.1 → 0.1.2
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/README.md +97 -43
- package/lib/register-tools.js +6 -2
- package/lib/tools/hub-v2/client.d.ts +15 -0
- package/lib/tools/hub-v2/client.js +97 -0
- package/lib/tools/hub-v2/config.d.ts +34 -0
- package/lib/tools/hub-v2/config.js +297 -0
- package/lib/tools/hub-v2/docs.tools.d.ts +2 -0
- package/lib/tools/hub-v2/docs.tools.js +81 -0
- package/lib/tools/hub-v2/errors.d.ts +8 -0
- package/lib/tools/hub-v2/errors.js +27 -0
- package/lib/tools/hub-v2/index.d.ts +2 -0
- package/lib/tools/hub-v2/index.js +17 -0
- package/lib/tools/hub-v2/issues.tools.d.ts +2 -0
- package/lib/tools/hub-v2/issues.tools.js +154 -0
- package/lib/tools/hub-v2/projects.tools.d.ts +2 -0
- package/lib/tools/hub-v2/projects.tools.js +28 -0
- package/lib/tools/hub-v2/rd.tools.d.ts +2 -0
- package/lib/tools/hub-v2/rd.tools.js +202 -0
- package/lib/tools/hub-v2/schemas.d.ts +585 -0
- package/lib/tools/hub-v2/schemas.js +167 -0
- package/lib/tools/hub-v2/upload.tools.d.ts +2 -0
- package/lib/tools/hub-v2/upload.tools.js +150 -0
- package/lib/tools/index.d.ts +2 -0
- package/lib/tools/index.js +2 -0
- package/lib/utils/errors.d.ts +5 -0
- package/lib/utils/errors.js +12 -0
- package/lib/utils/result.d.ts +11 -2
- package/lib/utils/result.js +99 -3
- package/package.json +7 -3
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rdUpdateProgressSchema = exports.rdStageTaskCreateSchema = exports.rdStageTasksListSchema = exports.rdAdvanceStageSchema = exports.rdCreateSchema = exports.rdGetSchema = exports.rdListSchema = exports.markdownImageUploadSchema = exports.issueUpdateSchema = exports.issueCommentSchema = exports.issueCreateSchema = exports.issueGetSchema = exports.issuesListSchema = exports.docsGetBySlugSchema = exports.docsGetSchema = exports.docsListSchema = exports.pagingSchema = exports.projectSelectorSchema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
exports.projectSelectorSchema = zod_1.z.object({
|
|
6
|
+
project: zod_1.z.string().trim().min(1).optional(),
|
|
7
|
+
projectKey: zod_1.z.string().trim().min(1).optional(),
|
|
8
|
+
}).strict();
|
|
9
|
+
exports.pagingSchema = zod_1.z.object({
|
|
10
|
+
page: zod_1.z.number().int().min(1).optional(),
|
|
11
|
+
pageSize: zod_1.z.number().int().min(1).max(200).optional(),
|
|
12
|
+
}).strict();
|
|
13
|
+
exports.docsListSchema = exports.projectSelectorSchema.extend({
|
|
14
|
+
page: zod_1.z.number().int().min(1).optional(),
|
|
15
|
+
pageSize: zod_1.z.number().int().min(1).max(200).optional(),
|
|
16
|
+
status: zod_1.z.enum(["draft", "published", "archived"]).optional(),
|
|
17
|
+
keyword: zod_1.z.string().trim().min(1).optional(),
|
|
18
|
+
category: zod_1.z.string().trim().min(1).optional(),
|
|
19
|
+
categoryId: zod_1.z.string().trim().min(1).optional(),
|
|
20
|
+
}).strict();
|
|
21
|
+
exports.docsGetSchema = exports.projectSelectorSchema.extend({
|
|
22
|
+
docId: zod_1.z.string().trim().min(1),
|
|
23
|
+
}).strict();
|
|
24
|
+
exports.docsGetBySlugSchema = exports.projectSelectorSchema.extend({
|
|
25
|
+
slug: zod_1.z.string().trim().min(1),
|
|
26
|
+
contentOnly: zod_1.z.boolean().optional(),
|
|
27
|
+
}).strict();
|
|
28
|
+
exports.issuesListSchema = exports.projectSelectorSchema.extend({
|
|
29
|
+
page: zod_1.z.number().int().min(1).optional(),
|
|
30
|
+
pageSize: zod_1.z.number().int().min(1).max(200).optional(),
|
|
31
|
+
keyword: zod_1.z.string().trim().min(1).optional(),
|
|
32
|
+
status: zod_1.z.array(zod_1.z.string().trim().min(1)).optional(),
|
|
33
|
+
priority: zod_1.z.array(zod_1.z.string().trim().min(1)).optional(),
|
|
34
|
+
assigneeId: zod_1.z.string().trim().min(1).optional(),
|
|
35
|
+
verifierId: zod_1.z.string().trim().min(1).optional(),
|
|
36
|
+
rdItemId: zod_1.z.string().trim().min(1).optional(),
|
|
37
|
+
}).strict();
|
|
38
|
+
exports.issueGetSchema = exports.projectSelectorSchema.extend({
|
|
39
|
+
issueId: zod_1.z.string().trim().min(1),
|
|
40
|
+
}).strict();
|
|
41
|
+
const issueTypeSchema = zod_1.z.enum(["bug", "feature", "change", "improvement", "task", "test"]);
|
|
42
|
+
const issuePrioritySchema = zod_1.z.enum(["low", "medium", "high", "critical"]);
|
|
43
|
+
exports.issueCreateSchema = exports.projectSelectorSchema.extend({
|
|
44
|
+
title: zod_1.z.string().trim().min(1),
|
|
45
|
+
description: zod_1.z.string().optional(),
|
|
46
|
+
type: issueTypeSchema.optional(),
|
|
47
|
+
priority: issuePrioritySchema.optional(),
|
|
48
|
+
assigneeId: zod_1.z.string().trim().nullable().optional(),
|
|
49
|
+
verifierId: zod_1.z.string().trim().nullable().optional(),
|
|
50
|
+
rdItemId: zod_1.z.string().trim().nullable().optional(),
|
|
51
|
+
moduleCode: zod_1.z.string().trim().optional(),
|
|
52
|
+
versionCode: zod_1.z.string().trim().optional(),
|
|
53
|
+
environmentCode: zod_1.z.string().trim().optional(),
|
|
54
|
+
confirm: zod_1.z.boolean().optional(),
|
|
55
|
+
}).strict();
|
|
56
|
+
exports.issueCommentSchema = exports.projectSelectorSchema.extend({
|
|
57
|
+
issueId: zod_1.z.string().trim().min(1),
|
|
58
|
+
content: zod_1.z.string().min(1),
|
|
59
|
+
mentions: zod_1.z.array(zod_1.z.string().trim().min(1)).optional(),
|
|
60
|
+
confirm: zod_1.z.boolean().optional(),
|
|
61
|
+
}).strict();
|
|
62
|
+
exports.issueUpdateSchema = exports.projectSelectorSchema.extend({
|
|
63
|
+
issueId: zod_1.z.string().trim().min(1),
|
|
64
|
+
title: zod_1.z.string().trim().optional(),
|
|
65
|
+
description: zod_1.z.string().nullable().optional(),
|
|
66
|
+
type: issueTypeSchema.optional(),
|
|
67
|
+
priority: issuePrioritySchema.optional(),
|
|
68
|
+
rdItemId: zod_1.z.string().trim().nullable().optional(),
|
|
69
|
+
moduleCode: zod_1.z.string().trim().nullable().optional(),
|
|
70
|
+
versionCode: zod_1.z.string().trim().nullable().optional(),
|
|
71
|
+
environmentCode: zod_1.z.string().trim().nullable().optional(),
|
|
72
|
+
confirm: zod_1.z.boolean().optional(),
|
|
73
|
+
}).strict();
|
|
74
|
+
exports.markdownImageUploadSchema = exports.projectSelectorSchema.extend({
|
|
75
|
+
filePath: zod_1.z.string().trim().min(1).optional(),
|
|
76
|
+
contentBase64: zod_1.z.string().trim().min(1).optional(),
|
|
77
|
+
fileName: zod_1.z.string().trim().min(1).optional(),
|
|
78
|
+
mimeType: zod_1.z.string().trim().min(1).optional(),
|
|
79
|
+
alt: zod_1.z.string().trim().min(1).optional(),
|
|
80
|
+
confirm: zod_1.z.boolean().optional(),
|
|
81
|
+
}).strict();
|
|
82
|
+
exports.rdListSchema = exports.projectSelectorSchema.extend({
|
|
83
|
+
page: zod_1.z.number().int().min(1).optional(),
|
|
84
|
+
pageSize: zod_1.z.number().int().min(1).max(200).optional(),
|
|
85
|
+
keyword: zod_1.z.string().trim().min(1).optional(),
|
|
86
|
+
stageId: zod_1.z.string().trim().min(1).optional(),
|
|
87
|
+
status: zod_1.z.array(zod_1.z.string().trim().min(1)).optional(),
|
|
88
|
+
type: zod_1.z.array(zod_1.z.string().trim().min(1)).optional(),
|
|
89
|
+
priority: zod_1.z.array(zod_1.z.string().trim().min(1)).optional(),
|
|
90
|
+
assigneeId: zod_1.z.string().trim().min(1).optional(),
|
|
91
|
+
}).strict();
|
|
92
|
+
exports.rdGetSchema = exports.projectSelectorSchema.extend({
|
|
93
|
+
itemId: zod_1.z.string().trim().min(1),
|
|
94
|
+
}).strict();
|
|
95
|
+
const rdTypeSchema = zod_1.z.enum([
|
|
96
|
+
"feature_dev",
|
|
97
|
+
"tech_refactor",
|
|
98
|
+
"integration",
|
|
99
|
+
"env_setup",
|
|
100
|
+
"bug_fix",
|
|
101
|
+
"requirement_confirmation",
|
|
102
|
+
"solution_design",
|
|
103
|
+
"testing_validation",
|
|
104
|
+
"delivery_launch",
|
|
105
|
+
"project_closure",
|
|
106
|
+
]);
|
|
107
|
+
const rdPrioritySchema = zod_1.z.enum(["low", "medium", "high", "critical"]);
|
|
108
|
+
const rdInitialStageTaskSchema = zod_1.z.object({
|
|
109
|
+
templateId: zod_1.z.string().trim().min(1).nullable().optional(),
|
|
110
|
+
title: zod_1.z.string().trim().min(1),
|
|
111
|
+
description: zod_1.z.string().trim().nullable().optional(),
|
|
112
|
+
ownerId: zod_1.z.string().trim().nullable().optional(),
|
|
113
|
+
plannedStartAt: zod_1.z.string().trim().nullable().optional(),
|
|
114
|
+
plannedEndAt: zod_1.z.string().trim().nullable().optional(),
|
|
115
|
+
}).strict();
|
|
116
|
+
const rdStageTaskTemplateSelectionSchema = zod_1.z.object({
|
|
117
|
+
templateId: zod_1.z.string().trim().min(1),
|
|
118
|
+
ownerId: zod_1.z.string().trim().nullable().optional(),
|
|
119
|
+
}).strict();
|
|
120
|
+
exports.rdCreateSchema = exports.projectSelectorSchema.extend({
|
|
121
|
+
title: zod_1.z.string().trim().min(1),
|
|
122
|
+
description: zod_1.z.string().optional(),
|
|
123
|
+
stageId: zod_1.z.string().trim().nullable().optional(),
|
|
124
|
+
type: rdTypeSchema.optional(),
|
|
125
|
+
priority: rdPrioritySchema.optional(),
|
|
126
|
+
memberIds: zod_1.z.array(zod_1.z.string().trim().min(1)).min(1),
|
|
127
|
+
verifierId: zod_1.z.string().trim().nullable().optional(),
|
|
128
|
+
planStartAt: zod_1.z.string().trim().optional(),
|
|
129
|
+
planEndAt: zod_1.z.string().trim().optional(),
|
|
130
|
+
stageTasks: zod_1.z.array(rdInitialStageTaskSchema).optional(),
|
|
131
|
+
stageTaskTemplates: zod_1.z.array(rdStageTaskTemplateSelectionSchema).optional(),
|
|
132
|
+
confirm: zod_1.z.boolean().optional(),
|
|
133
|
+
}).strict();
|
|
134
|
+
exports.rdAdvanceStageSchema = exports.projectSelectorSchema.extend({
|
|
135
|
+
itemId: zod_1.z.string().trim().min(1),
|
|
136
|
+
stageId: zod_1.z.string().trim().min(1),
|
|
137
|
+
memberIds: zod_1.z.array(zod_1.z.string().trim().min(1)).optional(),
|
|
138
|
+
description: zod_1.z.string().trim().min(1).optional(),
|
|
139
|
+
planStartAt: zod_1.z.string().trim().min(1).optional(),
|
|
140
|
+
planEndAt: zod_1.z.string().trim().min(1).optional(),
|
|
141
|
+
stageTasks: zod_1.z.array(rdInitialStageTaskSchema).optional(),
|
|
142
|
+
stageTaskTemplates: zod_1.z.array(rdStageTaskTemplateSelectionSchema).optional(),
|
|
143
|
+
confirm: zod_1.z.boolean().optional(),
|
|
144
|
+
}).strict();
|
|
145
|
+
exports.rdStageTasksListSchema = exports.projectSelectorSchema.extend({
|
|
146
|
+
itemId: zod_1.z.string().trim().min(1),
|
|
147
|
+
}).strict();
|
|
148
|
+
exports.rdStageTaskCreateSchema = exports.projectSelectorSchema.extend({
|
|
149
|
+
itemId: zod_1.z.string().trim().min(1),
|
|
150
|
+
title: zod_1.z.string().trim().min(1).max(200),
|
|
151
|
+
description: zod_1.z.string().trim().max(2000).nullable().optional(),
|
|
152
|
+
ownerIds: zod_1.z.array(zod_1.z.string().trim().min(1)).min(1),
|
|
153
|
+
plannedStartAt: zod_1.z.string().trim().nullable().optional(),
|
|
154
|
+
plannedEndAt: zod_1.z.string().trim().nullable().optional(),
|
|
155
|
+
sortOrder: zod_1.z.number().int().optional(),
|
|
156
|
+
remark: zod_1.z.string().trim().max(1000).nullable().optional(),
|
|
157
|
+
confirm: zod_1.z.boolean().optional(),
|
|
158
|
+
}).strict();
|
|
159
|
+
exports.rdUpdateProgressSchema = exports.projectSelectorSchema.extend({
|
|
160
|
+
itemId: zod_1.z.string().trim().min(1),
|
|
161
|
+
progress: zod_1.z.number().int().min(0).max(100),
|
|
162
|
+
note: zod_1.z.string().trim().optional(),
|
|
163
|
+
blockReason: zod_1.z.string().trim().min(1).max(500).optional(),
|
|
164
|
+
resolveBlockId: zod_1.z.string().trim().min(1).optional(),
|
|
165
|
+
stageTaskId: zod_1.z.string().trim().min(1).optional(),
|
|
166
|
+
confirm: zod_1.z.boolean().optional(),
|
|
167
|
+
}).strict();
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.hubV2UploadTools = hubV2UploadTools;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const client_1 = require("./client");
|
|
10
|
+
const config_1 = require("./config");
|
|
11
|
+
const schemas_1 = require("./schemas");
|
|
12
|
+
const result_1 = require("../../utils/result");
|
|
13
|
+
function hubV2UploadTools() {
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
name: "hub_v2_upload_markdown_image",
|
|
17
|
+
description: "Upload a local or base64 image with Personal Token and return Markdown for Hub V2 content.",
|
|
18
|
+
riskLevel: "write",
|
|
19
|
+
inputSchema: schemas_1.markdownImageUploadSchema,
|
|
20
|
+
allowPreviewWhenBlocked: true,
|
|
21
|
+
isConfirmed: (args) => args.confirm === true,
|
|
22
|
+
async handler(args) {
|
|
23
|
+
const path = "/uploads/markdown";
|
|
24
|
+
if (!args.confirm) {
|
|
25
|
+
return (0, result_1.ok)("hub_v2_upload_markdown_image", {
|
|
26
|
+
code: "PREVIEW",
|
|
27
|
+
message: "set confirm=true to upload this image",
|
|
28
|
+
data: {
|
|
29
|
+
method: "POST",
|
|
30
|
+
path,
|
|
31
|
+
requiredScope: "issue:create:write or issue:update:write or issue:comment:write or rd:create:write or rd:stage-task:write or rd:transition:write or rd:edit:write",
|
|
32
|
+
maxBytes: maxUploadBytes(),
|
|
33
|
+
input: {
|
|
34
|
+
mode: args.filePath ? "filePath" : "contentBase64",
|
|
35
|
+
fileName: args.fileName,
|
|
36
|
+
mimeType: args.mimeType,
|
|
37
|
+
alt: args.alt,
|
|
38
|
+
hasFilePath: Boolean(args.filePath),
|
|
39
|
+
hasContentBase64: Boolean(args.contentBase64),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const ctx = (0, config_1.resolveHubV2Context)(args, "personal");
|
|
45
|
+
const client = new client_1.HubV2Client(ctx);
|
|
46
|
+
const file = resolveUploadFile(args);
|
|
47
|
+
const form = new FormData();
|
|
48
|
+
const bytes = new Uint8Array(file.content.byteLength);
|
|
49
|
+
bytes.set(file.content);
|
|
50
|
+
form.append("file", new Blob([bytes], { type: file.mimeType }), file.fileName);
|
|
51
|
+
if (args.alt) {
|
|
52
|
+
form.append("alt", args.alt);
|
|
53
|
+
}
|
|
54
|
+
const data = await client.multipart("POST", client.personalUrl("/uploads/markdown"), form);
|
|
55
|
+
return (0, result_1.ok)("hub_v2_upload_markdown_image", data);
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
function resolveUploadFile(args) {
|
|
61
|
+
if (Boolean(args.filePath) === Boolean(args.contentBase64)) {
|
|
62
|
+
throw new Error("provide exactly one of filePath or contentBase64");
|
|
63
|
+
}
|
|
64
|
+
if (args.filePath) {
|
|
65
|
+
const resolvedPath = node_path_1.default.resolve(args.filePath);
|
|
66
|
+
assertAllowedFilePath(resolvedPath);
|
|
67
|
+
if (!(0, node_fs_1.existsSync)(resolvedPath) || !(0, node_fs_1.statSync)(resolvedPath).isFile()) {
|
|
68
|
+
throw new Error(`upload file not found: ${resolvedPath}`);
|
|
69
|
+
}
|
|
70
|
+
const fileSize = (0, node_fs_1.statSync)(resolvedPath).size;
|
|
71
|
+
assertUploadSize(fileSize);
|
|
72
|
+
return {
|
|
73
|
+
content: (0, node_fs_1.readFileSync)(resolvedPath),
|
|
74
|
+
fileName: node_path_1.default.basename(resolvedPath),
|
|
75
|
+
mimeType: args.mimeType ?? inferMimeType(resolvedPath),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const fileName = args.fileName?.trim();
|
|
79
|
+
if (!args.contentBase64 || !fileName) {
|
|
80
|
+
throw new Error("contentBase64 and fileName are required");
|
|
81
|
+
}
|
|
82
|
+
const content = decodeBase64Content(args.contentBase64);
|
|
83
|
+
return {
|
|
84
|
+
content,
|
|
85
|
+
fileName,
|
|
86
|
+
mimeType: args.mimeType ?? inferMimeType(fileName),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function maxUploadBytes() {
|
|
90
|
+
const value = Number.parseInt(String(process.env.NGM_MCP_MAX_UPLOAD_BYTES ?? ""), 10);
|
|
91
|
+
return Number.isFinite(value) && value > 0 ? value : 5 * 1024 * 1024;
|
|
92
|
+
}
|
|
93
|
+
function assertUploadSize(fileSize) {
|
|
94
|
+
const maxBytes = maxUploadBytes();
|
|
95
|
+
if (fileSize > maxBytes) {
|
|
96
|
+
throw new Error(`upload image is too large: ${fileSize} bytes exceeds NGM_MCP_MAX_UPLOAD_BYTES=${maxBytes}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function decodeBase64Content(value) {
|
|
100
|
+
const normalized = value.trim().replace(/^data:[^,]+,/, "").replace(/\s+/g, "");
|
|
101
|
+
if (!normalized) {
|
|
102
|
+
throw new Error("contentBase64 is empty");
|
|
103
|
+
}
|
|
104
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(normalized) || normalized.length % 4 !== 0) {
|
|
105
|
+
throw new Error("contentBase64 is not a valid base64 string");
|
|
106
|
+
}
|
|
107
|
+
const firstPaddingIndex = normalized.indexOf("=");
|
|
108
|
+
if (firstPaddingIndex !== -1 && !/^={1,2}$/.test(normalized.slice(firstPaddingIndex))) {
|
|
109
|
+
throw new Error("contentBase64 is not a valid base64 string");
|
|
110
|
+
}
|
|
111
|
+
const content = Buffer.from(normalized, "base64");
|
|
112
|
+
if (content.byteLength === 0) {
|
|
113
|
+
throw new Error("contentBase64 is empty");
|
|
114
|
+
}
|
|
115
|
+
assertUploadSize(content.byteLength);
|
|
116
|
+
return content;
|
|
117
|
+
}
|
|
118
|
+
function assertAllowedFilePath(filePath) {
|
|
119
|
+
const roots = [
|
|
120
|
+
process.env.NGM_WORKSPACE_ROOT,
|
|
121
|
+
process.env.NGM_MCP_UPLOAD_ROOT,
|
|
122
|
+
process.cwd(),
|
|
123
|
+
]
|
|
124
|
+
.map((item) => item?.trim())
|
|
125
|
+
.filter((item) => Boolean(item))
|
|
126
|
+
.map((item) => node_path_1.default.resolve(item));
|
|
127
|
+
const allowed = roots.some((root) => filePath === root || filePath.startsWith(`${root}${node_path_1.default.sep}`));
|
|
128
|
+
if (!allowed) {
|
|
129
|
+
throw new Error("filePath must be under NGM_WORKSPACE_ROOT, NGM_MCP_UPLOAD_ROOT, or the current workspace");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function inferMimeType(fileName) {
|
|
133
|
+
switch (node_path_1.default.extname(fileName).toLowerCase()) {
|
|
134
|
+
case ".png":
|
|
135
|
+
return "image/png";
|
|
136
|
+
case ".jpg":
|
|
137
|
+
case ".jpeg":
|
|
138
|
+
return "image/jpeg";
|
|
139
|
+
case ".gif":
|
|
140
|
+
return "image/gif";
|
|
141
|
+
case ".webp":
|
|
142
|
+
return "image/webp";
|
|
143
|
+
case ".bmp":
|
|
144
|
+
return "image/bmp";
|
|
145
|
+
case ".svg":
|
|
146
|
+
return "image/svg+xml";
|
|
147
|
+
default:
|
|
148
|
+
return "application/octet-stream";
|
|
149
|
+
}
|
|
150
|
+
}
|
package/lib/tools/index.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export type McpToolDefinition<TSchema extends z.AnyZodObject = z.AnyZodObject> =
|
|
|
7
7
|
description: string;
|
|
8
8
|
riskLevel: ToolRiskLevel;
|
|
9
9
|
inputSchema: TSchema;
|
|
10
|
+
allowPreviewWhenBlocked?: boolean;
|
|
11
|
+
isConfirmed?: (args: z.infer<TSchema>) => boolean;
|
|
10
12
|
handler(args: z.infer<TSchema>, context: ToolContext): Promise<ToolResult> | ToolResult;
|
|
11
13
|
};
|
|
12
14
|
export declare function allTools(): McpToolDefinition[];
|
package/lib/tools/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.allTools = allTools;
|
|
4
4
|
const git_tools_1 = require("./git.tools");
|
|
5
|
+
const hub_v2_1 = require("./hub-v2");
|
|
5
6
|
const log_tools_1 = require("./log.tools");
|
|
6
7
|
const project_tools_1 = require("./project.tools");
|
|
7
8
|
const proxy_tools_1 = require("./proxy.tools");
|
|
@@ -15,5 +16,6 @@ function allTools() {
|
|
|
15
16
|
...(0, git_tools_1.gitTools)(),
|
|
16
17
|
...(0, runtime_tools_1.runtimeTools)(),
|
|
17
18
|
...(0, proxy_tools_1.proxyTools)(),
|
|
19
|
+
...(0, hub_v2_1.hubV2Tools)(),
|
|
18
20
|
];
|
|
19
21
|
}
|
package/lib/utils/errors.d.ts
CHANGED
package/lib/utils/errors.js
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.errorMessage = errorMessage;
|
|
4
|
+
exports.errorMetadata = errorMetadata;
|
|
4
5
|
function errorMessage(error) {
|
|
5
6
|
if (error instanceof Error && error.message) {
|
|
6
7
|
return error.message;
|
|
7
8
|
}
|
|
8
9
|
return String(error ?? "Unknown error");
|
|
9
10
|
}
|
|
11
|
+
function errorMetadata(error) {
|
|
12
|
+
if (!error || typeof error !== "object") {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
const record = error;
|
|
16
|
+
return {
|
|
17
|
+
code: typeof record.code === "string" ? record.code : undefined,
|
|
18
|
+
status: typeof record.status === "number" ? record.status : undefined,
|
|
19
|
+
detail: record.detail,
|
|
20
|
+
};
|
|
21
|
+
}
|
package/lib/utils/result.d.ts
CHANGED
|
@@ -7,10 +7,19 @@ export type ToolErrorResult = {
|
|
|
7
7
|
ok: false;
|
|
8
8
|
tool: string;
|
|
9
9
|
error: string;
|
|
10
|
+
code?: string;
|
|
11
|
+
status?: number;
|
|
12
|
+
detail?: unknown;
|
|
13
|
+
};
|
|
14
|
+
export type ToolResult<T = unknown> = (ToolSuccessResult<T> | ToolErrorResult) & {
|
|
15
|
+
truncated?: true;
|
|
10
16
|
};
|
|
11
|
-
export type ToolResult<T = unknown> = ToolSuccessResult<T> | ToolErrorResult;
|
|
12
17
|
export declare function ok<T>(tool: string, data: T): ToolSuccessResult<T>;
|
|
13
|
-
export declare function fail(tool: string, error: string
|
|
18
|
+
export declare function fail(tool: string, error: string, metadata?: {
|
|
19
|
+
code?: string;
|
|
20
|
+
status?: number;
|
|
21
|
+
detail?: unknown;
|
|
22
|
+
}): ToolErrorResult;
|
|
14
23
|
export declare function toMcpTextResult(result: ToolResult): {
|
|
15
24
|
content: Array<{
|
|
16
25
|
type: "text";
|
package/lib/utils/result.js
CHANGED
|
@@ -6,16 +6,112 @@ exports.toMcpTextResult = toMcpTextResult;
|
|
|
6
6
|
function ok(tool, data) {
|
|
7
7
|
return { ok: true, tool, data };
|
|
8
8
|
}
|
|
9
|
-
function fail(tool, error) {
|
|
10
|
-
return {
|
|
9
|
+
function fail(tool, error, metadata = {}) {
|
|
10
|
+
return {
|
|
11
|
+
ok: false,
|
|
12
|
+
tool,
|
|
13
|
+
error,
|
|
14
|
+
...(metadata.code ? { code: metadata.code } : {}),
|
|
15
|
+
...(metadata.status ? { status: metadata.status } : {}),
|
|
16
|
+
...(metadata.detail !== undefined ? { detail: metadata.detail } : {}),
|
|
17
|
+
};
|
|
11
18
|
}
|
|
12
19
|
function toMcpTextResult(result) {
|
|
20
|
+
const maxChars = maxResultChars();
|
|
21
|
+
const text = JSON.stringify(result, null, 2);
|
|
22
|
+
if (text.length <= maxChars) {
|
|
23
|
+
return {
|
|
24
|
+
content: [
|
|
25
|
+
{
|
|
26
|
+
type: "text",
|
|
27
|
+
text,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const truncatedText = stringifyWithinLimit((preview) => buildTruncatedResult(result, text.length, preview), text, maxChars);
|
|
13
33
|
return {
|
|
14
34
|
content: [
|
|
15
35
|
{
|
|
16
36
|
type: "text",
|
|
17
|
-
text:
|
|
37
|
+
text: truncatedText,
|
|
18
38
|
},
|
|
19
39
|
],
|
|
20
40
|
};
|
|
21
41
|
}
|
|
42
|
+
function maxResultChars() {
|
|
43
|
+
const value = Number.parseInt(String(process.env.NGM_MCP_MAX_RESULT_CHARS ?? ""), 10);
|
|
44
|
+
return Number.isFinite(value) && value > 0 ? value : 120000;
|
|
45
|
+
}
|
|
46
|
+
function buildTruncatedResult(result, originalLength, preview) {
|
|
47
|
+
if (result.ok) {
|
|
48
|
+
return {
|
|
49
|
+
ok: true,
|
|
50
|
+
tool: result.tool,
|
|
51
|
+
truncated: true,
|
|
52
|
+
data: {
|
|
53
|
+
originalLength,
|
|
54
|
+
preview,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
tool: result.tool,
|
|
61
|
+
error: result.error,
|
|
62
|
+
code: result.code,
|
|
63
|
+
status: result.status,
|
|
64
|
+
truncated: true,
|
|
65
|
+
detail: {
|
|
66
|
+
originalLength,
|
|
67
|
+
preview,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function stringifyWithinLimit(build, sourceText, maxChars) {
|
|
72
|
+
let previewLength = Math.min(sourceText.length, Math.max(0, maxChars - 1000));
|
|
73
|
+
while (previewLength >= 0) {
|
|
74
|
+
const text = JSON.stringify(build(sourceText.slice(0, previewLength)), null, 2);
|
|
75
|
+
if (text.length <= maxChars) {
|
|
76
|
+
return text;
|
|
77
|
+
}
|
|
78
|
+
if (previewLength === 0) {
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
previewLength = Math.max(0, Math.floor(previewLength / 2));
|
|
82
|
+
}
|
|
83
|
+
return minimalTruncatedJson(maxChars);
|
|
84
|
+
}
|
|
85
|
+
function minimalTruncatedJson(maxChars) {
|
|
86
|
+
const buildWithDetail = (error) => JSON.stringify({
|
|
87
|
+
ok: false,
|
|
88
|
+
tool: "mcp",
|
|
89
|
+
error,
|
|
90
|
+
truncated: true,
|
|
91
|
+
detail: {
|
|
92
|
+
originalLength: null,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
let error = "MCP result exceeded NGM_MCP_MAX_RESULT_CHARS";
|
|
96
|
+
let text = buildWithDetail(error);
|
|
97
|
+
while (text.length > maxChars && error.length > 0) {
|
|
98
|
+
error = error.slice(0, -1);
|
|
99
|
+
text = buildWithDetail(error);
|
|
100
|
+
}
|
|
101
|
+
if (text.length <= maxChars) {
|
|
102
|
+
return text;
|
|
103
|
+
}
|
|
104
|
+
const buildMinimal = (minimalError) => JSON.stringify({
|
|
105
|
+
ok: false,
|
|
106
|
+
tool: "mcp",
|
|
107
|
+
error: minimalError,
|
|
108
|
+
truncated: true,
|
|
109
|
+
});
|
|
110
|
+
error = "MCP result truncated";
|
|
111
|
+
text = buildMinimal(error);
|
|
112
|
+
while (text.length > maxChars && error.length > 0) {
|
|
113
|
+
error = error.slice(0, -1);
|
|
114
|
+
text = buildMinimal(error);
|
|
115
|
+
}
|
|
116
|
+
return text.length <= maxChars ? text : "{}";
|
|
117
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yinuo-ngm/mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Local MCP stdio server for ng-manager core capabilities.",
|
|
5
5
|
"author": "ZhangJing <892295834@qq.com>",
|
|
6
6
|
"license": "ISC",
|
|
@@ -22,20 +22,24 @@
|
|
|
22
22
|
"node": ">=18"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
|
+
"inspect": "npm run build && mcp-inspector node lib/index.js",
|
|
26
|
+
"inspect:dev": "mcp-inspector tsx src/index.ts",
|
|
25
27
|
"dev": "tsx watch src/index.ts",
|
|
26
28
|
"build": "tsc -b tsconfig.json",
|
|
29
|
+
"test": "npm run build && node --test test",
|
|
27
30
|
"start": "node lib/index.js"
|
|
28
31
|
},
|
|
29
32
|
"dependencies": {
|
|
30
33
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
31
|
-
"@yinuo-ngm/core": "^0.1.
|
|
34
|
+
"@yinuo-ngm/core": "^0.1.15",
|
|
32
35
|
"zod": "^3.25.76"
|
|
33
36
|
},
|
|
34
37
|
"devDependencies": {
|
|
38
|
+
"@modelcontextprotocol/inspector": "^0.15.0",
|
|
35
39
|
"tsx": "^4.21.0"
|
|
36
40
|
},
|
|
37
41
|
"publishConfig": {
|
|
38
42
|
"access": "public"
|
|
39
43
|
},
|
|
40
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "9c45bd57798a539e3b6cf40dd56f7cdffdc02589"
|
|
41
45
|
}
|