secondbrainos-mcp-server 1.5.4 → 1.7.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 CHANGED
@@ -25,8 +25,8 @@ secondbrainos-mcp USER_ID USER_SECRET
25
25
  This will:
26
26
  1. Verify your account credentials
27
27
  2. Confirm you have access to the Claude MCP feature
28
- 3. Create the necessary configuration files for Claude Desktop
29
- 4. Configure the server to dynamically fetch your available API endpoints
28
+ 3. Store credentials securely at `~/.secondbrainos/credentials.json`
29
+ 4. Configure Claude Desktop and Claude Code to use `serve` mode (credentials read from file, not embedded in config)
30
30
 
31
31
  ### Claude Code
32
32
 
@@ -52,16 +52,20 @@ The server uses `@samchon/openapi` library for robust OpenAPI handling, providin
52
52
  - Support for complex parameters and nested objects
53
53
 
54
54
  ### Prompts
55
- The server exposes three types of MCP prompts, available in the client's attach menu:
55
+ The server exposes four types of MCP prompts, available as slash commands (e.g. `/secondbrainos:agent_my_agent`):
56
+
57
+ #### Start (`start_secondbrainos`)
58
+ - Returns a context document with SBOS terminology, agent architecture, and an index of all available agents and skills with their slash commands
59
+ - Useful for orienting Claude at the start of a session
56
60
 
57
61
  #### Skills (Workflows)
58
- - Automatically discovers your workflows via the `runPromptChain` service
62
+ - Discovered via `runPromptChain` on first `ListPrompts` call, then cached for the session
59
63
  - Each skill appears with a `[Skill]` prefix and returns a structured document with `skill_id`, `name`, `description`, and an ordered list of prompts (metadata only, no instructions)
60
64
  - Supports an optional `user_input` argument to provide additional context
61
65
 
62
66
  #### Agents
63
- - Discovers your AI agents via the `getAIAgentsSchema` service
64
- - Each agent appears with an `[Agent]` prefix and returns a structured document containing `agent_id`, `name`, `description`, `behaviour_and_instructions`, `searchMyKnowledge_collection_id`, `actions` (with id, name, description, body_parameters), and `workflows` (with enriched prompt metadata)
67
+ - Discovered via `getAIAgentsSchema` on first `ListPrompts` call, then cached for the session
68
+ - Each agent appears with an `[Agent]` prefix and returns a structured document containing `agent_id`, `name`, `description`, `behaviour_and_instructions`, `searchMyKnowledge_collection_id`, `actions` (with id, name, description, body_parameters), and `workflows` (with prompt order and description)
65
69
  - Supports an optional `user_input` argument
66
70
 
67
71
  #### Knowledge Bases
@@ -132,20 +136,29 @@ secondbrainos-mcp-server/
132
136
  └── README.md # This file
