secondbrainos-mcp-server 1.0.8 → 1.1.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 -1
- package/bin/cli.js +1 -1
- package/build/index.js +110 -3
- package/package.json +3 -3
- package/build/test-mcp-conversion.js +0 -135
package/README.md
CHANGED
|
@@ -41,6 +41,13 @@ The server uses `@samchon/openapi` library for robust OpenAPI handling, providin
|
|
|
41
41
|
- Automatic request formatting based on OpenAPI specifications
|
|
42
42
|
- Support for complex parameters and nested objects
|
|
43
43
|
|
|
44
|
+
### Prompts (Workflows)
|
|
45
|
+
The server exposes your Second Brain OS workflows as MCP prompts, making them available in Claude Desktop's attach menu:
|
|
46
|
+
- Automatically discovers your workflows via the `runPromptChain` service
|
|
47
|
+
- Each workflow appears as a selectable prompt with its name and description
|
|
48
|
+
- Selecting a prompt fetches the full prompt chain (ordered instructions) and injects them into the conversation
|
|
49
|
+
- Supports an optional `user_input` argument to provide additional context
|
|
50
|
+
|
|
44
51
|
### Better Error Handling
|
|
45
52
|
- Detailed error messages for debugging
|
|
46
53
|
- Proper handling of authentication failures, bad requests, and service errors
|
|
@@ -117,7 +124,8 @@ The server requires the following environment variables:
|
|
|
117
124
|
1. **Schema Fetching**: On startup, the server fetches your personalized OpenAPI schema from Second Brain OS
|
|
118
125
|
2. **Schema Conversion**: The `@samchon/openapi` library converts the schema to an optimized format for LLM function calling
|
|
119
126
|
3. **MCP Tools**: Each API endpoint becomes an MCP tool that Claude can use
|
|
120
|
-
4. **
|
|
127
|
+
4. **MCP Prompts**: If the `runPromptChain` service is available, your workflows are exposed as selectable prompts in the client UI
|
|
128
|
+
5. **Function Execution**: When Claude calls a tool, the server executes the corresponding API call with proper authentication
|
|
121
129
|
|
|
122
130
|
## Troubleshooting
|
|
123
131
|
|
package/bin/cli.js
CHANGED
|
@@ -158,7 +158,7 @@ async function main() {
|
|
|
158
158
|
|
|
159
159
|
// Verify user with the cloud function
|
|
160
160
|
try {
|
|
161
|
-
const verificationUrl = 'https://us-central1-second-brain-os.cloudfunctions.net/
|
|
161
|
+
const verificationUrl = 'https://us-central1-second-brain-os.cloudfunctions.net/gcf-sbos-verifyuserdatapublic';
|
|
162
162
|
const response = await axios.post(verificationUrl, {
|
|
163
163
|
"x-user-api-key": `Bearer ${USER_ID}:${USER_SECRET}`
|
|
164
164
|
});
|
package/build/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import { ListResourcesRequestSchema, ReadResourceRequestSchema, ListToolsRequestSchema, CallToolRequestSchema, ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { ListResourcesRequestSchema, ReadResourceRequestSchema, ListToolsRequestSchema, CallToolRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
4
4
|
import { OpenApi, HttpLlm } from "@samchon/openapi";
|
|
5
5
|
import axios from "axios";
|
|
6
6
|
import dotenv from "dotenv";
|
|
@@ -30,13 +30,30 @@ class SecondBrainOSServer {
|
|
|
30
30
|
this.functionMap.set(operation.operationId, func);
|
|
31
31
|
}
|
|
32
32
|
});
|
|
33
|
+
// Initialize workflow name-to-ID map
|
|
34
|
+
this.workflowNameToId = new Map();
|
|
35
|
+
// Discover runPromptChain path from the OpenAPI schema
|
|
36
|
+
this.runPromptChainPath = null;
|
|
37
|
+
if (initialSchema.paths) {
|
|
38
|
+
for (const [path, pathItem] of Object.entries(initialSchema.paths)) {
|
|
39
|
+
for (const operation of Object.values(pathItem)) {
|
|
40
|
+
if (operation.operationId === 'runPromptChain') {
|
|
41
|
+
this.runPromptChainPath = path;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (this.runPromptChainPath)
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
33
49
|
this.server = new Server({
|
|
34
50
|
name: "secondbrainos-server",
|
|
35
51
|
version: "1.0.0"
|
|
36
52
|
}, {
|
|
37
53
|
capabilities: {
|
|
38
54
|
resources: {},
|
|
39
|
-
tools: {}
|
|
55
|
+
tools: {},
|
|
56
|
+
prompts: {}
|
|
40
57
|
}
|
|
41
58
|
});
|
|
42
59
|
this.setupHandlers();
|
|
@@ -164,6 +181,96 @@ class SecondBrainOSServer {
|
|
|
164
181
|
throw error;
|
|
165
182
|
}
|
|
166
183
|
});
|
|
184
|
+
// List available prompts (user's workflows)
|
|
185
|
+
this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
186
|
+
if (!this.runPromptChainPath) {
|
|
187
|
+
return { prompts: [] };
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const data = await this.callRunPromptChain('', '');
|
|
191
|
+
const workflows = data.workflows || [];
|
|
192
|
+
// Populate name-to-ID map
|
|
193
|
+
this.workflowNameToId.clear();
|
|
194
|
+
for (const wf of workflows) {
|
|
195
|
+
if (wf.name && wf.workflow_id) {
|
|
196
|
+
this.workflowNameToId.set(wf.name, wf.workflow_id);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
prompts: workflows.map((wf) => ({
|
|
201
|
+
name: wf.name || wf.workflow_id,
|
|
202
|
+
description: wf.description || wf.name,
|
|
203
|
+
arguments: [
|
|
204
|
+
{
|
|
205
|
+
name: "user_input",
|
|
206
|
+
description: "Optional context or input to guide the workflow execution",
|
|
207
|
+
required: false
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
}))
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
console.error('Failed to fetch workflows for prompts/list:', error);
|
|
215
|
+
return { prompts: [] };
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
// Get a specific prompt (workflow's prompt chain)
|
|
219
|
+
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
220
|
+
const promptName = request.params.name;
|
|
221
|
+
const workflowId = this.workflowNameToId.get(promptName) || promptName;
|
|
222
|
+
const userInput = request.params.arguments?.user_input;
|
|
223
|
+
// Fetch workflow details to get prompt IDs
|
|
224
|
+
const workflowData = await this.callRunPromptChain('workflow', workflowId);
|
|
225
|
+
const promptIds = workflowData.prompt_id || [];
|
|
226
|
+
if (promptIds.length === 0) {
|
|
227
|
+
throw new McpError(ErrorCode.InvalidRequest, `No prompts found for workflow: ${workflowId}`);
|
|
228
|
+
}
|
|
229
|
+
// Fetch each prompt's instructions
|
|
230
|
+
const promptResults = await Promise.all(promptIds.map((promptId) => this.callRunPromptChain('prompt', promptId)));
|
|
231
|
+
// Sort by order and build messages
|
|
232
|
+
const sortedPrompts = promptResults.sort((a, b) => (a.order || 0) - (b.order || 0));
|
|
233
|
+
const messages = [];
|
|
234
|
+
for (const prompt of sortedPrompts) {
|
|
235
|
+
messages.push({
|
|
236
|
+
role: "user",
|
|
237
|
+
content: {
|
|
238
|
+
type: "text",
|
|
239
|
+
text: prompt.instructions
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
// If user provided input, append it as the final message
|
|
244
|
+
if (userInput) {
|
|
245
|
+
messages.push({
|
|
246
|
+
role: "user",
|
|
247
|
+
content: {
|
|
248
|
+
type: "text",
|
|
249
|
+
text: userInput
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
description: `Workflow prompt chain (${sortedPrompts.length} steps)`,
|
|
255
|
+
messages
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
async callRunPromptChain(entity, entityId) {
|
|
260
|
+
if (!this.runPromptChainPath) {
|
|
261
|
+
throw new McpError(ErrorCode.InternalError, 'runPromptChain service not available for this user');
|
|
262
|
+
}
|
|
263
|
+
const url = `${this.baseUrl}${this.runPromptChainPath}`;
|
|
264
|
+
const response = await axios.post(url, {
|
|
265
|
+
entity,
|
|
266
|
+
entity_id: entityId
|
|
267
|
+
}, {
|
|
268
|
+
headers: {
|
|
269
|
+
'Authorization': `Bearer ${this.userId}:${this.userSecret}`,
|
|
270
|
+
'Content-Type': 'application/json'
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
return response.data;
|
|
167
274
|
}
|
|
168
275
|
setupErrorHandling() {
|
|
169
276
|
// Error handling is now built into HttpLlm.execute
|
|
@@ -179,7 +286,7 @@ class SecondBrainOSServer {
|
|
|
179
286
|
async function fetchSchema() {
|
|
180
287
|
const userId = process.env.USER_ID;
|
|
181
288
|
try {
|
|
182
|
-
const response = await axios.post('https://
|
|
289
|
+
const response = await axios.post('https://schema.secondbrainos.com', { user_id: userId }, {
|
|
183
290
|
headers: {
|
|
184
291
|
'Content-Type': 'application/json'
|
|
185
292
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "secondbrainos-mcp-server",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Second Brain OS MCP Server for Claude Desktop",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"secondbrainos-mcp": "bin/cli.js"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
|
-
"build/",
|
|
12
|
+
"build/index.js",
|
|
13
13
|
"bin/",
|
|
14
14
|
"LICENSE",
|
|
15
15
|
"README.md"
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"author": "Umair Kamil",
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
35
35
|
"@samchon/openapi": "^2.0.0",
|
|
36
36
|
"axios": "^1.8.2",
|
|
37
37
|
"dotenv": "^16.4.7",
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { OpenApi, HttpLlm } from "@samchon/openapi";
|
|
2
|
-
import axios from "axios";
|
|
3
|
-
import dotenv from "dotenv";
|
|
4
|
-
import fs from "fs/promises";
|
|
5
|
-
import path from "path";
|
|
6
|
-
import { fileURLToPath } from 'url';
|
|
7
|
-
import { dirname } from 'path';
|
|
8
|
-
// Get __dirname equivalent for ES modules
|
|
9
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = dirname(__filename);
|
|
11
|
-
// Load environment variables
|
|
12
|
-
dotenv.config();
|
|
13
|
-
// Function to fetch the schema from the API
|
|
14
|
-
async function fetchSchema() {
|
|
15
|
-
const userId = process.env.USER_ID;
|
|
16
|
-
if (!userId) {
|
|
17
|
-
throw new Error("USER_ID environment variable is required");
|
|
18
|
-
}
|
|
19
|
-
console.log("Fetching schema for user:", userId);
|
|
20
|
-
try {
|
|
21
|
-
const response = await axios.post('https://us-central1-second-brain-os.cloudfunctions.net/generate-open-api-schema', { user_id: userId }, {
|
|
22
|
-
headers: {
|
|
23
|
-
'Content-Type': 'application/json'
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
return response.data;
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
console.error('Failed to fetch schema:', error);
|
|
30
|
-
throw new Error('Failed to fetch API schema');
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
async function testMCPConversion() {
|
|
34
|
-
try {
|
|
35
|
-
console.log("=== MCP Conversion Test ===\n");
|
|
36
|
-
// Step 1: Fetch the original schema
|
|
37
|
-
console.log("1. Fetching OpenAPI schema...");
|
|
38
|
-
const originalSchema = await fetchSchema();
|
|
39
|
-
console.log("✓ Schema fetched successfully");
|
|
40
|
-
console.log(` - Title: ${originalSchema.info?.title}`);
|
|
41
|
-
console.log(` - Version: ${originalSchema.info?.version}`);
|
|
42
|
-
console.log(` - Number of paths: ${Object.keys(originalSchema.paths || {}).length}\n`);
|
|
43
|
-
// Save original schema for reference
|
|
44
|
-
await fs.writeFile(path.join(__dirname, '../output/original-schema.json'), JSON.stringify(originalSchema, null, 2));
|
|
45
|
-
console.log("✓ Original schema saved to output/original-schema.json\n");
|
|
46
|
-
// Step 2: Convert using OpenApi
|
|
47
|
-
console.log("2. Converting schema using @samchon/openapi...");
|
|
48
|
-
const document = OpenApi.convert(originalSchema);
|
|
49
|
-
console.log("✓ Schema converted to OpenApi format\n");
|
|
50
|
-
// Step 3: Create LLM application
|
|
51
|
-
console.log("3. Creating LLM application...");
|
|
52
|
-
const application = HttpLlm.application({
|
|
53
|
-
model: "chatgpt",
|
|
54
|
-
document
|
|
55
|
-
});
|
|
56
|
-
console.log("✓ LLM application created");
|
|
57
|
-
console.log(` - Number of functions: ${application.functions.length}\n`);
|
|
58
|
-
// Step 4: Create MCP-compatible tools
|
|
59
|
-
console.log("4. Converting to MCP tool format...");
|
|
60
|
-
const mcpTools = [];
|
|
61
|
-
const functionMap = new Map();
|
|
62
|
-
application.functions.forEach(func => {
|
|
63
|
-
const operation = func.operation();
|
|
64
|
-
const operationId = operation.operationId || func.name;
|
|
65
|
-
functionMap.set(operationId, func);
|
|
66
|
-
const mcpTool = {
|
|
67
|
-
name: operationId,
|
|
68
|
-
description: func.description || operationId,
|
|
69
|
-
inputSchema: func.parameters
|
|
70
|
-
};
|
|
71
|
-
mcpTools.push(mcpTool);
|
|
72
|
-
});
|
|
73
|
-
console.log(`✓ Converted ${mcpTools.length} tools to MCP format\n`);
|
|
74
|
-
// Step 5: Save the results
|
|
75
|
-
console.log("5. Saving conversion results...");
|
|
76
|
-
// Create output directory if it doesn't exist
|
|
77
|
-
await fs.mkdir(path.join(__dirname, '../output'), { recursive: true });
|
|
78
|
-
// Save MCP tools
|
|
79
|
-
await fs.writeFile(path.join(__dirname, '../output/mcp-tools.json'), JSON.stringify(mcpTools, null, 2));
|
|
80
|
-
console.log("✓ MCP tools saved to output/mcp-tools.json");
|
|
81
|
-
// Save function details
|
|
82
|
-
const functionDetails = application.functions.map(func => {
|
|
83
|
-
const operation = func.operation();
|
|
84
|
-
// Extract method and path from the route property if available
|
|
85
|
-
let method;
|
|
86
|
-
let path;
|
|
87
|
-
// The operation might have these properties in different ways depending on the OpenAPI version
|
|
88
|
-
// Let's check the actual structure
|
|
89
|
-
const route = operation.route;
|
|
90
|
-
if (route) {
|
|
91
|
-
method = route.method;
|
|
92
|
-
path = route.path;
|
|
93
|
-
}
|
|
94
|
-
return {
|
|
95
|
-
name: func.name,
|
|
96
|
-
operationId: operation.operationId,
|
|
97
|
-
method,
|
|
98
|
-
path,
|
|
99
|
-
description: func.description,
|
|
100
|
-
parameters: func.parameters,
|
|
101
|
-
operation: {
|
|
102
|
-
summary: operation.summary,
|
|
103
|
-
description: operation.description,
|
|
104
|
-
tags: operation.tags
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
});
|
|
108
|
-
await fs.writeFile(path.join(__dirname, '../output/function-details.json'), JSON.stringify(functionDetails, null, 2));
|
|
109
|
-
console.log("✓ Function details saved to output/function-details.json\n");
|
|
110
|
-
// Step 6: Display summary
|
|
111
|
-
console.log("=== Conversion Summary ===");
|
|
112
|
-
console.log(`Total tools converted: ${mcpTools.length}`);
|
|
113
|
-
console.log("\nSample tools:");
|
|
114
|
-
mcpTools.slice(0, 5).forEach(tool => {
|
|
115
|
-
console.log(` - ${tool.name}: ${tool.description}`);
|
|
116
|
-
});
|
|
117
|
-
if (mcpTools.length > 5) {
|
|
118
|
-
console.log(` ... and ${mcpTools.length - 5} more`);
|
|
119
|
-
}
|
|
120
|
-
console.log("\n✓ Test completed successfully!");
|
|
121
|
-
console.log("\nCheck the 'output' directory for:");
|
|
122
|
-
console.log(" - original-schema.json: The raw OpenAPI schema from the API");
|
|
123
|
-
console.log(" - mcp-tools.json: Tools in MCP format");
|
|
124
|
-
console.log(" - function-details.json: Detailed function information");
|
|
125
|
-
}
|
|
126
|
-
catch (error) {
|
|
127
|
-
console.error("\n❌ Test failed:", error);
|
|
128
|
-
if (error instanceof Error) {
|
|
129
|
-
console.error("Error details:", error.message);
|
|
130
|
-
}
|
|
131
|
-
process.exit(1);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
// Run the test
|
|
135
|
-
testMCPConversion().catch(console.error);
|