bluelamp 4.2.1 → 4.2.3
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/bin/bluelamp-vscode-base +1 -1
- package/lib/auth/InteractiveLogin.js +1 -1
- package/lib/auth/PortalAuthClient.js +1 -1
- package/lib/auth/TokenManager.js +1 -1
- package/lib/mcp-gmail/index.js +149 -1
- package/lib/mcp-password-manager/index.js +7 -0
- package/lib/mcp-server/index.cjs +1 -1
- package/lib/mcp-server/portal-api-client.cjs +1 -1
- package/package.json +1 -1
- package/scripts/playwright-launch.js +1 -1
package/lib/mcp-gmail/index.js
CHANGED
|
@@ -5,6 +5,14 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextpro
|
|
|
5
5
|
import { google } from 'googleapis';
|
|
6
6
|
import * as fs from 'node:fs';
|
|
7
7
|
import * as path from 'node:path';
|
|
8
|
+
|
|
9
|
+
// BlueLamp認証チェック
|
|
10
|
+
const TOKEN_FILE_PATH = path.join(process.env.HOME || '/tmp', '.bluelamp-vscode', 'portal-token.enc');
|
|
11
|
+
if (!fs.existsSync(TOKEN_FILE_PATH)) {
|
|
12
|
+
console.error('❌ BlueLamp認証が必要です。bluelampコマンドでログインしてください。');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
8
16
|
// --- Config ---
|
|
9
17
|
const CONFIG_DIR = path.join(process.env.HOME || '/tmp', '.config', 'gmail-mcp');
|
|
10
18
|
const TOKENS_DIR = path.join(CONFIG_DIR, 'tokens');
|
|
@@ -12,7 +20,7 @@ const CREDENTIALS_PATH = path.join(CONFIG_DIR, 'credentials.json');
|
|
|
12
20
|
const ACCOUNTS_PATH = path.join(CONFIG_DIR, 'accounts.json');
|
|
13
21
|
// Legacy single-token path (for migration)
|
|
14
22
|
const LEGACY_TOKEN_PATH = path.join(CONFIG_DIR, 'token.json');
|
|
15
|
-
const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
|
|
23
|
+
const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly', 'https://www.googleapis.com/auth/gmail.compose'];
|
|
16
24
|
const DEFAULT_TEXT_BODY_LIMIT = 10000;
|
|
17
25
|
// --- Account discovery ---
|
|
18
26
|
function getAccounts() {
|
|
@@ -285,6 +293,36 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
285
293
|
required: ['items'],
|
|
286
294
|
},
|
|
287
295
|
},
|
|
296
|
+
{
|
|
297
|
+
name: 'prepare_outbox',
|
|
298
|
+
description: 'Stage a new message for review with optional file attachments. Saved to outbox for user approval before delivery.',
|
|
299
|
+
inputSchema: {
|
|
300
|
+
type: 'object',
|
|
301
|
+
properties: {
|
|
302
|
+
account: { type: 'string', description: 'Sender email account' },
|
|
303
|
+
to: { type: 'array', items: { type: 'string' }, description: 'Recipient email addresses' },
|
|
304
|
+
cc: { type: 'array', items: { type: 'string' }, description: 'CC email addresses (optional)' },
|
|
305
|
+
bcc: { type: 'array', items: { type: 'string' }, description: 'BCC email addresses (optional)' },
|
|
306
|
+
subject: { type: 'string', description: 'Email subject' },
|
|
307
|
+
body: { type: 'string', description: 'Email body (plain text)' },
|
|
308
|
+
html_body: { type: 'string', description: 'Email body (HTML, optional - overrides body if set)' },
|
|
309
|
+
attachments: {
|
|
310
|
+
type: 'array',
|
|
311
|
+
items: {
|
|
312
|
+
type: 'object',
|
|
313
|
+
properties: {
|
|
314
|
+
file_path: { type: 'string', description: 'Absolute path to the file to attach' },
|
|
315
|
+
filename: { type: 'string', description: 'Override filename (optional, defaults to basename)' },
|
|
316
|
+
},
|
|
317
|
+
required: ['file_path'],
|
|
318
|
+
},
|
|
319
|
+
description: 'File attachments (optional)',
|
|
320
|
+
},
|
|
321
|
+
reply_to_message_id: { type: 'string', description: 'Gmail message ID to reply to (optional, sets In-Reply-To and References headers)' },
|
|
322
|
+
},
|
|
323
|
+
required: ['account', 'to', 'subject', 'body'],
|
|
324
|
+
},
|
|
325
|
+
},
|
|
288
326
|
],
|
|
289
327
|
}));
|
|
290
328
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -497,6 +535,116 @@ ${htmlBody}
|
|
|
497
535
|
}
|
|
498
536
|
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
|
|
499
537
|
}
|
|
538
|
+
case 'prepare_outbox': {
|
|
539
|
+
const { account, to, cc, bcc, subject, body, html_body, attachments, reply_to_message_id } = args;
|
|
540
|
+
const auth = getAuthenticatedClient(account);
|
|
541
|
+
if (!auth) {
|
|
542
|
+
return {
|
|
543
|
+
content: [{ type: 'text', text: `Account ${account} not authenticated. Use authorize_account first.` }],
|
|
544
|
+
isError: true,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
const gmail = getGmail(auth);
|
|
548
|
+
const boundary = `boundary_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
549
|
+
const mimeLines = [];
|
|
550
|
+
mimeLines.push(`To: ${to.join(', ')}`);
|
|
551
|
+
if (cc?.length) mimeLines.push(`Cc: ${cc.join(', ')}`);
|
|
552
|
+
if (bcc?.length) mimeLines.push(`Bcc: ${bcc.join(', ')}`);
|
|
553
|
+
mimeLines.push(`Subject: =?UTF-8?B?${Buffer.from(subject).toString('base64')}?=`);
|
|
554
|
+
mimeLines.push('MIME-Version: 1.0');
|
|
555
|
+
if (reply_to_message_id) {
|
|
556
|
+
try {
|
|
557
|
+
const origMsg = await gmail.users.messages.get({ userId: 'me', id: reply_to_message_id, format: 'metadata', metadataHeaders: ['Message-ID'] });
|
|
558
|
+
const origMsgId = getHeader(origMsg.data.payload?.headers || [], 'Message-ID');
|
|
559
|
+
if (origMsgId) {
|
|
560
|
+
mimeLines.push(`In-Reply-To: ${origMsgId}`);
|
|
561
|
+
mimeLines.push(`References: ${origMsgId}`);
|
|
562
|
+
}
|
|
563
|
+
} catch {}
|
|
564
|
+
}
|
|
565
|
+
const hasAttachments = attachments?.length > 0;
|
|
566
|
+
if (hasAttachments) {
|
|
567
|
+
mimeLines.push(`Content-Type: multipart/mixed; boundary="${boundary}"`);
|
|
568
|
+
mimeLines.push('');
|
|
569
|
+
mimeLines.push(`--${boundary}`);
|
|
570
|
+
}
|
|
571
|
+
if (html_body) {
|
|
572
|
+
mimeLines.push('Content-Type: text/html; charset=UTF-8');
|
|
573
|
+
mimeLines.push('Content-Transfer-Encoding: base64');
|
|
574
|
+
mimeLines.push('');
|
|
575
|
+
mimeLines.push(Buffer.from(html_body).toString('base64'));
|
|
576
|
+
} else {
|
|
577
|
+
mimeLines.push('Content-Type: text/plain; charset=UTF-8');
|
|
578
|
+
mimeLines.push('Content-Transfer-Encoding: base64');
|
|
579
|
+
mimeLines.push('');
|
|
580
|
+
mimeLines.push(Buffer.from(body).toString('base64'));
|
|
581
|
+
}
|
|
582
|
+
if (hasAttachments) {
|
|
583
|
+
for (const att of attachments) {
|
|
584
|
+
const filePath = att.file_path;
|
|
585
|
+
if (!fs.existsSync(filePath)) {
|
|
586
|
+
return { content: [{ type: 'text', text: `Attachment file not found: ${filePath}` }], isError: true };
|
|
587
|
+
}
|
|
588
|
+
const fileData = fs.readFileSync(filePath);
|
|
589
|
+
const fileName = att.filename || path.basename(filePath);
|
|
590
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
591
|
+
const mimeTypes = {
|
|
592
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
593
|
+
'.xls': 'application/vnd.ms-excel',
|
|
594
|
+
'.pdf': 'application/pdf',
|
|
595
|
+
'.csv': 'text/csv',
|
|
596
|
+
'.doc': 'application/msword',
|
|
597
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
598
|
+
'.zip': 'application/zip',
|
|
599
|
+
'.png': 'image/png',
|
|
600
|
+
'.jpg': 'image/jpeg',
|
|
601
|
+
'.jpeg': 'image/jpeg',
|
|
602
|
+
};
|
|
603
|
+
const mimeType = mimeTypes[ext] || 'application/octet-stream';
|
|
604
|
+
const encodedFilename = `=?UTF-8?B?${Buffer.from(fileName).toString('base64')}?=`;
|
|
605
|
+
mimeLines.push(`--${boundary}`);
|
|
606
|
+
mimeLines.push(`Content-Type: ${mimeType}; name="${encodedFilename}"`);
|
|
607
|
+
mimeLines.push(`Content-Disposition: attachment; filename="${encodedFilename}"`);
|
|
608
|
+
mimeLines.push('Content-Transfer-Encoding: base64');
|
|
609
|
+
mimeLines.push('');
|
|
610
|
+
mimeLines.push(fileData.toString('base64'));
|
|
611
|
+
}
|
|
612
|
+
mimeLines.push(`--${boundary}--`);
|
|
613
|
+
}
|
|
614
|
+
const rawMessage = mimeLines.join('\r\n');
|
|
615
|
+
const encodedMessage = Buffer.from(rawMessage)
|
|
616
|
+
.toString('base64')
|
|
617
|
+
.replace(/\+/g, '-')
|
|
618
|
+
.replace(/\//g, '_')
|
|
619
|
+
.replace(/=+$/, '');
|
|
620
|
+
const draft = await gmail.users.drafts.create({
|
|
621
|
+
userId: 'me',
|
|
622
|
+
requestBody: {
|
|
623
|
+
message: {
|
|
624
|
+
raw: encodedMessage,
|
|
625
|
+
threadId: reply_to_message_id ? (await gmail.users.messages.get({ userId: 'me', id: reply_to_message_id, format: 'minimal' })).data.threadId || undefined : undefined,
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
const attNames = hasAttachments
|
|
630
|
+
? attachments.map(a => a.filename || path.basename(a.file_path))
|
|
631
|
+
: [];
|
|
632
|
+
return {
|
|
633
|
+
content: [{
|
|
634
|
+
type: 'text',
|
|
635
|
+
text: JSON.stringify({
|
|
636
|
+
success: true,
|
|
637
|
+
draftId: draft.data.id,
|
|
638
|
+
messageId: draft.data.message?.id,
|
|
639
|
+
to,
|
|
640
|
+
cc: cc || [],
|
|
641
|
+
subject,
|
|
642
|
+
attachments: attNames,
|
|
643
|
+
message: 'Draft created successfully. Open Gmail to review and send.',
|
|
644
|
+
}, null, 2),
|
|
645
|
+
}],
|
|
646
|
+
};
|
|
647
|
+
}
|
|
500
648
|
default:
|
|
501
649
|
return {
|
|
502
650
|
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
@@ -7,6 +7,13 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import initSqlJs from "sql.js";
|
|
10
|
+
|
|
11
|
+
// BlueLamp認証チェック
|
|
12
|
+
const TOKEN_FILE_PATH = join(homedir(), '.bluelamp-vscode', 'portal-token.enc');
|
|
13
|
+
if (!existsSync(TOKEN_FILE_PATH)) {
|
|
14
|
+
console.error('❌ BlueLamp認証が必要です。bluelampコマンドでログインしてください。');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
10
17
|
const ALGORITHM = "aes-256-gcm";
|
|
11
18
|
const VAULT_DIR = join(homedir(), ".musuhi");
|
|
12
19
|
const DB_PATH = join(VAULT_DIR, "vault.db");
|
package/lib/mcp-server/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const{Server:Server}=require("@modelcontextprotocol/sdk/server/index.js"),{StdioServerTransport:StdioServerTransport}=require("@modelcontextprotocol/sdk/server/stdio.js"),{CallToolRequestSchema:CallToolRequestSchema,ListToolsRequestSchema:ListToolsRequestSchema}=require("@modelcontextprotocol/sdk/types.js"),{getPromptFromPortal:getPromptFromPortal,listAvailableMcps:listAvailableMcps,getMcpInfo:getMcpInfo,callMcpTool:callMcpTool,deployUserMcp:deployUserMcp,listUserCreatedMcps:listUserCreatedMcps,deleteUserMcp:deleteUserMcp}=require("./portal-api-client.cjs"),{logger:logger}=require("../utils/logger"),{t:t,setLanguage:setLanguage}=require("../i18n"),path=require("path"),fs=require("fs"),os=require("os"),dotenvLocalPath=path.join(__dirname,"../../.env.local"),dotenvPath=path.join(__dirname,"../../.env");fs.existsSync(dotenvLocalPath)?(require("dotenv").config({path:dotenvLocalPath}),logger.debug("[MCP Server] Loaded .env.local")):fs.existsSync(dotenvPath)&&(require("dotenv").config({path:dotenvPath}),logger.debug("[MCP Server] Loaded .env"));const KEYWORD=process.env.BLUELAMP_KEYWORD||"BL-要件定義",TOKEN=process.env.BLUELAMP_TOKEN,PORTAL_URL=process.env.PORTAL_URL||"https://bluelamp-6clpzmy5pa-an.a.run.app",LANGUAGE=process.env.BLUELAMP_LANGUAGE||"ja";setLanguage(LANGUAGE);const LOG_DIR=path.join(os.homedir(),".bluelamp-vscode","logs"),LOG_FILE=path.join(LOG_DIR,"mcp.log");function log(e){const t=`[${(new Date).toISOString()}] ${e}\n`;fs.appendFileSync(LOG_FILE,t),logger.info(`[MCP Server] ${e}`)}fs.existsSync(LOG_DIR)||fs.mkdirSync(LOG_DIR,{recursive:!0}),log(`MCP Server started - Log file: ${LOG_FILE}`),log("Environment variables:"),log(` KEYWORD: ${KEYWORD}`),log(" TOKEN: "+(TOKEN?TOKEN.substring(0,20)+"...":"NOT SET")),log(` PORTAL_URL: ${PORTAL_URL}`),log(` LANGUAGE: ${LANGUAGE}`),log(` .env file exists: ${fs.existsSync(dotenvPath)}`);class BlueLampVSCodeServer{constructor(){this.server=new Server({name:"bluelamp",version:"1.0.0"},{capabilities:{tools:{}}}),this.setupHandlers()}setupHandlers(){this.server.setRequestHandler(ListToolsRequestSchema,async()=>{log("[MCP Server] ListTools request received");const e={type:"object",properties:{keyword:{type:"string",description:t("mcp.keywordDescription")}},required:["keyword"]};return{tools:[{name:"load_knowledge",description:t("mcp.toolDescriptionLoadKnowledge"),annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:e},{name:"list_available_mcps",description:t("mcp.toolDescriptionListMcps"),annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:{type:"object",properties:{category:{type:"string",enum:["productivity","development","communication","data","media","custom"],description:t("mcp.listMcpsCategoryDescription")},search:{type:"string",description:t("mcp.listMcpsSearchDescription")}},required:[]}},{name:"get_mcp_info",description:t("mcp.toolDescriptionGetMcpInfo"),annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:{type:"object",properties:{mcp_id:{type:"string",description:t("mcp.getMcpInfoIdDescription")}},required:["mcp_id"]}},{name:"call_tool",description:t("mcp.toolDescriptionCallTool"),annotations:{readOnlyHint:!1,idempotentHint:!1},inputSchema:{type:"object",properties:{mcp_id:{type:"string",description:t("mcp.callToolMcpIdDescription")},tool_name:{type:"string",description:t("mcp.callToolNameDescription")},arguments:{type:"object",description:t("mcp.callToolArgsDescription")}},required:["mcp_id","tool_name"]}},{name:"create_user_mcp",description:t("mcp.toolDescriptionCreateUserMcp"),annotations:{readOnlyHint:!1,idempotentHint:!1},inputSchema:{type:"object",properties:{name:{type:"string",description:t("mcp.createMcpNameDescription")},description:{type:"string",description:t("mcp.createMcpDescDescription")},tools:{type:"string",description:t("mcp.createMcpToolsDescription")},code:{type:"string",description:t("mcp.createMcpCodeDescription")},category:{type:"string",enum:["productivity","development","communication","data","media","custom"],description:t("mcp.createMcpCategoryDescription")},envVars:{type:"string",description:t("mcp.createMcpEnvVarsDescription")}},required:["name","description","code"]}},{name:"list_user_mcps",description:t("mcp.toolDescriptionListUserMcps"),annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:{type:"object",properties:{},required:[]}},{name:"delete_user_mcp",description:t("mcp.toolDescriptionDeleteUserMcp"),annotations:{readOnlyHint:!1,idempotentHint:!1},inputSchema:{type:"object",properties:{mcp_id:{type:"string",description:t("mcp.deleteUserMcpIdDescription")}},required:["mcp_id"]}}]}}),this.server.setRequestHandler(CallToolRequestSchema,async e=>{const{name:r}=e.params;if("load_knowledge"===r){const{keyword:r}=e.params.arguments;log(`[MCP Server] load_knowledge called with keyword: ${r}`),log("[MCP Server] TOKEN: "+(TOKEN?TOKEN.substring(0,20)+"...":"missing")),log(`[MCP Server] PORTAL_URL: ${PORTAL_URL}`),log(`[MCP Server] LANGUAGE: ${LANGUAGE}`);try{const e=await getPromptFromPortal(r,TOKEN,LANGUAGE);if(!e||!e.success){const o=e?.error||t("mcp.knowledgeNotFound",{keyword:r});return log(`[MCP Server] Knowledge loading failed: ${o}`),{content:[{type:"text",text:`❌ ${t("mcp.portalApiFailed",{error:o})}`}],isError:!0}}const o="ja"!==LANGUAGE?"\n\n[Note] The user's preferred language is English.":"";return{content:[{type:"text",text:`✅ ${t("mcp.knowledgeLoadSuccess")}\n\n${e.knowledge}${o}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n${t("mcp.knowledgeApplied")}`}]}}catch(e){return log(`[MCP Server] Exception in load_knowledge: ${e.message}`),{content:[{type:"text",text:`❌ ${t("mcp.knowledgeLoadError",{error:e.message})}`}],isError:!0}}}if("list_available_mcps"===r){const{category:r,search:o}=e.params.arguments||{};log(`[MCP Server] list_available_mcps called: category=${r}, search=${o}`);try{const e=await listAvailableMcps({category:r,search:o},TOKEN,LANGUAGE);if(!e||!e.success)return{content:[{type:"text",text:`❌ ${t("mcp.listMcpsFailed",{error:e?.error||"Unknown"})}`}],isError:!0};const n=e.data||[];let s=`📋 ${t("mcp.listMcpsTitle")}\n\n`;return 0===n.length?s+=t("mcp.listMcpsEmpty"):n.forEach(e=>{const t=e.tools?.length||0;s+=`**${e.name}** (${e.mcpId})\n`,s+=` ${e.description}\n`,s+=` カテゴリ: ${e.category} | ツール数: ${t} | アクセス: ${e.hasAccess?"✅":"❌"}\n\n`}),{content:[{type:"text",text:s}]}}catch(e){return log(`[MCP Server] list_available_mcps error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("get_mcp_info"===r){const{mcp_id:r}=e.params.arguments;log(`[MCP Server] get_mcp_info called: mcpId=${r}`);try{const e=await getMcpInfo(r,TOKEN,LANGUAGE);if(!e||!e.success)return{content:[{type:"text",text:`❌ ${t("mcp.getMcpInfoFailed",{error:e?.error||"Unknown"})}`}],isError:!0};const o=e.data;let n=`🔧 **${o.name}** (${o.mcpId})\n\n`;return n+=`${o.description}\n\n`,n+=`カテゴリ: ${o.category}\n`,n+=`アクセス: ${o.hasAccess?"利用可能":"利用不可"}\n`,n+=`ステータス: ${o.status}\n\n`,o.tools&&o.tools.length>0&&(n+=`### ツール一覧 (${o.tools.length})\n\n`,o.tools.forEach(e=>{n+=`- **${e.name}**: ${e.description}\n`,e.inputSchema?.properties&&Object.entries(e.inputSchema.properties).forEach(([e,t])=>{n+=` - \`${e}\` (${t.type||"any"}): ${t.description||""}\n`})})),o.requiredCredentials&&o.requiredCredentials.length>0&&(n+="\n### 必要な認証情報\n",o.requiredCredentials.forEach(e=>{n+=`- **${e.displayName}** (${e.key}): ${e.required?"必須":"任意"}\n`})),{content:[{type:"text",text:n}]}}catch(e){return log(`[MCP Server] get_mcp_info error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("call_tool"===r){const{mcp_id:r,tool_name:o,arguments:n}=e.params.arguments;log(`[MCP Server] call_tool called: mcpId=${r}, tool=${o}`);try{const e=await callMcpTool(r,o,n||{},TOKEN);if(!e||!e.success)return{content:[{type:"text",text:`❌ ${t("mcp.callToolFailed",{error:e?.error||"Unknown"})}`}],isError:!0};const s=e.data;return"string"==typeof s?{content:[{type:"text",text:s}]}:{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(e){return log(`[MCP Server] call_tool error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("create_user_mcp"===r){const r=e.params.arguments;log(`[MCP Server] create_user_mcp called: name=${r.name}`);try{let e=r.tools;if("string"==typeof e)try{e=JSON.parse(e)}catch(t){e=[]}let o=r.envVars;if("string"==typeof o)try{o=JSON.parse(o)}catch(e){o=void 0}const n={code:r.code,name:r.name,description:r.description||"",tools:e||[],category:r.category||"custom",envVars:o},s=await deployUserMcp(n,TOKEN);if(!s||!s.success)return{content:[{type:"text",text:`❌ ${s?.details?`${t("mcp.createMcpScanFailed")}\n${JSON.stringify(s.details,null,2)}`:t("mcp.createMcpFailed",{error:s?.error||"Unknown"})}`}],isError:!0};const c=s.data;return{content:[{type:"text",text:`✅ ${t("mcp.createMcpSuccess")}\n\n**MCP ID**: ${c.mcpId}\n**Endpoint**: ${c.endpoint}\n**有効化**: ${c.enabled?"自動有効化済み":"未有効化"}`}]}}catch(e){return log(`[MCP Server] create_user_mcp error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("list_user_mcps"===r){log("[MCP Server] list_user_mcps called");try{const e=await listUserCreatedMcps(TOKEN);if(!e||!e.success)return{content:[{type:"text",text:`❌ ${e?.error||"Unknown"}`}],isError:!0};const r=e.data?.mcps||[];let o=`📋 ${t("mcp.listUserMcpsTitle")}\n\n`;return 0===r.length?o+=t("mcp.listUserMcpsEmpty"):r.forEach(e=>{o+=`**${e.name}** (${e.mcpId})\n`,o+=` ${e.description||""}\n`,o+=` カテゴリ: ${e.category} | ツール数: ${e.toolCount||0} | ステータス: ${e.status}\n`,e.endpoint&&(o+=` エンドポイント: ${e.endpoint}\n`),o+="\n"}),{content:[{type:"text",text:o}]}}catch(e){return log(`[MCP Server] list_user_mcps error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("delete_user_mcp"===r){const{mcp_id:r}=e.params.arguments;log(`[MCP Server] delete_user_mcp called: mcpId=${r}`);try{const e=await deleteUserMcp(r,TOKEN);return e&&e.success?{content:[{type:"text",text:`✅ ${t("mcp.deleteUserMcpSuccess",{mcpId:r})}`}]}:{content:[{type:"text",text:`❌ ${t("mcp.deleteUserMcpFailed",{error:e?.error||"Unknown"})}`}],isError:!0}}catch(e){return log(`[MCP Server] delete_user_mcp error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}return{content:[{type:"text",text:`Unknown tool: ${e.params.name}`}],isError:!0}})}async run(){const e=new StdioServerTransport;if(await this.server.connect(e),logger.info("BlueLamp VSCode MCP server started"),KEYWORD&&TOKEN){log(`[MCP Server] Auto-loading knowledge for keyword: ${KEYWORD} (lang: ${LANGUAGE})`);const e=await getPromptFromPortal(KEYWORD,TOKEN,LANGUAGE);e&&e.success?log(`[MCP Server] Auto-load successful: ${e.title}`):log(`[MCP Server] Auto-load failed for keyword: ${KEYWORD}`)}}}const server=new BlueLampVSCodeServer;server.run().catch(e=>logger.error(e.message));
|
|
2
|
+
const{Server:Server}=require("@modelcontextprotocol/sdk/server/index.js"),{StdioServerTransport:StdioServerTransport}=require("@modelcontextprotocol/sdk/server/stdio.js"),{CallToolRequestSchema:CallToolRequestSchema,ListToolsRequestSchema:ListToolsRequestSchema}=require("@modelcontextprotocol/sdk/types.js"),{getPromptFromPortal:getPromptFromPortal}=require("./portal-api-client.cjs"),{logger:logger}=require("../utils/logger"),{t:t,setLanguage:setLanguage}=require("../i18n"),path=require("path"),fs=require("fs"),os=require("os"),dotenvLocalPath=path.join(__dirname,"../../.env.local"),dotenvPath=path.join(__dirname,"../../.env");fs.existsSync(dotenvLocalPath)?(require("dotenv").config({path:dotenvLocalPath}),logger.debug("[MCP Server] Loaded .env.local")):fs.existsSync(dotenvPath)&&(require("dotenv").config({path:dotenvPath}),logger.debug("[MCP Server] Loaded .env"));const KEYWORD=process.env.BLUELAMP_KEYWORD||"BL-要件定義",TOKEN=process.env.BLUELAMP_TOKEN,PORTAL_URL=process.env.PORTAL_URL||"https://bluelamp-6clpzmy5pa-an.a.run.app",LANGUAGE=process.env.BLUELAMP_LANGUAGE||"ja";setLanguage(LANGUAGE);const LOG_DIR=path.join(os.homedir(),".bluelamp-vscode","logs"),LOG_FILE=path.join(LOG_DIR,"mcp.log");function log(e){const r=`[${(new Date).toISOString()}] ${e}\n`;fs.appendFileSync(LOG_FILE,r),logger.info(`[MCP Server] ${e}`)}fs.existsSync(LOG_DIR)||fs.mkdirSync(LOG_DIR,{recursive:!0}),log(`MCP Server started - Log file: ${LOG_FILE}`),log("Environment variables:"),log(` KEYWORD: ${KEYWORD}`),log(" TOKEN: "+(TOKEN?TOKEN.substring(0,20)+"...":"NOT SET")),log(` PORTAL_URL: ${PORTAL_URL}`),log(` LANGUAGE: ${LANGUAGE}`),log(` .env file exists: ${fs.existsSync(dotenvPath)}`);class BlueLampVSCodeServer{constructor(){this.server=new Server({name:"bluelamp",version:"1.0.0"},{capabilities:{tools:{}}}),this.setupHandlers()}setupHandlers(){this.server.setRequestHandler(ListToolsRequestSchema,async()=>{log("[MCP Server] ListTools request received");const e={type:"object",properties:{keyword:{type:"string",description:t("mcp.keywordDescription")}},required:["keyword"]};return{tools:[{name:"load_knowledge",description:t("mcp.toolDescriptionLoadKnowledge"),annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:e}]}}),this.server.setRequestHandler(CallToolRequestSchema,async e=>{const{name:r}=e.params;if("load_knowledge"===r){const{keyword:r}=e.params.arguments;log(`[MCP Server] load_knowledge called with keyword: ${r}`),log("[MCP Server] TOKEN: "+(TOKEN?TOKEN.substring(0,20)+"...":"missing")),log(`[MCP Server] PORTAL_URL: ${PORTAL_URL}`),log(`[MCP Server] LANGUAGE: ${LANGUAGE}`);try{const e=await getPromptFromPortal(r,TOKEN,LANGUAGE);if(!e||!e.success){const o=e?.error||t("mcp.knowledgeNotFound",{keyword:r});return log(`[MCP Server] Knowledge loading failed: ${o}`),{content:[{type:"text",text:`❌ ${t("mcp.portalApiFailed",{error:o})}`}],isError:!0}}const o="ja"!==LANGUAGE?"\n\n[Note] The user's preferred language is English.":"";return{content:[{type:"text",text:`✅ ${t("mcp.knowledgeLoadSuccess")}\n\n${e.knowledge}${o}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n${t("mcp.knowledgeApplied")}`}]}}catch(e){return log(`[MCP Server] Exception in load_knowledge: ${e.message}`),{content:[{type:"text",text:`❌ ${t("mcp.knowledgeLoadError",{error:e.message})}`}],isError:!0}}}return{content:[{type:"text",text:`Unknown tool: ${e.params.name}`}],isError:!0}})}async run(){const e=new StdioServerTransport;if(await this.server.connect(e),logger.info("BlueLamp VSCode MCP server started"),KEYWORD&&TOKEN){log(`[MCP Server] Auto-loading knowledge for keyword: ${KEYWORD} (lang: ${LANGUAGE})`);const e=await getPromptFromPortal(KEYWORD,TOKEN,LANGUAGE);e&&e.success?log(`[MCP Server] Auto-load successful: ${e.title}`):log(`[MCP Server] Auto-load failed for keyword: ${KEYWORD}`)}}}const server=new BlueLampVSCodeServer;server.run().catch(e=>logger.error(e.message));
|