133
137
  ```
134
138
 
135
- ## Environment Variables
139
+ ## Credentials
136
140
 
137
- The server requires the following environment variables:
138
- - `USER_ID`: Your Second Brain OS user ID
139
- - `USER_SECRET`: Your Second Brain OS user secret
140
- - `API_BASE_URL` (optional): Override the default API base URL
141
+ Credentials are stored securely at `~/.secondbrainos/credentials.json` (created during setup with mode `0o600`). The server reads from this file in `serve` mode. Environment variables `USER_ID`, `USER_SECRET`, and `API_BASE_URL` can override the stored credentials if set.
141
142
 
142
143
  ## How It Works
143
144
 
144
- 1. **Schema Fetching**: On startup, the server fetches your personalized OpenAPI schema from Second Brain OS
145
+ 1. **Schema Fetching**: On startup, the server fetches your personalized OpenAPI schema from `schema.secondbrainos.com`
145
146
  2. **Schema Conversion**: The `@samchon/openapi` library converts the schema to an optimized format for LLM function calling
146
147
  3. **MCP Tools**: Each API endpoint becomes an MCP tool that Claude can use
147
- 4. **MCP Prompts**: Your skills (workflows), AI agents, and knowledge bases are exposed as selectable prompts in the client UI
148
- 5. **Function Execution**: When Claude calls a tool, the server executes the corresponding API call with proper authentication
148
+ 4. **MCP Prompts**: On first `ListPrompts` call (automatic on session connect), the server fetches all agents via `getAIAgentsSchema` and all workflows via `runPromptChain`, caches them in memory, and exposes them as slash commands
149
+ 5. **Slash Commands**: When a user runs a slash command (e.g. `/secondbrainos:agent_my_agent`), the server returns the full agent/skill definition from cache — no additional API calls, no credit cost
150
+ 6. **Function Execution**: When Claude calls a tool, the server executes the corresponding API call via `api.secondbrainos.com` with proper authentication
151
+ 7. **File Upload**: Local file paths in tool arguments are automatically uploaded to GCS via signed URL before the API call
152
+
153
+ ### Credit Cost
154
+
155
+ | Event | Credits |
156
+ |-------|---------|
157
+ | Session init | n workflows + n agents |
158
+ | Slash commands | 0 (cached) |
159
+ | Tool calls | Billed per call via service router |
160
+
161
+ See [`docs/architecture.md`](docs/architecture.md) for detailed diagrams.
149
162
 
150
163
  ## Troubleshooting
151
164
 
package/bin/cli.js CHANGED
@@ -332,13 +332,12 @@ async function main() {
332
332
  const hadExistingSecondBrainOS = !!existingConfig.mcpServers.secondbrainos;
333
333
 
334
334
  // Add or update the secondbrainos server configuration
335
+ // Uses serve mode which reads credentials from ~/.secondbrainos/credentials.json
336
+ const cliPath = path.join(packageRoot, 'bin', 'cli.js');
335
337
  existingConfig.mcpServers.secondbrainos = {
336
338
  command: nodePath,
337
- args: [serverPath],
339
+ args: [cliPath, 'serve'],
338
340
  env: {
339
- USER_ID,
340
- USER_SECRET,
341
- API_BASE_URL: 'https://api.secondbrainos.com',
342
341
  NODE_PATH: getNodeModulesPath()
343
342
  }
344
343
  };
@@ -362,42 +361,6 @@ async function main() {
362
361
  const cliPath = path.join(packageRoot, 'bin', 'cli.js');
363
362
  const claudeCodeConfigured = await configureClaudeCode(cliPath, nodePath);
364
363
 
365
- // Add SessionStart hook to global Claude Code settings
366
- if (claudeCodeConfigured) {
367
- try {
368
- const claudeSettingsPath = path.join(homedir(), '.claude', 'settings.json');
369
- let settings = {};
370
- try {
371
- settings = JSON.parse(await fs.readFile(claudeSettingsPath, 'utf8'));
372
- } catch { /* file may not exist yet */ }
373
-
374
- if (!settings.hooks) settings.hooks = {};
375
- if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
376
-
377
- // Check if our hook already exists
378
- const skillFilePath = path.join(homedir(), '.claude', 'skills', 'secondbrainos', 'server-use', 'SKILL.md');
379
- const hookCommand = `cat "${skillFilePath}" 2>/dev/null || true`;
380
- const hasHook = settings.hooks.SessionStart.some(
381
- entry => entry.hooks && entry.hooks.some(h => h.command && h.command.includes('secondbrainos/server-use/SKILL.md'))
382
- );
383
-
384
- if (!hasHook) {
385
- settings.hooks.SessionStart.push({
386
- hooks: [
387
- {
388
- type: "command",
389
- command: hookCommand
390
- }
391
- ]
392
- });
393
- await fs.writeFile(claudeSettingsPath, JSON.stringify(settings, null, 2));
394
- console.log('SessionStart hook configured for SBOS context loading.');
395
- }
396
- } catch (error) {
397
- console.log(`Note: Could not configure SessionStart hook: ${error.message}`);
398
- }
399
- }
400
-
401
364
  console.log('\nInstallation successful!');
402
365
  console.log('\nClaude Desktop:');
403
366
  console.log('1. Completely quit Claude Desktop if it is running');
package/build/index.js CHANGED
@@ -5,9 +5,8 @@ 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
8
  import path from 'path';
10
- import { homedir } from 'os';
9
+ import fs from 'fs';
11
10
  dotenv.config();
12
11
  function toSnakeCase(str) {
13
12
  return str
@@ -15,12 +14,6 @@ function toSnakeCase(str) {
15
14
  .replace(/[^a-z0-9]+/g, '_')
16
15
  .replace(/^_+|_+$/g, '');
17
16
  }
18
- function toKebabCase(str) {
19
- return str
20
- .toLowerCase()
21
- .replace(/[^a-z0-9]+/g, '-')
22
- .replace(/^-+|-+$/g, '');
23
- }
24
17
  class SecondBrainOSServer {
25
18
  constructor(initialSchema) {
26
19
  this.originalSpec = initialSchema;
@@ -80,7 +73,7 @@ class SecondBrainOSServer {
80
73
  tools: {},
81
74
  prompts: {}
82
75
  },
83
- instructions: "SBOS provides tools for managing knowledge along with definitions for sub-agents and associated skills. For full server context, see the skills at ~/.claude/skills/secondbrainos/"
76
+ instructions: "SBOS provides tools for managing knowledge along with definitions for sub-agents and associated skills. Run /secondbrainos:start_secondbrainos to load full SBOS context."
84
77
  });
85
78
  this.setupHandlers();
86
79
  this.setupErrorHandling();
@@ -253,6 +246,13 @@ class SecondBrainOSServer {
253
246
  console.error('Failed to fetch agents for prompts/list:', error);
254
247
  }
255
248
  }
249
+ // Add start_secondbrainos prompt — returns the server-use context document
250
+ prompts.push({
251
+ name: "start_secondbrainos",
252
+ title: "[Start] Second Brain OS",
253
+ description: "Load SBOS context: terminology, architecture, and available agents & skills index",
254
+ arguments: []
255
+ });
256
256
  // Add Knowledge Bases prompt (collects searchMyKnowledge_collection_ids from agents)
257
257
  if (this.getAIAgentsSchemaPath) {
258
258
  try {
@@ -285,6 +285,22 @@ class SecondBrainOSServer {
285
285
  this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
286
286
  const promptName = request.params.name;
287
287
  const userInput = request.params.arguments?.user_input;
288
+ // Check if this is the start_secondbrainos prompt
289
+ if (promptName === "start_secondbrainos") {
290
+ const content = await this.buildServerUseContent();
291
+ return {
292
+ description: "Second Brain OS — Server Context",
293
+ messages: [
294
+ {
295
+ role: "user",
296
+ content: {
297
+ type: "text",
298
+ text: content
299
+ }
300
+ }
301
+ ]
302
+ };
303
+ }
288
304
  // Check if this is the Knowledge Bases prompt
289
305
  if (promptName === "knowledge_bases") {
290
306
  const agents = await this.fetchAndEnrichAgents();
@@ -322,23 +338,14 @@ class SecondBrainOSServer {
322
338
  if (agentId) {
323
339
  return this.buildAgentPrompt(agentId, userInput);
324
340
  }
325
- // Otherwise treat as a workflow prompt
341
+ // Otherwise treat as a workflow prompt — use cached data
326
342
  const workflowId = this.workflowNameToId.get(promptName) || promptName;
327
- // Single call list response now includes prompt metadata per workflow
328
- const workflowsData = await this.callRunPromptChain('', '');
329
- const workflowMeta = (workflowsData.workflows || []).find((wf) => wf.workflow_id === workflowId);
330
- const prompts = workflowMeta?.prompts || [];
331
- if (prompts.length === 0) {
343
+ const workflows = await this.fetchAndEnrichWorkflows();
344
+ const workflowMeta = workflows.find((wf) => wf.workflow_id === workflowId);
345
+ if (!workflowMeta || !workflowMeta.prompts || workflowMeta.prompts.length === 0) {
332
346
  throw new McpError(ErrorCode.InvalidRequest, `No prompts found for workflow: ${workflowId}`);
333
347
  }
334
- const sortedPrompts = prompts
335
- .map((p) => ({
336
- id: p.prompt_id,
337
- name: p.name || '',
338
- order: p.order || 0,
339
- description: p.description || ''
340
- }))
341
- .sort((a, b) => (a.order || 0) - (b.order || 0));
348
+ const sortedPrompts = workflowMeta.prompts;
342
349
  const workflowDocument = {
343
350
  skill_id: workflowId,
344
351
  name: workflowMeta?.name || promptName,
@@ -418,15 +425,12 @@ class SecondBrainOSServer {
418
425
  messages
419
426
  };
420
427
  }
421
- async callRunPromptChain(entity, entityId) {
428
+ async callRunPromptChain() {
422
429
  if (!this.runPromptChainPath) {
423
430
  throw new McpError(ErrorCode.InternalError, 'runPromptChain service not available for this user');
424
431
  }
425
432
  const url = `${this.baseUrl}${this.runPromptChainPath}`;
426
- const response = await axios.post(url, {
427
- entity,
428
- entity_id: entityId
429
- }, {
433
+ const response = await axios.post(url, {}, {
430
434
  headers: {
431
435
  'Authorization': `Bearer ${this.userId}:${this.userSecret}`,
432
436
  'Content-Type': 'application/json'
@@ -523,20 +527,10 @@ class SecondBrainOSServer {
523
527
  async fetchAndEnrichAgents() {
524
528
  if (this.cachedAgents)
525
529
  return this.cachedAgents;
526
- // Fetch agents and all workflows in parallel
527
- const [agentData, workflows] = await Promise.all([
528
- this.callGetAIAgentsSchema(),
529
- this.fetchAndEnrichWorkflows()
530
- ]);
530
+ // Single API call getAIAgentsSchema now returns prompt order/description
531
+ const agentData = await this.callGetAIAgentsSchema();
531
532
  const agents = agentData.agents || [];
532
- // Build a prompt lookup from the already-fetched workflows
533
- const promptLookup = new Map();
534
- for (const wf of workflows) {
535
- for (const p of wf.prompts || []) {
536
- promptLookup.set(p.id, p);
537
- }
538
- }
539
- // Enrich agent workflows using the lookup — no extra API calls
533
+ // Sort prompts within each workflow by order
540
534
  for (const agent of agents) {
541
535
  if (!agent.workflows)
542
536
  continue;
@@ -544,16 +538,13 @@ class SecondBrainOSServer {
544
538
  if (!workflow.prompts || workflow.prompts.length === 0)
545
539
  continue;
546
540
  workflow.prompts = workflow.prompts
547
- .map((prompt) => {
548
- const enriched = promptLookup.get(prompt.id);
549
- return {
550
- id: prompt.id,
551
- name: enriched?.name || prompt.name,
552
- order: enriched?.order || 0,
553
- description: enriched?.description || ''
554
- };
555
- })
556
- .sort((a, b) => (a.order || 0) - (b.order || 0));
541
+ .map((prompt) => ({
542
+ id: prompt.id,
543
+ name: prompt.name || '',
544
+ order: prompt.order || 0,
545
+ description: prompt.description || ''
546
+ }))
547
+ .sort((a, b) => (Number(a.order) || 0) - (Number(b.order) || 0));
557
548
  }
558
549
  }
559
550
  this.cachedAgents = agents;
@@ -563,7 +554,7 @@ class SecondBrainOSServer {
563
554
  if (this.cachedWorkflows)
564
555
  return this.cachedWorkflows;
565
556
  // Single API call — now returns all workflows with prompt metadata
566
- const data = await this.callRunPromptChain('', '');
557
+ const data = await this.callRunPromptChain();
567
558
  const workflows = (data.workflows || []).filter((wf) => wf.name && wf.workflow_id);
568
559
  const enriched = workflows.map((wf) => ({
569
560
  ...wf,
@@ -580,25 +571,17 @@ class SecondBrainOSServer {
580
571
  return enriched;
581
572
  }
582
573
  /**
583
- * Write Claude Code skill files to ~/.claude/skills/secondbrainos/
584
- * Called on every server startup so skills stay in sync with SBOS.
574
+ * Build the server-use context document content.
575
+ * Used by the start_secondbrainos prompt.
585
576
  */
586
- async writeSkillFiles() {
587
- const skillsBase = path.join(homedir(), '.claude', 'skills', 'secondbrainos');
588
- // Helper to write a SKILL.md file
589
- const writeSkill = (dir, content) => {
590
- fs.mkdirSync(dir, { recursive: true });
591
- fs.writeFileSync(path.join(dir, 'SKILL.md'), content, 'utf-8');
592
- };
593
- // 1. server-use — context document for Claude Code sessions
594
- // Gather agent and workflow names for the index
577
+ async buildServerUseContent() {
595
578
  let agentIndex = '';
596
579
  let skillIndex = '';
597
580
  try {
598
581
  const agents = await this.fetchAndEnrichAgents();
599
582
  agentIndex = agents
600
583
  .filter((a) => a.name && a.id)
601
- .map((a) => `- ${a.name} → \`/secondbrainos:agent_${toSnakeCase(a.name)}\``)
584
+ .map((a) => `- ${a.name} (id: ${a.id}) → \`/secondbrainos:agent_${toSnakeCase(a.name)}\`${a.description ? `\n ${a.description}` : ''}`)
602
585
  .join('\n');
