poker-mcp 1.0.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 +241 -0
- package/config/mcp-manifest.json +1713 -0
- package/config/package-lock.json +1335 -0
- package/config/poker_mcp.code-workspace +8 -0
- package/package.json +51 -0
- package/src/config/ConfigManager.js +120 -0
- package/src/config/default.json +62 -0
- package/src/mcp/handlers/bodyHandlers.js +63 -0
- package/src/mcp/handlers/buildupFactorHandlers.js +43 -0
- package/src/mcp/handlers/calculationHandlers.js +265 -0
- package/src/mcp/handlers/detectorHandlers.js +201 -0
- package/src/mcp/handlers/index.js +43 -0
- package/src/mcp/handlers/sourceHandlers.js +25 -0
- package/src/mcp/handlers/transformHandlers.js +26 -0
- package/src/mcp/handlers/unitHandlers.js +305 -0
- package/src/mcp/handlers/zoneHandlers.js +83 -0
- package/src/mcp/middleware/errorHandler.js +49 -0
- package/src/mcp/middleware/requestValidator.js +129 -0
- package/src/mcp/server.js +76 -0
- package/src/mcp/tools/bodyTools.js +185 -0
- package/src/mcp/tools/buildupFactorTools.js +82 -0
- package/src/mcp/tools/calculationTools.js +58 -0
- package/src/mcp/tools/commonTools.js +22 -0
- package/src/mcp/tools/detectorTools.js +131 -0
- package/src/mcp/tools/index.js +34 -0
- package/src/mcp/tools/sourceTools.js +726 -0
- package/src/mcp/tools/sourceTools_fixed_part2.js +41 -0
- package/src/mcp/tools/transformTools.js +73 -0
- package/src/mcp/tools/unitTools.js +137 -0
- package/src/mcp/tools/zoneTools.js +65 -0
- package/src/mcp_server_stdio_v4.js +45 -0
- package/src/routes/restApi.js +184 -0
- package/src/services/CalculationService.js +525 -0
- package/src/services/DataManager.js +738 -0
- package/src/services/TaskManager.js +1622 -0
- package/src/utils/MaterialAlternatives.js +189 -0
- package/src/utils/errorHandler.js +68 -0
- package/src/utils/errors.js +37 -0
- package/src/utils/logger.js +24 -0
- package/src/utils/logger_no_color.js +28 -0
- package/src/utils/mcpErrors.js +225 -0
- package/src/validators/CMBValidator.js +191 -0
- package/src/validators/DetectorValidator.js +512 -0
- package/src/validators/ManifestValidator.js +249 -0
- package/src/validators/NuclideValidator.js +171 -0
- package/src/validators/PhysicsValidator.js +401 -0
- package/src/validators/PhysicsValidatorSimple.js +87 -0
- package/src/validators/SourceValidator.js +515 -0
- package/src/validators/TransformValidator.js +472 -0
- package/src/validators/UnitValidator.js +562 -0
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "poker-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "YAML-based input file management tool for radiation shielding calculation code POKER with MCP support",
|
|
5
|
+
"main": "src/mcp_server_stdio_v4.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"poker-mcp": "src/mcp_server_stdio_v4.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/mcp_server_stdio_v4.js",
|
|
12
|
+
"dev": "nodemon src/mcp_server_stdio_v4.js",
|
|
13
|
+
"prepare": "echo 'NPX ready: npx poker-mcp'"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"model-context-protocol",
|
|
18
|
+
"yaml",
|
|
19
|
+
"radiation",
|
|
20
|
+
"shielding",
|
|
21
|
+
"nuclear",
|
|
22
|
+
"radioisotope",
|
|
23
|
+
"medical",
|
|
24
|
+
"accelerator",
|
|
25
|
+
"POKER",
|
|
26
|
+
"physics",
|
|
27
|
+
"validation",
|
|
28
|
+
"npx",
|
|
29
|
+
"point-kernel"
|
|
30
|
+
],
|
|
31
|
+
"author": "Yoshihiro Hirao <tora@pointkernel.com>",
|
|
32
|
+
"license": "ISC",
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18.0.0"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.7.0",
|
|
38
|
+
"content-type": "^1.0.5",
|
|
39
|
+
"cors": "^2.8.5",
|
|
40
|
+
"express": "^5.0.1",
|
|
41
|
+
"js-yaml": "^4.1.0",
|
|
42
|
+
"raw-body": "^3.0.0",
|
|
43
|
+
"winston": "^3.17.0",
|
|
44
|
+
"zod": "^3.24.2"
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"src/",
|
|
48
|
+
"config/",
|
|
49
|
+
"README.md"
|
|
50
|
+
]
|
|
51
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// src/config/ConfigManager.js
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { logger } from '../utils/logger.js';
|
|
5
|
+
import { DataError } from '../utils/errors.js';
|
|
6
|
+
|
|
7
|
+
export class ConfigManager {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.config = null;
|
|
10
|
+
this.configPath = 'src/config/default.json';
|
|
11
|
+
this.userConfigPath = 'config.json';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async load() {
|
|
15
|
+
try {
|
|
16
|
+
// デフォルト設定を読み込み
|
|
17
|
+
const defaultConfig = await this.loadJsonFile(this.configPath);
|
|
18
|
+
|
|
19
|
+
// ユーザー設定があれば読み込んでマージ
|
|
20
|
+
let userConfig = {};
|
|
21
|
+
try {
|
|
22
|
+
userConfig = await this.loadJsonFile(this.userConfigPath);
|
|
23
|
+
logger.info('ユーザー設定をロードしました', { path: this.userConfigPath });
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (error.code !== 'ENOENT') {
|
|
26
|
+
logger.warn('ユーザー設定の読み込みに失敗しました', { error: error.message });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 環境変数でオーバーライド
|
|
31
|
+
this.config = this.mergeConfigs(defaultConfig, userConfig);
|
|
32
|
+
this.applyEnvironmentOverrides();
|
|
33
|
+
|
|
34
|
+
logger.info('設定を読み込みました', {
|
|
35
|
+
port: this.config.server.port,
|
|
36
|
+
logLevel: this.config.logging.level
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
} catch (error) {
|
|
40
|
+
logger.error('設定の読み込みに失敗しました', { error: error.message });
|
|
41
|
+
throw new DataError('設定の読み込みに失敗しました', 'CONFIG_LOAD');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async loadJsonFile(filePath) {
|
|
46
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
47
|
+
return JSON.parse(content);
|
|
48
|
+
}
|
|
49
|
+
mergeConfigs(defaultConfig, userConfig) {
|
|
50
|
+
return this.deepMerge(defaultConfig, userConfig);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
deepMerge(target, source) {
|
|
54
|
+
const result = { ...target };
|
|
55
|
+
|
|
56
|
+
for (const key in source) {
|
|
57
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
58
|
+
result[key] = this.deepMerge(target[key] || {}, source[key]);
|
|
59
|
+
} else {
|
|
60
|
+
result[key] = source[key];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
applyEnvironmentOverrides() {
|
|
68
|
+
// 環境変数による設定オーバーライド
|
|
69
|
+
if (process.env.PORT) {
|
|
70
|
+
this.config.server.port = parseInt(process.env.PORT);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (process.env.LOG_LEVEL) {
|
|
74
|
+
this.config.logging.level = process.env.LOG_LEVEL;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (process.env.MAX_BACKUPS) {
|
|
78
|
+
this.config.data.maxBackups = parseInt(process.env.MAX_BACKUPS);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
get(path, defaultValue = undefined) {
|
|
83
|
+
return this.getByPath(this.config, path, defaultValue);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getByPath(obj, path, defaultValue) {
|
|
87
|
+
const keys = path.split('.');
|
|
88
|
+
let result = obj;
|
|
89
|
+
|
|
90
|
+
for (const key of keys) {
|
|
91
|
+
if (result && typeof result === 'object' && key in result) {
|
|
92
|
+
result = result[key];
|
|
93
|
+
} else {
|
|
94
|
+
return defaultValue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// よく使う設定のゲッター
|
|
102
|
+
getServerConfig() {
|
|
103
|
+
return this.config.server;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getDataConfig() {
|
|
107
|
+
return this.config.data;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
getLoggingConfig() {
|
|
111
|
+
return this.config.logging;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
getMaterialProperties() {
|
|
115
|
+
return this.config.physics.materials;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// シングルトンインスタンス
|
|
120
|
+
export const configManager = new ConfigManager();
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"server": {
|
|
3
|
+
"port": 3001,
|
|
4
|
+
"host": "localhost",
|
|
5
|
+
"cors": {
|
|
6
|
+
"enabled": true,
|
|
7
|
+
"origins": ["*"]
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"data": {
|
|
11
|
+
"yamlFile": "tasks/poker.yaml",
|
|
12
|
+
"pendingFile": "tasks/pending_changes.json",
|
|
13
|
+
"backupDir": "backups",
|
|
14
|
+
"maxBackups": 10
|
|
15
|
+
},
|
|
16
|
+
"logging": {
|
|
17
|
+
"level": "info",
|
|
18
|
+
"maxFiles": 5,
|
|
19
|
+
"maxSize": "10m",
|
|
20
|
+
"datePattern": "YYYY-MM-DD"
|
|
21
|
+
},
|
|
22
|
+
"physics": {
|
|
23
|
+
"materials": {
|
|
24
|
+
"Concrete": {
|
|
25
|
+
"densityRange": { "min": 1.8, "max": 2.5 },
|
|
26
|
+
"type": "structural",
|
|
27
|
+
"description": "一般的なコンクリート",
|
|
28
|
+
"defaultDensity": 2.1
|
|
29
|
+
},
|
|
30
|
+
"Heavy_concrete_T": {
|
|
31
|
+
"densityRange": { "min": 3.5, "max": 4.2 },
|
|
32
|
+
"type": "shielding",
|
|
33
|
+
"description": "重コンクリート(タングステン系)",
|
|
34
|
+
"defaultDensity": 3.86
|
|
35
|
+
},
|
|
36
|
+
"Iron": {
|
|
37
|
+
"densityRange": { "min": 7.6, "max": 7.9 },
|
|
38
|
+
"type": "metal",
|
|
39
|
+
"description": "鉄",
|
|
40
|
+
"defaultDensity": 7.8
|
|
41
|
+
},
|
|
42
|
+
"Lead": {
|
|
43
|
+
"densityRange": { "min": 11.0, "max": 11.4 },
|
|
44
|
+
"type": "heavy_metal",
|
|
45
|
+
"description": "鉛",
|
|
46
|
+
"defaultDensity": 11.0
|
|
47
|
+
},
|
|
48
|
+
"Water": {
|
|
49
|
+
"densityRange": { "min": 0.95, "max": 1.05 },
|
|
50
|
+
"type": "liquid",
|
|
51
|
+
"description": "水",
|
|
52
|
+
"defaultDensity": 1.0
|
|
53
|
+
},
|
|
54
|
+
"Air": {
|
|
55
|
+
"densityRange": { "min": 0.001, "max": 0.002 },
|
|
56
|
+
"type": "gas",
|
|
57
|
+
"description": "空気",
|
|
58
|
+
"defaultDensity": 0.001205
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// mcp/handlers/bodyHandlers.js
|
|
2
|
+
import { validateBodyRequest } from '../middleware/requestValidator.js';
|
|
3
|
+
import { ValidationError } from '../../utils/errors.js';
|
|
4
|
+
import { logger } from '../../utils/logger.js';
|
|
5
|
+
|
|
6
|
+
export function createBodyHandlers(taskManager) {
|
|
7
|
+
return {
|
|
8
|
+
async proposeBody(args) {
|
|
9
|
+
try {
|
|
10
|
+
validateBodyRequest(args);
|
|
11
|
+
const result = await taskManager.proposeBody(args.name, args.type, args);
|
|
12
|
+
return { success: true, message: result };
|
|
13
|
+
} catch (error) {
|
|
14
|
+
logger.error('proposeBodyハンドラーエラー', { args, error: error.message });
|
|
15
|
+
|
|
16
|
+
// CMB専用エラーハンドリング
|
|
17
|
+
if (error.message && (
|
|
18
|
+
error.message.includes('演算式で参照されている立体が存在しません') ||
|
|
19
|
+
error.message.includes('循環参照が検出されました') ||
|
|
20
|
+
error.message.includes('禁止された演算子') ||
|
|
21
|
+
error.message.includes('括弧が正しく対応していません') ||
|
|
22
|
+
error.message.includes('CMB演算式は空にできません') ||
|
|
23
|
+
error.message.includes('参照が複雑すぎます')
|
|
24
|
+
)) {
|
|
25
|
+
return {
|
|
26
|
+
success: false,
|
|
27
|
+
error: error.message,
|
|
28
|
+
details: {
|
|
29
|
+
field: error.field || 'expression',
|
|
30
|
+
value: error.value,
|
|
31
|
+
type: 'CMB_VALIDATION_ERROR'
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
async updateBody(args) {
|
|
41
|
+
try {
|
|
42
|
+
if (!args.name) throw new ValidationError('立体名は必須です', 'name', args.name);
|
|
43
|
+
const { name, ...updates } = args;
|
|
44
|
+
const result = await taskManager.updateBody(name, updates);
|
|
45
|
+
return { success: true, message: result };
|
|
46
|
+
} catch (error) {
|
|
47
|
+
logger.error('updateBodyハンドラーエラー', { args, error: error.message });
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
async deleteBody(args) {
|
|
53
|
+
try {
|
|
54
|
+
if (!args.name) throw new ValidationError('立体名は必須です', 'name', args.name);
|
|
55
|
+
const result = await taskManager.deleteBody(args.name);
|
|
56
|
+
return { success: true, message: result };
|
|
57
|
+
} catch (error) {
|
|
58
|
+
logger.error('deleteBodyハンドラーエラー', { args, error: error.message });
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// mcp/handlers/buildupFactorHandlers.js
|
|
2
|
+
import { validateBuildupFactorRequest } from '../middleware/requestValidator.js';
|
|
3
|
+
import { ValidationError } from '../../utils/errors.js';
|
|
4
|
+
|
|
5
|
+
export function createBuildupFactorHandlers(taskManager) {
|
|
6
|
+
return {
|
|
7
|
+
async proposeBuildupFactor(args) {
|
|
8
|
+
validateBuildupFactorRequest(args);
|
|
9
|
+
|
|
10
|
+
// 必須パラメータとして扱い
|
|
11
|
+
if (args.use_slant_correction === undefined || args.use_finite_medium_correction === undefined) {
|
|
12
|
+
throw new ValidationError('use_slant_correction と use_finite_medium_correction は必須パラメータです');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const result = await taskManager.proposeBuildupFactor(
|
|
16
|
+
args.material,
|
|
17
|
+
args.use_slant_correction,
|
|
18
|
+
args.use_finite_medium_correction
|
|
19
|
+
);
|
|
20
|
+
return { success: true, message: result };
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
async updateBuildupFactor(args) {
|
|
24
|
+
if (!args.material) throw new ValidationError('材料名は必須です', 'material', args.material);
|
|
25
|
+
const { material, ...updates } = args;
|
|
26
|
+
const result = await taskManager.updateBuildupFactor(material, updates);
|
|
27
|
+
return { success: true, message: result };
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
async deleteBuildupFactor(args) {
|
|
31
|
+
if (!args.material) throw new ValidationError('材料名は必須です', 'material', args.material);
|
|
32
|
+
const result = await taskManager.deleteBuildupFactor(args.material);
|
|
33
|
+
return { success: true, message: result };
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
async changeOrderBuildupFactor(args) {
|
|
37
|
+
if (!args.material) throw new ValidationError('材料名は必須です', 'material', args.material);
|
|
38
|
+
if (typeof args.newIndex !== 'number') throw new ValidationError('新しいインデックスは数値です', 'newIndex', args.newIndex);
|
|
39
|
+
const result = await taskManager.changeOrderBuildupFactor(args.material, args.newIndex);
|
|
40
|
+
return { success: true, message: result };
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// mcp/handlers/calculationHandlers.js
|
|
2
|
+
import { CalculationService } from '../../services/CalculationService.js';
|
|
3
|
+
import { ValidationError, CalculationError } from '../../utils/errors.js';
|
|
4
|
+
import { logger } from '../../utils/logger.js';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
export function createCalculationHandlers(taskManager) {
|
|
8
|
+
const calculationService = new CalculationService();
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
async executeCalculation(args) {
|
|
12
|
+
try {
|
|
13
|
+
// 引数検証
|
|
14
|
+
if (!args.yaml_file) {
|
|
15
|
+
throw new ValidationError('YAML file is required', 'yaml_file', args.yaml_file);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 絶対パス形式の事前チェック(詳細検証はサービス層で実行)
|
|
19
|
+
const absolutePathPattern = /^([a-zA-Z]:[\\\/]|\/)/;
|
|
20
|
+
if (!absolutePathPattern.test(args.yaml_file)) {
|
|
21
|
+
throw new ValidationError(
|
|
22
|
+
'YAML file must be specified with absolute path. Examples: C:\\path\\to\\file.yaml or /path/to/file.yaml',
|
|
23
|
+
'yaml_file',
|
|
24
|
+
args.yaml_file
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// YAML拡張子チェック
|
|
29
|
+
if (!args.yaml_file.match(/\.(yaml|yml)$/i)) {
|
|
30
|
+
throw new ValidationError(
|
|
31
|
+
'YAML file must have .yaml or .yml extension',
|
|
32
|
+
'yaml_file',
|
|
33
|
+
args.yaml_file
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// サマリーオプション検証
|
|
38
|
+
const summaryOptions = args.summary_options || {};
|
|
39
|
+
if (summaryOptions.show_source_data === false &&
|
|
40
|
+
summaryOptions.show_total_dose === false) {
|
|
41
|
+
logger.warn('Both show_source_data and show_total_dose are false, this may result in minimal output');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 出力ファイルの自動パス解決
|
|
45
|
+
const outputFiles = args.output_files || {};
|
|
46
|
+
const resolvedOutputFiles = {};
|
|
47
|
+
|
|
48
|
+
if (Object.keys(outputFiles).length > 0) {
|
|
49
|
+
// 入力YAMLファイルのディレクトリを取得
|
|
50
|
+
const inputDir = path.dirname(args.yaml_file);
|
|
51
|
+
|
|
52
|
+
// 各出力ファイルの絶対パス化
|
|
53
|
+
for (const [key, fileName] of Object.entries(outputFiles)) {
|
|
54
|
+
if (fileName) {
|
|
55
|
+
// ファイル名の拡張子チェック
|
|
56
|
+
if (!fileName.match(/\.(yaml|yml)$/i)) {
|
|
57
|
+
throw new ValidationError(
|
|
58
|
+
`Output file ${key} must have .yaml or .yml extension`,
|
|
59
|
+
`output_files.${key}`,
|
|
60
|
+
fileName
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 絶対パスに解決
|
|
65
|
+
resolvedOutputFiles[key] = path.join(inputDir, fileName);
|
|
66
|
+
|
|
67
|
+
logger.debug('Output file path resolved', {
|
|
68
|
+
key,
|
|
69
|
+
fileName,
|
|
70
|
+
inputDir,
|
|
71
|
+
resolvedPath: resolvedOutputFiles[key]
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 出力ファイル検証(従来の検証は不要、上記で実施済み)
|
|
78
|
+
|
|
79
|
+
logger.info('Starting radiation shielding calculation', {
|
|
80
|
+
yamlFile: args.yaml_file,
|
|
81
|
+
summaryOptions,
|
|
82
|
+
outputFiles: outputFiles,
|
|
83
|
+
resolvedOutputFiles
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// 計算実行(絶対パス化済みの出力ファイルパスを使用)
|
|
87
|
+
const result = await calculationService.executeCalculation(
|
|
88
|
+
args.yaml_file,
|
|
89
|
+
summaryOptions,
|
|
90
|
+
resolvedOutputFiles // 絶対パス化済み
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// 出力ファイル検証(絶対パス化済みのパスを使用)
|
|
94
|
+
const fileVerification = await calculationService.verifyOutputFiles(resolvedOutputFiles);
|
|
95
|
+
|
|
96
|
+
// 成功レスポンス
|
|
97
|
+
const response = {
|
|
98
|
+
success: true,
|
|
99
|
+
message: 'Radiation shielding calculation completed successfully',
|
|
100
|
+
calculation: {
|
|
101
|
+
input_file: args.yaml_file,
|
|
102
|
+
execution_time_ms: result.executionTime,
|
|
103
|
+
timestamp: result.summary.timestamp,
|
|
104
|
+
options: result.summary.options
|
|
105
|
+
},
|
|
106
|
+
outputs: {
|
|
107
|
+
console: {
|
|
108
|
+
stdout: result.outputs.stdout ? 'Available (see details)' : 'Empty',
|
|
109
|
+
stderr: result.outputs.stderr ? 'Available (see details)' : 'Empty',
|
|
110
|
+
stdout_length: result.outputs.stdout.length,
|
|
111
|
+
stderr_length: result.outputs.stderr.length
|
|
112
|
+
},
|
|
113
|
+
files: fileVerification
|
|
114
|
+
},
|
|
115
|
+
details: {
|
|
116
|
+
stdout: result.outputs.stdout,
|
|
117
|
+
stderr: result.outputs.stderr,
|
|
118
|
+
summary_options_applied: result.summary.options,
|
|
119
|
+
poker_cui_command: 'poker_cui executed successfully'
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// 出力ファイル生成の警告
|
|
124
|
+
if (!fileVerification.allFilesGenerated) {
|
|
125
|
+
response.warnings = ['Some output files were not generated. Check calculation logs for details.'];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
logger.info('Calculation completed successfully', {
|
|
129
|
+
yamlFile: args.yaml_file,
|
|
130
|
+
executionTime: result.executionTime,
|
|
131
|
+
outputFiles: Object.keys(fileVerification).filter(k => k !== 'allFilesGenerated')
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return response;
|
|
135
|
+
|
|
136
|
+
} catch (error) {
|
|
137
|
+
logger.error('executeCalculation handler error', {
|
|
138
|
+
args,
|
|
139
|
+
error: error.message,
|
|
140
|
+
errorType: error.constructor.name
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// CalculationErrorの場合はMCPエラーコードも含める
|
|
144
|
+
if (error instanceof CalculationError) {
|
|
145
|
+
return {
|
|
146
|
+
success: false,
|
|
147
|
+
error: error.message,
|
|
148
|
+
error_type: error.code,
|
|
149
|
+
context: error.context,
|
|
150
|
+
mcp_error_code: error.mcpErrorCode,
|
|
151
|
+
details: {
|
|
152
|
+
category: 'calculation_error',
|
|
153
|
+
recoverable: this._isRecoverableError(error.code),
|
|
154
|
+
suggestions: this._getErrorSuggestions(error.code, error.context)
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ValidationErrorの場合
|
|
160
|
+
if (error instanceof ValidationError) {
|
|
161
|
+
return {
|
|
162
|
+
success: false,
|
|
163
|
+
error: error.message,
|
|
164
|
+
error_type: 'VALIDATION_ERROR',
|
|
165
|
+
field: error.field,
|
|
166
|
+
value: error.value,
|
|
167
|
+
mcp_error_code: -32047, // Invalid calculation parameters
|
|
168
|
+
details: {
|
|
169
|
+
category: 'parameter_error',
|
|
170
|
+
recoverable: true,
|
|
171
|
+
suggestions: ['Check parameter format and values', 'Ensure all required parameters are provided']
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// その他のエラー
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* エラーが回復可能かどうかを判定
|
|
183
|
+
* @param {string} errorCode エラーコード
|
|
184
|
+
* @returns {boolean} 回復可能かどうか
|
|
185
|
+
*/
|
|
186
|
+
_isRecoverableError(errorCode) {
|
|
187
|
+
const recoverableErrors = [
|
|
188
|
+
'YAML_FILE_NOT_FOUND',
|
|
189
|
+
'INVALID_YAML_SYNTAX',
|
|
190
|
+
'OUTPUT_DIRECTORY_ACCESS_DENIED',
|
|
191
|
+
'INVALID_CALCULATION_PARAMETERS'
|
|
192
|
+
];
|
|
193
|
+
return recoverableErrors.includes(errorCode);
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* エラーに対する解決提案を生成
|
|
198
|
+
* @param {string} errorCode エラーコード
|
|
199
|
+
* @param {Object} context エラーコンテキスト
|
|
200
|
+
* @returns {Array<string>} 解決提案のリスト
|
|
201
|
+
*/
|
|
202
|
+
_getErrorSuggestions(errorCode, context = {}) {
|
|
203
|
+
switch (errorCode) {
|
|
204
|
+
case 'POKER_CUI_NOT_AVAILABLE':
|
|
205
|
+
return [
|
|
206
|
+
'Install poker_cui command',
|
|
207
|
+
'Ensure poker_cui is in your system PATH',
|
|
208
|
+
'Check if poker package is properly installed'
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
case 'YAML_FILE_VALIDATION_FAILED':
|
|
212
|
+
return [
|
|
213
|
+
'Use absolute path format: C:\\path\\to\\file.yaml (Windows) or /path/to/file.yaml (Unix/Linux)',
|
|
214
|
+
`Check if file exists: ${context.yamlFile}`,
|
|
215
|
+
'Ensure file has proper read permissions',
|
|
216
|
+
'Verify file has .yaml or .yml extension'
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
case 'YAML_FILE_NOT_FOUND':
|
|
220
|
+
return [
|
|
221
|
+
`Check if file exists: ${context.yamlFile}`,
|
|
222
|
+
'Use absolute path to the YAML file',
|
|
223
|
+
'Ensure file has proper read permissions'
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
case 'INVALID_YAML_SYNTAX':
|
|
227
|
+
return [
|
|
228
|
+
'Validate YAML syntax using a YAML validator',
|
|
229
|
+
'Check for proper indentation and structure',
|
|
230
|
+
'Ensure all required sections (body, zone, source) are present',
|
|
231
|
+
context.syntaxError ? `Fix: ${context.syntaxError}` : 'Review YAML format'
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
case 'OUTPUT_DIRECTORY_ACCESS_DENIED':
|
|
235
|
+
return [
|
|
236
|
+
'Check write permissions on output directory',
|
|
237
|
+
'Create output directory if it does not exist',
|
|
238
|
+
'Use absolute paths for output files'
|
|
239
|
+
];
|
|
240
|
+
|
|
241
|
+
case 'CALCULATION_TIMEOUT':
|
|
242
|
+
return [
|
|
243
|
+
'Simplify the calculation model to reduce computation time',
|
|
244
|
+
'Increase timeout value if calculation is expected to take longer',
|
|
245
|
+
'Check system resources (CPU, memory)'
|
|
246
|
+
];
|
|
247
|
+
|
|
248
|
+
case 'CALCULATION_EXECUTION_FAILED':
|
|
249
|
+
return [
|
|
250
|
+
'Review calculation input parameters',
|
|
251
|
+
'Check YAML file for physics validity',
|
|
252
|
+
'Examine poker_cui output for specific error messages',
|
|
253
|
+
context.stderr ? 'Check stderr output for detailed error information' : 'Review calculation setup'
|
|
254
|
+
];
|
|
255
|
+
|
|
256
|
+
default:
|
|
257
|
+
return [
|
|
258
|
+
'Review input parameters',
|
|
259
|
+
'Check system requirements',
|
|
260
|
+
'Consult documentation for troubleshooting'
|
|
261
|
+
];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|