mes-mcp 0.2.1 → 0.2.3

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 CHANGED
@@ -23,6 +23,36 @@ MES_REGISTER_DYNAMIC_TOOLS=false
23
23
 
24
24
  `MES_REGISTER_DYNAMIC_TOOLS` 默认关闭。关闭时 MCP 只注册稳定接口工具,通过后端动作目录发现并执行全部授权业务能力;打开后会把所有授权页面动作额外展开成 `mes.<动作编码>` 工具,适合明确需要工具直出且客户端能承载大量工具的场景。
25
25
 
26
+ ## 运行
27
+
28
+ 正式接入推荐用 `npx` 拉取 npm 上的最新版本,不需要全局安装:
29
+
30
+ ```bash
31
+ npx -y mes-mcp@latest
32
+ ```
33
+
34
+ MCP 客户端配置建议使用:
35
+
36
+ ```json
37
+ {
38
+ "mcpServers": {
39
+ "mes-mcp": {
40
+ "command": "npx",
41
+ "args": ["-y", "mes-mcp@latest"],
42
+ "env": {
43
+ "MES_BASE_URL": "https://your-mes.example.com",
44
+ "MES_API_PREFIX": "/api",
45
+ "MES_AGENT_TOKEN": "BearerTokenWithoutBearerPrefix",
46
+ "MES_TIMEOUT_MS": "30000",
47
+ "MES_REGISTER_DYNAMIC_TOOLS": "false"
48
+ }
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ `npx` 会在 MCP 客户端启动或重连时使用 npm 上的最新版本;已经运行中的 `mes-mcp` 进程不会热更新,发版后需要重启或刷新当前 AI 助手的 MCP 连接。
55
+
26
56
  ## 开发
27
57
 
28
58
  ```bash
