@aiquants/html-to-markdown 0.1.7 → 0.2.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 +27 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +2 -2
- package/dist/mcp-server-B5lrAMe3.cjs +1 -0
- package/dist/{mcp-server-CpmnW1p8.js → mcp-server-Ct2TYHcU.js} +110 -29
- package/dist/mcp-server-streamable-B3R3qi85.cjs +1 -0
- package/dist/{mcp-server-streamable-CQD5zK30.js → mcp-server-streamable-C6-qOLdK.js} +106 -11
- package/dist/mcp-streamable.cjs +1 -1
- package/dist/mcp-streamable.js +1 -1
- package/dist/mcp.cjs +1 -1
- package/dist/mcp.js +1 -1
- package/dist/src/core.d.ts +1 -1
- package/dist/src/i18n.d.ts +1 -1
- package/dist/src/index.d.ts +4 -4
- package/dist/src/mcp-server-streamable.d.ts +2 -0
- package/dist/src/mcp-server.d.ts +2 -0
- package/dist/src/plugins/index.d.ts +5 -5
- package/package.json +7 -2
- package/dist/mcp-server-D4Mj1IQ9.cjs +0 -1
- package/dist/mcp-server-streamable-Bu7QAr3W.cjs +0 -1
package/README.md
CHANGED
|
@@ -133,9 +133,35 @@ npx --package=@aiquants/html-to-markdown aiq-html2md-mcp-stream
|
|
|
133
133
|
#### MCP Tools Available
|
|
134
134
|
|
|
135
135
|
- **html_to_markdown**: Convert HTML content from a URL to Markdown format
|
|
136
|
-
- `url` (required): The URL of the web page to convert
|
|
136
|
+
- `url` (required*): The URL of the web page to convert
|
|
137
|
+
- `html_content` (required*): HTML content as a string to convert (alternative to URL)
|
|
137
138
|
- `locale` (optional): Browser locale (`en-US` or `ja-JP`, defaults to `en-US`)
|
|
138
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
|
+
|
|
143
|
+
*Either `url` or `html_content` is required
|
|
144
|
+
|
|
145
|
+
**File Saving Options:**
|
|
146
|
+
|
|
147
|
+
- **`save_path`**: Specify the complete file path including filename and extension where you want to save the content.
|
|
148
|
+
- Example: `/path/to/output/my-page.md`
|
|
149
|
+
- The directory will be created automatically if it doesn't exist.
|
|
150
|
+
- You cannot use both `save_path` and `save_directory` at the same time.
|
|
151
|
+
|
|
152
|
+
- **`save_directory`**: Specify only the directory where you want to save the file. The filename will be auto-generated based on the URL.
|
|
153
|
+
- Example: `/path/to/output/` (filename will be auto-generated like `page.md`)
|
|
154
|
+
- For URLs like `https://example.com/articles/my-article`, the filename becomes `my-article.md`
|
|
155
|
+
- The directory will be created automatically if it doesn't exist.
|
|
156
|
+
- You cannot use both `save_path` and `save_directory` at the same time.
|
|
157
|
+
|
|
158
|
+
**File Saving Behavior:**
|
|
159
|
+
|
|
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`
|
|
139
165
|
|
|
140
166
|
- **html_to_markdown_streamable**: Same as above but with streamable MCP support for progress updates
|
|
141
167
|
|
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-
|
|
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-B5lrAMe3.cjs"),c=require("./mcp-server-streamable-B3R3qi85.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-DU_246Q7.js";
|
|
9
|
-
import { c, r } from "./mcp-server-
|
|
10
|
-
import { c as c2, r as r2 } from "./mcp-server-streamable-
|
|
9
|
+
import { c, r } from "./mcp-server-Ct2TYHcU.js";
|
|
10
|
+
import { c as c2, r as r2 } from "./mcp-server-streamable-C6-qOLdK.js";
|
|
11
11
|
export {
|
|
12
12
|
c as createMcpServer,
|
|
13
13
|
c2 as createStreamableMcpServer,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const t=new(require("happy-dom").Window);Object.assign(global,{document:t.document,window:t,self:t});const e=require("./version-DTaUKNBS.cjs"),r=require("node:process"),n=require("fs"),o=require("path"),a=require("./core-D2dlEt1l.cjs");class s{append(t){this._buffer=this._buffer?Buffer.concat([this._buffer,t]):t}readMessage(){if(!this._buffer)return null;const t=this._buffer.indexOf("\n");if(-1===t)return null;const r=this._buffer.toString("utf8",0,t).replace(/\r$/,"");return this._buffer=this._buffer.subarray(t+1),function(t){return e.JSONRPCMessageSchema.parse(JSON.parse(t))}(r)}clear(){this._buffer=void 0}}class i{constructor(t=r.stdin,e=r.stdout){this._stdin=t,this._stdout=e,this._readBuffer=new s,this._started=!1,this._ondata=t=>{this._readBuffer.append(t),this.processReadBuffer()},this._onerror=t=>{var e;null===(e=this.onerror)||void 0===e||e.call(this,t)}}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 t,e;;)try{const e=this._readBuffer.readMessage();if(null===e)break;null===(t=this.onmessage)||void 0===t||t.call(this,e)}catch(r){null===(e=this.onerror)||void 0===e||e.call(this,r)}}async close(){var t;this._stdin.off("data",this._ondata),this._stdin.off("error",this._onerror);0===this._stdin.listenerCount("data")&&this._stdin.pause(),this._readBuffer.clear(),null===(t=this.onclose)||void 0===t||t.call(this)}send(t){return new Promise(e=>{const r=function(t){return JSON.stringify(t)+"\n"}(t);this._stdout.write(r)?e():this._stdout.once("drain",e)})}}function c(t,e){if(t&&"direct-html-input"!==t)try{let e=new URL(t).pathname.split("/").pop()||"page";return e=e.replace(/\.[^.]*$/,""),e=e.replace(/[<>:"/\\|?*]/g,"_"),e&&!e.match(/^\.+$/)||(e="page"),e}catch{return"page"}return"page"}async function h(t,e,r,a,s){if(!e&&!r)return null;let i;if(e)i=o.resolve(e);else{if(!r)return null;{const t=c(a),e="html"===s?".html":".md";i=o.resolve(r,`${t}${e}`)}}return await n.promises.mkdir(o.dirname(i),{recursive:!0}),await n.promises.writeFile(i,t,"utf-8"),i}class l{constructor(){this.version=e.getPackageVersion(),this.server=new e.Server({name:"@aiquants/html-to-markdown",version:this.version,description:"A tool to dynamically fetch a web page from a given URL and convert it to Markdown."},{capabilities:{tools:{}}}),this.setupToolHandlers()}setupToolHandlers(){this.server.setRequestHandler(e.ListToolsRequestSchema,async()=>({tools:[{name:"html_to_markdown",description:"Convert HTML content from a URL or HTML string to Markdown format. Supports dynamic content loading and preserves table structures.",inputSchema:{type:"object",properties:{url:{type:"string",description:"The URL of the web page to convert to Markdown"},html_content:{type:"string",description:"HTML content as a string to convert to Markdown (alternative to URL)"},locale:{type:"string",description:"The locale to use for browser context (en-US or ja-JP)",enum:["en-US","ja-JP"],default:"en-US"},output_format:{type:"string",description:"Output format: 'markdown' (default), 'html', or 'both'",enum:["markdown","html","both"],default:"markdown"},save_path:{type:"string",description:"Full path (including filename) where to save the converted content. Cannot be used with save_directory."},save_directory:{type:"string",description:"Directory path where to save the converted content with auto-generated filename. Cannot be used with save_path."}},anyOf:[{required:["url"]},{required:["html_content"]}]}}]})),this.server.setRequestHandler(e.CallToolRequestSchema,async t=>{if("html_to_markdown"===t.params.name)return await this.handleHtmlToMarkdown(t.params.arguments);throw new Error(`Unknown tool: ${t.params.name}`)})}async handleHtmlToMarkdown(t){try{const e=function(t){if(!t||"object"!=typeof t)throw new Error("Arguments are required");const{url:e,html_content:r,locale:n="en-US",output_format:o="markdown",save_path:a,save_directory:s}=t;if(!e&&!r)throw new Error("Either 'url' or 'html_content' is required");if(e&&"string"!=typeof e)throw new Error("'url' must be a string");if(r&&"string"!=typeof r)throw new Error("'html_content' must be a string");if(n&&!["en-US","ja-JP"].includes(n))throw new Error("'locale' must be 'en-US' or 'ja-JP'");if(o&&!["markdown","html","both"].includes(o))throw new Error("'output_format' must be 'markdown', 'html', or 'both'");if(a&&"string"!=typeof a)throw new Error("'save_path' must be a string");if(s&&"string"!=typeof s)throw new Error("'save_directory' must be a string");if(a&&s)throw new Error("Cannot specify both 'save_path' and 'save_directory'");return{url:e,html_content:r,locale:n,output_format:o,save_path:a,save_directory:s}}(t),{url:r,html_content:n,locale:s="en-US",output_format:i="markdown",save_path:l,save_directory:d}=e;if(r)try{new URL(r)}catch{throw new Error(`Invalid URL: ${r}`)}const u=await a.htmlToMarkdown(r||"direct-html-input",{locale:s,htmlContent:n}),m=[];if(l||d)switch(i){case"markdown":{const t=await h(u.markdown,l,d,r,"markdown");t&&m.push(t);break}case"html":{const t=await h(u.html,l,d,r,"html");t&&m.push(t);break}case"both":{const t=l?l.endsWith(".md")?l:`${l}.md`:d?o.join(d,`${c(r)}.md`):void 0;if(t){const e=await h(u.markdown,t,void 0,r,"markdown");e&&m.push(e)}const e=l?l.endsWith(".html")?l:`${l}.html`:d?o.join(d,`${c(r)}.html`):void 0;if(e){const t=await h(u.html,e,void 0,r,"html");t&&m.push(t)}break}}let f="";switch(i){case"markdown":f=u.markdown;break;case"html":f=u.html;break;case"both":f=`=== Markdown ===\n${u.markdown}\n\n=== HTML ===\n${u.html}`;break;default:throw new Error(`Invalid output format: ${i}`)}m.length>0&&(f+=`\n\n=== Files Saved ===\n${m.map(t=>`- ${t}`).join("\n")}`);return{content:[{type:"text",text:f}],isError:!1}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:String(e)}`}],isError:!0}}}async start(){const t=new i;await this.server.connect(t)}}const d=()=>new l;exports.createMcpServer=d,exports.runMcpServer=async()=>{const t=d();await t.start()};
|
|
@@ -7,6 +7,8 @@ Object.assign(global, {
|
|
|
7
7
|
});
|
|
8
8
|
import { J as JSONRPCMessageSchema, g as getPackageVersion, S as Server, L as ListToolsRequestSchema, C as CallToolRequestSchema } from "./version-BRVBmpor.js";
|
|
9
9
|
import process from "node:process";
|
|
10
|
+
import { promises } from "fs";
|
|
11
|
+
import { join, resolve, dirname } from "path";
|
|
10
12
|
import { h as htmlToMarkdown } from "./core-DU_246Q7.js";
|
|
11
13
|
class ReadBuffer {
|
|
12
14
|
append(chunk) {
|
|
@@ -86,12 +88,12 @@ class StdioServerTransport {
|
|
|
86
88
|
(_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
87
89
|
}
|
|
88
90
|
send(message) {
|
|
89
|
-
return new Promise((
|
|
91
|
+
return new Promise((resolve2) => {
|
|
90
92
|
const json = serializeMessage(message);
|
|
91
93
|
if (this._stdout.write(json)) {
|
|
92
|
-
|
|
94
|
+
resolve2();
|
|
93
95
|
} else {
|
|
94
|
-
this._stdout.once("drain",
|
|
96
|
+
this._stdout.once("drain", resolve2);
|
|
95
97
|
}
|
|
96
98
|
});
|
|
97
99
|
}
|
|
@@ -100,7 +102,7 @@ function parseHtmlToMarkdownArgs(args) {
|
|
|
100
102
|
if (!args || typeof args !== "object") {
|
|
101
103
|
throw new Error("Arguments are required");
|
|
102
104
|
}
|
|
103
|
-
const { url, html_content, locale = "en-US", output_format = "markdown" } = args;
|
|
105
|
+
const { url, html_content, locale = "en-US", output_format = "markdown", save_path, save_directory } = args;
|
|
104
106
|
if (!url && !html_content) {
|
|
105
107
|
throw new Error("Either 'url' or 'html_content' is required");
|
|
106
108
|
}
|
|
@@ -116,13 +118,59 @@ function parseHtmlToMarkdownArgs(args) {
|
|
|
116
118
|
if (output_format && !["markdown", "html", "both"].includes(output_format)) {
|
|
117
119
|
throw new Error("'output_format' must be 'markdown', 'html', or 'both'");
|
|
118
120
|
}
|
|
121
|
+
if (save_path && typeof save_path !== "string") {
|
|
122
|
+
throw new Error("'save_path' must be a string");
|
|
123
|
+
}
|
|
124
|
+
if (save_directory && typeof save_directory !== "string") {
|
|
125
|
+
throw new Error("'save_directory' must be a string");
|
|
126
|
+
}
|
|
127
|
+
if (save_path && save_directory) {
|
|
128
|
+
throw new Error("Cannot specify both 'save_path' and 'save_directory'");
|
|
129
|
+
}
|
|
119
130
|
return {
|
|
120
131
|
url,
|
|
121
132
|
html_content,
|
|
122
133
|
locale,
|
|
123
|
-
output_format
|
|
134
|
+
output_format,
|
|
135
|
+
save_path,
|
|
136
|
+
save_directory
|
|
124
137
|
};
|
|
125
138
|
}
|
|
139
|
+
function generateFilename(url, outputFormat) {
|
|
140
|
+
if (url && url !== "direct-html-input") {
|
|
141
|
+
try {
|
|
142
|
+
const urlObj = new URL(url);
|
|
143
|
+
let filename = urlObj.pathname.split("/").pop() || "page";
|
|
144
|
+
filename = filename.replace(/\.[^.]*$/, "");
|
|
145
|
+
filename = filename.replace(/[<>:"/\\|?*]/g, "_");
|
|
146
|
+
if (!filename || filename.match(/^\.+$/)) {
|
|
147
|
+
filename = "page";
|
|
148
|
+
}
|
|
149
|
+
return filename;
|
|
150
|
+
} catch {
|
|
151
|
+
return "page";
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return "page";
|
|
155
|
+
}
|
|
156
|
+
async function saveContentToFile(content, savePath, saveDirectory, url, outputFormat) {
|
|
157
|
+
if (!savePath && !saveDirectory) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
let finalPath;
|
|
161
|
+
if (savePath) {
|
|
162
|
+
finalPath = resolve(savePath);
|
|
163
|
+
} else if (saveDirectory) {
|
|
164
|
+
const filename = generateFilename(url);
|
|
165
|
+
const extension = outputFormat === "html" ? ".html" : ".md";
|
|
166
|
+
finalPath = resolve(saveDirectory, `${filename}${extension}`);
|
|
167
|
+
} else {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
await promises.mkdir(dirname(finalPath), { recursive: true });
|
|
171
|
+
await promises.writeFile(finalPath, content, "utf-8");
|
|
172
|
+
return finalPath;
|
|
173
|
+
}
|
|
126
174
|
class HtmlToMarkdownMcpServer {
|
|
127
175
|
constructor() {
|
|
128
176
|
this.version = getPackageVersion();
|
|
@@ -173,6 +221,14 @@ class HtmlToMarkdownMcpServer {
|
|
|
173
221
|
description: "Output format: 'markdown' (default), 'html', or 'both'",
|
|
174
222
|
enum: ["markdown", "html", "both"],
|
|
175
223
|
default: "markdown"
|
|
224
|
+
},
|
|
225
|
+
save_path: {
|
|
226
|
+
type: "string",
|
|
227
|
+
description: "Full path (including filename) where to save the converted content. Cannot be used with save_directory."
|
|
228
|
+
},
|
|
229
|
+
save_directory: {
|
|
230
|
+
type: "string",
|
|
231
|
+
description: "Directory path where to save the converted content with auto-generated filename. Cannot be used with save_path."
|
|
176
232
|
}
|
|
177
233
|
},
|
|
178
234
|
anyOf: [{ required: ["url"] }, { required: ["html_content"] }]
|
|
@@ -198,7 +254,7 @@ class HtmlToMarkdownMcpServer {
|
|
|
198
254
|
async handleHtmlToMarkdown(args) {
|
|
199
255
|
try {
|
|
200
256
|
const validatedArgs = parseHtmlToMarkdownArgs(args);
|
|
201
|
-
const { url, html_content, locale = "en-US", output_format = "markdown" } = validatedArgs;
|
|
257
|
+
const { url, html_content, locale = "en-US", output_format = "markdown", save_path, save_directory } = validatedArgs;
|
|
202
258
|
if (url) {
|
|
203
259
|
try {
|
|
204
260
|
new URL(url);
|
|
@@ -210,39 +266,64 @@ class HtmlToMarkdownMcpServer {
|
|
|
210
266
|
locale,
|
|
211
267
|
htmlContent: html_content
|
|
212
268
|
});
|
|
213
|
-
|
|
269
|
+
const savedPaths = [];
|
|
270
|
+
if (save_path || save_directory) {
|
|
271
|
+
switch (output_format) {
|
|
272
|
+
case "markdown": {
|
|
273
|
+
const savedPath = await saveContentToFile(result.markdown, save_path, save_directory, url, "markdown");
|
|
274
|
+
if (savedPath) savedPaths.push(savedPath);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case "html": {
|
|
278
|
+
const savedPath = await saveContentToFile(result.html, save_path, save_directory, url, "html");
|
|
279
|
+
if (savedPath) savedPaths.push(savedPath);
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
case "both": {
|
|
283
|
+
const markdownPath = save_path ? save_path.endsWith(".md") ? save_path : `${save_path}.md` : save_directory ? join(save_directory, `${generateFilename(url)}.md`) : void 0;
|
|
284
|
+
if (markdownPath) {
|
|
285
|
+
const savedMarkdownPath = await saveContentToFile(result.markdown, markdownPath, void 0, url, "markdown");
|
|
286
|
+
if (savedMarkdownPath) savedPaths.push(savedMarkdownPath);
|
|
287
|
+
}
|
|
288
|
+
const htmlPath = save_path ? save_path.endsWith(".html") ? save_path : `${save_path}.html` : save_directory ? join(save_directory, `${generateFilename(url)}.html`) : void 0;
|
|
289
|
+
if (htmlPath) {
|
|
290
|
+
const savedHtmlPath = await saveContentToFile(result.html, htmlPath, void 0, url, "html");
|
|
291
|
+
if (savedHtmlPath) savedPaths.push(savedHtmlPath);
|
|
292
|
+
}
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
let responseText = "";
|
|
214
298
|
switch (output_format) {
|
|
215
299
|
case "markdown":
|
|
216
|
-
|
|
217
|
-
{
|
|
218
|
-
type: "text",
|
|
219
|
-
text: result.markdown
|
|
220
|
-
}
|
|
221
|
-
];
|
|
300
|
+
responseText = result.markdown;
|
|
222
301
|
break;
|
|
223
302
|
case "html":
|
|
224
|
-
|
|
225
|
-
{
|
|
226
|
-
type: "text",
|
|
227
|
-
text: result.html
|
|
228
|
-
}
|
|
229
|
-
];
|
|
303
|
+
responseText = result.html;
|
|
230
304
|
break;
|
|
231
305
|
case "both":
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
{
|
|
238
|
-
type: "text",
|
|
239
|
-
text: result.html
|
|
240
|
-
}
|
|
241
|
-
];
|
|
306
|
+
responseText = `=== Markdown ===
|
|
307
|
+
${result.markdown}
|
|
308
|
+
|
|
309
|
+
=== HTML ===
|
|
310
|
+
${result.html}`;
|
|
242
311
|
break;
|
|
243
312
|
default:
|
|
244
313
|
throw new Error(`Invalid output format: ${output_format}`);
|
|
245
314
|
}
|
|
315
|
+
if (savedPaths.length > 0) {
|
|
316
|
+
responseText += `
|
|
317
|
+
|
|
318
|
+
=== Files Saved ===
|
|
319
|
+
${savedPaths.map((path) => `- ${path}`).join("\n")}`;
|
|
320
|
+
}
|
|
321
|
+
const content = [
|
|
322
|
+
{
|
|
323
|
+
type: "text",
|
|
324
|
+
text: responseText
|
|
325
|
+
}
|
|
326
|
+
];
|
|
246
327
|
return {
|
|
247
328
|
content,
|
|
248
329
|
isError: false
|