secondbrainos-mcp-server 1.2.5 → 1.3.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/build/index.js +91 -9
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -5,7 +5,15 @@ import { OpenApi, HttpLlm } from "@samchon/openapi";
|
|
|
5
5
|
import axios from "axios";
|
|
6
6
|
import dotenv from "dotenv";
|
|
7
7
|
import yaml from 'js-yaml';
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
8
10
|
dotenv.config();
|
|
11
|
+
function toSnakeCase(str) {
|
|
12
|
+
return str
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
15
|
+
.replace(/^_+|_+$/g, '');
|
|
16
|
+
}
|
|
9
17
|
class SecondBrainOSServer {
|
|
10
18
|
constructor(initialSchema) {
|
|
11
19
|
this.originalSpec = initialSchema;
|
|
@@ -37,6 +45,7 @@ class SecondBrainOSServer {
|
|
|
37
45
|
// Discover service paths from the OpenAPI schema
|
|
38
46
|
this.runPromptChainPath = null;
|
|
39
47
|
this.getAIAgentsSchemaPath = null;
|
|
48
|
+
this.generateUploadURLPath = null;
|
|
40
49
|
if (initialSchema.paths) {
|
|
41
50
|
for (const [path, pathItem] of Object.entries(initialSchema.paths)) {
|
|
42
51
|
for (const operation of Object.values(pathItem)) {
|
|
@@ -46,8 +55,11 @@ class SecondBrainOSServer {
|
|
|
46
55
|
if (operation.operationId === 'getAIAgentsSchema') {
|
|
47
56
|
this.getAIAgentsSchemaPath = path;
|
|
48
57
|
}
|
|
58
|
+
if (operation.operationId === 'generateFileUploadGoogleCloudStorageURL') {
|
|
59
|
+
this.generateUploadURLPath = path;
|
|
60
|
+
}
|
|
49
61
|
}
|
|
50
|
-
if (this.runPromptChainPath && this.getAIAgentsSchemaPath)
|
|
62
|
+
if (this.runPromptChainPath && this.getAIAgentsSchemaPath && this.generateUploadURLPath)
|
|
51
63
|
break;
|
|
52
64
|
}
|
|
53
65
|
}
|
|
@@ -125,6 +137,8 @@ class SecondBrainOSServer {
|
|
|
125
137
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown function: ${request.params.name}`);
|
|
126
138
|
}
|
|
127
139
|
try {
|
|
140
|
+
// Intercept file_path arguments: upload local files to GCS
|
|
141
|
+
const processedArgs = await this.interceptFilePathArgs((request.params.arguments || {}));
|
|
128
142
|
// Use HttpLlm.execute for better error handling and type safety
|
|
129
143
|
const result = await HttpLlm.execute({
|
|
130
144
|
connection: {
|
|
@@ -135,7 +149,7 @@ class SecondBrainOSServer {
|
|
|
135
149
|
},
|
|
136
150
|
application: this.application, // Type assertion to avoid generic issues
|
|
137
151
|
function: func,
|
|
138
|
-
input:
|
|
152
|
+
input: processedArgs
|
|
139
153
|
});
|
|
140
154
|
return {
|
|
141
155
|
content: [{
|
|
@@ -197,13 +211,15 @@ class SecondBrainOSServer {
|
|
|
197
211
|
this.workflowNameToId.clear();
|
|
198
212
|
for (const wf of workflows) {
|
|
199
213
|
if (wf.name && wf.workflow_id) {
|
|
200
|
-
|
|
214
|
+
const snakeName = `skill_${toSnakeCase(wf.name)}`;
|
|
215
|
+
this.workflowNameToId.set(snakeName, wf.workflow_id);
|
|
201
216
|
}
|
|
202
217
|
}
|
|
203
218
|
for (const wf of workflows) {
|
|
204
219
|
const skillName = wf.name || wf.workflow_id;
|
|
220
|
+
const snakeName = `skill_${toSnakeCase(skillName)}`;
|
|
205
221
|
prompts.push({
|
|
206
|
-
name:
|
|
222
|
+
name: snakeName,
|
|
207
223
|
title: `[Skill] ${skillName}`,
|
|
208
224
|
description: wf.description || wf.name,
|
|
209
225
|
arguments: [
|
|
@@ -227,10 +243,10 @@ class SecondBrainOSServer {
|
|
|
227
243
|
this.agentNameToId.clear();
|
|
228
244
|
for (const agent of agents) {
|
|
229
245
|
if (agent.name && agent.id) {
|
|
230
|
-
const
|
|
231
|
-
this.agentNameToId.set(
|
|
246
|
+
const snakeName = `agent_${toSnakeCase(agent.name)}`;
|
|
247
|
+
this.agentNameToId.set(snakeName, agent.id);
|
|
232
248
|
prompts.push({
|
|
233
|
-
name:
|
|
249
|
+
name: snakeName,
|
|
234
250
|
title: `[Agent] ${agent.name}`,
|
|
235
251
|
description: agent.description || agent.name,
|
|
236
252
|
arguments: [
|
|
@@ -257,7 +273,7 @@ class SecondBrainOSServer {
|
|
|
257
273
|
.filter((id) => id && id.length > 0);
|
|
258
274
|
if (collectionIds.length > 0) {
|
|
259
275
|
prompts.push({
|
|
260
|
-
name: "
|
|
276
|
+
name: "knowledge_bases",
|
|
261
277
|
title: "[Knowledge Bases]",
|
|
262
278
|
description: "Knowledge base collection IDs available to agents",
|
|
263
279
|
arguments: [
|
|
@@ -281,7 +297,7 @@ class SecondBrainOSServer {
|
|
|
281
297
|
const promptName = request.params.name;
|
|
282
298
|
const userInput = request.params.arguments?.user_input;
|
|
283
299
|
// Check if this is the Knowledge Bases prompt
|
|
284
|
-
if (promptName === "
|
|
300
|
+
if (promptName === "knowledge_bases") {
|
|
285
301
|
const agents = await this.fetchAndEnrichAgents();
|
|
286
302
|
const collectionIds = agents
|
|
287
303
|
.map((a) => a.searchMyKnowledge_collection_id)
|
|
@@ -448,6 +464,71 @@ class SecondBrainOSServer {
|
|
|
448
464
|
});
|
|
449
465
|
return response.data;
|
|
450
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* Scans tool arguments for file_path parameters containing local paths,
|
|
469
|
+
* uploads those files to GCS via signed URL, and replaces the values with GCS paths.
|
|
470
|
+
* - https:// paths are passed through
|
|
471
|
+
* - http:// paths are rejected
|
|
472
|
+
* - Everything else is treated as a local file path
|
|
473
|
+
*/
|
|
474
|
+
async interceptFilePathArgs(args) {
|
|
475
|
+
const result = { ...args };
|
|
476
|
+
for (const [key, value] of Object.entries(result)) {
|
|
477
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
478
|
+
result[key] = await this.interceptFilePathArgs(value);
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
if (typeof value !== 'string' || !key.toLowerCase().includes('file_path')) {
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
const trimmed = value.trim();
|
|
485
|
+
// https:// — pass through
|
|
486
|
+
if (trimmed.startsWith('https://'))
|
|
487
|
+
continue;
|
|
488
|
+
// http:// — reject
|
|
489
|
+
if (trimmed.startsWith('http://')) {
|
|
490
|
+
throw new McpError(ErrorCode.InvalidRequest, `Insecure http:// URLs are not allowed for file_path. Use https:// or a local file path.`);
|
|
491
|
+
}
|
|
492
|
+
// Local file path — validate, upload via signed URL, replace
|
|
493
|
+
if (!this.generateUploadURLPath) {
|
|
494
|
+
throw new McpError(ErrorCode.InternalError, 'File upload service (generateFileUploadGoogleCloudStorageURL) is not available for this user. Cannot upload local files.');
|
|
495
|
+
}
|
|
496
|
+
const ext = path.extname(trimmed).toLowerCase();
|
|
497
|
+
if (!SecondBrainOSServer.ALLOWED_EXTENSIONS.includes(ext)) {
|
|
498
|
+
throw new McpError(ErrorCode.InvalidRequest, `Unsupported file extension "${ext}" for file_path. Only ${SecondBrainOSServer.ALLOWED_EXTENSIONS.join(', ')} are supported.`);
|
|
499
|
+
}
|
|
500
|
+
if (!fs.existsSync(trimmed)) {
|
|
501
|
+
throw new McpError(ErrorCode.InvalidRequest, `Local file not found: ${trimmed}`);
|
|
502
|
+
}
|
|
503
|
+
const fileContent = fs.readFileSync(trimmed, 'utf-8');
|
|
504
|
+
if (!fileContent.trim()) {
|
|
505
|
+
throw new McpError(ErrorCode.InvalidRequest, `File is empty: ${trimmed}`);
|
|
506
|
+
}
|
|
507
|
+
const fileName = path.basename(trimmed);
|
|
508
|
+
const fileSize = Buffer.byteLength(fileContent, 'utf-8');
|
|
509
|
+
// Step 1: Get signed upload URL from the GCF
|
|
510
|
+
const uploadUrlResponse = await axios.post(`${this.baseUrl}${this.generateUploadURLPath}`, {
|
|
511
|
+
file_name: fileName,
|
|
512
|
+
file_size: fileSize,
|
|
513
|
+
mime_type: 'text/plain'
|
|
514
|
+
}, {
|
|
515
|
+
headers: {
|
|
516
|
+
'Authorization': `Bearer ${this.userId}:${this.userSecret}`,
|
|
517
|
+
'Content-Type': 'application/json'
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
const { upload_url, gs_path } = uploadUrlResponse.data;
|
|
521
|
+
// Step 2: PUT file content directly to GCS via signed URL
|
|
522
|
+
await axios.put(upload_url, fileContent, {
|
|
523
|
+
headers: {
|
|
524
|
+
'Content-Type': 'text/plain'
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
console.error(`Uploaded local file to ${gs_path}`);
|
|
528
|
+
result[key] = gs_path;
|
|
529
|
+
}
|
|
530
|
+
return result;
|
|
531
|
+
}
|
|
451
532
|
async fetchAndEnrichAgents() {
|
|
452
533
|
if (this.cachedAgents)
|
|
453
534
|
return this.cachedAgents;
|
|
@@ -491,6 +572,7 @@ class SecondBrainOSServer {
|
|
|
491
572
|
console.error("Second Brain OS MCP server running on stdio");
|
|
492
573
|
}
|
|
493
574
|
}
|
|
575
|
+
SecondBrainOSServer.ALLOWED_EXTENSIONS = ['.txt', '.md'];
|
|
494
576
|
// Function to fetch the schema from the API
|
|
495
577
|
async function fetchSchema() {
|
|
496
578
|
const userId = process.env.USER_ID;
|