MCP Server
by mokemoke0821
Verified
// MCPサーバーとファイル操作モジュールの統合
// ファイル操作機能を既存のMCPサーバーに統合するための修正ファイル
// 拡張ファイル操作モジュールをインポート
const fileOperations = require('./file-operations');
const fileWatcher = require('./file-watcher');
const path = require('path');
const fs = require('fs');
// グローバル設定
let globalConfig = null;
// アクティブなウォッチャーのリスナー登録状況を追跡
const activeListeners = new Map();
// MCPサーバー起動時の初期化関数
async function initMCPFileOperations(config) {
try {
console.log('拡張ファイル操作モジュールを初期化しています...');
// config.jsonからの設定を使用して初期化
const fileOpConfig = config.fileOperations || {};
// グローバル設定を保存
globalConfig = config;
// セキュリティ設定
const securityConfig = {
allowedPaths: fileOpConfig.allowedPaths || [],
enablePathTraversalProtection:
fileOpConfig.security?.enablePathTraversalProtection !== undefined
? fileOpConfig.security.enablePathTraversalProtection
: true,
enableSymlinkProtection:
fileOpConfig.security?.enableSymlinkProtection !== undefined
? fileOpConfig.security.enableSymlinkProtection
: true,
maxPathLength:
fileOpConfig.security?.maxPathLength || 260,
whitelistedExtensions:
fileOpConfig.security?.whitelistedExtensions || ['*'],
blacklistedExtensions:
fileOpConfig.security?.blacklistedExtensions ||
['.exe', '.bat', '.cmd', '.ps1', '.sh', '.com']
};
// バックアップ設定
const backupConfig = {
enableBackups:
fileOpConfig.backup?.enableBackups !== undefined
? fileOpConfig.backup.enableBackups
: true,
backupDir:
fileOpConfig.backup?.backupDir || './backups',
backupRetentionDays:
fileOpConfig.backup?.backupRetentionDays || 7
};
// その他の設定
const otherConfig = {
maxFileSize: fileOpConfig.maxFileSize || 10485760,
tempDir: fileOpConfig.temp?.tempDir || './temp',
streamLargeFiles:
fileOpConfig.advanced?.streamLargeFiles !== undefined
? fileOpConfig.advanced.streamLargeFiles
: true,
streamThreshold:
fileOpConfig.advanced?.streamThreshold || 5242880,
detailedErrorMessages:
fileOpConfig.advanced?.detailedErrorMessages !== undefined
? fileOpConfig.advanced.detailedErrorMessages
: true,
logAllOperations:
fileOpConfig.advanced?.logAllOperations !== undefined
? fileOpConfig.advanced.logAllOperations
: true
};
// ファイル監視設定の更新
const watcherConfig = {
usePolling: fileOpConfig.watcher?.usePolling !== undefined
? fileOpConfig.watcher.usePolling
: process.platform === 'win32',
pollingInterval: fileOpConfig.watcher?.pollingInterval || 5000,
maxWatchers: fileOpConfig.watcher?.maxWatchers || 50,
recursive: fileOpConfig.watcher?.recursive !== undefined
? fileOpConfig.watcher.recursive
: true,
persistent: fileOpConfig.watcher?.persistent !== undefined
? fileOpConfig.watcher.persistent
: true
};
// ファイル監視設定を更新
fileWatcher.updateWatchConfig(watcherConfig);
// モジュールの初期化
const initResult = await fileOperations.initialize({
security: securityConfig,
utils: {
...backupConfig,
tempDir: otherConfig.tempDir
},
maxFileSize: otherConfig.maxFileSize,
logFileOperations: otherConfig.logAllOperations,
detailedErrorMessages: otherConfig.detailedErrorMessages
});
if (initResult.success) {
console.log('拡張ファイル操作モジュールが正常に初期化されました');
// バックアップディレクトリとテンポラリディレクトリの作成
ensureDirectoryExists(backupConfig.backupDir);
ensureDirectoryExists(otherConfig.tempDir);
return true;
} else {
console.error(`拡張ファイル操作モジュールの初期化に失敗しました: ${initResult.error}`);
return false;
}
} catch (err) {
console.error(`拡張ファイル操作モジュールの初期化中にエラーが発生しました: ${err.message}`);
return false;
}
}
// ディレクトリが存在することを確認する補助関数
function ensureDirectoryExists(dirPath) {
try {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
console.log(`ディレクトリを作成しました: ${dirPath}`);
}
} catch (err) {
console.error(`ディレクトリの作成に失敗しました: ${dirPath}, エラー: ${err.message}`);
}
}
// エラーレスポンスを標準化する関数
function createErrorResponse(error, operation) {
const errorResponse = {
success: false,
operation,
error: error.message || '不明なエラー',
timestamp: new Date().toISOString()
};
if (globalConfig?.fileOperations?.advanced?.detailedErrorMessages) {
errorResponse.details = {
stack: error.stack,
code: error.code
};
}
return errorResponse;
}
// 成功レスポンスを標準化する関数
function createSuccessResponse(data, operation) {
return {
success: true,
operation,
data,
timestamp: new Date().toISOString()
};
}
// MCP Tools の統合関数
function setupMCPFileTools(server) {
if (!server) {
console.error('MCPサーバーインスタンスが提供されていません');
return false;
}
// 拡張ファイル読み取りツール
server.tool("extendedReadFile", "拡張ファイル読み取り機能", {
filePath: String,
options: {
encoding: String,
offset: Number,
limit: Number,
asBase64: Boolean
}
}, async ({ filePath, options = {} }) => {
try {
const result = await fileOperations.readFile(filePath, options);
if (result.success) {
return {
content: [{ type: "text", text: result.data }]
};
} else {
return {
content: [{ type: "text", text: `ファイル読み取りエラー: ${result.error}` }],
isError: true
};
}
} catch (error) {
return {
content: [{
type: "text",
text: `予期せぬエラー: ${error.message}`
}],
isError: true
};
}
});
// 拡張ファイル書き込みツール
server.tool("extendedWriteFile", "拡張ファイル書き込み機能", {
filePath: String,
content: String,
options: {
encoding: String,
append: Boolean,
createBackup: Boolean
}
}, async ({ filePath, content, options = {} }) => {
try {
const result = await fileOperations.writeFile(filePath, content, options);
if (result.success) {
return {
content: [{ type: "text", text: `ファイル ${filePath} は正常に書き込まれました` }]
};
} else {
return {
content: [{ type: "text", text: `ファイル書き込みエラー: ${result.error}` }],
isError: true
};
}
} catch (error) {
return {
content: [{
type: "text",
text: `予期せぬエラー: ${error.message}`
}],
isError: true
};
}
});
// ディレクトリ一覧ツール
server.tool("extendedListDirectory", "ディレクトリ内のファイル・フォルダ一覧表示", {
directoryPath: String,
options: {
recursive: Boolean,
includeHidden: Boolean,
pattern: String
}
}, async ({ directoryPath, options = {} }) => {
try {
const result = await fileOperations.listDirectory(directoryPath, options);
if (result.success) {
return {
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }]
};
} else {
return {
content: [{ type: "text", text: `ディレクトリ一覧エラー: ${result.error}` }],
isError: true
};
}
} catch (error) {
return {
content: [{
type: "text",
text: `予期せぬエラー: ${error.message}`
}],
isError: true
};
}
});
// ファイル情報取得ツール
server.tool("getFileInfo", "ファイル情報の詳細を取得", {
filePath: String,
options: {
resolveSymlinks: Boolean
}
}, async ({ filePath, options = {} }) => {
try {
const result = await fileOperations.getFileInfo(filePath, options);
if (result.success) {
return {
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }]
};
} else {
return {
content: [{ type: "text", text: `ファイル情報取得エラー: ${result.error}` }],
isError: true
};
}
} catch (error) {
return {
content: [{
type: "text",
text: `予期せぬエラー: ${error.message}`
}],
isError: true
};
}
});
// ファイルコピーツール
server.tool("copyFile", "ファイルコピー", {
sourcePath: String,
destinationPath: String,
options: {
overwrite: Boolean,
createBackup: Boolean
}
}, async ({ sourcePath, destinationPath, options = {} }) => {
try {
const result = await fileOperations.copyFile(sourcePath, destinationPath, options);
if (result.success) {
return {
content: [{ type: "text", text: `ファイルが正常にコピーされました: ${sourcePath} → ${destinationPath}` }]
};
} else {
return {
content: [{ type: "text", text: `ファイルコピーエラー: ${result.error}` }],
isError: true
};
}
} catch (error) {
return {
content: [{
type: "text",
text: `予期せぬエラー: ${error.message}`
}],
isError: true
};
}
});
// ファイル移動ツール
server.tool("moveFile", "ファイル移動", {
sourcePath: String,
destinationPath: String,
options: {
overwrite: Boolean,
createBackup: Boolean
}
}, async ({ sourcePath, destinationPath, options = {} }) => {
try {
const result = await fileOperations.moveFile(sourcePath, destinationPath, options);
if (result.success) {
return {
content: [{ type: "text", text: `ファイルが正常に移動されました: ${sourcePath} → ${destinationPath}` }]
};
} else {
return {
content: [{ type: "text", text: `ファイル移動エラー: ${result.error}` }],
isError: true
};
}
} catch (error) {
return {
content: [{
type: "text",
text: `予期せぬエラー: ${error.message}`
}],
isError: true
};
}
});
// ファイル削除ツール
server.tool("deleteFile", "ファイル削除", {
filePath: String,
options: {
force: Boolean,
createBackup: Boolean,
recursive: Boolean
}
}, async ({ filePath, options = {} }) => {
try {
const result = await fileOperations.deleteFile(filePath, options);
if (result.success) {
return {
content: [{ type: "text", text: `ファイルが正常に削除されました: ${filePath}` }]
};
} else {
return {
content: [{ type: "text", text: `ファイル削除エラー: ${result.error}` }],
isError: true
};
}
} catch (error) {
return {
content: [{
type: "text",
text: `予期せぬエラー: ${error.message}`
}],
isError: true
};
}
});
// === ファイル監視ツール ===
// ファイル監視開始ツール
server.tool("watchPath", "ファイルやディレクトリの変更を監視", {
targetPath: String,
options: {
recursive: Boolean,
usePolling: Boolean,
pollingInterval: Number,
persistent: Boolean
}
}, async ({ targetPath, options = {} }) => {
try {
const watcherId = await fileWatcher.watchPath(targetPath, options);
return {
content: [{
type: "text",
text: `ファイル監視を開始しました: ${targetPath} (監視ID: ${watcherId})`
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `ファイル監視開始エラー: ${error.message}`
}],
isError: true
};
}
});
// ファイル監視停止ツール
server.tool("unwatchPath", "ファイル監視を停止", {
watcherId: String
}, async ({ watcherId }) => {
try {
const result = fileWatcher.unwatchPath(watcherId);
if (result) {
// リスナー登録の削除
if (activeListeners.has(watcherId)) {
const listeners = activeListeners.get(watcherId);
for (const { event, listener } of listeners) {
fileWatcher.off(event, listener);
}
activeListeners.delete(watcherId);
}
return {
content: [{ type: "text", text: `ファイル監視を停止しました (監視ID: ${watcherId})` }]
};
} else {
return {
content: [{ type: "text", text: `指定された監視IDは存在しません: ${watcherId}` }],
isError: true
};
}
} catch (error) {
return {
content: [{
type: "text",
text: `ファイル監視停止エラー: ${error.message}`
}],
isError: true
};
}
});
// 全てのファイル監視を停止ツール
server.tool("unwatchAll", "全てのファイル監視を停止", {}, async () => {
try {
const count = fileWatcher.unwatchAll();
// 全てのリスナー登録を削除
for (const [watcherId, listeners] of activeListeners.entries()) {
for (const { event, listener } of listeners) {
fileWatcher.off(event, listener);
}
}
activeListeners.clear();
return {
content: [{ type: "text", text: `${count}件のファイル監視を停止しました` }]
};
} catch (error) {
return {
content: [{
type: "text",
text: `ファイル監視停止エラー: ${error.message}`
}],
isError: true
};
}
});
// アクティブなファイル監視一覧ツール
server.tool("listWatchers", "アクティブなファイル監視の一覧を取得", {}, async () => {
try {
const watchers = fileWatcher.listWatchers();
return {
content: [{ type: "text", text: JSON.stringify(watchers, null, 2) }]
};
} catch (error) {
return {
content: [{
type: "text",
text: `ファイル監視一覧取得エラー: ${error.message}`
}],
isError: true
};
}
});
// ファイル監視イベントリスナー登録ツール
server.tool("addWatchListener", "ファイル監視イベントリスナーを登録", {
watcherId: String,
events: [String],
callbackUrl: String
}, async ({ watcherId, events, callbackUrl }) => {
try {
// 監視IDの存在確認
const watchers = fileWatcher.listWatchers();
const watcherExists = watchers.some(w => w.watcherId === watcherId);
if (!watcherExists) {
return {
content: [{ type: "text", text: `指定された監視IDは存在しません: ${watcherId}` }],
isError: true
};
}
// イベントの検証
const validEvents = events.filter(event =>
fileWatcher.supportedEvents.includes(event) ||
event === 'watcher-start' ||
event === 'watcher-stop'
);
if (validEvents.length === 0) {
return {
content: [{ type: "text", text: `有効なイベントが指定されていません。サポートされているイベント: ${fileWatcher.supportedEvents.join(', ')}, watcher-start, watcher-stop` }],
isError: true
};
}
// リスナーのセットアップ
const listeners = [];
for (const event of validEvents) {
const listener = (eventData) => {
// ここでWebhookを呼び出す処理を実装
// 実際の実装では、callbackUrlにPOSTリクエストを送信する
console.log(`イベント ${event} が発生しました: ${JSON.stringify(eventData)}`);
// 本来はWebhookを呼び出すコード
// fetch(callbackUrl, {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ event, data: eventData, watcherId })
// });
};
fileWatcher.on(event, listener);
listeners.push({ event, listener });
}
// リスナーを保存
if (activeListeners.has(watcherId)) {
activeListeners.get(watcherId).push(...listeners);
} else {
activeListeners.set(watcherId, listeners);
}
return {
content: [{ type: "text", text: `ファイル監視イベントリスナーを登録しました: ${validEvents.join(', ')} (監視ID: ${watcherId})` }]
};
} catch (error) {
return {
content: [{
type: "text",
text: `イベントリスナー登録エラー: ${error.message}`
}],
isError: true
};
}
});
// ファイル監視イベントリスナー削除ツール
server.tool("removeWatchListener", "ファイル監視イベントリスナーを削除", {
watcherId: String,
events: [String]
}, async ({ watcherId, events }) => {
try {
if (!activeListeners.has(watcherId)) {
return {
content: [{ type: "text", text: `指定された監視IDのリスナーは登録されていません: ${watcherId}` }],
isError: true
};
}
const listeners = activeListeners.get(watcherId);
const removedEvents = [];
if (events && events.length > 0) {
// 特定のイベントのみ削除
const remainingListeners = [];
for (const listener of listeners) {
if (events.includes(listener.event)) {
fileWatcher.off(listener.event, listener.listener);
removedEvents.push(listener.event);
} else {
remainingListeners.push(listener);
}
}
if (remainingListeners.length > 0) {
activeListeners.set(watcherId, remainingListeners);
} else {
activeListeners.delete(watcherId);
}
} else {
// 全てのイベントを削除
for (const listener of listeners) {
fileWatcher.off(listener.event, listener.listener);
removedEvents.push(listener.event);
}
activeListeners.delete(watcherId);
}
return {
content: [{ type: "text", text: `ファイル監視イベントリスナーを削除しました: ${removedEvents.join(', ')} (監視ID: ${watcherId})` }]
};
} catch (error) {
return {
content: [{
type: "text",
text: `イベントリスナー削除エラー: ${error.message}`
}],
isError: true
};
}
});
console.log('拡張ファイル操作ツールとファイル監視ツールがMCPサーバーに正常に統合されました');
return true;
}
// エクスポート
module.exports = {
initMCPFileOperations,
setupMCPFileTools,
createErrorResponse,
createSuccessResponse
};