@tencentcloud/tccc-mcp-server 0.0.10 → 0.0.14
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 +2 -2
- package/package.json +1 -1
- package/src/analytics.mjs +266 -0
- package/src/custom-tools.mjs +56 -1
- package/src/index.mjs +58 -25
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ Then just ask your AI assistant in natural language:
|
|
|
84
84
|
> How many voice AI agents are there in this application?
|
|
85
85
|
> How can I try the TCCC AI agent / AI outbound call demo?
|
|
86
86
|
|
|
87
|
-
## Capability overview (87 APIs +
|
|
87
|
+
## Capability overview (87 APIs + 2 helpers)
|
|
88
88
|
|
|
89
89
|
| Category | Count | Representative tools |
|
|
90
90
|
|---|---|---|
|
|
@@ -261,7 +261,7 @@ npx -y @tencentcloud/tccc-mcp-server --version
|
|
|
261
261
|
> 这个应用下有多少个 AI 智能体?
|
|
262
262
|
> 我怎么体验 TCCC AI 智能体 / 智能体对话 / AI 外呼?
|
|
263
263
|
|
|
264
|
-
## 能力概览(87 个接口 +
|
|
264
|
+
## 能力概览(87 个接口 + 2 个辅助工具)
|
|
265
265
|
|
|
266
266
|
| 分类 | 数量 | 代表接口 |
|
|
267
267
|
|---|---|---|
|
package/package.json
CHANGED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { randomBytes, randomUUID } from 'node:crypto';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
export const OTLP_DOMAIN = 'otlp.tccc.qcloud.com';
|
|
7
|
+
export const OTLP_INTL_DOMAIN = 'otlp.connect.tencentcloud.com';
|
|
8
|
+
export const OTLP_ID = 'tccc';
|
|
9
|
+
export const OTLP_INTL_ID = 'tcccsg';
|
|
10
|
+
// 预留美国站 ID 覆盖位;当前公开代码里没有稳定的美国站号段,默认复用国际站 ID。
|
|
11
|
+
export const OTLP_US_ID = process.env.TCCC_MCP_OTLP_US_ID || OTLP_INTL_ID;
|
|
12
|
+
|
|
13
|
+
const SERVER_NAME = 'tccc-mcp-server';
|
|
14
|
+
const DEFAULT_SDK_APP_ID = process.env.TCCC_SDK_APP_ID || '';
|
|
15
|
+
const REPORT_DISABLED = /^(1|true|yes|off)$/i.test(process.env.TCCC_MCP_OTLP_DISABLED || '');
|
|
16
|
+
const REPORT_TIMEOUT_MS = Number(process.env.TCCC_MCP_OTLP_TIMEOUT_MS || 1500);
|
|
17
|
+
const INCLUDE_RAW_QUESTION = /^(1|true|yes)$/i.test(process.env.TCCC_MCP_ANALYTICS_INCLUDE_RAW_QUESTION || '');
|
|
18
|
+
const SESSION_ID = randomUUID();
|
|
19
|
+
const loggerMap = new Map();
|
|
20
|
+
|
|
21
|
+
// ULID: 26 位 Crockford Base32(10 位毫秒时间戳 + 16 位随机),可排序、对 URL 安全。
|
|
22
|
+
const ULID_ENCODING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
|
23
|
+
const ULID_PATTERN = /^[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
24
|
+
|
|
25
|
+
function ulid() {
|
|
26
|
+
let time = Date.now();
|
|
27
|
+
let timePart = '';
|
|
28
|
+
for (let i = 0; i < 10; i++) {
|
|
29
|
+
timePart = ULID_ENCODING[time % 32] + timePart;
|
|
30
|
+
time = Math.floor(time / 32);
|
|
31
|
+
}
|
|
32
|
+
// 256 % 32 === 0,取模无偏
|
|
33
|
+
const randomPart = Array.from(randomBytes(16), (b) => ULID_ENCODING[b % 32]).join('');
|
|
34
|
+
return timePart + randomPart;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const USER_ID_FILE = process.env.TCCC_MCP_USER_ID_FILE || join(homedir(), '.tccc-mcp-server', 'user-id');
|
|
38
|
+
|
|
39
|
+
function getOrCreateUserId() {
|
|
40
|
+
try {
|
|
41
|
+
if (existsSync(USER_ID_FILE)) {
|
|
42
|
+
const cached = readFileSync(USER_ID_FILE, 'utf8').trim();
|
|
43
|
+
if (ULID_PATTERN.test(cached)) return cached;
|
|
44
|
+
}
|
|
45
|
+
mkdirSync(dirname(USER_ID_FILE), { recursive: true });
|
|
46
|
+
const id = ulid();
|
|
47
|
+
writeFileSync(USER_ID_FILE, id, 'utf8');
|
|
48
|
+
return id;
|
|
49
|
+
} catch {
|
|
50
|
+
// 缓存不可读写(如只读文件系统)时降级为本次进程内的临时 ID
|
|
51
|
+
return ulid();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const USER_ID = process.env.TCCC_MCP_USER_ID || getOrCreateUserId();
|
|
56
|
+
|
|
57
|
+
const SINGAPORE_RANGES = [
|
|
58
|
+
[20000000, 30000000],
|
|
59
|
+
[1720000000, 1730000000],
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const USA_RANGES = [];
|
|
63
|
+
|
|
64
|
+
export function isSingaporeSite(sdkAppId) {
|
|
65
|
+
const n = Number(sdkAppId);
|
|
66
|
+
return Number.isFinite(n) && SINGAPORE_RANGES.some(([lo, hi]) => n >= lo && n < hi);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function isUSASite(sdkAppId) {
|
|
70
|
+
const n = Number(sdkAppId);
|
|
71
|
+
return Number.isFinite(n) && USA_RANGES.some(([lo, hi]) => n >= lo && n < hi);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function isIntlSite(sdkAppId) {
|
|
75
|
+
return isSingaporeSite(sdkAppId) || isUSASite(sdkAppId);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function GET_OTLP_DOMAIN_BY_SDKAPPID(sdkAppId) {
|
|
79
|
+
if (isIntlSite(sdkAppId)) {
|
|
80
|
+
return OTLP_INTL_DOMAIN;
|
|
81
|
+
}
|
|
82
|
+
return OTLP_DOMAIN;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function GET_OTLP_ID_BY_SDKAPPID(sdkAppId) {
|
|
86
|
+
if (isUSASite(sdkAppId)) {
|
|
87
|
+
return OTLP_US_ID;
|
|
88
|
+
}
|
|
89
|
+
if (isSingaporeSite(sdkAppId)) {
|
|
90
|
+
return OTLP_INTL_ID;
|
|
91
|
+
}
|
|
92
|
+
return OTLP_ID;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function getOtlpEndpoint(sdkAppId) {
|
|
96
|
+
if (process.env.TCCC_MCP_OTLP_ENDPOINT) return process.env.TCCC_MCP_OTLP_ENDPOINT;
|
|
97
|
+
const otlpDomain = GET_OTLP_DOMAIN_BY_SDKAPPID(sdkAppId);
|
|
98
|
+
return `https://${otlpDomain}/v1/logs`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function toAnyValue(value) {
|
|
102
|
+
if (value === undefined || value === null) return { stringValue: '' };
|
|
103
|
+
if (typeof value === 'boolean') return { boolValue: value };
|
|
104
|
+
if (typeof value === 'number') {
|
|
105
|
+
if (Number.isInteger(value)) return { intValue: String(value) };
|
|
106
|
+
return { doubleValue: value };
|
|
107
|
+
}
|
|
108
|
+
if (typeof value === 'bigint') return { intValue: value.toString() };
|
|
109
|
+
if (Array.isArray(value)) {
|
|
110
|
+
return { arrayValue: { values: value.map(toAnyValue) } };
|
|
111
|
+
}
|
|
112
|
+
if (typeof value === 'object') {
|
|
113
|
+
return {
|
|
114
|
+
kvlistValue: {
|
|
115
|
+
values: Object.entries(value).map(([key, item]) => ({ key, value: toAnyValue(item) })),
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return { stringValue: String(value) };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function toAttributes(attrs = {}) {
|
|
123
|
+
return Object.entries(attrs)
|
|
124
|
+
.filter(([, value]) => value !== undefined)
|
|
125
|
+
.map(([key, value]) => ({ key, value: toAnyValue(value) }));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function nowUnixNano() {
|
|
129
|
+
return (BigInt(Date.now()) * 1000000n).toString();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const SEVERITY = {
|
|
133
|
+
debug: { severityNumber: 5, severityText: 'debug' },
|
|
134
|
+
info: { severityNumber: 9, severityText: 'info' },
|
|
135
|
+
warn: { severityNumber: 13, severityText: 'warn' },
|
|
136
|
+
error: { severityNumber: 17, severityText: 'error' },
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
class Logger {
|
|
140
|
+
constructor({ logLevel = 0, url, id, name, attributes = {} }) {
|
|
141
|
+
this.logLevel = logLevel;
|
|
142
|
+
this.url = url;
|
|
143
|
+
this.id = id;
|
|
144
|
+
this.name = name;
|
|
145
|
+
this.attributes = attributes;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
emit({ level = 'info', msg, attributes = {} }) {
|
|
149
|
+
if (REPORT_DISABLED) return;
|
|
150
|
+
const severity = SEVERITY[level] || SEVERITY.info;
|
|
151
|
+
const payload = {
|
|
152
|
+
resourceLogs: [
|
|
153
|
+
{
|
|
154
|
+
resource: {
|
|
155
|
+
attributes: toAttributes({
|
|
156
|
+
'service.name': this.name,
|
|
157
|
+
'tps.tenant.id': this.id,
|
|
158
|
+
...this.attributes,
|
|
159
|
+
}),
|
|
160
|
+
},
|
|
161
|
+
scopeLogs: [
|
|
162
|
+
{
|
|
163
|
+
scope: { name: this.name },
|
|
164
|
+
logRecords: [
|
|
165
|
+
{
|
|
166
|
+
timeUnixNano: nowUnixNano(),
|
|
167
|
+
observedTimeUnixNano: nowUnixNano(),
|
|
168
|
+
...severity,
|
|
169
|
+
body: { stringValue: msg || '' },
|
|
170
|
+
attributes: toAttributes({
|
|
171
|
+
...attributes,
|
|
172
|
+
sessionId: SESSION_ID,
|
|
173
|
+
userId: USER_ID,
|
|
174
|
+
}),
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const controller = new AbortController();
|
|
184
|
+
const timer = setTimeout(() => controller.abort(), REPORT_TIMEOUT_MS);
|
|
185
|
+
if (typeof timer.unref === 'function') timer.unref();
|
|
186
|
+
|
|
187
|
+
fetch(this.url, {
|
|
188
|
+
method: 'POST',
|
|
189
|
+
headers: {
|
|
190
|
+
'Content-Type': 'application/json',
|
|
191
|
+
'X-Tps-TenantID': this.id,
|
|
192
|
+
},
|
|
193
|
+
body: JSON.stringify(payload),
|
|
194
|
+
signal: controller.signal,
|
|
195
|
+
})
|
|
196
|
+
.catch((err) => {
|
|
197
|
+
if (process.env.TCCC_DEBUG_ANALYTICS) {
|
|
198
|
+
console.error('[tccc-mcp] analytics report failed:', err?.message || err);
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
.finally(() => clearTimeout(timer));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function getLogger(sdkAppId, pkgVersion) {
|
|
206
|
+
const normalizedSdkAppId = String(sdkAppId || DEFAULT_SDK_APP_ID || 'unknown');
|
|
207
|
+
let logger = loggerMap.get(normalizedSdkAppId);
|
|
208
|
+
if (!logger) {
|
|
209
|
+
logger = new Logger({
|
|
210
|
+
logLevel: 0,
|
|
211
|
+
url: getOtlpEndpoint(normalizedSdkAppId),
|
|
212
|
+
id: GET_OTLP_ID_BY_SDKAPPID(normalizedSdkAppId),
|
|
213
|
+
name: SERVER_NAME,
|
|
214
|
+
attributes: {
|
|
215
|
+
pkgVersion,
|
|
216
|
+
sdkAppId: normalizedSdkAppId,
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
loggerMap.set(normalizedSdkAppId, logger);
|
|
220
|
+
}
|
|
221
|
+
return logger;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function inferSdkAppId(params = {}) {
|
|
225
|
+
for (const key of ['SdkAppId', 'sdkAppId']) {
|
|
226
|
+
const v = params[key];
|
|
227
|
+
if (v !== undefined && v !== null && v !== '') return String(v);
|
|
228
|
+
}
|
|
229
|
+
if (Array.isArray(params.SdkAppIds) && params.SdkAppIds.length > 0) return String(params.SdkAppIds[0]);
|
|
230
|
+
if (DEFAULT_SDK_APP_ID) return String(DEFAULT_SDK_APP_ID);
|
|
231
|
+
return 'unknown';
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function recordToolCall({ tool, params, pkgVersion, region, requestId, errorCode, errorMessage }) {
|
|
235
|
+
const sdkAppId = inferSdkAppId(params);
|
|
236
|
+
const logger = getLogger(sdkAppId, pkgVersion);
|
|
237
|
+
logger.emit({
|
|
238
|
+
level: errorCode ? 'warn' : 'info',
|
|
239
|
+
msg: `[mcp.tool_call] ${tool?.name || 'unknown'} ${errorCode ? 'failure' : 'success'}`,
|
|
240
|
+
attributes: {
|
|
241
|
+
eventName: 'mcp.tool_call',
|
|
242
|
+
toolName: tool?.name,
|
|
243
|
+
requestId,
|
|
244
|
+
errorCode,
|
|
245
|
+
errorMessage: errorMessage ? String(errorMessage).slice(0, 300) : undefined,
|
|
246
|
+
sdkAppId,
|
|
247
|
+
region,
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function recordUserIntent({ intent, confidence, rawQuestion, sdkAppId, pkgVersion }) {
|
|
253
|
+
const normalizedSdkAppId = String(sdkAppId || DEFAULT_SDK_APP_ID || 'unknown');
|
|
254
|
+
const logger = getLogger(normalizedSdkAppId, pkgVersion);
|
|
255
|
+
logger.emit({
|
|
256
|
+
level: 'info',
|
|
257
|
+
msg: `[mcp.user_intent] ${intent || 'unknown'}`,
|
|
258
|
+
attributes: {
|
|
259
|
+
eventName: 'mcp.user_intent',
|
|
260
|
+
intent: intent || 'unknown',
|
|
261
|
+
confidence: typeof confidence === 'number' ? confidence : undefined,
|
|
262
|
+
sdkAppId: normalizedSdkAppId,
|
|
263
|
+
rawQuestion: INCLUDE_RAW_QUESTION && rawQuestion ? String(rawQuestion).slice(0, 500) : undefined,
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
}
|
package/src/custom-tools.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { recordUserIntent } from './analytics.mjs';
|
|
4
5
|
|
|
5
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
7
|
const TCCC_AI_AGENT_DEMO_QRCODE_PATH = path.join(__dirname, 'assets', 'tccc-ai-agent-demo-qrcode.png');
|
|
@@ -31,12 +32,49 @@ export const CUSTOM_TOOLS = [
|
|
|
31
32
|
additionalProperties: false,
|
|
32
33
|
},
|
|
33
34
|
},
|
|
35
|
+
{
|
|
36
|
+
name: 'record_tccc_user_intent',
|
|
37
|
+
action: '__custom_record_tccc_user_intent',
|
|
38
|
+
custom: true,
|
|
39
|
+
analyticsOnly: true,
|
|
40
|
+
description:
|
|
41
|
+
'当用户咨询 tccc-mcp-server 或 TCCC 相关问题、但不一定需要立即调用具体业务 API 时,可调用本工具记录用户咨询意图分类,用于产品使用统计。不要记录 SecretId、SecretKey、Token、手机号、邮箱等敏感信息;rawQuestion 默认不上报原文,除非显式设置 TCCC_MCP_ANALYTICS_INCLUDE_RAW_QUESTION=1。',
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
intent: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
description: '意图分类,例如 ai_demo、human_ai_collaboration、service_efficiency、instance_management、staff_management、record_query、troubleshooting、product_consulting。',
|
|
48
|
+
},
|
|
49
|
+
confidence: {
|
|
50
|
+
type: 'number',
|
|
51
|
+
description: '分类置信度,0 到 1,可选。',
|
|
52
|
+
},
|
|
53
|
+
sdkAppId: {
|
|
54
|
+
type: 'string',
|
|
55
|
+
description: '如果上下文里已有 SdkAppId,可填写;否则留空。',
|
|
56
|
+
},
|
|
57
|
+
rawQuestion: {
|
|
58
|
+
type: 'string',
|
|
59
|
+
description: '可选原始问题。默认只上报长度,不上报原文;不要包含密钥、手机号、邮箱等敏感信息。',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
required: ['intent'],
|
|
63
|
+
additionalProperties: false,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
34
66
|
];
|
|
35
67
|
|
|
36
|
-
export async function handleCustomTool(tool, args = {}) {
|
|
68
|
+
export async function handleCustomTool(tool, args = {}, context = {}) {
|
|
37
69
|
switch (tool.action) {
|
|
38
70
|
case '__custom_get_tccc_ai_agent_demo_qrcode': {
|
|
39
71
|
const scenario = typeof args.scenario === 'string' && args.scenario.trim() ? args.scenario.trim() : 'TCCC AI 智能体';
|
|
72
|
+
recordUserIntent({
|
|
73
|
+
intent: 'ai_demo',
|
|
74
|
+
confidence: 1,
|
|
75
|
+
sdkAppId: args.sdkAppId,
|
|
76
|
+
pkgVersion: context.pkgVersion,
|
|
77
|
+
});
|
|
40
78
|
return {
|
|
41
79
|
content: [
|
|
42
80
|
{
|
|
@@ -53,6 +91,23 @@ export async function handleCustomTool(tool, args = {}) {
|
|
|
53
91
|
],
|
|
54
92
|
};
|
|
55
93
|
}
|
|
94
|
+
case '__custom_record_tccc_user_intent': {
|
|
95
|
+
recordUserIntent({
|
|
96
|
+
intent: args.intent,
|
|
97
|
+
confidence: args.confidence,
|
|
98
|
+
rawQuestion: args.rawQuestion,
|
|
99
|
+
sdkAppId: args.sdkAppId,
|
|
100
|
+
pkgVersion: context.pkgVersion,
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
content: [
|
|
104
|
+
{
|
|
105
|
+
type: 'text',
|
|
106
|
+
text: `已记录 TCCC 咨询意图:${args.intent}${args.topic ? `(${args.topic})` : ''}`,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
56
111
|
default:
|
|
57
112
|
throw new Error(`未知自定义工具: ${tool.name}`);
|
|
58
113
|
}
|
package/src/index.mjs
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
ListToolsRequestSchema,
|
|
25
25
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
26
26
|
import tencentcloud from 'tencentcloud-sdk-nodejs-ccc';
|
|
27
|
+
import { recordToolCall } from './analytics.mjs';
|
|
27
28
|
import { CUSTOM_TOOLS, handleCustomTool } from './custom-tools.mjs';
|
|
28
29
|
import { EXTRA_API_TOOLS } from './extra-api-tools.mjs';
|
|
29
30
|
|
|
@@ -152,12 +153,24 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
152
153
|
const tool = TOOLS.find((t) => t.name === name);
|
|
153
154
|
if (!tool) throw new Error(`未知工具: ${name}`);
|
|
154
155
|
|
|
156
|
+
let region = DEFAULT_REGION;
|
|
157
|
+
const finish = (result, meta = {}) => {
|
|
158
|
+
recordToolCall({
|
|
159
|
+
tool,
|
|
160
|
+
params,
|
|
161
|
+
pkgVersion: PKG.version,
|
|
162
|
+
region,
|
|
163
|
+
...meta,
|
|
164
|
+
});
|
|
165
|
+
return result;
|
|
166
|
+
};
|
|
167
|
+
|
|
155
168
|
const params = withDefaults(tool, args);
|
|
156
169
|
|
|
157
170
|
// 地域决策:显式 TCCC_REGION 环境变量 > 按本次调用生效的 SdkAppId 推导 > 启动默认值。
|
|
158
171
|
// 这样同一会话里混合调用中国站/新加坡站应用时,各自走对的地域。
|
|
159
172
|
const effectiveAppId = tool.inputSchema?.properties?.SdkAppId ? params.SdkAppId : undefined;
|
|
160
|
-
|
|
173
|
+
region =
|
|
161
174
|
REGION_ENV ||
|
|
162
175
|
(effectiveAppId !== undefined ? regionFromSdkAppId(effectiveAppId) : null) ||
|
|
163
176
|
DEFAULT_REGION;
|
|
@@ -168,21 +181,35 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
168
181
|
// 必填参数本地校验,避免白跑一次网络请求
|
|
169
182
|
const missing = (tool.inputSchema.required || []).filter((k) => params[k] === undefined);
|
|
170
183
|
if (missing.length) {
|
|
171
|
-
return
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
184
|
+
return finish(
|
|
185
|
+
{
|
|
186
|
+
isError: true,
|
|
187
|
+
content: [
|
|
188
|
+
{
|
|
189
|
+
type: 'text',
|
|
190
|
+
text: `缺少必填参数: ${missing.join(', ')}\n接口: ${tool.action}(${
|
|
191
|
+
tool.inputSchema.properties[missing[0]]?.description || ''
|
|
192
|
+
})`,
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
{ errorCode: 'MissingParameter', errorMessage: `缺少必填参数: ${missing.join(', ')}` }
|
|
197
|
+
);
|
|
182
198
|
}
|
|
183
199
|
|
|
184
200
|
if (tool.custom) {
|
|
185
|
-
|
|
201
|
+
try {
|
|
202
|
+
const res = await handleCustomTool(tool, params, { pkgVersion: PKG.version, region });
|
|
203
|
+
return finish(res);
|
|
204
|
+
} catch (err) {
|
|
205
|
+
return finish(
|
|
206
|
+
{
|
|
207
|
+
isError: true,
|
|
208
|
+
content: [{ type: 'text', text: `调用 ${tool.action} 失败: ${err?.message || err}` }],
|
|
209
|
+
},
|
|
210
|
+
{ errorCode: err?.code || 'CustomToolError', errorMessage: err?.message || String(err) }
|
|
211
|
+
);
|
|
212
|
+
}
|
|
186
213
|
}
|
|
187
214
|
|
|
188
215
|
try {
|
|
@@ -203,20 +230,26 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
203
230
|
summary = '';
|
|
204
231
|
}
|
|
205
232
|
}
|
|
206
|
-
return
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
233
|
+
return finish(
|
|
234
|
+
{
|
|
235
|
+
content: [
|
|
236
|
+
{
|
|
237
|
+
type: 'text',
|
|
238
|
+
text: (summary ? `${summary}\n\n` : '') + JSON.stringify(res, null, 2),
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
{ requestId: res?.RequestId }
|
|
243
|
+
);
|
|
214
244
|
} catch (err) {
|
|
215
245
|
const detail = [err?.code, err?.message, err?.requestId].filter(Boolean).join(' | ');
|
|
216
|
-
return
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
246
|
+
return finish(
|
|
247
|
+
{
|
|
248
|
+
isError: true,
|
|
249
|
+
content: [{ type: 'text', text: `调用 ${tool.action} 失败: ${detail || err}` }],
|
|
250
|
+
},
|
|
251
|
+
{ requestId: err?.requestId, errorCode: err?.code, errorMessage: err?.message || String(err) }
|
|
252
|
+
);
|
|
220
253
|
}
|
|
221
254
|
});
|
|
222
255
|
|