mes-mcp 0.1.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/.env.example +4 -0
- package/README.md +60 -0
- package/dist/clients/mes-agent-api.client.js +152 -0
- package/dist/config.js +24 -0
- package/dist/index.js +24 -0
- package/dist/prompts/register-prompts.js +28 -0
- package/dist/resources/register-resources.js +21 -0
- package/dist/tools/agent-tools.js +209 -0
- package/dist/tools/register-tools.js +62 -0
- package/package.json +38 -0
package/.env.example
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# MES MCP
|
|
2
|
+
|
|
3
|
+
MES MCP 是 MES 开放集成体系的 MCP 协议适配层,用于 Marvis 或其他 AI Agent 调用 MES 的 Agent OpenAPI 合同。
|
|
4
|
+
|
|
5
|
+
## 边界
|
|
6
|
+
|
|
7
|
+
- 只负责 MCP 工具、资源、提示词和协议输入输出。
|
|
8
|
+
- 写入类工具只调用 `mes-node` 的 `/integration/agent/*` 接口;通用读取工具只调用账号权限内的只读业务接口。
|
|
9
|
+
- 不直连数据库,不复制 MES 业务 Service,不绕过 `companyId`、RBAC、状态机、库存、质量和审计规则。
|
|
10
|
+
- 不保存用户明文账号密码;运行时使用 `mes-node` 创建的 `agent_mcp` 连接凭证 JWT。
|
|
11
|
+
|
|
12
|
+
## 配置
|
|
13
|
+
|
|
14
|
+
复制 `.env.example` 为 `.env`,填入开放集成里生成的 AI 智能体凭证:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
MES_BASE_URL=http://127.0.0.1:6033
|
|
18
|
+
MES_API_PREFIX=/api
|
|
19
|
+
MES_AGENT_TOKEN=BearerTokenWithoutBearerPrefix
|
|
20
|
+
MES_TIMEOUT_MS=30000
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 开发
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install
|
|
27
|
+
npm run build
|
|
28
|
+
npm run dev
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 工具
|
|
32
|
+
|
|
33
|
+
工具名与 `mes-node` Agent 合同编码一致:
|
|
34
|
+
|
|
35
|
+
- `document_intake.parse`
|
|
36
|
+
- `customer.match`
|
|
37
|
+
- `material.match`
|
|
38
|
+
- `sale_order.create`
|
|
39
|
+
- `sale_order.confirm`
|
|
40
|
+
- `production_plan.create_from_sale_order`
|
|
41
|
+
- `work_order.create_from_plan`
|
|
42
|
+
- `work_order.release`
|
|
43
|
+
- `sale_order.query`
|
|
44
|
+
- `work_order.query`
|
|
45
|
+
- `inventory.query`
|
|
46
|
+
- `mes_api.read`:按当前 MES 账号权限读取普通业务接口,例如 `/sale-order/list`、`/material/detail`。后端只放行只读类接口,写入动作必须使用专门 Agent 工具;读取调用会在 MES AI 调用日志里记录路径、请求摘要和结果摘要。
|
|
47
|
+
|
|
48
|
+
写入类工具支持 `executionMode`:
|
|
49
|
+
|
|
50
|
+
- `preview`:只预览,不写入。
|
|
51
|
+
- `confirm_required`:返回待确认内容,不写入。
|
|
52
|
+
- `commit`:提交到 MES,按当前账号权限、工具白名单、业务规则和幂等键执行。
|
|
53
|
+
|
|
54
|
+
## Marvis 接入建议
|
|
55
|
+
|
|
56
|
+
1. 在 MES 管理端“开放集成”创建凭证,类型选择 AI 智能体。
|
|
57
|
+
2. `agentClientId` 建议填写 `marvis` 或具体渠道名。
|
|
58
|
+
3. 授权范围建议先选“跟随账号权限”,最高执行模式建议先用 `confirm_required`;如果只想开放少量动作,再切换为“仅允许指定工具”。只读型 AI 凭证可以只选择 `mes_api.read`。
|
|
59
|
+
4. 将返回的 JWT 配置为 `MES_AGENT_TOKEN`。
|
|
60
|
+
5. Marvis 侧通过 MCP 调用工具;图片、PDF、Excel 可先由 Marvis 识别成结构化草稿,再调用 `document_intake.parse`、`customer.match`、`material.match` 和后续写入工具。
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
export class MesAgentApiClient {
|
|
2
|
+
config;
|
|
3
|
+
static WRITE_PATH_TOKENS = new Set([
|
|
4
|
+
"create",
|
|
5
|
+
"update",
|
|
6
|
+
"delete",
|
|
7
|
+
"remove",
|
|
8
|
+
"revoke",
|
|
9
|
+
"toggle",
|
|
10
|
+
"confirm",
|
|
11
|
+
"cancel",
|
|
12
|
+
"approve",
|
|
13
|
+
"reject",
|
|
14
|
+
"release",
|
|
15
|
+
"start",
|
|
16
|
+
"complete",
|
|
17
|
+
"close",
|
|
18
|
+
"submit",
|
|
19
|
+
"import",
|
|
20
|
+
"run",
|
|
21
|
+
"cleanup",
|
|
22
|
+
"rotate",
|
|
23
|
+
"test",
|
|
24
|
+
"retry",
|
|
25
|
+
"allocate",
|
|
26
|
+
"unallocate",
|
|
27
|
+
"reverse",
|
|
28
|
+
"convert",
|
|
29
|
+
"send",
|
|
30
|
+
"recall",
|
|
31
|
+
"scrap",
|
|
32
|
+
"activate",
|
|
33
|
+
"deprecate",
|
|
34
|
+
"purge",
|
|
35
|
+
"adjust",
|
|
36
|
+
"freeze",
|
|
37
|
+
"end",
|
|
38
|
+
"configure",
|
|
39
|
+
"generate",
|
|
40
|
+
"save",
|
|
41
|
+
"copy",
|
|
42
|
+
"archive",
|
|
43
|
+
"publish",
|
|
44
|
+
"restore",
|
|
45
|
+
"execute",
|
|
46
|
+
"handle",
|
|
47
|
+
"verify",
|
|
48
|
+
"pass",
|
|
49
|
+
"fail",
|
|
50
|
+
"lock",
|
|
51
|
+
"unlock",
|
|
52
|
+
"enable",
|
|
53
|
+
"disable",
|
|
54
|
+
"bind",
|
|
55
|
+
"unbind",
|
|
56
|
+
"link",
|
|
57
|
+
"unlink",
|
|
58
|
+
"upload",
|
|
59
|
+
"assign",
|
|
60
|
+
"claim",
|
|
61
|
+
"dispatch",
|
|
62
|
+
"receive",
|
|
63
|
+
"issue",
|
|
64
|
+
"return",
|
|
65
|
+
"split",
|
|
66
|
+
"mark",
|
|
67
|
+
"change",
|
|
68
|
+
"transfer",
|
|
69
|
+
"in",
|
|
70
|
+
"out",
|
|
71
|
+
]);
|
|
72
|
+
static WRITE_PATH_SEGMENTS = new Set([
|
|
73
|
+
"report",
|
|
74
|
+
"quick-report",
|
|
75
|
+
"report-operation",
|
|
76
|
+
"report-direct",
|
|
77
|
+
"batch-report",
|
|
78
|
+
]);
|
|
79
|
+
constructor(config) {
|
|
80
|
+
this.config = config;
|
|
81
|
+
}
|
|
82
|
+
async post(path, payload) {
|
|
83
|
+
const controller = new AbortController();
|
|
84
|
+
const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
85
|
+
try {
|
|
86
|
+
const response = await fetch(this.buildUrl(path), {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: {
|
|
89
|
+
Authorization: `Bearer ${this.config.mesAgentToken}`,
|
|
90
|
+
"Content-Type": "application/json",
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify(payload ?? {}),
|
|
93
|
+
signal: controller.signal,
|
|
94
|
+
});
|
|
95
|
+
const text = await response.text();
|
|
96
|
+
const body = this.parseBody(text);
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
const message = body?.msg || body?.message || text || response.statusText;
|
|
99
|
+
throw new Error(`MES ${response.status}: ${message}`);
|
|
100
|
+
}
|
|
101
|
+
if (body && typeof body.code === "number" && body.code !== 0) {
|
|
102
|
+
throw new Error(body.msg || body.message || `MES code ${body.code}`);
|
|
103
|
+
}
|
|
104
|
+
return (body && "data" in body ? body.data : body);
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
clearTimeout(timeout);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async postApiPath(path, payload) {
|
|
111
|
+
if (/^https?:\/\//i.test(path) || path.includes("..")) {
|
|
112
|
+
throw new Error("MES API path must be a relative API path");
|
|
113
|
+
}
|
|
114
|
+
const normalized = path.replace(/^\/+api\/?/i, "/").replace(/^\/?/, "/");
|
|
115
|
+
if (/^\/integration\/agent(\/|$)/i.test(normalized)) {
|
|
116
|
+
throw new Error("Use dedicated Agent tools for /integration/agent paths");
|
|
117
|
+
}
|
|
118
|
+
if (this.isWriteLikePath(normalized)) {
|
|
119
|
+
throw new Error("mes_api.read only supports read-only MES API paths");
|
|
120
|
+
}
|
|
121
|
+
return this.post(normalized, payload);
|
|
122
|
+
}
|
|
123
|
+
isWriteLikePath(path) {
|
|
124
|
+
const actionSegments = path
|
|
125
|
+
.toLowerCase()
|
|
126
|
+
.split(/[?#]/)[0]
|
|
127
|
+
.split("/")
|
|
128
|
+
.filter(Boolean);
|
|
129
|
+
const lastSegment = actionSegments[actionSegments.length - 1] ?? "";
|
|
130
|
+
if (MesAgentApiClient.WRITE_PATH_SEGMENTS.has(lastSegment)) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
return lastSegment
|
|
134
|
+
.split(/[._-]+/)
|
|
135
|
+
.filter(Boolean)
|
|
136
|
+
.some((token) => MesAgentApiClient.WRITE_PATH_TOKENS.has(token));
|
|
137
|
+
}
|
|
138
|
+
buildUrl(path) {
|
|
139
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
140
|
+
return `${this.config.mesBaseUrl}${this.config.mesApiPrefix}${normalizedPath}`;
|
|
141
|
+
}
|
|
142
|
+
parseBody(text) {
|
|
143
|
+
if (!text)
|
|
144
|
+
return null;
|
|
145
|
+
try {
|
|
146
|
+
return JSON.parse(text);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
function optionalEnv(name, fallback) {
|
|
3
|
+
const value = process.env[name]?.trim();
|
|
4
|
+
return value && value.length > 0 ? value : fallback;
|
|
5
|
+
}
|
|
6
|
+
function requiredEnv(name) {
|
|
7
|
+
const value = process.env[name]?.trim();
|
|
8
|
+
if (!value) {
|
|
9
|
+
throw new Error(`${name} is required`);
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
export function loadConfig() {
|
|
14
|
+
const timeoutMs = Number(optionalEnv("MES_TIMEOUT_MS", "30000"));
|
|
15
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
16
|
+
throw new Error("MES_TIMEOUT_MS must be a positive number");
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
mesBaseUrl: optionalEnv("MES_BASE_URL", "http://127.0.0.1:6033").replace(/\/+$/, ""),
|
|
20
|
+
mesApiPrefix: optionalEnv("MES_API_PREFIX", "/api").replace(/\/+$/, ""),
|
|
21
|
+
mesAgentToken: requiredEnv("MES_AGENT_TOKEN"),
|
|
22
|
+
timeoutMs,
|
|
23
|
+
};
|
|
24
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { loadConfig } from "./config.js";
|
|
5
|
+
import { MesAgentApiClient } from "./clients/mes-agent-api.client.js";
|
|
6
|
+
import { registerMesPrompts } from "./prompts/register-prompts.js";
|
|
7
|
+
import { registerMesResources } from "./resources/register-resources.js";
|
|
8
|
+
import { registerAgentTools } from "./tools/register-tools.js";
|
|
9
|
+
async function main() {
|
|
10
|
+
const config = loadConfig();
|
|
11
|
+
const client = new MesAgentApiClient(config);
|
|
12
|
+
const server = new McpServer({
|
|
13
|
+
name: "mes-mcp",
|
|
14
|
+
version: "0.1.0",
|
|
15
|
+
});
|
|
16
|
+
registerAgentTools(server, client);
|
|
17
|
+
registerMesResources(server, client);
|
|
18
|
+
registerMesPrompts(server);
|
|
19
|
+
await server.connect(new StdioServerTransport());
|
|
20
|
+
}
|
|
21
|
+
main().catch((error) => {
|
|
22
|
+
console.error(error instanceof Error ? error.message : error);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerMesPrompts(server) {
|
|
3
|
+
server.registerPrompt("mes_order_to_work_order", {
|
|
4
|
+
title: "MES order to work order flow",
|
|
5
|
+
description: "把图片/文本订单录入 MES 并逐步生成销售订单、生产计划和工单的标准提示词。",
|
|
6
|
+
argsSchema: {
|
|
7
|
+
context: z.string().optional(),
|
|
8
|
+
},
|
|
9
|
+
}, ({ context }) => ({
|
|
10
|
+
messages: [
|
|
11
|
+
{
|
|
12
|
+
role: "user",
|
|
13
|
+
content: {
|
|
14
|
+
type: "text",
|
|
15
|
+
text: [
|
|
16
|
+
"你是 MES 业务助手,必须按当前账号权限和工具返回结果执行。",
|
|
17
|
+
"不要猜测客户、物料、BOM、工艺路线、仓库等关键主数据;缺失时先查询或要求用户确认。",
|
|
18
|
+
"推荐流程:document_intake.parse -> customer.match / material.match -> sale_order.create -> sale_order.confirm -> production_plan.create_from_sale_order -> work_order.create_from_plan -> work_order.release。",
|
|
19
|
+
"写入类动作默认先使用 confirm_required 或 preview;用户明确确认后再使用 commit,并传 idempotencyKey。",
|
|
20
|
+
context ? `当前上下文:${context}` : "",
|
|
21
|
+
]
|
|
22
|
+
.filter(Boolean)
|
|
23
|
+
.join("\n"),
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function registerMesResources(server, client) {
|
|
2
|
+
server.registerResource("mes_agent_contracts", "mes://agent/contracts", {
|
|
3
|
+
title: "MES Agent Contracts",
|
|
4
|
+
description: "当前 AI 凭证允许调用的 MES Agent 合同列表。",
|
|
5
|
+
mimeType: "application/json",
|
|
6
|
+
}, async (uri) => {
|
|
7
|
+
const contracts = await client.post("/integration/agent/contracts/list", {
|
|
8
|
+
page: 1,
|
|
9
|
+
pageSize: 100,
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
contents: [
|
|
13
|
+
{
|
|
14
|
+
uri: uri.href,
|
|
15
|
+
mimeType: "application/json",
|
|
16
|
+
text: JSON.stringify(contracts, null, 2),
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const ExecutionModeSchema = z
|
|
3
|
+
.enum(["preview", "confirm_required", "commit"])
|
|
4
|
+
.optional();
|
|
5
|
+
const IdempotencySchema = z.string().min(1).max(160).optional();
|
|
6
|
+
const QuerySchema = z
|
|
7
|
+
.object({
|
|
8
|
+
page: z.number().int().positive().optional(),
|
|
9
|
+
pageSize: z.number().int().positive().max(100).optional(),
|
|
10
|
+
keyword: z.string().optional(),
|
|
11
|
+
})
|
|
12
|
+
.passthrough();
|
|
13
|
+
export const AGENT_TOOL_SPECS = [
|
|
14
|
+
{
|
|
15
|
+
name: "document_intake.parse",
|
|
16
|
+
title: "Parse order document",
|
|
17
|
+
description: "接收 Marvis/上游 AI 从图片、PDF、Excel、文本中识别出的订单资料,返回可确认的销售订单草稿。",
|
|
18
|
+
path: "/integration/agent/document-intake/parse",
|
|
19
|
+
readOnlyHint: true,
|
|
20
|
+
schema: z
|
|
21
|
+
.object({
|
|
22
|
+
executionMode: ExecutionModeSchema,
|
|
23
|
+
idempotencyKey: IdempotencySchema,
|
|
24
|
+
rawText: z.string().optional(),
|
|
25
|
+
imageUrl: z.string().url().optional(),
|
|
26
|
+
fileUrl: z.string().url().optional(),
|
|
27
|
+
extractedOrder: z
|
|
28
|
+
.object({
|
|
29
|
+
orderNo: z.string().optional(),
|
|
30
|
+
customerId: z.string().uuid().optional(),
|
|
31
|
+
customerName: z.string().optional(),
|
|
32
|
+
deliveryDate: z.string().optional(),
|
|
33
|
+
remarks: z.string().optional(),
|
|
34
|
+
items: z
|
|
35
|
+
.array(z
|
|
36
|
+
.object({
|
|
37
|
+
materialId: z.string().uuid().optional(),
|
|
38
|
+
materialCode: z.string().optional(),
|
|
39
|
+
materialName: z.string().optional(),
|
|
40
|
+
quantity: z.number().positive().optional(),
|
|
41
|
+
unitPrice: z.number().min(0).optional(),
|
|
42
|
+
remarks: z.string().optional(),
|
|
43
|
+
})
|
|
44
|
+
.passthrough())
|
|
45
|
+
.optional(),
|
|
46
|
+
})
|
|
47
|
+
.passthrough()
|
|
48
|
+
.optional(),
|
|
49
|
+
sourceMeta: z.record(z.string(), z.unknown()).optional(),
|
|
50
|
+
})
|
|
51
|
+
.passthrough(),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "customer.match",
|
|
55
|
+
title: "Match customer",
|
|
56
|
+
description: "按当前 MES 账号权限匹配启用中的客户主数据;创建销售单前用于确认客户 ID,避免 AI 直接猜测客户。",
|
|
57
|
+
path: "/integration/agent/customer/match",
|
|
58
|
+
readOnlyHint: true,
|
|
59
|
+
schema: QuerySchema.extend({
|
|
60
|
+
customerCode: z.string().optional(),
|
|
61
|
+
name: z.string().optional(),
|
|
62
|
+
contactName: z.string().optional(),
|
|
63
|
+
contactPhone: z.string().optional(),
|
|
64
|
+
}).passthrough(),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "material.match",
|
|
68
|
+
title: "Match material",
|
|
69
|
+
description: "按当前 MES 账号权限匹配启用中的物料主数据;创建销售单前用于确认物料 ID,避免用图片识别文本直接写入。",
|
|
70
|
+
path: "/integration/agent/material/match",
|
|
71
|
+
readOnlyHint: true,
|
|
72
|
+
schema: QuerySchema.extend({
|
|
73
|
+
code: z.string().optional(),
|
|
74
|
+
name: z.string().optional(),
|
|
75
|
+
spec: z.string().optional(),
|
|
76
|
+
barcode: z.string().optional(),
|
|
77
|
+
}).passthrough(),
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "sale_order.create",
|
|
81
|
+
title: "Create sale order",
|
|
82
|
+
description: "按当前 MES 账号权限创建销售订单;commit 才写入,preview/confirm_required 只返回待确认内容。",
|
|
83
|
+
path: "/integration/agent/sale-order/create",
|
|
84
|
+
destructiveHint: true,
|
|
85
|
+
schema: z
|
|
86
|
+
.object({
|
|
87
|
+
executionMode: ExecutionModeSchema,
|
|
88
|
+
idempotencyKey: IdempotencySchema,
|
|
89
|
+
orderNo: z.string().optional(),
|
|
90
|
+
customerId: z.string().uuid().optional(),
|
|
91
|
+
salesRepId: z.string().uuid().optional(),
|
|
92
|
+
deliveryDate: z.string().optional(),
|
|
93
|
+
paymentTerms: z.string().optional(),
|
|
94
|
+
remarks: z.string().optional(),
|
|
95
|
+
items: z.array(z
|
|
96
|
+
.object({
|
|
97
|
+
materialId: z.string().uuid(),
|
|
98
|
+
quantity: z.number().positive(),
|
|
99
|
+
unitPrice: z.number().min(0).optional(),
|
|
100
|
+
customerMaterialCode: z.string().optional(),
|
|
101
|
+
customerMaterialName: z.string().optional(),
|
|
102
|
+
customerMaterialSpec: z.string().optional(),
|
|
103
|
+
remarks: z.string().optional(),
|
|
104
|
+
})
|
|
105
|
+
.passthrough()),
|
|
106
|
+
})
|
|
107
|
+
.passthrough(),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "sale_order.confirm",
|
|
111
|
+
title: "Confirm sale order",
|
|
112
|
+
description: "确认销售订单,复用 MES 的审批门控、状态规则和明细校验;销售订单转生产计划前必须先确认。",
|
|
113
|
+
path: "/integration/agent/sale-order/confirm",
|
|
114
|
+
destructiveHint: true,
|
|
115
|
+
schema: z
|
|
116
|
+
.object({
|
|
117
|
+
executionMode: ExecutionModeSchema,
|
|
118
|
+
idempotencyKey: IdempotencySchema,
|
|
119
|
+
id: z.string().uuid(),
|
|
120
|
+
})
|
|
121
|
+
.passthrough(),
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: "production_plan.create_from_sale_order",
|
|
125
|
+
title: "Create production plan from sale order",
|
|
126
|
+
description: "将已确认销售订单转为生产计划,复用 MES 原有数量、BOM 和工艺校验。",
|
|
127
|
+
path: "/integration/agent/production-plan/create-from-sale-order",
|
|
128
|
+
destructiveHint: true,
|
|
129
|
+
schema: z
|
|
130
|
+
.object({
|
|
131
|
+
executionMode: ExecutionModeSchema,
|
|
132
|
+
idempotencyKey: IdempotencySchema,
|
|
133
|
+
saleOrderId: z.string().uuid(),
|
|
134
|
+
planStartDate: z.string(),
|
|
135
|
+
planEndDate: z.string(),
|
|
136
|
+
priority: z.number().int().min(1).max(10).optional(),
|
|
137
|
+
description: z.string().optional(),
|
|
138
|
+
items: z.array(z
|
|
139
|
+
.object({
|
|
140
|
+
saleOrderItemId: z.string().uuid(),
|
|
141
|
+
requiredQty: z.number().positive().optional(),
|
|
142
|
+
plannedLossRate: z.number().min(0).optional(),
|
|
143
|
+
plannedQty: z.number().positive().optional(),
|
|
144
|
+
bomHeaderId: z.string().uuid().optional(),
|
|
145
|
+
routeId: z.string().uuid().optional(),
|
|
146
|
+
})
|
|
147
|
+
.passthrough()),
|
|
148
|
+
})
|
|
149
|
+
.passthrough(),
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: "work_order.create_from_plan",
|
|
153
|
+
title: "Create work order from production plan",
|
|
154
|
+
description: "确认生产计划并自动创建工单,复用 MES 的 BOM 展开和工艺工序生成规则。",
|
|
155
|
+
path: "/integration/agent/work-order/create-from-plan",
|
|
156
|
+
destructiveHint: true,
|
|
157
|
+
schema: z
|
|
158
|
+
.object({
|
|
159
|
+
executionMode: ExecutionModeSchema,
|
|
160
|
+
idempotencyKey: IdempotencySchema,
|
|
161
|
+
planId: z.string().uuid(),
|
|
162
|
+
routeId: z.string().uuid().optional(),
|
|
163
|
+
bomHeaderId: z.string().uuid().optional(),
|
|
164
|
+
assigneeId: z.string().uuid().optional(),
|
|
165
|
+
receiptWarehouseId: z.string().uuid().optional(),
|
|
166
|
+
})
|
|
167
|
+
.passthrough(),
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "work_order.release",
|
|
171
|
+
title: "Release work order",
|
|
172
|
+
description: "下达工单,复用 MES 状态机、审批门控、物料需求展开和事件推送。",
|
|
173
|
+
path: "/integration/agent/work-order/release",
|
|
174
|
+
destructiveHint: true,
|
|
175
|
+
schema: z
|
|
176
|
+
.object({
|
|
177
|
+
executionMode: ExecutionModeSchema,
|
|
178
|
+
idempotencyKey: IdempotencySchema,
|
|
179
|
+
id: z.string().uuid(),
|
|
180
|
+
assigneeId: z.string().uuid().optional(),
|
|
181
|
+
receiptWarehouseId: z.string().uuid().optional(),
|
|
182
|
+
})
|
|
183
|
+
.passthrough(),
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "sale_order.query",
|
|
187
|
+
title: "Query sale orders",
|
|
188
|
+
description: "按当前 MES 账号权限查询销售订单列表。",
|
|
189
|
+
path: "/integration/agent/sale-order/query",
|
|
190
|
+
readOnlyHint: true,
|
|
191
|
+
schema: QuerySchema,
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: "work_order.query",
|
|
195
|
+
title: "Query work orders",
|
|
196
|
+
description: "按当前 MES 账号权限查询工单列表。",
|
|
197
|
+
path: "/integration/agent/work-order/query",
|
|
198
|
+
readOnlyHint: true,
|
|
199
|
+
schema: QuerySchema,
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: "inventory.query",
|
|
203
|
+
title: "Query inventory",
|
|
204
|
+
description: "按当前 MES 账号权限查询库存现有量和可用量。",
|
|
205
|
+
path: "/integration/agent/inventory/query",
|
|
206
|
+
readOnlyHint: true,
|
|
207
|
+
schema: QuerySchema,
|
|
208
|
+
},
|
|
209
|
+
];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { AGENT_TOOL_SPECS } from "./agent-tools.js";
|
|
3
|
+
function formatToolResult(result) {
|
|
4
|
+
return {
|
|
5
|
+
structuredContent: result && typeof result === "object" && !Array.isArray(result)
|
|
6
|
+
? result
|
|
7
|
+
: { value: result },
|
|
8
|
+
content: [
|
|
9
|
+
{
|
|
10
|
+
type: "text",
|
|
11
|
+
text: JSON.stringify(result, null, 2),
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function registerAgentTools(server, client) {
|
|
17
|
+
for (const spec of AGENT_TOOL_SPECS) {
|
|
18
|
+
server.registerTool(spec.name, {
|
|
19
|
+
title: spec.title,
|
|
20
|
+
description: spec.description,
|
|
21
|
+
inputSchema: spec.schema,
|
|
22
|
+
annotations: {
|
|
23
|
+
readOnlyHint: spec.readOnlyHint ?? false,
|
|
24
|
+
destructiveHint: spec.destructiveHint ?? false,
|
|
25
|
+
idempotentHint: true,
|
|
26
|
+
},
|
|
27
|
+
}, async (args) => {
|
|
28
|
+
const result = await client.post(spec.path, args ?? {});
|
|
29
|
+
return formatToolResult(result);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
server.registerTool("mes_api.read", {
|
|
33
|
+
title: "Read MES API",
|
|
34
|
+
description: "按当前 MES 账号权限读取系统内业务信息。仅用于 list/detail/view/read/export/stats/options 等只读类 POST 接口;创建、更新、删除、确认、下达等写入动作必须使用专门的 Agent 工具。",
|
|
35
|
+
inputSchema: z
|
|
36
|
+
.object({
|
|
37
|
+
path: z
|
|
38
|
+
.string()
|
|
39
|
+
.min(1)
|
|
40
|
+
.describe("MES API 相对路径,例如 /sale-order/list 或 /api/material/detail"),
|
|
41
|
+
payload: z
|
|
42
|
+
.record(z.string(), z.unknown())
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("POST 请求体,按对应 MES 页面接口参数传入"),
|
|
45
|
+
})
|
|
46
|
+
.passthrough(),
|
|
47
|
+
annotations: {
|
|
48
|
+
readOnlyHint: true,
|
|
49
|
+
destructiveHint: false,
|
|
50
|
+
idempotentHint: true,
|
|
51
|
+
},
|
|
52
|
+
}, async (args) => {
|
|
53
|
+
const parsed = z
|
|
54
|
+
.object({
|
|
55
|
+
path: z.string().min(1),
|
|
56
|
+
payload: z.record(z.string(), z.unknown()).optional(),
|
|
57
|
+
})
|
|
58
|
+
.parse(args);
|
|
59
|
+
const result = await client.postApiPath(parsed.path, parsed.payload ?? {});
|
|
60
|
+
return formatToolResult(result);
|
|
61
|
+
});
|
|
62
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mes-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "MES MCP adapter for Marvis and AI agents",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"mes-mcp": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
".env.example"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc -p tsconfig.json",
|
|
18
|
+
"dev": "tsx src/index.ts",
|
|
19
|
+
"prepublishOnly": "npm run build",
|
|
20
|
+
"start": "node dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
30
|
+
"dotenv": "^17.4.2",
|
|
31
|
+
"zod": "^4.3.6"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^22.15.29",
|
|
35
|
+
"tsx": "^4.20.6",
|
|
36
|
+
"typescript": "^5.9.3"
|
|
37
|
+
}
|
|
38
|
+
}
|