ym-openclaw-task 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/README.md +66 -0
- package/package.json +33 -0
- package/src/index.js +133 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# ym-openclaw-task
|
|
2
|
+
|
|
3
|
+
OpenClaw(龙虾)任务完成上报 MCP server。任务结束后自动将 **执行时间(精确到年月日时分秒)** 与 **完成概要** 上报到你指定的接口。
|
|
4
|
+
|
|
5
|
+
## 安装与运行
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npm start # 以 MCP server(stdio)方式启动
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 工具
|
|
13
|
+
|
|
14
|
+
| 工具 | 参数 | 说明 |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| `report_task` | `task_name`(必填) | 上报任务完成:raw JSON POST 到 REPORT_URL |
|
|
17
|
+
|
|
18
|
+
上报的 JSON 格式(**固定模板,三个字段**):
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"title": "下发给龙虾工作任务",
|
|
23
|
+
"content": "下发龙虾的任务:任务名已完成请前往查阅结果",
|
|
24
|
+
"datetime": "2026-09-13 06:12:38"
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- `title`:固定「下发给龙虾工作任务」
|
|
29
|
+
- `content`:固定模板「下发龙虾的任务:{任务名}已完成请前往查阅结果」
|
|
30
|
+
- `datetime`:完成后 +10 秒,精确到年月日时分秒
|
|
31
|
+
|
|
32
|
+
## 配置(环境变量)
|
|
33
|
+
|
|
34
|
+
| 变量 | 必填 | 说明 |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| `REPORT_URL` | 是 | 上报接口地址(POST) |
|
|
37
|
+
| `REPORT_HEADERS` | 否 | JSON 字符串,附加请求头 |
|
|
38
|
+
|
|
39
|
+
## 注册到 OpenClaw
|
|
40
|
+
|
|
41
|
+
发布到 npm 后:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
openclaw mcp add ym-openclaw-task \
|
|
45
|
+
--command npx --args -y ym-openclaw-task \
|
|
46
|
+
--env REPORT_URL=http://你的接口/report
|
|
47
|
+
openclaw daemon restart
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
OpenClaw 即可在任务结束时调用 `report_task` 工具完成上报。
|
|
51
|
+
|
|
52
|
+
## 接入 MCP 客户端(通用配置)
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"mcpServers": {
|
|
57
|
+
"ym-openclaw-task": {
|
|
58
|
+
"command": "npx",
|
|
59
|
+
"args": ["-y", "ym-openclaw-task"],
|
|
60
|
+
"env": {
|
|
61
|
+
"REPORT_URL": "http://你的接口/report"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ym-openclaw-task",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenClaw(龙虾)任务完成上报 MCP server:任务结束后自动将执行时间(精确到年月日时分秒)与完成概要上报到指定接口。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ym-openclaw-task": "src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"start": "node src/index.js",
|
|
16
|
+
"test": "node test/report.test.mjs"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"openclaw",
|
|
20
|
+
"mcp",
|
|
21
|
+
"report",
|
|
22
|
+
"task",
|
|
23
|
+
"webhook",
|
|
24
|
+
"model-context-protocol"
|
|
25
|
+
],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ym-openclaw-task — OpenClaw(龙虾)任务完成上报 MCP server(stdio)
|
|
4
|
+
*
|
|
5
|
+
* 工具 report_task:
|
|
6
|
+
* OpenClaw(或任何 MCP 客户端)在任务结束时调用它,把「执行时间(精确到年月日时分秒)
|
|
7
|
+
* + 完成概要」POST 到配置的 REPORT_URL。
|
|
8
|
+
*
|
|
9
|
+
* 注册到 OpenClaw(发布到 npm 后):
|
|
10
|
+
* openclaw mcp add ym-openclaw-task --command npx --args -y ym-openclaw-task \
|
|
11
|
+
* --env REPORT_URL=http://your-server/report
|
|
12
|
+
*
|
|
13
|
+
* 环境变量:
|
|
14
|
+
* REPORT_URL 必填,上报接口地址(POST)
|
|
15
|
+
* REPORT_HEADERS 可选,JSON 对象字符串,附加请求头(如 {"Authorization":"Bearer xxx"})
|
|
16
|
+
*/
|
|
17
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
18
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
19
|
+
import {
|
|
20
|
+
ListToolsRequestSchema,
|
|
21
|
+
CallToolRequestSchema,
|
|
22
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
23
|
+
|
|
24
|
+
/** 当前时间,格式 YYYY-MM-DD HH:mm:ss(本地时区,精确到秒) */
|
|
25
|
+
export function nowString(date = new Date()) {
|
|
26
|
+
const p = (n) => String(n).padStart(2, '0');
|
|
27
|
+
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())} ${p(date.getHours())}:${p(date.getMinutes())}:${p(date.getSeconds())}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 上报时间相对当前延后 10 秒(接口约定:完成后增加 10 秒) */
|
|
31
|
+
export const REPORT_DELAY_MS = 10000;
|
|
32
|
+
|
|
33
|
+
function loadEnv() {
|
|
34
|
+
const url = process.env.REPORT_URL || '';
|
|
35
|
+
let headers = {};
|
|
36
|
+
if (process.env.REPORT_HEADERS) {
|
|
37
|
+
try {
|
|
38
|
+
headers = JSON.parse(process.env.REPORT_HEADERS);
|
|
39
|
+
} catch {
|
|
40
|
+
process.stderr.write('[ym-openclaw-task] 警告: REPORT_HEADERS 不是合法 JSON,已忽略\n');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return { url, headers: { 'Content-Type': 'application/json', ...headers } };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 上报到指定接口,返回 { ok, status, responseText } */
|
|
47
|
+
export async function report(payload) {
|
|
48
|
+
const { url, headers } = loadEnv();
|
|
49
|
+
if (!url) {
|
|
50
|
+
throw new Error('未配置 REPORT_URL,无法上报。请在环境变量中设置 REPORT_URL。');
|
|
51
|
+
}
|
|
52
|
+
const resp = await fetch(url, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers,
|
|
55
|
+
body: JSON.stringify(payload),
|
|
56
|
+
});
|
|
57
|
+
const text = await resp.text();
|
|
58
|
+
if (!resp.ok) {
|
|
59
|
+
throw new Error(`上报失败 HTTP ${resp.status}: ${text.slice(0, 300)}`);
|
|
60
|
+
}
|
|
61
|
+
return { ok: true, status: resp.status, responseText: text };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** 固定标题 */
|
|
65
|
+
export const REPORT_TITLE = '下发给龙虾工作任务';
|
|
66
|
+
|
|
67
|
+
/** 组装上报体(raw JSON):title 固定 / content 固定模板含任务名 / datetime 完成后+10秒 */
|
|
68
|
+
export function buildReportPayload(taskName, now = new Date()) {
|
|
69
|
+
return {
|
|
70
|
+
title: REPORT_TITLE,
|
|
71
|
+
content: `下发龙虾的任务:${String(taskName ?? '')}已完成请前往查阅结果`,
|
|
72
|
+
datetime: nowString(new Date(now.getTime() + REPORT_DELAY_MS)),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const server = new Server(
|
|
77
|
+
{ name: 'ym-openclaw-task', version: '1.0.0' },
|
|
78
|
+
{ capabilities: { tools: {} } }
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const TOOLS = [
|
|
82
|
+
{
|
|
83
|
+
name: 'report_task',
|
|
84
|
+
description:
|
|
85
|
+
'上报 OpenClaw(龙虾)任务完成:title 固定「下发给龙虾工作任务」,content 固定模板「下发龙虾的任务:{任务名}已完成请前往查阅结果」,datetime 完成后+10秒(YYYY-MM-DD HH:mm:ss),raw JSON POST 到配置的 REPORT_URL。任务结束时调用。',
|
|
86
|
+
inputSchema: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
properties: {
|
|
89
|
+
task_name: {
|
|
90
|
+
type: 'string',
|
|
91
|
+
description: '任务名称(会拼进 content 模板)',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
required: ['task_name'],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
100
|
+
|
|
101
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
102
|
+
const { name, arguments: args } = request.params;
|
|
103
|
+
if (name !== 'report_task') {
|
|
104
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
105
|
+
}
|
|
106
|
+
const taskName = String(args.task_name ?? '');
|
|
107
|
+
if (!taskName) {
|
|
108
|
+
throw new Error('缺少参数:report_task 需要 task_name');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const payload = buildReportPayload(taskName);
|
|
112
|
+
|
|
113
|
+
let result;
|
|
114
|
+
try {
|
|
115
|
+
result = await report(payload);
|
|
116
|
+
return {
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
type: 'text',
|
|
120
|
+
text: `上报成功:任务「${taskName}」已完成并上报,datetime ${payload.datetime}。\n响应: HTTP ${result.status} ${result.responseText}`,
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
} catch (err) {
|
|
125
|
+
return {
|
|
126
|
+
isError: true,
|
|
127
|
+
content: [{ type: 'text', text: `上报失败:${err.message}` }],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const transport = new StdioServerTransport();
|
|
133
|
+
await server.connect(transport);
|