mulmocast-vision 1.0.5 → 1.0.7
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 +109 -1
- package/lib/html_class.d.ts +2 -0
- package/lib/html_class.js +119 -48
- package/lib/logger.d.ts +49 -0
- package/lib/logger.js +142 -0
- package/lib/mcp.js +25 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,9 +72,62 @@ You can instruct these actions via prompts.
|
|
|
72
72
|
- SWOT, PEST, and 3C Analysis
|
|
73
73
|
- Summary, Agenda, and Closing Slides
|
|
74
74
|
|
|
75
|
+
## Logging
|
|
76
|
+
|
|
77
|
+
mulmocast-vision automatically logs all operations and errors to help with debugging and monitoring.
|
|
78
|
+
|
|
79
|
+
### Log Location
|
|
80
|
+
|
|
81
|
+
Logs are saved in `/tmp/mulmocast-vision-mcp/` with daily rotation:
|
|
82
|
+
- Format: `mcp_yyyymmdd.log` (e.g., `mcp_20251206.log`)
|
|
83
|
+
- Each day creates a new log file
|
|
84
|
+
|
|
85
|
+
### What is Logged
|
|
86
|
+
|
|
87
|
+
- **MCP Server Operations**: Server initialization, tool calls, and results
|
|
88
|
+
- **File Operations**: Template reads, HTML/PNG generation, PDF creation
|
|
89
|
+
- **Errors**: Detailed error messages with full stack traces
|
|
90
|
+
- **Debug Information**: Template rendering, directory operations, and more
|
|
91
|
+
|
|
92
|
+
### Log Format
|
|
93
|
+
|
|
94
|
+
Logs are written in JSON Lines format for easy parsing:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"timestamp": "2025-01-15T10:30:45.123Z",
|
|
99
|
+
"level": "ERROR",
|
|
100
|
+
"message": "Template file not found",
|
|
101
|
+
"data": {
|
|
102
|
+
"errorMessage": "getHtml: file /path/to/template.html not exists.",
|
|
103
|
+
"errorName": "Error",
|
|
104
|
+
"stack": "Error: getHtml: file...\n at htmlPlugin.getHtml...",
|
|
105
|
+
"templateFilePath": "/path/to/template.html",
|
|
106
|
+
"functionName": "createAgendaSlide"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Custom Logger
|
|
112
|
+
|
|
113
|
+
You can replace the default logger with your own implementation (e.g., for telemetry):
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { setLogger, LoggerInterface } from 'mulmocast-vision/logger';
|
|
117
|
+
|
|
118
|
+
class CustomLogger implements LoggerInterface {
|
|
119
|
+
info(message: string, data?: unknown) {
|
|
120
|
+
// Your implementation
|
|
121
|
+
}
|
|
122
|
+
// ... other methods
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
setLogger(new CustomLogger());
|
|
126
|
+
```
|
|
127
|
+
|
|
75
128
|
## For Developers
|
|
76
129
|
|
|
77
|
-
MulmoCast Vision is open source, so you can apply various designs by modifying the HTML.
|
|
130
|
+
MulmoCast Vision is open source, so you can apply various designs by modifying the HTML.
|
|
78
131
|
For adding styles, please refer to [Style.ja.md](https://github.com/receptron/mulmocast-vision/blob/main/Style.ja.md).
|
|
79
132
|
|
|
80
133
|
### Official Repository & Package
|
|
@@ -139,3 +192,58 @@ MCP対応ツール(例: Claude Desktop)の設定ファイルに以下を追
|
|
|
139
192
|
- SWOT分析、PEST分析、3C分析
|
|
140
193
|
- サマリースライド、アジェンダスライド、クロージングスライド
|
|
141
194
|
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## ログ機能
|
|
198
|
+
|
|
199
|
+
mulmocast-visionは、デバッグや監視を支援するため、すべての操作とエラーを自動的にログに記録します。
|
|
200
|
+
|
|
201
|
+
### ログの保存場所
|
|
202
|
+
|
|
203
|
+
ログは `/tmp/mulmocast-vision-mcp/` に日次ローテーションで保存されます:
|
|
204
|
+
- 形式: `mcp_yyyymmdd.log` (例: `mcp_20251206.log`)
|
|
205
|
+
- 日付が変わると新しいログファイルが作成されます
|
|
206
|
+
|
|
207
|
+
### 記録される内容
|
|
208
|
+
|
|
209
|
+
- **MCPサーバー操作**: サーバーの初期化、ツール呼び出し、実行結果
|
|
210
|
+
- **ファイル操作**: テンプレート読み込み、HTML/PNG生成、PDF作成
|
|
211
|
+
- **エラー**: 詳細なエラーメッセージとスタックトレース
|
|
212
|
+
- **デバッグ情報**: テンプレートレンダリング、ディレクトリ操作など
|
|
213
|
+
|
|
214
|
+
### ログフォーマット
|
|
215
|
+
|
|
216
|
+
ログはJSON Lines形式で記録され、解析が容易です:
|
|
217
|
+
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"timestamp": "2025-01-15T10:30:45.123Z",
|
|
221
|
+
"level": "ERROR",
|
|
222
|
+
"message": "Template file not found",
|
|
223
|
+
"data": {
|
|
224
|
+
"errorMessage": "getHtml: file /path/to/template.html not exists.",
|
|
225
|
+
"errorName": "Error",
|
|
226
|
+
"stack": "Error: getHtml: file...\n at htmlPlugin.getHtml...",
|
|
227
|
+
"templateFilePath": "/path/to/template.html",
|
|
228
|
+
"functionName": "createAgendaSlide"
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### カスタムロガー
|
|
234
|
+
|
|
235
|
+
デフォルトのロガーを独自の実装(テレメトリーなど)に置き換えることができます:
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
import { setLogger, LoggerInterface } from 'mulmocast-vision/logger';
|
|
239
|
+
|
|
240
|
+
class CustomLogger implements LoggerInterface {
|
|
241
|
+
info(message: string, data?: unknown) {
|
|
242
|
+
// 独自の実装
|
|
243
|
+
}
|
|
244
|
+
// ... その他のメソッド
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
setLogger(new CustomLogger());
|
|
248
|
+
```
|
|
249
|
+
|
package/lib/html_class.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import nunjucks from "nunjucks";
|
|
1
2
|
import { type PluginOptionParams, type ToolArgs, type CreatePageOptions } from "./type";
|
|
2
3
|
export declare class htmlPlugin {
|
|
3
4
|
protected outputDir: string;
|
|
@@ -6,6 +7,7 @@ export declare class htmlPlugin {
|
|
|
6
7
|
protected templateDir: string;
|
|
7
8
|
protected sessionDir: string;
|
|
8
9
|
protected templateOptions: CreatePageOptions;
|
|
10
|
+
protected nunjucksEnv: nunjucks.Environment;
|
|
9
11
|
constructor({ outputDir, rootDir, templateOptions, htmlDir, // relative path
|
|
10
12
|
templateDir, }: {
|
|
11
13
|
outputDir?: string;
|
package/lib/html_class.js
CHANGED
|
@@ -10,6 +10,7 @@ const pdfkit_1 = __importDefault(require("pdfkit"));
|
|
|
10
10
|
const utils_1 = require("./utils");
|
|
11
11
|
const commons_1 = require("./commons");
|
|
12
12
|
const nunjucks_1 = __importDefault(require("nunjucks"));
|
|
13
|
+
const logger_1 = require("./logger");
|
|
13
14
|
class htmlPlugin {
|
|
14
15
|
constructor({ outputDir, rootDir, templateOptions,
|
|
15
16
|
// for html template. If templateDir exists, it takes precedence over htmlDir.
|
|
@@ -18,6 +19,8 @@ class htmlPlugin {
|
|
|
18
19
|
}) {
|
|
19
20
|
// api for mcp
|
|
20
21
|
this.callNamedFunction = async (functionName, args, options) => {
|
|
22
|
+
const logger = (0, logger_1.getLogger)();
|
|
23
|
+
logger.debug(`callNamedFunction: ${functionName}`, { args, options });
|
|
21
24
|
const member = this[functionName];
|
|
22
25
|
if (member && typeof member === "function") {
|
|
23
26
|
return member(args, { ...options, functionName });
|
|
@@ -25,79 +28,138 @@ class htmlPlugin {
|
|
|
25
28
|
return this.generateHtml(args, { ...options, functionName });
|
|
26
29
|
};
|
|
27
30
|
this.generateHtml = async (args, options) => {
|
|
31
|
+
const logger = (0, logger_1.getLogger)();
|
|
28
32
|
const { outputFileName, functionName, imageFilePath, htmlFilePath } = options ?? {};
|
|
29
33
|
if (!functionName) {
|
|
30
34
|
throw new Error("functionName is required");
|
|
31
35
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
logger.info("Generating HTML", { functionName, outputFileName });
|
|
37
|
+
try {
|
|
38
|
+
const html = this.getHtml(functionName, args);
|
|
39
|
+
const outfile = imageFilePath ?? path_1.default.resolve(this.outputDir, this.sessionDir, `${outputFileName}.png`);
|
|
40
|
+
const htmlFile = htmlFilePath ?? path_1.default.resolve(this.outputDir, this.sessionDir, `${outputFileName}.html`);
|
|
41
|
+
await (0, utils_1.createPage)(this.rootDir, outfile, html, { htmlFile, ...this.templateOptions });
|
|
42
|
+
logger.fileWrite(outfile);
|
|
43
|
+
logger.fileWrite(htmlFile);
|
|
44
|
+
logger.info("HTML generated successfully", { outfile, htmlFile });
|
|
45
|
+
return {
|
|
46
|
+
text: `html generated successfully to: ${outfile}`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
logger.error("Failed to generate HTML", error, {
|
|
51
|
+
functionName,
|
|
52
|
+
outputFileName,
|
|
53
|
+
args,
|
|
54
|
+
outputDir: this.outputDir,
|
|
55
|
+
sessionDir: this.sessionDir,
|
|
56
|
+
});
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
39
59
|
};
|
|
40
60
|
// for electron
|
|
41
61
|
this.getHtml = (functionName, args) => {
|
|
62
|
+
const logger = (0, logger_1.getLogger)();
|
|
42
63
|
const templateFileName = (0, commons_1.functionNameToTemplateName)(functionName);
|
|
43
64
|
const templateFilePath = path_1.default.resolve(this.templateDir ? this.templateDir : path_1.default.resolve(this.rootDir, "html", this.htmlDir), `${templateFileName}.html`);
|
|
44
65
|
if (!fs_1.default.existsSync(templateFilePath)) {
|
|
45
|
-
|
|
66
|
+
const error = new Error(`getHtml: file ${templateFilePath} not exists.`);
|
|
67
|
+
logger.error("Template file not found", error, {
|
|
68
|
+
templateFilePath,
|
|
69
|
+
functionName,
|
|
70
|
+
templateFileName,
|
|
71
|
+
templateDir: this.templateDir,
|
|
72
|
+
htmlDir: this.htmlDir,
|
|
73
|
+
rootDir: this.rootDir,
|
|
74
|
+
});
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
logger.fileRead(templateFilePath);
|
|
78
|
+
logger.debug("Rendering template", { templateFileName, args });
|
|
79
|
+
try {
|
|
80
|
+
// Use the configured Nunjucks environment instead of the default
|
|
81
|
+
// This ensures the FileSystemLoader can find the template
|
|
82
|
+
return this.nunjucksEnv.render(`${templateFileName}.html`, args);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
logger.error("Template rendering failed", error, { templateFilePath, templateFileName, args });
|
|
86
|
+
throw error;
|
|
46
87
|
}
|
|
47
|
-
return nunjucks_1.default.render(templateFilePath, args);
|
|
48
88
|
};
|
|
49
89
|
// for mcp
|
|
50
90
|
this.setDirectory = async (args, __options) => {
|
|
91
|
+
const logger = (0, logger_1.getLogger)();
|
|
51
92
|
this.sessionDir = args.directoryName;
|
|
52
93
|
const outputDir = path_1.default.resolve(this.outputDir, this.sessionDir);
|
|
53
|
-
|
|
94
|
+
logger.info("Setting directory", { sessionDir: this.sessionDir, outputDir });
|
|
54
95
|
(0, utils_1.mkdir)(outputDir);
|
|
55
96
|
return {
|
|
56
97
|
text: `set directory: ${this.sessionDir}`,
|
|
57
98
|
};
|
|
58
99
|
};
|
|
59
100
|
this.createPDF = async (args, __options) => {
|
|
101
|
+
const logger = (0, logger_1.getLogger)();
|
|
60
102
|
const { filename, images } = args ?? {};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
top: 0,
|
|
79
|
-
bottom: 0,
|
|
80
|
-
left: 0,
|
|
81
|
-
right: 0,
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
doc.pipe(fs_1.default.createWriteStream(path_1.default.resolve(outputDir, filename)));
|
|
85
|
-
files.forEach((f, i) => {
|
|
86
|
-
if (i > 0) {
|
|
87
|
-
doc.addPage({
|
|
88
|
-
size: [pageWidth, pageHeight],
|
|
89
|
-
margins: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
90
|
-
});
|
|
103
|
+
logger.info("Creating PDF", { filename, imagesCount: images?.length });
|
|
104
|
+
try {
|
|
105
|
+
const imageWidth = 1536;
|
|
106
|
+
const imageHeight = 1024;
|
|
107
|
+
const pageWidth = imageWidth * 0.75; // 1152pt
|
|
108
|
+
const pageHeight = imageHeight * 0.75; // 768pt
|
|
109
|
+
const outputDir = path_1.default.resolve(this.outputDir, this.sessionDir);
|
|
110
|
+
const files = images ??
|
|
111
|
+
fs_1.default
|
|
112
|
+
.readdirSync(outputDir)
|
|
113
|
+
.filter((f) => f.toLowerCase().endsWith(".png"))
|
|
114
|
+
.sort(new Intl.Collator("ja", { numeric: true }).compare);
|
|
115
|
+
if (files.length === 0) {
|
|
116
|
+
const error = new Error("No PNG files found");
|
|
117
|
+
logger.error("No PNG files found", error, { outputDir });
|
|
118
|
+
console.error("no PNG file");
|
|
119
|
+
return;
|
|
91
120
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
121
|
+
logger.debug("PNG files found", { count: files.length, files });
|
|
122
|
+
const doc = new pdfkit_1.default({
|
|
123
|
+
size: [pageWidth, pageHeight],
|
|
124
|
+
margins: {
|
|
125
|
+
top: 0,
|
|
126
|
+
bottom: 0,
|
|
127
|
+
left: 0,
|
|
128
|
+
right: 0,
|
|
129
|
+
},
|
|
95
130
|
});
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
131
|
+
const pdfPath = path_1.default.resolve(outputDir, filename);
|
|
132
|
+
doc.pipe(fs_1.default.createWriteStream(pdfPath));
|
|
133
|
+
files.forEach((f, i) => {
|
|
134
|
+
const imagePath = path_1.default.join(outputDir, f);
|
|
135
|
+
logger.fileRead(imagePath);
|
|
136
|
+
if (i > 0) {
|
|
137
|
+
doc.addPage({
|
|
138
|
+
size: [pageWidth, pageHeight],
|
|
139
|
+
margins: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
doc.image(imagePath, 0, 0, {
|
|
143
|
+
width: pageWidth,
|
|
144
|
+
height: pageHeight,
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
doc.end();
|
|
148
|
+
logger.fileWrite(pdfPath);
|
|
149
|
+
logger.info("PDF created successfully", { pdfPath, pageCount: files.length });
|
|
150
|
+
return {
|
|
151
|
+
text: `pdf created: ${outputDir}`,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
logger.error("Failed to create PDF", error, {
|
|
156
|
+
filename,
|
|
157
|
+
images,
|
|
158
|
+
outputDir: this.outputDir,
|
|
159
|
+
sessionDir: this.sessionDir,
|
|
160
|
+
});
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
101
163
|
};
|
|
102
164
|
this.dumpMulmoScript = () => { };
|
|
103
165
|
this.outputDir = outputDir ?? (0, utils_1.getOutDir)();
|
|
@@ -106,6 +168,15 @@ class htmlPlugin {
|
|
|
106
168
|
this.htmlDir = htmlDir ?? "html2";
|
|
107
169
|
this.templateDir = templateDir ?? "";
|
|
108
170
|
this.templateOptions = templateOptions ?? {};
|
|
171
|
+
// Configure Nunjucks environment with FileSystemLoader
|
|
172
|
+
const templatePath = this.templateDir ? this.templateDir : path_1.default.resolve(this.rootDir, "html", this.htmlDir);
|
|
173
|
+
const loader = new nunjucks_1.default.FileSystemLoader(templatePath, {
|
|
174
|
+
noCache: true, // Disable cache to ensure fresh templates
|
|
175
|
+
});
|
|
176
|
+
this.nunjucksEnv = new nunjucks_1.default.Environment(loader, {
|
|
177
|
+
autoescape: false, // Don't escape HTML
|
|
178
|
+
throwOnUndefined: false, // Don't throw on undefined variables
|
|
179
|
+
});
|
|
109
180
|
}
|
|
110
181
|
}
|
|
111
182
|
exports.htmlPlugin = htmlPlugin;
|
package/lib/logger.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
type LogLevel = "INFO" | "ERROR" | "WARN" | "DEBUG";
|
|
2
|
+
export interface LogEntry {
|
|
3
|
+
timestamp: string;
|
|
4
|
+
level: LogLevel;
|
|
5
|
+
message: string;
|
|
6
|
+
data?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface LoggerInterface {
|
|
9
|
+
info(message: string, data?: unknown): void;
|
|
10
|
+
error(message: string, error?: Error | unknown, data?: unknown): void;
|
|
11
|
+
warn(message: string, data?: unknown): void;
|
|
12
|
+
debug(message: string, data?: unknown): void;
|
|
13
|
+
fileRead(filePath: string): void;
|
|
14
|
+
fileWrite(filePath: string, size?: number): void;
|
|
15
|
+
fileDelete(filePath: string): void;
|
|
16
|
+
toolCall(toolName: string, args: unknown): void;
|
|
17
|
+
toolResult(toolName: string, success: boolean, result?: unknown): void;
|
|
18
|
+
}
|
|
19
|
+
declare class FileLogger implements LoggerInterface {
|
|
20
|
+
private logDir;
|
|
21
|
+
private logFile;
|
|
22
|
+
constructor(logDir?: string);
|
|
23
|
+
private formatTimestamp;
|
|
24
|
+
private writeLog;
|
|
25
|
+
info(message: string, data?: unknown): void;
|
|
26
|
+
error(message: string, error?: Error | unknown, data?: unknown): void;
|
|
27
|
+
warn(message: string, data?: unknown): void;
|
|
28
|
+
debug(message: string, data?: unknown): void;
|
|
29
|
+
fileRead(filePath: string): void;
|
|
30
|
+
fileWrite(filePath: string, size?: number): void;
|
|
31
|
+
fileDelete(filePath: string): void;
|
|
32
|
+
toolCall(toolName: string, args: unknown): void;
|
|
33
|
+
toolResult(toolName: string, success: boolean, result?: unknown): void;
|
|
34
|
+
}
|
|
35
|
+
declare class NoOpLogger implements LoggerInterface {
|
|
36
|
+
info(__message: string, __data?: unknown): void;
|
|
37
|
+
error(__message: string, __error?: Error | unknown, __data?: unknown): void;
|
|
38
|
+
warn(__message: string, __data?: unknown): void;
|
|
39
|
+
debug(__message: string, __data?: unknown): void;
|
|
40
|
+
fileRead(__filePath: string): void;
|
|
41
|
+
fileWrite(__filePath: string, __size?: number): void;
|
|
42
|
+
fileDelete(__filePath: string): void;
|
|
43
|
+
toolCall(__toolName: string, __args: unknown): void;
|
|
44
|
+
toolResult(__toolName: string, __success: boolean, __result?: unknown): void;
|
|
45
|
+
}
|
|
46
|
+
export declare const setLogger: (logger: LoggerInterface) => void;
|
|
47
|
+
export declare const getLogger: () => LoggerInterface;
|
|
48
|
+
export declare const logger: LoggerInterface;
|
|
49
|
+
export { FileLogger, NoOpLogger };
|
package/lib/logger.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.NoOpLogger = exports.FileLogger = exports.logger = exports.getLogger = exports.setLogger = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
// Default file-based logger implementation
|
|
11
|
+
class FileLogger {
|
|
12
|
+
constructor(logDir) {
|
|
13
|
+
this.logDir = logDir ?? path_1.default.join(os_1.default.tmpdir(), "mulmocast-vision-mcp");
|
|
14
|
+
// Generate date suffix (yyyymmdd)
|
|
15
|
+
const now = new Date();
|
|
16
|
+
const year = now.getFullYear();
|
|
17
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
18
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
19
|
+
const dateSuffix = `${year}${month}${day}`;
|
|
20
|
+
this.logFile = path_1.default.join(this.logDir, `mcp_${dateSuffix}.log`);
|
|
21
|
+
// Ensure log directory exists
|
|
22
|
+
if (!fs_1.default.existsSync(this.logDir)) {
|
|
23
|
+
fs_1.default.mkdirSync(this.logDir, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
formatTimestamp() {
|
|
27
|
+
return new Date().toISOString();
|
|
28
|
+
}
|
|
29
|
+
writeLog(level, message, data) {
|
|
30
|
+
const timestamp = this.formatTimestamp();
|
|
31
|
+
const logEntry = {
|
|
32
|
+
timestamp,
|
|
33
|
+
level,
|
|
34
|
+
message,
|
|
35
|
+
};
|
|
36
|
+
if (data !== undefined) {
|
|
37
|
+
logEntry.data = data;
|
|
38
|
+
}
|
|
39
|
+
const logLine = JSON.stringify(logEntry) + "\n";
|
|
40
|
+
try {
|
|
41
|
+
fs_1.default.appendFileSync(this.logFile, logLine);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
// Fallback to console.error if file write fails
|
|
45
|
+
console.error("Failed to write to log file:", error);
|
|
46
|
+
console.error(logLine);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
info(message, data) {
|
|
50
|
+
this.writeLog("INFO", message, data);
|
|
51
|
+
}
|
|
52
|
+
error(message, error, data) {
|
|
53
|
+
const errorData = {};
|
|
54
|
+
if (error instanceof Error) {
|
|
55
|
+
errorData.errorMessage = error.message;
|
|
56
|
+
errorData.errorName = error.name;
|
|
57
|
+
errorData.stack = error.stack;
|
|
58
|
+
// Include any custom properties on the error
|
|
59
|
+
const errorObj = error;
|
|
60
|
+
Object.keys(error).forEach((key) => {
|
|
61
|
+
if (key !== "message" && key !== "name" && key !== "stack") {
|
|
62
|
+
errorData[key] = errorObj[key];
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
else if (error !== undefined) {
|
|
67
|
+
errorData.error = error;
|
|
68
|
+
}
|
|
69
|
+
const combinedData = data ? { ...errorData, ...data } : errorData;
|
|
70
|
+
this.writeLog("ERROR", message, Object.keys(combinedData).length > 0 ? combinedData : undefined);
|
|
71
|
+
}
|
|
72
|
+
warn(message, data) {
|
|
73
|
+
this.writeLog("WARN", message, data);
|
|
74
|
+
}
|
|
75
|
+
debug(message, data) {
|
|
76
|
+
this.writeLog("DEBUG", message, data);
|
|
77
|
+
}
|
|
78
|
+
fileRead(filePath) {
|
|
79
|
+
this.info("File read", { operation: "read", filePath });
|
|
80
|
+
}
|
|
81
|
+
fileWrite(filePath, size) {
|
|
82
|
+
this.info("File write", { operation: "write", filePath, size });
|
|
83
|
+
}
|
|
84
|
+
fileDelete(filePath) {
|
|
85
|
+
this.info("File delete", { operation: "delete", filePath });
|
|
86
|
+
}
|
|
87
|
+
toolCall(toolName, args) {
|
|
88
|
+
this.info("Tool called", { toolName, args });
|
|
89
|
+
}
|
|
90
|
+
toolResult(toolName, success, result) {
|
|
91
|
+
this.info("Tool result", { toolName, success, result });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.FileLogger = FileLogger;
|
|
95
|
+
// No-op logger for disabling logging
|
|
96
|
+
class NoOpLogger {
|
|
97
|
+
info(__message, __data) { }
|
|
98
|
+
error(__message, __error, __data) { }
|
|
99
|
+
warn(__message, __data) { }
|
|
100
|
+
debug(__message, __data) { }
|
|
101
|
+
fileRead(__filePath) { }
|
|
102
|
+
fileWrite(__filePath, __size) { }
|
|
103
|
+
fileDelete(__filePath) { }
|
|
104
|
+
toolCall(__toolName, __args) { }
|
|
105
|
+
toolResult(__toolName, __success, __result) { }
|
|
106
|
+
}
|
|
107
|
+
exports.NoOpLogger = NoOpLogger;
|
|
108
|
+
// Logger manager
|
|
109
|
+
class LoggerManager {
|
|
110
|
+
constructor() {
|
|
111
|
+
this.logger = new FileLogger();
|
|
112
|
+
}
|
|
113
|
+
setLogger(logger) {
|
|
114
|
+
this.logger = logger;
|
|
115
|
+
}
|
|
116
|
+
getLogger() {
|
|
117
|
+
return this.logger;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Export singleton instance
|
|
121
|
+
const loggerManager = new LoggerManager();
|
|
122
|
+
const setLogger = (logger) => {
|
|
123
|
+
loggerManager.setLogger(logger);
|
|
124
|
+
};
|
|
125
|
+
exports.setLogger = setLogger;
|
|
126
|
+
const getLogger = () => {
|
|
127
|
+
return loggerManager.getLogger();
|
|
128
|
+
};
|
|
129
|
+
exports.getLogger = getLogger;
|
|
130
|
+
// Proxy object that delegates to current logger
|
|
131
|
+
// This ensures that the exported logger updates when setLogger() is called
|
|
132
|
+
exports.logger = {
|
|
133
|
+
info: (message, data) => loggerManager.getLogger().info(message, data),
|
|
134
|
+
error: (message, error, data) => loggerManager.getLogger().error(message, error, data),
|
|
135
|
+
warn: (message, data) => loggerManager.getLogger().warn(message, data),
|
|
136
|
+
debug: (message, data) => loggerManager.getLogger().debug(message, data),
|
|
137
|
+
fileRead: (filePath) => loggerManager.getLogger().fileRead(filePath),
|
|
138
|
+
fileWrite: (filePath, size) => loggerManager.getLogger().fileWrite(filePath, size),
|
|
139
|
+
fileDelete: (filePath) => loggerManager.getLogger().fileDelete(filePath),
|
|
140
|
+
toolCall: (toolName, args) => loggerManager.getLogger().toolCall(toolName, args),
|
|
141
|
+
toolResult: (toolName, success, result) => loggerManager.getLogger().toolResult(toolName, success, result),
|
|
142
|
+
};
|
package/lib/mcp.js
CHANGED
|
@@ -9,7 +9,9 @@ const html_class_1 = require("./html_class");
|
|
|
9
9
|
const commons_1 = require("./commons");
|
|
10
10
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
11
11
|
const utils_1 = require("./utils");
|
|
12
|
+
const logger_1 = require("./logger");
|
|
12
13
|
const getServer = (handler) => {
|
|
14
|
+
const logger = (0, logger_1.getLogger)();
|
|
13
15
|
const server = new index_js_1.Server({
|
|
14
16
|
name: "mulmocast-vision",
|
|
15
17
|
version: "1.0.2",
|
|
@@ -18,18 +20,22 @@ const getServer = (handler) => {
|
|
|
18
20
|
tools: {},
|
|
19
21
|
},
|
|
20
22
|
});
|
|
23
|
+
logger.info("MCP Server initialized", { name: "mulmocast-vision", version: "1.0.2" });
|
|
21
24
|
// List available tools
|
|
22
25
|
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
26
|
+
logger.debug("List tools requested");
|
|
23
27
|
return (0, commons_1.openAIToolsToAnthropicTools)([...tools_1.tools, ...tools_1.mcp_tools]);
|
|
24
28
|
});
|
|
25
29
|
// Handle tool calls
|
|
26
30
|
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
27
31
|
const { name, arguments: args } = request.params;
|
|
32
|
+
logger.toolCall(name, args);
|
|
28
33
|
console.error([...tools_1.tools, ...tools_1.mcp_tools]);
|
|
29
34
|
try {
|
|
30
35
|
if (args) {
|
|
31
36
|
const fileName = (0, commons_1.generateUniqueId)();
|
|
32
37
|
const result = await handler.callNamedFunction(name, args, { outputFileName: fileName });
|
|
38
|
+
logger.toolResult(name, true, result);
|
|
33
39
|
return {
|
|
34
40
|
content: [
|
|
35
41
|
{
|
|
@@ -43,6 +49,8 @@ const getServer = (handler) => {
|
|
|
43
49
|
}
|
|
44
50
|
catch (error) {
|
|
45
51
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
52
|
+
logger.toolResult(name, false, { error: errorMessage });
|
|
53
|
+
logger.error(`Tool call failed: ${name}`, error, { toolName: name, args });
|
|
46
54
|
return {
|
|
47
55
|
content: [
|
|
48
56
|
{
|
|
@@ -58,12 +66,22 @@ const getServer = (handler) => {
|
|
|
58
66
|
};
|
|
59
67
|
exports.getServer = getServer;
|
|
60
68
|
const main = async () => {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
const logger = (0, logger_1.getLogger)();
|
|
70
|
+
try {
|
|
71
|
+
logger.info("Starting MCP server");
|
|
72
|
+
const rootDir = (0, utils_1.getRootDir)();
|
|
73
|
+
const outputDir = (0, utils_1.getOutDir)();
|
|
74
|
+
const htmlDir = "html2";
|
|
75
|
+
logger.info("Configuration loaded", { rootDir, outputDir, htmlDir });
|
|
76
|
+
const handler = new html_class_1.htmlPlugin({ outputDir, rootDir, htmlDir });
|
|
77
|
+
const server = (0, exports.getServer)(handler);
|
|
78
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
79
|
+
await server.connect(transport);
|
|
80
|
+
logger.info("MCP server connected and ready");
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
logger.error("Failed to start MCP server", error);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
68
86
|
};
|
|
69
87
|
main();
|