@aiquants/html-to-markdown 0.2.6 → 0.4.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 CHANGED
@@ -136,12 +136,31 @@ npx --package=@aiquants/html-to-markdown aiq-html2md-mcp-stream
136
136
  - `url` (required*): The URL of the web page to convert
137
137
  - `html_content` (required*): HTML content as a string to convert (alternative to URL)
138
138
  - `locale` (optional): Browser locale (`en-US` or `ja-JP`, defaults to `en-US`)
139
- - `output_format` (optional): Output format (`markdown`, `html`, or `both`, defaults to `markdown`)
140
- - `save_path` (optional): Full path (including filename) where to save the converted content
141
- - `save_directory` (optional): Directory path where to save the converted content with auto-generated filename
142
139
 
143
140
  *Either `url` or `html_content` is required
144
141
 
142
+ - **save_content_to_file**: Save text content to a file with specified path or directory
143
+ - `content` (required): Text content to save to file (will be saved as Markdown format)
144
+ - `save_path` (optional): Complete file path including filename to save content
145
+ - `save_directory` (optional): Directory path to save content with auto-generated filename
146
+ - `filename` (optional): Base filename to use when save_directory is specified (extension .md will be added automatically)
147
+
148
+ - **url_to_markdown_file**: Convert web pages or HTML strings directly to Markdown files (combines conversion and file saving)
149
+ - `url` (required*): The URL of the web page to convert and save
150
+ - `html_content` (required*): HTML content as a string to convert and save (alternative to URL)
151
+ - `locale` (optional): Browser locale (`en-US` or `ja-JP`, defaults to `en-US`)
152
+ - `save_path` (optional): Complete file path including filename to save the converted content
153
+ - `save_directory` (optional): Directory path to save the converted content with auto-generated filename
154
+ - `filename` (optional): Base filename to use when save_directory is specified (extension .md will be added automatically)
155
+
156
+ *Either `url` or `html_content` is required, and either `save_path` or `save_directory` is required
157
+
158
+ **Streamable MCP Tools (for real-time progress updates):**
159
+
160
+ - **html_to_markdown_streamable**: Same as `html_to_markdown` but with real-time progress updates
161
+ - **save_content_to_file**: Same file saving functionality as in standard MCP
162
+ - **url_to_markdown_file_streamable**: Same as `url_to_markdown_file` but with real-time progress updates and streaming support
163
+
145
164
  **File Saving Options:**
146
165
 
147
166
  - **`save_path`**: Specify the complete file path including filename and extension where you want to save the content.
@@ -157,13 +176,75 @@ npx --package=@aiquants/html-to-markdown aiq-html2md-mcp-stream
157
176
 
158
177
  **File Saving Behavior:**
159
178
 
160
- - When `output_format` is `markdown`: Only a `.md` file is saved
161
- - When `output_format` is `html`: Only a `.html` file is saved
162
- - When `output_format` is `both`: Both `.md` and `.html` files are saved
163
- - With `save_path`: Files are saved as `path.md` and `path.html`
164
- - With `save_directory`: Files are saved as `filename.md` and `filename.html`
179
+ - All content is saved as Markdown format (`.md` files)
180
+ - The directory will be created automatically if it doesn't exist
181
+ - With `save_path`: File is saved to the exact specified path
182
+ - With `save_directory`: File is saved with auto-generated filename based on the URL or custom filename if provided
183
+
184
+ #### MCP Usage Examples
185
+
186
+ ##### Example 1: Basic HTML to Markdown conversion
187
+
188
+ ```json
189
+ {
190
+ "method": "tools/call",
191
+ "params": {
192
+ "name": "html_to_markdown",
193
+ "arguments": {
194
+ "url": "https://example.com",
195
+ "locale": "en-US"
196
+ }
197
+ }
198
+ }
199
+ ```
200
+
201
+ ##### Example 2: Save content to a specific file
202
+
203
+ ```json
204
+ {
205
+ "method": "tools/call",
206
+ "params": {
207
+ "name": "save_content_to_file",
208
+ "arguments": {
209
+ "content": "# My Content\n\nThis is my markdown content.",
210
+ "save_path": "/path/to/my-file.md"
211
+ }
212
+ }
213
+ }
214
+ ```
215
+
216
+ ##### Example 3: Convert URL directly to Markdown file (One-step operation)
165
217
 
