@abtnode/blocklet-services 1.16.52-beta-20250916-025146-35d976f4 → 1.16.52-beta-20250917-070942-7ee57044
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/api/routes/mcp.js +235 -1
- package/api/routes/oauth/server.js +4 -4
- package/api/services/mcp/server.js +1 -1
- package/package.json +24 -24
package/api/routes/mcp.js
CHANGED
|
@@ -4,13 +4,27 @@ const get = require('lodash/get');
|
|
|
4
4
|
const getBlockletInfo = require('@blocklet/meta/lib/info');
|
|
5
5
|
const { checkPublicAccess } = require('@blocklet/meta/lib/util');
|
|
6
6
|
// eslint-disable-next-line import/no-unresolved
|
|
7
|
-
const { StreamableHTTPServerTransport } = require('@
|
|
7
|
+
const { StreamableHTTPServerTransport } = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
8
|
+
// eslint-disable-next-line import/no-unresolved
|
|
9
|
+
const { Client } = require('@modelcontextprotocol/sdk/client/index.js');
|
|
10
|
+
// eslint-disable-next-line import/no-unresolved
|
|
11
|
+
const { StreamableHTTPClientTransport } = require('@modelcontextprotocol/sdk/client/streamableHttp.js');
|
|
12
|
+
const { z } = require('zod');
|
|
13
|
+
const flatten = require('lodash/flatten');
|
|
14
|
+
const { version } = require('../../package.json');
|
|
8
15
|
|
|
9
16
|
const { initMcpServer } = require('../services/mcp/server');
|
|
10
17
|
const logger = require('../libs/logger')('mcp:server:routes');
|
|
11
18
|
|
|
12
19
|
const isMCPSupported = (b) => get(b.meta, 'capabilities.mcp', false);
|
|
13
20
|
|
|
21
|
+
// Zod schema for MCP tools request validation
|
|
22
|
+
const mcpToolsRequestSchema = z.object({
|
|
23
|
+
name: z.string().min(1, 'Tool name is required'),
|
|
24
|
+
componentDid: z.string().min(1, 'Component DID is required'),
|
|
25
|
+
input: z.any(),
|
|
26
|
+
});
|
|
27
|
+
|
|
14
28
|
const transport = new StreamableHTTPServerTransport({
|
|
15
29
|
sessionIdGenerator: undefined, // set to undefined for stateless servers
|
|
16
30
|
});
|
|
@@ -130,5 +144,225 @@ module.exports = {
|
|
|
130
144
|
}
|
|
131
145
|
}
|
|
132
146
|
});
|
|
147
|
+
|
|
148
|
+
server.get(
|
|
149
|
+
joinURL(WELLKNOWN_SERVICE_PATH_PREFIX, '/mcp/tools'),
|
|
150
|
+
/**
|
|
151
|
+
* @see https://team.arcblock.io/comment/discussions/aPYG3ocSGNIhFHFr5wFcquZx#53f1867b-3439-43c8-b63c-bf53fffcfe8c
|
|
152
|
+
* @param {import('express').Request} req
|
|
153
|
+
* @param {import('express').Response} res
|
|
154
|
+
* @returns
|
|
155
|
+
*/
|
|
156
|
+
async (req, res) => {
|
|
157
|
+
if (!req.user) {
|
|
158
|
+
return res.status(401).json({ error: 'Unauthorized' });
|
|
159
|
+
}
|
|
160
|
+
const blocklet = await req.getBlocklet();
|
|
161
|
+
const info = getBlockletInfo(blocklet);
|
|
162
|
+
const mapServers = blocklet.children
|
|
163
|
+
.map((x) => {
|
|
164
|
+
if (isMCPSupported(x)) {
|
|
165
|
+
return {
|
|
166
|
+
name: x.meta.title,
|
|
167
|
+
description: x.meta.description,
|
|
168
|
+
componentDid: x.meta.did,
|
|
169
|
+
appPid: blocklet.appPid,
|
|
170
|
+
endpoint: joinURL(info.appUrl, x.mountPoint, '/mcp'),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return null;
|
|
175
|
+
})
|
|
176
|
+
.filter(Boolean);
|
|
177
|
+
|
|
178
|
+
const toolMartixs = await Promise.all(
|
|
179
|
+
mapServers.map(async (x) => {
|
|
180
|
+
const client = new Client({
|
|
181
|
+
name: 'blocklet-service-mcp-client',
|
|
182
|
+
version,
|
|
183
|
+
});
|
|
184
|
+
const currentTransport = new StreamableHTTPClientTransport(new URL(x.endpoint), {
|
|
185
|
+
requestInit: {
|
|
186
|
+
headers: {
|
|
187
|
+
Cookie: req.headers.cookie,
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
await client.connect(currentTransport);
|
|
192
|
+
const { tools } = await client.listTools().catch((error) => {
|
|
193
|
+
console.error('error listing tools', error);
|
|
194
|
+
return { tools: [] };
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return tools.map((tool) => ({
|
|
198
|
+
...tool,
|
|
199
|
+
appPid: x.appPid,
|
|
200
|
+
componentDid: x.componentDid,
|
|
201
|
+
}));
|
|
202
|
+
})
|
|
203
|
+
);
|
|
204
|
+
const tools = flatten(toolMartixs);
|
|
205
|
+
|
|
206
|
+
return res.send(tools);
|
|
207
|
+
}
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
server.get(
|
|
211
|
+
joinURL(WELLKNOWN_SERVICE_PATH_PREFIX, '/mcp/tools.js'),
|
|
212
|
+
/**
|
|
213
|
+
*
|
|
214
|
+
* @see https://team.arcblock.io/comment/discussions/aPYG3ocSGNIhFHFr5wFcquZx#53f1867b-3439-43c8-b63c-bf53fffcfe8c
|
|
215
|
+
* @param {import('express').Request} req
|
|
216
|
+
* @param {import('express').Response} res
|
|
217
|
+
* @returns
|
|
218
|
+
*/
|
|
219
|
+
async (req, res) => {
|
|
220
|
+
const blocklet = await req.getBlocklet();
|
|
221
|
+
const info = getBlockletInfo(blocklet);
|
|
222
|
+
const toolsApiUrl = joinURL(info.appUrl, WELLKNOWN_SERVICE_PATH_PREFIX, '/mcp/tools');
|
|
223
|
+
|
|
224
|
+
// 定义为 js
|
|
225
|
+
res.type('js');
|
|
226
|
+
|
|
227
|
+
// @note: 这么做的好处是,未登录到登录成功之后,页面没有刷新也能很快地注册 tools
|
|
228
|
+
return res.send(`
|
|
229
|
+
function getCookiesAsObject() {
|
|
230
|
+
const cookieStr = document.cookie;
|
|
231
|
+
if (!cookieStr.trim()) {
|
|
232
|
+
return {};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const cookies = {};
|
|
236
|
+
const cookiePairs = cookieStr.split('; ');
|
|
237
|
+
|
|
238
|
+
cookiePairs.forEach(pair => {
|
|
239
|
+
const eqIndex = pair.indexOf('=');
|
|
240
|
+
if (eqIndex === -1) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const key = pair.substring(0, eqIndex);
|
|
245
|
+
const value = pair.substring(eqIndex + 1);
|
|
246
|
+
const decodedKey = decodeURIComponent(key);
|
|
247
|
+
const decodedValue = decodeURIComponent(value);
|
|
248
|
+
cookies[decodedKey] = decodedValue;
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
return cookies;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function registerTools() {
|
|
255
|
+
try {
|
|
256
|
+
const cookies = getCookiesAsObject();
|
|
257
|
+
if (cookies.login_token) {
|
|
258
|
+
// 发送 GET 请求获取工具列表,携带 cookie
|
|
259
|
+
const response = await fetch('${toolsApiUrl}', {
|
|
260
|
+
method: 'GET',
|
|
261
|
+
credentials: 'include', // 自动携带 cookie
|
|
262
|
+
headers: {
|
|
263
|
+
'Content-Type': 'application/json'
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
if (response.ok) {
|
|
268
|
+
const tools = await response.json();
|
|
269
|
+
dsBridge.call("arc__registerTools", tools);
|
|
270
|
+
console.info("registerTools success");
|
|
271
|
+
} else {
|
|
272
|
+
console.error("Failed to fetch tools:", response.status, response.statusText);
|
|
273
|
+
dsBridge.call("arc__registerTools", []);
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
dsBridge.call("arc__registerTools", []);
|
|
277
|
+
}
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.error("registerTools", error);
|
|
280
|
+
dsBridge.call("arc__registerTools", []);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
let lastLoginToken = null;
|
|
285
|
+
async function watchCookieChanges() {
|
|
286
|
+
const cookies = getCookiesAsObject();
|
|
287
|
+
const currentLoginToken = cookies.login_token || null;
|
|
288
|
+
|
|
289
|
+
if (currentLoginToken !== lastLoginToken) {
|
|
290
|
+
lastLoginToken = currentLoginToken;
|
|
291
|
+
await registerTools();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
registerTools();
|
|
296
|
+
|
|
297
|
+
const cookies = getCookiesAsObject();
|
|
298
|
+
lastLoginToken = cookies.login_token || null;
|
|
299
|
+
setInterval(watchCookieChanges, 1000);
|
|
300
|
+
`);
|
|
301
|
+
}
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
server.post(
|
|
305
|
+
joinURL(WELLKNOWN_SERVICE_PATH_PREFIX, '/mcp/tools'),
|
|
306
|
+
/**
|
|
307
|
+
* @see https://team.arcblock.io/comment/discussions/aPYG3ocSGNIhFHFr5wFcquZx#53f1867b-3439-43c8-b63c-bf53fffcfe8c
|
|
308
|
+
* @param {import('express').Request} req
|
|
309
|
+
* @param {import('express').Response} res
|
|
310
|
+
*
|
|
311
|
+
*/
|
|
312
|
+
async (req, res) => {
|
|
313
|
+
if (!req.user) {
|
|
314
|
+
return res.status(401).json({ error: 'Unauthorized' });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Validate request body with Zod
|
|
318
|
+
let name;
|
|
319
|
+
let componentDid;
|
|
320
|
+
let input;
|
|
321
|
+
try {
|
|
322
|
+
const validatedData = await mcpToolsRequestSchema.parseAsync(req.body);
|
|
323
|
+
({ name, componentDid, input } = validatedData);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.error(error);
|
|
326
|
+
if (error instanceof z.ZodError) {
|
|
327
|
+
return res.status(400).json({
|
|
328
|
+
error: error.errors.map((e) => ({
|
|
329
|
+
field: e.path.join('.'),
|
|
330
|
+
message: e.message,
|
|
331
|
+
})),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
return res.status(400).json({ error: error.message });
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const blocklet = await req.getBlocklet();
|
|
338
|
+
const info = getBlockletInfo(blocklet);
|
|
339
|
+
const mountPoint = blocklet.children.find((x) => {
|
|
340
|
+
return x.meta.did === componentDid && isMCPSupported(x);
|
|
341
|
+
})?.mountPoint;
|
|
342
|
+
if (!mountPoint) {
|
|
343
|
+
return res.status(404).json({ error: `Component not found by ${componentDid}` });
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const endpoint = joinURL(info.appUrl, mountPoint, '/mcp');
|
|
347
|
+
const client = new Client({
|
|
348
|
+
name: 'blocklet-service-proxy',
|
|
349
|
+
version,
|
|
350
|
+
});
|
|
351
|
+
const currentTransport = new StreamableHTTPClientTransport(new URL(endpoint), {
|
|
352
|
+
requestInit: {
|
|
353
|
+
headers: {
|
|
354
|
+
Cookie: req.headers.cookie,
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
await client.connect(currentTransport);
|
|
359
|
+
const output = await client.callTool({
|
|
360
|
+
name,
|
|
361
|
+
arguments: input,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
return res.send(output);
|
|
365
|
+
}
|
|
366
|
+
);
|
|
133
367
|
},
|
|
134
368
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/* eslint-disable import/no-unresolved */
|
|
2
2
|
const { joinURL } = require('ufo');
|
|
3
3
|
const { OAUTH_ENDPOINTS, OAUTH_CLIENT_SECRET_TTL, WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
|
|
4
|
-
const { authorizationHandler } = require('@
|
|
5
|
-
const { tokenHandler } = require('@
|
|
6
|
-
const { revocationHandler } = require('@
|
|
7
|
-
const { clientRegistrationHandler } = require('@
|
|
4
|
+
const { authorizationHandler } = require('@modelcontextprotocol/sdk/server/auth/handlers/authorize.js');
|
|
5
|
+
const { tokenHandler } = require('@modelcontextprotocol/sdk/server/auth/handlers/token.js');
|
|
6
|
+
const { revocationHandler } = require('@modelcontextprotocol/sdk/server/auth/handlers/revoke.js');
|
|
7
|
+
const { clientRegistrationHandler } = require('@modelcontextprotocol/sdk/server/auth/handlers/register.js');
|
|
8
8
|
|
|
9
9
|
const { createBlockletOAuthServerProvider } = require('../../services/oauth/server');
|
|
10
10
|
const { redirectWithoutCache, getRedirectUrl } = require('../../util');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// eslint-disable-next-line import/no-unresolved
|
|
2
|
-
const { McpServer } = require('@
|
|
2
|
+
const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { initDbhubServer } = require('@blocklet/dbhub');
|
|
5
5
|
const logger = require('../../libs/logger')('mcp:server');
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.16.52-beta-
|
|
6
|
+
"version": "1.16.52-beta-20250917-070942-7ee57044",
|
|
7
7
|
"description": "Provide unified services for every blocklet",
|
|
8
8
|
"main": "api/index.js",
|
|
9
9
|
"files": [
|
|
@@ -34,17 +34,17 @@
|
|
|
34
34
|
"author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
|
|
35
35
|
"license": "Apache-2.0",
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@abtnode/analytics": "1.16.52-beta-
|
|
38
|
-
"@abtnode/auth": "1.16.52-beta-
|
|
39
|
-
"@abtnode/connect-storage": "1.16.52-beta-
|
|
40
|
-
"@abtnode/constant": "1.16.52-beta-
|
|
41
|
-
"@abtnode/core": "1.16.52-beta-
|
|
42
|
-
"@abtnode/cron": "1.16.52-beta-
|
|
43
|
-
"@abtnode/db-cache": "1.16.52-beta-
|
|
44
|
-
"@abtnode/logger": "1.16.52-beta-
|
|
45
|
-
"@abtnode/models": "1.16.52-beta-
|
|
46
|
-
"@abtnode/router-templates": "1.16.52-beta-
|
|
47
|
-
"@abtnode/util": "1.16.52-beta-
|
|
37
|
+
"@abtnode/analytics": "1.16.52-beta-20250917-070942-7ee57044",
|
|
38
|
+
"@abtnode/auth": "1.16.52-beta-20250917-070942-7ee57044",
|
|
39
|
+
"@abtnode/connect-storage": "1.16.52-beta-20250917-070942-7ee57044",
|
|
40
|
+
"@abtnode/constant": "1.16.52-beta-20250917-070942-7ee57044",
|
|
41
|
+
"@abtnode/core": "1.16.52-beta-20250917-070942-7ee57044",
|
|
42
|
+
"@abtnode/cron": "1.16.52-beta-20250917-070942-7ee57044",
|
|
43
|
+
"@abtnode/db-cache": "1.16.52-beta-20250917-070942-7ee57044",
|
|
44
|
+
"@abtnode/logger": "1.16.52-beta-20250917-070942-7ee57044",
|
|
45
|
+
"@abtnode/models": "1.16.52-beta-20250917-070942-7ee57044",
|
|
46
|
+
"@abtnode/router-templates": "1.16.52-beta-20250917-070942-7ee57044",
|
|
47
|
+
"@abtnode/util": "1.16.52-beta-20250917-070942-7ee57044",
|
|
48
48
|
"@arcblock/did": "1.24.0",
|
|
49
49
|
"@arcblock/did-connect-js": "1.24.0",
|
|
50
50
|
"@arcblock/did-ext": "1.24.0",
|
|
@@ -54,19 +54,18 @@
|
|
|
54
54
|
"@arcblock/jwt": "1.24.0",
|
|
55
55
|
"@arcblock/validator": "1.24.0",
|
|
56
56
|
"@arcblock/ws": "1.24.0",
|
|
57
|
-
"@blocklet/constant": "1.16.52-beta-
|
|
57
|
+
"@blocklet/constant": "1.16.52-beta-20250917-070942-7ee57044",
|
|
58
58
|
"@blocklet/dbhub": "^0.2.9",
|
|
59
|
-
"@blocklet/env": "1.16.52-beta-
|
|
59
|
+
"@blocklet/env": "1.16.52-beta-20250917-070942-7ee57044",
|
|
60
60
|
"@blocklet/error": "^0.2.5",
|
|
61
61
|
"@blocklet/form-builder": "^0.1.12",
|
|
62
62
|
"@blocklet/form-collector": "^0.1.8",
|
|
63
|
-
"@blocklet/images": "1.16.52-beta-
|
|
64
|
-
"@blocklet/js-sdk": "1.16.52-beta-
|
|
65
|
-
"@blocklet/
|
|
66
|
-
"@blocklet/
|
|
67
|
-
"@blocklet/
|
|
68
|
-
"@blocklet/
|
|
69
|
-
"@blocklet/server-js": "1.16.52-beta-20250916-025146-35d976f4",
|
|
63
|
+
"@blocklet/images": "1.16.52-beta-20250917-070942-7ee57044",
|
|
64
|
+
"@blocklet/js-sdk": "1.16.52-beta-20250917-070942-7ee57044",
|
|
65
|
+
"@blocklet/meta": "1.16.52-beta-20250917-070942-7ee57044",
|
|
66
|
+
"@blocklet/rate-limit": "1.16.52-beta-20250917-070942-7ee57044",
|
|
67
|
+
"@blocklet/sdk": "1.16.52-beta-20250917-070942-7ee57044",
|
|
68
|
+
"@blocklet/server-js": "1.16.52-beta-20250917-070942-7ee57044",
|
|
70
69
|
"@blocklet/theme": "^3.1.40",
|
|
71
70
|
"@blocklet/theme-builder": "0.4.6",
|
|
72
71
|
"@blocklet/uploader-server": "^0.2.10",
|
|
@@ -76,6 +75,7 @@
|
|
|
76
75
|
"@iconify-icons/material-symbols": "^1.2.58",
|
|
77
76
|
"@iconify-icons/tabler": "^1.2.95",
|
|
78
77
|
"@iconify/react": "^5.2.1",
|
|
78
|
+
"@modelcontextprotocol/sdk": "^1.18.0",
|
|
79
79
|
"@ocap/client": "1.24.0",
|
|
80
80
|
"@ocap/util": "1.24.0",
|
|
81
81
|
"@react-email/components": "^0.0.34",
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
"whatwg-url": "14.0.0"
|
|
126
126
|
},
|
|
127
127
|
"devDependencies": {
|
|
128
|
-
"@abtnode/ux": "1.16.52-beta-
|
|
128
|
+
"@abtnode/ux": "1.16.52-beta-20250917-070942-7ee57044",
|
|
129
129
|
"@arcblock/bridge": "^3.1.40",
|
|
130
130
|
"@arcblock/did-connect-react": "^3.1.40",
|
|
131
131
|
"@arcblock/icons": "^3.1.40",
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
"@blocklet/did-space-react": "^1.1.23",
|
|
136
136
|
"@blocklet/launcher-layout": "^3.1.40",
|
|
137
137
|
"@blocklet/payment-react": "^1.20.8",
|
|
138
|
-
"@blocklet/tracker": "1.16.52-beta-
|
|
138
|
+
"@blocklet/tracker": "1.16.52-beta-20250917-070942-7ee57044",
|
|
139
139
|
"@blocklet/ui-react": "^3.1.40",
|
|
140
140
|
"@blocklet/uploader": "^0.2.10",
|
|
141
141
|
"@emotion/react": "^11.14.0",
|
|
@@ -215,5 +215,5 @@
|
|
|
215
215
|
"url": "https://github.com/ArcBlock/blocklet-server/issues",
|
|
216
216
|
"email": "shijun@arcblock.io"
|
|
217
217
|
},
|
|
218
|
-
"gitHead": "
|
|
218
|
+
"gitHead": "ac4ec647df35a480b28351b3bca6d3e59c64fff1"
|
|
219
219
|
}
|