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.
Files changed (2) hide show
  1. package/build/index.js +91 -9
  2. 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: request.params.arguments || {}
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
- this.workflowNameToId.set(wf.name, wf.workflow_id);
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: skillName,
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 agentPromptName = `[Agent] ${agent.name}`;
231
- this.agentNameToId.set(agentPromptName, agent.id);
246
+ const snakeName = `agent_${toSnakeCase(agent.name)}`;
247
+ this.agentNameToId.set(snakeName, agent.id);
232
248
  prompts.push({
233
- name: agentPromptName,
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: "Knowledge Bases",
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 === "Knowledge Bases") {
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "secondbrainos-mcp-server",
3
- "version": "1.2.5",
3
+ "version": "1.3.0",
4
4
  "description": "Second Brain OS MCP Server for Claude Desktop",
5
5
  "type": "module",
6
6
  "main": "build/index.js",