603
586
  }
604
587
  catch { /* skip if unavailable */ }
@@ -606,11 +589,11 @@ class SecondBrainOSServer {
606
589
  const workflows = await this.fetchAndEnrichWorkflows();
607
590
  skillIndex = workflows
608
591
  .filter((wf) => wf.name)
609
- .map((wf) => `- ${wf.name} → \`/secondbrainos:skill_${toSnakeCase(wf.name)}\``)
592
+ .map((wf) => `- ${wf.name} (id: ${wf.workflow_id}) → \`/secondbrainos:skill_${toSnakeCase(wf.name)}\`${wf.description ? `\n ${wf.description}` : ''}`)
610
593
  .join('\n');
611
594
  }
612
595
  catch { /* skip if unavailable */ }
613
- const serverUseContent = `---
596
+ return `---
614
597
  name: server-use
615
598
  description: Second Brain OS server context — terminology, architecture, and available agents/skills index
616
599
  user-invocable: false
@@ -630,14 +613,13 @@ An agent definition combines:
630
613
  2. **Services (Tools)** — the MCP tools the agent can use
631
614
  3. **Workflows (Skills)** — the prompt chains the agent follows
632
615
 
633
- This means you can spin up sub-agents from an agent's skill definitions (see \`~/.claude/skills/secondbrainos/tabs/\`).
616
+ ## Loading Agent & Skill Details
617
+ - To get full agent definitions (behaviour, knowledge, actions, workflows): use the \`getAIAgentsSchema\` tool
618
+ - To get full skill/workflow details and run prompt chains: use the \`runPromptChain\` tool
634
619
 
635
620
  ## Accessing Agents & Skills
636
- - Skill files live at \`~/.claude/skills/secondbrainos/\` (tabs/, workflows/, knowledge-bases/)
637
- - All skill files are set to \`user-invocable: false\` to avoid duplicating slash commands already available via MCP prompts
638
621
  - **Users** invoke agents or skills via slash commands (e.g. \`/secondbrainos:agent_...\` or \`/secondbrainos:skill_...\`)
639
622
  - **Users** cannot invoke MCP tools directly via slash commands — they must ask you to load and call the relevant tool
640
- - When using an agent with sub-agents, load the necessary skills at the start of the conversation by reading the agent's skill files
641
623
 
642
624
  ## Available Agents
643
625
  ${agentIndex || '_None configured_'}
@@ -645,85 +627,12 @@ ${agentIndex || '_None configured_'}
645
627
  ## Available Skills
646
628
  ${skillIndex || '_None configured_'}
647
629
  `;