166
- - **html_to_markdown_streamable**: Same as above but with streamable MCP support for progress updates
218
+ ```json
219
+ {
220
+ "method": "tools/call",
221
+ "params": {
222
+ "name": "url_to_markdown_file",
223
+ "arguments": {
224
+ "url": "https://example.com/article",
225
+ "save_directory": "/path/to/output/",
226
+ "filename": "my-article",
227
+ "locale": "en-US"
228
+ }
229
+ }
230
+ }
231
+ ```
232
+
233
+ ##### Example 4: Streamable conversion with real-time progress
234
+
235
+ ```json
236
+ {
237
+ "method": "tools/call",
238
+ "params": {
239
+ "name": "url_to_markdown_file_streamable",
240
+ "arguments": {
241
+ "url": "https://example.com/large-page",
242
+ "save_path": "/path/to/large-page.md",
243
+ "locale": "ja-JP"
244
+ }
245
+ }
246
+ }
247
+ ```
167
248
 
168
249
  ### Using MCP Server Programmatically
169
250
 
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- const e=new(require("happy-dom").Window);Object.assign(global,{document:e.document,window:e,self:e}),Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("./core-D2dlEt1l.cjs"),t=require("./mcp-server-Bo_mCvzz.cjs"),c=require("./mcp-server-streamable-Cc4QsZQe.cjs");exports.getMessage=r.getMessage,exports.htmlToMarkdown=r.htmlToMarkdown,exports.createMcpServer=t.createMcpServer,exports.runMcpServer=t.runMcpServer,exports.createStreamableMcpServer=c.createStreamableMcpServer,exports.runStreamableMcpServer=c.runStreamableMcpServer;
1
+ const e=new(require("happy-dom").Window);Object.assign(global,{document:e.document,window:e,self:e}),Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("./core-D2dlEt1l.cjs"),t=require("./mcp-server-Co1kuNGX.cjs"),c=require("./mcp-server-streamable-BNn6Kd9b.cjs");exports.getMessage=r.getMessage,exports.htmlToMarkdown=r.htmlToMarkdown,exports.createMcpServer=t.createMcpServer,exports.runMcpServer=t.runMcpServer,exports.createStreamableMcpServer=c.createStreamableMcpServer,exports.runStreamableMcpServer=c.runStreamableMcpServer;
package/dist/index.js CHANGED
@@ -6,8 +6,8 @@ Object.assign(global, {
6
6
  self: window
7
7
  });
8
8
  import { g, h } from "./core-CouDTni-.js";
