@actiondock/mcp 2.0.12 → 2.2.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 +9 -9
- package/dist/adapter.d.ts +17 -2
- package/dist/adapter.js +395 -0
- package/dist/execution-mode.d.ts +30 -0
- package/dist/execution-mode.js +63 -0
- package/dist/http.d.ts +1 -1
- package/dist/http.js +221 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +6 -574
- package/dist/register-tasks-extension.d.ts +9 -0
- package/dist/register-tasks-extension.js +69 -0
- package/dist/schemas.d.ts +2 -1
- package/dist/schemas.js +30 -0
- package/dist/stdio-host.d.ts +2 -0
- package/dist/stdio-host.js +13 -0
- package/dist/stdio.d.ts +7 -2
- package/dist/stdio.js +114 -0
- package/dist/types.d.ts +34 -9
- package/dist/types.js +33 -0
- package/package.json +6 -6
package/dist/http.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { ACTIONDOCK_VERSION, DEFAULT_MAX_BODY_BYTES, formatHostForUrl, isLoopbackHost, launchHttpServer, resolveCorsHeaders, verifyBearerToken, UNAUTHORIZED, } from "@actiondock/core";
|
|
2
|
+
import { createMcpHandler } from "@modelcontextprotocol/server";
|
|
3
|
+
import { createActionDockMcpServer, resolveTarget } from "./adapter.js";
|
|
4
|
+
/**
|
|
5
|
+
* Starts an ActionDock MCP server over HTTP transport.
|
|
6
|
+
*/
|
|
7
|
+
export function startMcpHttpServer(options = {}) {
|
|
8
|
+
const hostInstance = typeof options.host === "object" && options.host !== null
|
|
9
|
+
? options.host
|
|
10
|
+
: undefined;
|
|
11
|
+
const hostString = typeof options.host === "string" ? options.host : "127.0.0.1";
|
|
12
|
+
const port = options.port ?? 5178;
|
|
13
|
+
const host = hostString;
|
|
14
|
+
const token = options.token;
|
|
15
|
+
// Non-loopback address requires token authentication by default
|
|
16
|
+
if (!isLoopbackHost(host) && !token && !options.allowInsecureNoAuth) {
|
|
17
|
+
throw new Error("Authentication token is required when binding to a non-loopback address. Use --allow-insecure-no-auth to override.");
|
|
18
|
+
}
|
|
19
|
+
return (async () => {
|
|
20
|
+
// 先一次性解析 target,工厂闭包直接捕获已解析产物,消除选项重复展开
|
|
21
|
+
const { target } = await resolveTarget({
|
|
22
|
+
...options,
|
|
23
|
+
host: hostInstance ?? (typeof options.host === "object" ? options.host : undefined),
|
|
24
|
+
});
|
|
25
|
+
const handler = createMcpHandler(() => createActionDockMcpServer({ target }), {
|
|
26
|
+
onerror: (err) => {
|
|
27
|
+
process.stderr.write(`[MCP HTTP Error] ${err?.message || String(err)}\n`);
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
const server = await launchHttpServer(port, host, async (req) => {
|
|
31
|
+
const origin = req.headers.get("origin");
|
|
32
|
+
const corsHeaders = resolveCorsHeaders(origin, options.corsOrigins);
|
|
33
|
+
if (req.method === "OPTIONS") {
|
|
34
|
+
return new Response(null, {
|
|
35
|
+
status: 204,
|
|
36
|
+
headers: corsHeaders,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const url = new URL(req.url);
|
|
40
|
+
const pathname = url.pathname;
|
|
41
|
+
const verifyOptions = { allowQueryToken: options.allowQueryToken };
|
|
42
|
+
// 1. Health check
|
|
43
|
+
if (pathname === "/health" || pathname === "/api/v1/health") {
|
|
44
|
+
if (!verifyBearerToken(req, token, verifyOptions)) {
|
|
45
|
+
return new Response(JSON.stringify({
|
|
46
|
+
ok: false,
|
|
47
|
+
error: {
|
|
48
|
+
code: UNAUTHORIZED,
|
|
49
|
+
message: "Invalid or missing Bearer token",
|
|
50
|
+
},
|
|
51
|
+
}), {
|
|
52
|
+
status: 401,
|
|
53
|
+
headers: {
|
|
54
|
+
"Content-Type": "application/json",
|
|
55
|
+
...corsHeaders,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return new Response(JSON.stringify({
|
|
60
|
+
status: "ok",
|
|
61
|
+
protocol: "mcp",
|
|
62
|
+
version: ACTIONDOCK_VERSION,
|
|
63
|
+
timestamp: new Date().toISOString(),
|
|
64
|
+
}), {
|
|
65
|
+
status: 200,
|
|
66
|
+
headers: {
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
...corsHeaders,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// 2. Authentication check
|
|
73
|
+
if (!verifyBearerToken(req, token, verifyOptions)) {
|
|
74
|
+
return new Response(JSON.stringify({
|
|
75
|
+
jsonrpc: "2.0",
|
|
76
|
+
error: {
|
|
77
|
+
code: -32000,
|
|
78
|
+
message: "Unauthorized: Invalid or missing Bearer token",
|
|
79
|
+
},
|
|
80
|
+
id: null,
|
|
81
|
+
}), {
|
|
82
|
+
status: 401,
|
|
83
|
+
headers: {
|
|
84
|
+
"Content-Type": "application/json",
|
|
85
|
+
...corsHeaders,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// 3. Delegate MCP endpoint
|
|
90
|
+
if (pathname === "/mcp" || pathname === "/") {
|
|
91
|
+
const maxBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
|
|
92
|
+
const contentLengthHeader = req.headers.get("content-length");
|
|
93
|
+
if (contentLengthHeader) {
|
|
94
|
+
const parsedLength = parseInt(contentLengthHeader, 10);
|
|
95
|
+
if (!isNaN(parsedLength) && parsedLength > maxBytes) {
|
|
96
|
+
return new Response(JSON.stringify({
|
|
97
|
+
ok: false,
|
|
98
|
+
error: {
|
|
99
|
+
code: "REQUEST_TOO_LARGE",
|
|
100
|
+
message: "Request body exceeds maximum allowed size",
|
|
101
|
+
},
|
|
102
|
+
}), {
|
|
103
|
+
status: 413,
|
|
104
|
+
headers: {
|
|
105
|
+
"Content-Type": "application/json",
|
|
106
|
+
...corsHeaders,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
let currentReq = req;
|
|
112
|
+
if (req.body && req.method !== "GET" && req.method !== "HEAD") {
|
|
113
|
+
const reader = req.body.getReader();
|
|
114
|
+
const chunks = [];
|
|
115
|
+
let totalBytes = 0;
|
|
116
|
+
let tooLarge = false;
|
|
117
|
+
try {
|
|
118
|
+
while (true) {
|
|
119
|
+
const { done, value } = await reader.read();
|
|
120
|
+
if (done)
|
|
121
|
+
break;
|
|
122
|
+
if (value) {
|
|
123
|
+
totalBytes += value.byteLength;
|
|
124
|
+
if (totalBytes > maxBytes) {
|
|
125
|
+
tooLarge = true;
|
|
126
|
+
await reader.cancel();
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
chunks.push(value);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
reader.releaseLock();
|
|
135
|
+
}
|
|
136
|
+
if (tooLarge) {
|
|
137
|
+
return new Response(JSON.stringify({
|
|
138
|
+
ok: false,
|
|
139
|
+
error: {
|
|
140
|
+
code: "REQUEST_TOO_LARGE",
|
|
141
|
+
message: "Request body exceeds maximum allowed size",
|
|
142
|
+
},
|
|
143
|
+
}), {
|
|
144
|
+
status: 413,
|
|
145
|
+
headers: {
|
|
146
|
+
"Content-Type": "application/json",
|
|
147
|
+
...corsHeaders,
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const merged = new Uint8Array(totalBytes);
|
|
152
|
+
let offset = 0;
|
|
153
|
+
for (const chunk of chunks) {
|
|
154
|
+
merged.set(chunk, offset);
|
|
155
|
+
offset += chunk.byteLength;
|
|
156
|
+
}
|
|
157
|
+
currentReq = new Request(req.url, {
|
|
158
|
+
method: req.method,
|
|
159
|
+
headers: req.headers,
|
|
160
|
+
body: merged,
|
|
161
|
+
signal: req.signal,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const mcpResponse = await handler.fetch(currentReq);
|
|
165
|
+
if (Object.keys(corsHeaders).length > 0) {
|
|
166
|
+
const newHeaders = new Headers(mcpResponse.headers);
|
|
167
|
+
for (const [k, v] of Object.entries(corsHeaders)) {
|
|
168
|
+
newHeaders.set(k, String(v));
|
|
169
|
+
}
|
|
170
|
+
return new Response(mcpResponse.body, {
|
|
171
|
+
status: mcpResponse.status,
|
|
172
|
+
statusText: mcpResponse.statusText,
|
|
173
|
+
headers: newHeaders,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return mcpResponse;
|
|
177
|
+
}
|
|
178
|
+
return new Response(JSON.stringify({
|
|
179
|
+
error: "Not Found",
|
|
180
|
+
}), {
|
|
181
|
+
status: 404,
|
|
182
|
+
headers: {
|
|
183
|
+
"Content-Type": "application/json",
|
|
184
|
+
...corsHeaders,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
if (server.ready) {
|
|
189
|
+
await server.ready;
|
|
190
|
+
}
|
|
191
|
+
const actualHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
192
|
+
const url = `http://${formatHostForUrl(actualHost)}:${server.port}`;
|
|
193
|
+
return {
|
|
194
|
+
port: server.port ?? port,
|
|
195
|
+
host,
|
|
196
|
+
url,
|
|
197
|
+
target,
|
|
198
|
+
stop: async () => {
|
|
199
|
+
let targetError;
|
|
200
|
+
try {
|
|
201
|
+
await target.close();
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
targetError = err;
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
await server.stop(true);
|
|
208
|
+
}
|
|
209
|
+
catch (serverErr) {
|
|
210
|
+
if (targetError) {
|
|
211
|
+
throw new AggregateError([targetError, serverErr], "Failed to stop MCP HTTP server and target");
|
|
212
|
+
}
|
|
213
|
+
throw serverErr;
|
|
214
|
+
}
|
|
215
|
+
if (targetError) {
|
|
216
|
+
throw targetError;
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
})();
|
|
221
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export * from "./types";
|
|
2
|
-
export * from "./schemas";
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./
|
|
5
|
-
export * from "./
|
|
1
|
+
export * from "./types.js";
|
|
2
|
+
export * from "./schemas.js";
|
|
3
|
+
export * from "./execution-mode.js";
|
|
4
|
+
export * from "./adapter.js";
|
|
5
|
+
export * from "./stdio.js";
|
|
6
|
+
export * from "./http.js";
|