@@ -1,3 +1,28 @@
1
+ function buildMesErrorMessage(path, status, message) {
2
+ const hints = [];
3
+ if (/pageSize must not be greater than 100/i.test(message)) {
4
+ hints.push("MES 分页上限是 100;请把 pageSize 调整到 100 或更小。");
5
+ }
6
+ if (message.includes("AI Agent 凭证只能访问动态业务动作接口")) {
7
+ hints.push("该路径可能属于受控动态业务动作或当前凭证未允许 mes_api.read;请优先用 mes_action.list/detail/execute 调用对应 actionCode。");
8
+ }
9
+ if (message.includes("最高只允许")) {
10
+ hints.push("当前 AI 凭证最高执行模式不足;请改用 preview/confirm_required,或在 MES 开放集成里创建允许 commit 的 AI 凭证。");
11
+ }
12
+ if (message.includes("待确认 AI 调用不存在")) {
13
+ hints.push("AI 待确认单按公司/租户隔离;请使用生成该确认单的同一公司账号审批,并确认 invocationId 未过期或未被处理。");
14
+ }
15
+ if (/^\/?integration\/agent(\/|$)/i.test(path)) {
16
+ hints.push("不要通过 mes_api.read 访问 /integration/agent/*;请使用 mes_action.* 专用工具。");
17
+ }
18
+ return [
19
+ `MES ${status}: ${message}`,
20
+ `path: ${path}`,
21
+ hints.length > 0 ? `hints: ${hints.join(" ")}` : undefined,
22
+ ]
23
+ .filter(Boolean)
24
+ .join("\n");
25
+ }
1
26
  export class MesAgentApiClient {
2
27
  config;
3
28
  businessActionsPromise;
@@ -21,10 +46,10 @@ export class MesAgentApiClient {
21
46
  const body = this.parseBody(text);
22
47
  if (!response.ok) {
23
48
  const message = body?.msg || body?.message || text || response.statusText;
24
- throw new Error(`MES ${response.status}: ${message}`);
49
+ throw new Error(buildMesErrorMessage(path, response.status, message));
25
50
  }
26
51
  if (body && typeof body.code === "number" && body.code !== 0) {
27
- throw new Error(body.msg || body.message || `MES code ${body.code}`);
52
+ throw new Error(buildMesErrorMessage(path, body.code, body.msg || body.message || `MES code ${body.code}`));
28
53
  }
29
54
  return (body && "data" in body ? body.data : body);
30
55
  }
package/dist/config.js CHANGED
@@ -3,6 +3,10 @@ function optionalEnv(name, fallback) {
3
3
  const value = process.env[name]?.trim();
4
4
  return value && value.length > 0 ? value : fallback;
5
5
  }
6
+ function optionalRawEnv(name) {
7
+ const value = process.env[name]?.trim();
8
+ return value && value.length > 0 ? value : undefined;
9
+ }
6
10
  function requiredEnv(name) {
7
11
  const value = process.env[name]?.trim();
8
12
  if (!value) {
@@ -20,16 +24,71 @@ function optionalBooleanEnv(name, fallback) {
20
24
  return false;
21
25
  throw new Error(`${name} must be a boolean`);
22
26
  }
27
+ function decodeBase64UrlJson(segment) {
28
+ try {
29
+ const normalized = segment.replace(/-/g, "+").replace(/_/g, "/");
30
+ const padded = normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), "=");
31
+ return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
37
+ function asString(value) {
38
+ return typeof value === "string" && value.length > 0 ? value : undefined;
39
+ }
40
+ function asNumber(value) {
41
+ return typeof value === "number" && Number.isFinite(value)
42
+ ? value
43
+ : undefined;
44
+ }
45
+ function decodeAgentToken(token) {
46
+ const [, payloadSegment] = token.split(".");
47
+ if (!payloadSegment)
48
+ return {};
49
+ const payload = decodeBase64UrlJson(payloadSegment);
50
+ if (!payload)
51
+ return {};
52
+ return {
53
+ subject: asString(payload.sub),
54
+ username: asString(payload.username),
55
+ tokenType: asString(payload.tokenType),
56
+ apiKeyId: asString(payload.apiKeyId),
57
+ clientType: asString(payload.clientType),
58
+ agentClientId: asString(payload.agentClientId),
59
+ issuedAt: asNumber(payload.iat),
60
+ expiresAt: asNumber(payload.exp),
61
+ };
62
+ }
63
+ function assertExpectedTokenValue(label, actual, expected) {
64
+ if (!expected)
65
+ return;
66
+ if (actual === expected)
67
+ return;
68
+ throw new Error(`${label} mismatch for MES_AGENT_TOKEN: expected ${expected}, got ${actual ?? "<missing>"}`);
69
+ }
23
70
  export function loadConfig() {
24
71
  const timeoutMs = Number(optionalEnv("MES_TIMEOUT_MS", "30000"));
25
72
  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
26
73
  throw new Error("MES_TIMEOUT_MS must be a positive number");
27
74
  }
75
+ const mesAgentToken = requiredEnv("MES_AGENT_TOKEN");
76
+ const tokenInfo = decodeAgentToken(mesAgentToken);
77
+ const expectedUsername = optionalRawEnv("MES_EXPECTED_USERNAME");
78
+ const expectedAgentClientId = optionalRawEnv("MES_EXPECTED_AGENT_CLIENT_ID");
79
+ const expectedApiKeyId = optionalRawEnv("MES_EXPECTED_API_KEY_ID");
80
+ assertExpectedTokenValue("username", tokenInfo.username, expectedUsername);
81
+ assertExpectedTokenValue("agentClientId", tokenInfo.agentClientId, expectedAgentClientId);
82
+ assertExpectedTokenValue("apiKeyId", tokenInfo.apiKeyId, expectedApiKeyId);
28
83
  return {
29
84
  mesBaseUrl: optionalEnv("MES_BASE_URL", "http://127.0.0.1:6033").replace(/\/+$/, ""),
30
85
  mesApiPrefix: optionalEnv("MES_API_PREFIX", "/api").replace(/\/+$/, ""),
31
- mesAgentToken: requiredEnv("MES_AGENT_TOKEN"),
86
+ mesAgentToken,
32
87
  timeoutMs,
33
88
  registerDynamicTools: optionalBooleanEnv("MES_REGISTER_DYNAMIC_TOOLS", false),
89
+ tokenInfo,
90
+ expectedUsername,
91
+ expectedAgentClientId,
92
+ expectedApiKeyId,
34
93
  };
35
94
  }
package/dist/index.js CHANGED
@@ -6,12 +6,46 @@ import { MesAgentApiClient } from "./clients/mes-agent-api.client.js";
6
6
  import { registerMesPrompts } from "./prompts/register-prompts.js";
7
7
  import { registerMesResources } from "./resources/register-resources.js";
8
8
  import { registerAllAgentTools } from "./tools/register-tools.js";
9
+ function maskId(value) {
10
+ if (!value)
11
+ return "<unknown>";
12
+ if (value.length <= 12)
13
+ return value;
14
+ return `${value.slice(0, 8)}...${value.slice(-4)}`;
15
+ }
16
+ function formatUnixSeconds(value) {
17
+ if (!value)
18
+ return "<unknown>";
19
+ const date = new Date(value * 1000);
20
+ if (Number.isNaN(date.getTime()))
21
+ return "<invalid>";
22
+ return date.toISOString();
23
+ }
24
+ function logStartupDiagnostics(config) {
25
+ const lines = [
26
+ "[mes-mcp] starting",
27
+ `[mes-mcp] endpoint=${config.mesBaseUrl}${config.mesApiPrefix}`,
28
+ `[mes-mcp] token username=${config.tokenInfo.username ?? "<unknown>"} apiKeyId=${maskId(config.tokenInfo.apiKeyId)} agentClientId=${config.tokenInfo.agentClientId ?? "<unknown>"} expiresAt=${formatUnixSeconds(config.tokenInfo.expiresAt)}`,
29
+ `[mes-mcp] dynamicTools=${config.registerDynamicTools ? "enabled" : "disabled"} (${config.registerDynamicTools ? "mes.<actionCode> tools will be registered" : "use mes_action.list/detail/execute; set MES_REGISTER_DYNAMIC_TOOLS=true only when the client needs expanded tools"})`,
30
+ ];
31
+ if (config.expectedUsername) {
32
+ lines.push(`[mes-mcp] expected username=${config.expectedUsername}`);
33
+ }
34
+ if (config.expectedAgentClientId) {
35
+ lines.push(`[mes-mcp] expected agentClientId=${config.expectedAgentClientId}`);
36
+ }
37
+ if (config.expectedApiKeyId) {
38
+ lines.push(`[mes-mcp] expected apiKeyId=${maskId(config.expectedApiKeyId)}`);
39
+ }
40
+ console.error(lines.join("\n"));
41
+ }
9
42
  async function main() {
10
43
  const config = loadConfig();
44
+ logStartupDiagnostics(config);
11
45
  const client = new MesAgentApiClient(config);
12
46
  const server = new McpServer({
13
47
  name: "mes-mcp",
14
- version: "0.2.0",
48
+ version: "0.2.3",
15
49
  });
16
50
  await registerAllAgentTools(server, client, {
17
51
  registerDynamicTools: config.registerDynamicTools,
@@ -16,6 +16,8 @@ export function registerMesPrompts(server) {
16
16
  "你是 MES 业务助手,必须按当前账号权限和工具返回结果执行。",
17
17
  "不要猜测客户、物料、BOM、工艺路线、仓库等关键主数据;缺失时先查询或要求用户确认。",
18
18
  "默认先用 mes_action.list 或 mes://agent/actions 发现当前账号能做的页面业务动作,再用 mes_action.detail 查看字段,最后用 mes_action.execute 执行;不要依赖工具列表里必须存在 mes.<动作编码>。",
19
+ "mes_action.execute 返回的是执行信封,真实业务返回在 data 字段;confirm_required 产生的 invocationId 必须由同一公司/租户下有权限的账号审批。",
20
+ "mes_api.read 只用于普通业务只读接口,pageSize 最大 100;如果返回 _mcpWarnings 或提示需要动态动作,请按提示调整。",
19
21
  "销售到出库的流程也走动态动作:先查客户和物料,再创建销售单、处理审批、确认销售单、转生产计划、排产或生成工单、报工、质检、发货出库。",
20
22
  "写入类动作默认先使用 confirm_required 或 preview;preview 不写入,confirm_required 生成待确认记录,commit 只有凭证允许时才真实提交。",
21
23
  context ? `当前上下文:${context}` : "",
@@ -40,6 +40,7 @@ export function registerMesResources(server, client) {
40
40
  "2. 使用 `mes_action.detail` 查看目标动作的路径、权限、DTO 名称和输入 schema。",
41
41
  "3. 只读查询使用 `mes_api.read` 调用普通业务只读 POST 接口。",
42
42
  "4. 写入、确认、下达、报工、质检、出入库等动作使用 `mes_action.execute` 或可选的 `mes.<动作编码>`。",
43
+ "5. `MES_REGISTER_DYNAMIC_TOOLS=false` 是默认推荐配置;此时工具列表只有 `mes_api.read`、`mes_action.list`、`mes_action.detail`、`mes_action.execute` 等基础工具。只有客户端必须直观看到 `mes.<动作编码>` 时才开启动态工具展开。",
43
44
  "",
44
45
  "## 写入规则",
45
46
  "",
@@ -47,6 +48,19 @@ export function registerMesResources(server, client) {
47
48
  "- `confirm_required` 生成待确认调用,不直接写入。",
48
49
  "- `commit` 只有凭证最高执行模式允许时才真实提交。",
49
50
  "- 写入类 `confirm_required` 和 `commit` 必须有幂等键;MCP 会为单次工具调用自动生成,跨重试批处理应显式传入同一个 `idempotencyKey`。",
51
+ "- `mes_action.execute` 返回执行信封,真实业务返回在 `data` 字段;`invocationId` 是 AI 调用日志 / 待确认单的标识。",
52
+ "- 待确认单按公司/租户隔离,必须由生成确认单的同一公司下有权限的账号审批。",
53
+ "",
54
+ "## 读取和分页",
55
+ "",
56
+ "- `mes_api.read` 只用于普通业务只读接口,不用于 `/integration/agent/*`。",
57
+ "- MES 分页 `pageSize` 最大为 100;MCP 会裁剪超限值,并通过 `_mcpWarnings` 返回提示。",
58
+ "- 如果只读路径被提示需要动态业务动作,请改用 `mes_action.list/detail/execute` 查找并调用对应 actionCode。",
59
+ "",
60
+ "## 防误连",
61
+ "",
62
+ "- MCP 启动时会在 stderr 打印 MES 地址、Token 用户名、apiKeyId、agentClientId 和动态工具状态。",
63
+ "- 可配置 `MES_EXPECTED_USERNAME`、`MES_EXPECTED_AGENT_CLIENT_ID`、`MES_EXPECTED_API_KEY_ID` 做本地启动校验,避免使用旧 `.env` 误连错误账号或凭证。",
50
64
  "",
51
65
  "## 禁止事项",
52
66
  "",
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { z } from "zod";
3
+ const MAX_MES_PAGE_SIZE = 100;
3
4
  const ExecutionModeSchema = z
4
5
  .enum(["preview", "confirm_required", "commit"])
5
6
  .optional();
@@ -18,23 +19,94 @@ const DynamicActionSchema = z
18
19
  .describe("MES 页面业务动作请求体;也可以直接把 DTO 字段放在顶层。"),
19
20
  })
20
21
  .passthrough();
21
- function formatToolResult(result) {
22
+ function withWarnings(result, warnings) {
23
+ if (result && typeof result === "object" && !Array.isArray(result)) {
24
+ return {
25
+ ...result,
26
+ ...(warnings.length > 0 ? { _mcpWarnings: warnings } : {}),
27
+ };
28
+ }
29
+ return {
30
+ value: result,
31
+ ...(warnings.length > 0 ? { _mcpWarnings: warnings } : {}),
32
+ };
33
+ }
34
+ function formatToolResult(result, warnings = []) {
35
+ const structuredContent = withWarnings(result, warnings);
36
+ return {
37
+ structuredContent,
38
+ content: [
39
+ {
40
+ type: "text",
41
+ text: JSON.stringify(structuredContent, null, 2),
42
+ },
43
+ ],
44
+ };
45
+ }
46
+ function errorMessage(error) {
47
+ return error instanceof Error ? error.message : String(error);
48
+ }
49
+ function errorHints(message) {
50
+ const hints = [];
51
+ if (/pageSize/i.test(message)) {
52
+ hints.push("MES 分页上限是 100;MCP 会对 mes_api.read 和 mes_action.list 自动裁剪 pageSize。");
53
+ }
54
+ if (message.includes("AI Agent 凭证只能访问动态业务动作接口")) {
55
+ hints.push("请改用 mes_action.list/detail/execute 查找并调用对应 actionCode。");
56
+ }
57
+ if (message.includes("最高只允许")) {
58
+ hints.push("请改用 preview/confirm_required,或重新创建允许 commit 的 AI 凭证。");
59
+ }
60
+ if (message.includes("待确认 AI 调用不存在")) {
61
+ hints.push("确认单按公司/租户隔离;请切回生成确认单的同一公司账号审批。");
62
+ }
63
+ return hints;
64
+ }
65
+ function formatToolError(error, context) {
66
+ const message = errorMessage(error);
67
+ const structuredContent = {
68
+ error: {
69
+ message,
70
+ toolName: context.toolName,
71
+ ...(context.actionCode ? { actionCode: context.actionCode } : {}),
72
+ ...(context.path ? { path: context.path } : {}),
73
+ hints: errorHints(message),
74
+ },
75
+ };
22
76
  return {
23
- structuredContent: result && typeof result === "object" && !Array.isArray(result)
24
- ? result
25
- : { value: result },
77
+ isError: true,
78
+ structuredContent,
26
79
  content: [
27
80
  {
28
81
  type: "text",
29
- text: JSON.stringify(result, null, 2),
82
+ text: JSON.stringify(structuredContent, null, 2),
30
83
  },
31
84
  ],
32
85
  };
33
86
  }
87
+ async function runTool(context, handler) {
88
+ try {
89
+ return await handler();
90
+ }
91
+ catch (error) {
92
+ return formatToolError(error, context);
93
+ }
94
+ }
95
+ function normalizePageSize(payload, warnings) {
96
+ const pageSize = payload.pageSize;
97
+ if (typeof pageSize !== "number" || pageSize <= MAX_MES_PAGE_SIZE) {
98
+ return payload;
99
+ }
100
+ warnings.push({
101
+ code: "pageSize_clamped",
102
+ message: `MES pageSize 最大为 ${MAX_MES_PAGE_SIZE},已从 ${pageSize} 自动调整为 ${MAX_MES_PAGE_SIZE}。`,
103
+ });
104
+ return { ...payload, pageSize: MAX_MES_PAGE_SIZE };
105
+ }
34
106
  export function registerAgentTools(server, client) {
35
107
  server.registerTool("mes_api.read", {
36
108
  title: "Read MES API",
37
- description: "按当前 MES 账号权限读取系统内业务信息。仅用于 list/detail/view/read/export/stats/options 等只读类 POST 接口;创建、更新、删除、确认、下达等写入动作必须使用专门的 Agent 工具。",
109
+ description: "按当前 MES 账号权限读取系统内业务信息。仅用于普通业务的 list/detail/view/read/export/stats/options 等只读类 POST 接口;创建、更新、删除、确认、下达等写入动作必须使用 mes_action.execute。分页 pageSize 最大为 100,MCP 会自动裁剪超限值并在 _mcpWarnings 中提示。",
38
110
  inputSchema: z
39
111
  .object({
40
112
  path: z
@@ -53,14 +125,18 @@ export function registerAgentTools(server, client) {
53
125
  idempotentHint: true,
54
126
  },
55
127
  }, async (args) => {
56
- const parsed = z
57
- .object({
58
- path: z.string().min(1),
59
- payload: z.record(z.string(), z.unknown()).optional(),
60
- })
61
- .parse(args);
62
- const result = await client.postApiPath(parsed.path, parsed.payload ?? {});
63
- return formatToolResult(result);
128
+ return runTool({ toolName: "mes_api.read" }, async () => {
129
+ const parsed = z
130
+ .object({
131
+ path: z.string().min(1),
132
+ payload: z.record(z.string(), z.unknown()).optional(),
133
+ })
134
+ .parse(args);
135
+ const warnings = [];
136
+ const payload = normalizePageSize(parsed.payload ?? {}, warnings);
137
+ const result = await client.postApiPath(parsed.path, payload);
138
+ return formatToolResult(result, warnings);
139
+ });
64
140
  });
65
141
  }
66
142
  function normalizeDynamicArgs(args) {
@@ -86,12 +162,14 @@ function actionDescription(action) {
86
162
  const modeText = action.isWrite
87
163
  ? "写入动作,confirm_required 会生成待确认单,commit 会真实写入 MES;两种模式可显式提供 idempotencyKey,不传时 MCP 会为本次工具调用自动生成,preview 不写入。"
88
164
  : "只读动作,固定按 preview 执行。";
165
+ const resultText = "返回格式固定为 { actionCode, executionMode, status, invocationId, bizObjectType, bizObjectId, data };真实业务返回在 data 字段。";
89
166
  return [
90
167
  action.description,
91
168
  `页面接口:POST ${action.path}。`,
92
169
  action.inputDto ? `请求 DTO:${action.inputDto}。` : undefined,
93
170
  permissionText,
94
171
  modeText,
172
+ resultText,
95
173
  ]
96
174
  .filter(Boolean)
97
175
  .join("\n");
@@ -117,8 +195,24 @@ function registerGenericActionTools(server, client) {
117
195
  idempotentHint: true,
118
196
  },
119
197
  }, async (args) => {
120
- const result = await client.post("/integration/agent/actions/list", args ?? {});
121
- return formatToolResult(result);
198
+ return runTool({ toolName: "mes_action.list" }, async () => {
199
+ const parsed = z
200
+ .object({
201
+ page: z.number().int().positive().optional(),
202
+ pageSize: z.number().int().positive().optional(),
203
+ keyword: z.string().optional(),
204
+ module: z.string().optional(),
205
+ resource: z.string().optional(),
206
+ operation: z.string().optional(),
207
+ write: z.boolean().optional(),
208
+ })
209
+ .passthrough()
210
+ .parse(args ?? {});
211
+ const warnings = [];
212
+ const payload = normalizePageSize(parsed, warnings);
213
+ const result = await client.post("/integration/agent/actions/list", payload);
214
+ return formatToolResult(result, warnings);
215
+ });
122
216
  });
123
217
  server.registerTool("mes_action.detail", {
124
218
  title: "Get MES business action detail",
@@ -132,12 +226,19 @@ function registerGenericActionTools(server, client) {
132
226
  idempotentHint: true,
133
227
  },
134
228
  }, async (args) => {
135
- const result = await client.post("/integration/agent/actions/detail", args ?? {});
136
- return formatToolResult(result);
229
+ return runTool({ toolName: "mes_action.detail" }, async () => {
230
+ const parsed = z
231
+ .object({
232
+ actionCode: z.string().min(1),
233
+ })
234
+ .parse(args ?? {});
235
+ const result = await client.post("/integration/agent/actions/detail", parsed);
236
+ return formatToolResult(result);
237
+ });
137
238
  });
138
239
  server.registerTool("mes_action.execute", {
139
240
  title: "Execute MES business action",
140
- description: "执行一个由 mes_action.list 发现的 MES 页面业务动作。后端复用页面 Controller、DTO、权限、租户、状态机和调用日志。",
241
+ description: "执行一个由 mes_action.list 发现的 MES 页面业务动作。后端复用页面 Controller、DTO、权限、租户、状态机和调用日志。返回体是执行信封:真实业务结果在 data 字段;confirm_required 返回待确认 invocationId,必须由同一公司/租户下有权限的账号审批。",
141
242
  inputSchema: z
142
243
  .object({
143
244
  actionCode: z.string().min(1),
@@ -152,27 +253,29 @@ function registerGenericActionTools(server, client) {
152
253
  idempotentHint: true,
153
254
  },
154
255
  }, async (args) => {
155
- const parsed = z
156
- .object({
157
- actionCode: z.string().min(1),
158
- executionMode: ExecutionModeSchema,
159
- idempotencyKey: z.string().min(1).max(160).optional(),
160
- payload: z.record(z.string(), z.unknown()).optional(),
161
- })
162
- .passthrough()
163
- .parse(args ?? {});
164
- const { actionCode, executionMode, idempotencyKey, payload, ...rest } = parsed;
165
- const action = await client.getBusinessAction(actionCode);
166
- const result = await client.post("/integration/agent/actions/execute", {
167
- actionCode,
168
- executionMode,
169
- idempotencyKey: idempotencyKey ??
170
- (shouldEnsureIdempotencyKey(action, executionMode)
171
- ? buildIdempotencyKey(actionCode)
172
- : undefined),
173
- payload: payload ?? rest,
256
+ return runTool({ toolName: "mes_action.execute" }, async () => {
257
+ const parsed = z
258
+ .object({
259
+ actionCode: z.string().min(1),
260
+ executionMode: ExecutionModeSchema,
261
+ idempotencyKey: z.string().min(1).max(160).optional(),
262
+ payload: z.record(z.string(), z.unknown()).optional(),
263
+ })
264
+ .passthrough()
265
+ .parse(args ?? {});
266
+ const { actionCode, executionMode, idempotencyKey, payload, ...rest } = parsed;
267
+ const action = await client.getBusinessAction(actionCode);
268
+ const result = await client.post("/integration/agent/actions/execute", {
269
+ actionCode,
270
+ executionMode,
271
+ idempotencyKey: idempotencyKey ??
272
+ (shouldEnsureIdempotencyKey(action, executionMode)
273
+ ? buildIdempotencyKey(actionCode)
274
+ : undefined),
275
+ payload: payload ?? rest,
276
+ });
277
+ return formatToolResult(result);
174
278
  });
175
- return formatToolResult(result);
176
279
  });
177
280
  }
178
281
  async function registerDynamicBusinessActionTools(server, client) {
@@ -193,16 +296,18 @@ async function registerDynamicBusinessActionTools(server, client) {
193
296
  idempotentHint: true,
194
297
  },
195
298
  }, async (args) => {
196
- const normalized = normalizeDynamicArgs(args);
197
- const result = await client.post("/integration/agent/actions/execute", {
198
- actionCode: action.actionCode,
199
- ...normalized,
200
- idempotencyKey: normalized.idempotencyKey ??
201
- (shouldEnsureIdempotencyKey(action, normalized.executionMode)
202
- ? buildIdempotencyKey(action.actionCode)
203
- : undefined),
299
+ return runTool({ toolName, actionCode: action.actionCode }, async () => {
300
+ const normalized = normalizeDynamicArgs(args);
301
+ const result = await client.post("/integration/agent/actions/execute", {
302
+ actionCode: action.actionCode,
303
+ ...normalized,
304
+ idempotencyKey: normalized.idempotencyKey ??
305
+ (shouldEnsureIdempotencyKey(action, normalized.executionMode)
306
+ ? buildIdempotencyKey(action.actionCode)
307
+ : undefined),
308
+ });
309
+ return formatToolResult(result);
204
310
  });
205
- return formatToolResult(result);
206
311
  });
207
312
  }
208
313
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mes-mcp",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "description": "MES MCP adapter for Marvis and AI agents",
6
6
  "license": "UNLICENSED",