648
- writeSkill(path.join(skillsBase, 'server-use'), serverUseContent);
649
- // 2. tabs (agents)
650
- if (this.getAIAgentsSchemaPath) {
651
- try {
652
- const agents = await this.fetchAndEnrichAgents();
653
- for (const agent of agents) {
654
- if (!agent.name || !agent.id)
655
- continue;
656
- const folderName = toKebabCase(agent.name);
657
- const agentDocument = {
658
- agent_id: agent.id,
659
- name: agent.name,
660
- description: agent.description || '',
661
- behaviour_and_instructions: agent.behaviour_and_instructions || '',
662
- searchMyKnowledge_collection_id: agent.searchMyKnowledge_collection_id || '',
663
- actions: agent.actions || [],
664
- workflows: (agent.workflows || []).map((wf) => ({
665
- id: wf.id,
666
- name: wf.name,
667
- prompts: (wf.prompts || []).map((p) => ({
668
- id: p.id,
669
- name: p.name,
670
- order: p.order,
671
- description: p.description
672
- }))
673
- }))
674
- };
675
- writeSkill(path.join(skillsBase, 'tabs', folderName), `---\nname: ${folderName}\ndescription: "${agent.description || agent.name}"\nuser-invocable: false\n---\n\n${JSON.stringify(agentDocument, null, 2)}\n`);
676
- }
677
- }
678
- catch (error) {
679
- console.error('Failed to write agent skill files:', error);
680
- }
681
- }
682
- // 3. workflows (uses cached enriched data)
683
- if (this.runPromptChainPath) {
684
- try {
685
- const workflows = await this.fetchAndEnrichWorkflows();
686
- for (const wf of workflows) {
687
- const folderName = toKebabCase(wf.name);
688
- const workflowDocument = {
689
- skill_id: wf.workflow_id,
690
- name: wf.name,
691
- description: wf.description || '',
692
- prompts: wf.prompts
693
- };
694
- writeSkill(path.join(skillsBase, 'workflows', folderName), `---\nname: ${folderName}\ndescription: "${wf.description || wf.name}"\nuser-invocable: false\n---\n\n${JSON.stringify(workflowDocument, null, 2)}\n`);
695
- }
696
- }
697
- catch (error) {
698
- console.error('Failed to write workflow skill files:', error);
699
- }
700
- }
701
- // 4. knowledgebases
702
- if (this.getAIAgentsSchemaPath) {
703
- try {
704
- const agents = await this.fetchAndEnrichAgents();
705
- const collectionIds = agents
706
- .map((a) => a.searchMyKnowledge_collection_id)
707
- .filter((id) => id && id.length > 0);
708
- if (collectionIds.length > 0) {
709
- const kbDocument = { searchMyKnowledge_collection_ids: collectionIds };
710
- writeSkill(path.join(skillsBase, 'knowledge-bases'), `---\nname: knowledge-bases\ndescription: Knowledge base collection IDs available to agents\nuser-invocable: false\n---\n\n${JSON.stringify(kbDocument, null, 2)}\n`);
711
- }
712
- }
713
- catch (error) {
714
- console.error('Failed to write knowledge bases skill file:', error);
715
- }
716
- }
717
- console.error('Skill files written to ~/.claude/skills/secondbrainos/');
718
630
  }
719
631
  setupErrorHandling() {
720
632
  // Error handling is now built into HttpLlm.execute
721
633
  // This method is kept for future error handling implementations
722
634
  }
723
635
  async run() {
724
- // Write skill files before connecting so Claude Code sees them at session start.
725
- // This is fast now (~2-3s) since enrichment uses single API calls.
726
- await this.writeSkillFiles().catch(err => console.error('Failed to write skill files:', err));
727
636
  const transport = new StdioServerTransport();
728
637
  await this.server.connect(transport);
729
638
  console.error("Second Brain OS MCP server running on stdio");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "secondbrainos-mcp-server",
3
- "version": "1.5.4",
3
+ "version": "1.7.0",
4
4
  "description": "Second Brain OS MCP Server for Claude Desktop",
5
5
  "type": "module",
6
6
  "main": "build/index.js",