9
- import { c, r } from "./mcp-server-B8hz3lXI.js";
10
- import { c as c2, r as r2 } from "./mcp-server-streamable-3B3B1fuS.js";
9
+ import { c, r } from "./mcp-server-DzB_5cTs.js";
10
+ import { c as c2, r as r2 } from "./mcp-server-streamable-CcZ7Lsel.js";
11
11
  export {
12
12
  c as createMcpServer,
13
13
  c2 as createStreamableMcpServer,
@@ -0,0 +1 @@
1
+ const e=new(require("happy-dom").Window);Object.assign(global,{document:e.document,window:e,self:e});const t=require("./version-dpS3SSul.cjs"),r=require("node:process"),a=require("./core-D2dlEt1l.cjs");class n{append(e){this._buffer=this._buffer?Buffer.concat([this._buffer,e]):e}readMessage(){if(!this._buffer)return null;const e=this._buffer.indexOf("\n");if(-1===e)return null;const r=this._buffer.toString("utf8",0,e).replace(/\r$/,"");return this._buffer=this._buffer.subarray(e+1),function(e){return t.JSONRPCMessageSchema.parse(JSON.parse(e))}(r)}clear(){this._buffer=void 0}}class s{constructor(e=r.stdin,t=r.stdout){this._stdin=e,this._stdout=t,this._readBuffer=new n,this._started=!1,this._ondata=e=>{this._readBuffer.append(e),this.processReadBuffer()},this._onerror=e=>{var t;null===(t=this.onerror)||void 0===t||t.call(this,e)}}async start(){if(this._started)throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");this._started=!0,this._stdin.on("data",this._ondata),this._stdin.on("error",this._onerror)}processReadBuffer(){for(var e,t;;)try{const t=this._readBuffer.readMessage();if(null===t)break;null===(e=this.onmessage)||void 0===e||e.call(this,t)}catch(r){null===(t=this.onerror)||void 0===t||t.call(this,r)}}async close(){var e;this._stdin.off("data",this._ondata),this._stdin.off("error",this._onerror);0===this._stdin.listenerCount("data")&&this._stdin.pause(),this._readBuffer.clear(),null===(e=this.onclose)||void 0===e||e.call(this)}send(e){return new Promise(t=>{const r=function(e){return JSON.stringify(e)+"\n"}(e);this._stdout.write(r)?t():this._stdout.once("drain",t)})}}class o{constructor(){this.version=t.getPackageVersion(),this.server=new t.Server({name:"@aiquants/html-to-markdown",version:this.version,description:"Convert web pages and HTML to Markdown with JavaScript dynamic content support and table structure preservation. Includes separate file saving tool."},{capabilities:{tools:{}}}),this.setupToolHandlers()}setupToolHandlers(){this.server.setRequestHandler(t.ListToolsRequestSchema,async()=>({tools:[{name:"html_to_markdown",description:"Convert web pages or HTML strings to Markdown format. Handles JavaScript-rendered dynamic content and preserves table structures.",inputSchema:t.MCP_TOOL_SCHEMAS.htmlToMarkdown},{name:"save_content_to_file",description:"Save text content to a file with specified path or directory. Supports automatic filename generation and format detection.",inputSchema:t.MCP_TOOL_SCHEMAS.saveContentToFile},{name:"url_to_markdown_file",description:"Convert web pages or HTML strings directly to Markdown files. Combines HTML-to-Markdown conversion and file saving in one step. Handles JavaScript-rendered dynamic content.",inputSchema:t.MCP_TOOL_SCHEMAS.urlToMarkdownFile}]})),this.server.setRequestHandler(t.CallToolRequestSchema,async e=>{switch(e.params.name){case"html_to_markdown":return await this.handleHtmlToMarkdown(e.params.arguments);case"save_content_to_file":return await this.handleSaveContentToFile(e.params.arguments);case"url_to_markdown_file":return await this.handleUrlToMarkdownFile(e.params.arguments);default:throw new Error(`Unknown tool: ${e.params.name}`)}})}async handleHtmlToMarkdown(e){try{const r=t.parseHtmlToMarkdownArgs(e),{url:n,html_content:s,locale:o="en-US"}=r;if(n)try{new URL(n)}catch{throw new Error(`Invalid URL: ${n}`)}const i=(await a.htmlToMarkdown(n||"direct-html-input",{locale:o,htmlContent:s})).markdown;return t.createSuccessResult(i)}catch(r){return t.createErrorResult(r)}}async handleSaveContentToFile(e){try{const r=t.parseSaveContentArgs(e),{content:a,save_path:n,save_directory:s,filename:o}=r,i=`File saved successfully to: ${await t.saveContentToFile(a,n,s,o)}`;return t.createSuccessResult(i)}catch(r){return t.createErrorResult(r)}}async handleUrlToMarkdownFile(e){try{const r=t.parseUrlToMarkdownFileArgs(e),{url:n,html_content:s,locale:o="en-US",save_path:i,save_directory:c,filename:l}=r;if(n)try{new URL(n)}catch{throw new Error(`Invalid URL: ${n}`)}const d=(await a.htmlToMarkdown(n||"direct-html-input",{locale:o,htmlContent:s})).markdown;let u=l;!u&&n&&(u=this.generateFilenameFromUrl(n));const h=`URL successfully converted to Markdown and saved to: ${await t.saveContentToFile(d,i,c,u)}`;return t.createSuccessResult(h)}catch(r){return t.createErrorResult(r)}}generateFilenameFromUrl(e){try{let t=new URL(e).pathname.split("/").pop()||"page";return t=t.replace(/\.[^.]*$/,""),t=t.replace(/[<>:"/\\|?*]/g,"_"),t&&!t.match(/^\.+$/)||(t="page"),t}catch{return"page"}}async start(){const e=new s;await this.server.connect(e)}}const i=()=>new o;exports.createMcpServer=i,exports.runMcpServer=async()=>{const e=i();await e.start()};
@@ -0,0 +1,267 @@
1
+ import { Window } from "happy-dom";
2
+ const window = new Window();
3
+ Object.assign(global, {
4
+ document: window.document,
5
+ window,
6
+ self: window
7
+ });
8
+ import { J as JSONRPCMessageSchema, g as getPackageVersion, S as Server, L as ListToolsRequestSchema, M as MCP_TOOL_SCHEMAS, C as CallToolRequestSchema, p as parseHtmlToMarkdownArgs, c as createSuccessResult, a as createErrorResult, b as parseSaveContentArgs, s as saveContentToFile, d as parseUrlToMarkdownFileArgs } from "./version-BSh9aXhF.js";
9
+ import process from "node:process";
10
+ import { h as htmlToMarkdown } from "./core-CouDTni-.js";
11
+ class ReadBuffer {
12
+ append(chunk) {
13
+ this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk;
14
+ }
15
+ readMessage() {
16
+ if (!this._buffer) {
17
+ return null;
18
+ }
19
+ const index = this._buffer.indexOf("\n");
20
+ if (index === -1) {
21
+ return null;
22
+ }
23
+ const line = this._buffer.toString("utf8", 0, index).replace(/\r$/, "");
24
+ this._buffer = this._buffer.subarray(index + 1);
25
+ return deserializeMessage(line);
26
+ }
27
+ clear() {
28
+ this._buffer = void 0;
29
+ }
30
+ }
31
+ function deserializeMessage(line) {
32
+ return JSONRPCMessageSchema.parse(JSON.parse(line));
33
+ }
34
+ function serializeMessage(message) {
35
+ return JSON.stringify(message) + "\n";
36
+ }
37
+ class StdioServerTransport {
38
+ constructor(_stdin = process.stdin, _stdout = process.stdout) {
39
+ this._stdin = _stdin;
40
+ this._stdout = _stdout;
41
+ this._readBuffer = new ReadBuffer();
42
+ this._started = false;
43
+ this._ondata = (chunk) => {
44
+ this._readBuffer.append(chunk);
45
+ this.processReadBuffer();
46
+ };
47
+ this._onerror = (error) => {
48
+ var _a;
49
+ (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
50
+ };
51
+ }
52
+ /**
53
+ * Starts listening for messages on stdin.
54
+ */
55
+ async start() {
56
+ if (this._started) {
57
+ throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");
58
+ }
59
+ this._started = true;
60
+ this._stdin.on("data", this._ondata);
61
+ this._stdin.on("error", this._onerror);
62
+ }
63
+ processReadBuffer() {
64
+ var _a, _b;
65
+ while (true) {
66
+ try {
67
+ const message = this._readBuffer.readMessage();
68
+ if (message === null) {
69
+ break;
70
+ }
71
+ (_a = this.onmessage) === null || _a === void 0 ? void 0 : _a.call(this, message);
72
+ } catch (error) {
73
+ (_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, error);
74
+ }
75
+ }
76
+ }
77
+ async close() {
78
+ var _a;
79
+ this._stdin.off("data", this._ondata);
80
+ this._stdin.off("error", this._onerror);
81
+ const remainingDataListeners = this._stdin.listenerCount("data");
82
+ if (remainingDataListeners === 0) {
83
+ this._stdin.pause();
84
+ }
85
+ this._readBuffer.clear();
86
+ (_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
87
+ }
88
+ send(message) {
89
+ return new Promise((resolve) => {
90
+ const json = serializeMessage(message);
91
+ if (this._stdout.write(json)) {
92
+ resolve();
93
+ } else {
94
+ this._stdout.once("drain", resolve);
95
+ }
96
+ });
97
+ }
98
+ }
99
+ class HtmlToMarkdownMcpServer {
100
+ constructor() {
101
+ this.version = getPackageVersion();
102
+ this.server = new Server(
103
+ {
104
+ name: "@aiquants/html-to-markdown",
105
+ version: this.version,
106
+ description: "Convert web pages and HTML to Markdown with JavaScript dynamic content support and table structure preservation. Includes separate file saving tool."
107
+ },
108
+ {
109
+ capabilities: {
110
+ tools: {}
111
+ }
112
+ }
113
+ );
114
+ this.setupToolHandlers();
115
+ }
116
+ /**
117
+ * Setup tool handlers for the MCP server.
118
+ * MCP サーバーのツールハンドラーを設定
119
+ */
120
+ setupToolHandlers() {
121
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
122
+ return {
123
+ tools: [
124
+ {
125
+ name: "html_to_markdown",
126
+ description: "Convert web pages or HTML strings to Markdown format. Handles JavaScript-rendered dynamic content and preserves table structures.",
127
+ inputSchema: MCP_TOOL_SCHEMAS.htmlToMarkdown
128
+ },
129
+ {
130
+ name: "save_content_to_file",
131
+ description: "Save text content to a file with specified path or directory. Supports automatic filename generation and format detection.",
132
+ inputSchema: MCP_TOOL_SCHEMAS.saveContentToFile
133
+ },
134
+ {
135
+ name: "url_to_markdown_file",
136
+ description: "Convert web pages or HTML strings directly to Markdown files. Combines HTML-to-Markdown conversion and file saving in one step. Handles JavaScript-rendered dynamic content.",
137
+ inputSchema: MCP_TOOL_SCHEMAS.urlToMarkdownFile
138
+ }
139
+ ]
140
+ };
141
+ });
142
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
143
+ switch (request.params.name) {
144
+ case "html_to_markdown": {
145
+ return await this.handleHtmlToMarkdown(request.params.arguments);
146
+ }
147
+ case "save_content_to_file": {
148
+ return await this.handleSaveContentToFile(request.params.arguments);
149
+ }
150
+ case "url_to_markdown_file": {
151
+ return await this.handleUrlToMarkdownFile(request.params.arguments);
152
+ }
153
+ default:
154
+ throw new Error(`Unknown tool: ${request.params.name}`);
155
+ }
156
+ });
157
+ }
158
+ /**
159
+ * Handle HTML to Markdown conversion tool call.
160
+ * HTML から Markdown への変換ツール呼び出しを処理
161
+ */
162
+ async handleHtmlToMarkdown(args) {
163
+ try {
164
+ const validatedArgs = parseHtmlToMarkdownArgs(args);
165
+ const { url, html_content, locale = "en-US" } = validatedArgs;
166
+ if (url) {
167
+ try {
168
+ new URL(url);
169
+ } catch {
170
+ throw new Error(`Invalid URL: ${url}`);
171
+ }
172
+ }
173
+ const result = await htmlToMarkdown(url || "direct-html-input", {
174
+ locale,
175
+ htmlContent: html_content
176
+ });
177
+ const responseText = result.markdown;
178
+ return createSuccessResult(responseText);
179
+ } catch (error) {
180
+ return createErrorResult(error);
181
+ }
182
+ }
183
+ /**
184
+ * Handle save content to file tool call.
185
+ * ファイル保存ツール呼び出しを処理
186
+ */
187
+ async handleSaveContentToFile(args) {
188
+ try {
189
+ const validatedArgs = parseSaveContentArgs(args);
190
+ const { content, save_path, save_directory, filename } = validatedArgs;
191
+ const savedPath = await saveContentToFile(content, save_path, save_directory, filename);
192
+ const responseText = `File saved successfully to: ${savedPath}`;
193
+ return createSuccessResult(responseText);
194
+ } catch (error) {
195
+ return createErrorResult(error);
196
+ }
197
+ }
198
+ /**
199
+ * Handle URL to Markdown file conversion tool call.
200
+ * URL から Markdown ファイルへの変換ツール呼び出しを処理
201
+ */
202
+ async handleUrlToMarkdownFile(args) {
203
+ try {
204
+ const validatedArgs = parseUrlToMarkdownFileArgs(args);
205
+ const { url, html_content, locale = "en-US", save_path, save_directory, filename } = validatedArgs;
206
+ if (url) {
207
+ try {
208
+ new URL(url);
209
+ } catch {
210
+ throw new Error(`Invalid URL: ${url}`);
211
+ }
212
+ }
213
+ const result = await htmlToMarkdown(url || "direct-html-input", {
214
+ locale,
215
+ htmlContent: html_content
216
+ });
217
+ const markdownContent = result.markdown;
218
+ let finalFilename = filename;
219
+ if (!finalFilename && url) {
220
+ finalFilename = this.generateFilenameFromUrl(url);
221
+ }
222
+ const savedPath = await saveContentToFile(markdownContent, save_path, save_directory, finalFilename);
223
+ const responseText = `URL successfully converted to Markdown and saved to: ${savedPath}`;
224
+ return createSuccessResult(responseText);
225
+ } catch (error) {
226
+ return createErrorResult(error);
227
+ }
228
+ }
229
+ /**
230
+ * Generate filename from URL.
231
+ * URL からファイル名を生成
232
+ */
233
+ generateFilenameFromUrl(url) {
234
+ try {
235
+ const urlObj = new URL(url);
236
+ let filename = urlObj.pathname.split("/").pop() || "page";
237
+ filename = filename.replace(/\.[^.]*$/, "");
238
+ filename = filename.replace(/[<>:"/\\|?*]/g, "_");
239
+ if (!filename || filename.match(/^\.+$/)) {
240
+ filename = "page";
241
+ }
242
+ return filename;
243
+ } catch {
244
+ return "page";
245
+ }
246
+ }
247
+ /**
248
+ * Start the MCP server.
249
+ * MCP サーバーを開始
250
+ */
251
+ async start() {
252
+ const transport = new StdioServerTransport();
253
+ await this.server.connect(transport);
254
+ console.info("HTML to Markdown MCP server started");
255
+ }
256
+ }
257
+ const createMcpServer = () => {
258
+ return new HtmlToMarkdownMcpServer();
259
+ };
260
+ const runMcpServer = async () => {
261
+ const server = createMcpServer();
262
+ await server.start();
263
+ };
264
+ export {
265
+ createMcpServer as c,
266
+ runMcpServer as r
267
+ };