@imenam/mcp-documentor 1.0.102 → 1.0.104
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 +2 -0
- package/dist/src/gui-worker.js +12 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -152,6 +152,8 @@ Images can be embedded in any documentation entry using standard markdown syntax
|
|
|
152
152
|
 <!-- external URL -->
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
+
The GUI serves uploaded images under `/api/images/<project>/<file>`, where `<project>` and `<file>` are single path segments; any other shape is refused. Images are served with `X-Content-Type-Options: nosniff` and a `sandbox` Content-Security-Policy, so an SVG opened on its own runs no script.
|
|
156
|
+
|
|
155
157
|
**Via the MCP tool** — use `upload_image` with the absolute path of the file on the server:
|
|
156
158
|
|
|
157
159
|
```json
|
package/dist/src/gui-worker.js
CHANGED
|
@@ -1016,6 +1016,8 @@ app.post("/api/images", async (c) => {
|
|
|
1016
1016
|
const project = c.req.query("project");
|
|
1017
1017
|
if (!project)
|
|
1018
1018
|
return c.json({ error: "project is required" }, 400);
|
|
1019
|
+
if (!isSafeSegment(project))
|
|
1020
|
+
return c.json({ error: "project must be a single path segment" }, 400);
|
|
1019
1021
|
let body;
|
|
1020
1022
|
try {
|
|
1021
1023
|
body = await c.req.parseBody();
|
|
@@ -1035,6 +1037,8 @@ app.post("/api/images", async (c) => {
|
|
|
1035
1037
|
});
|
|
1036
1038
|
app.get("/api/images/:project", (c) => {
|
|
1037
1039
|
const project = c.req.param("project");
|
|
1040
|
+
if (!isSafeSegment(project))
|
|
1041
|
+
return c.json({ error: "project must be a single path segment" }, 400);
|
|
1038
1042
|
const assetsDir = path.join(dataDir, "projects", project, "assets");
|
|
1039
1043
|
if (!fs.existsSync(assetsDir))
|
|
1040
1044
|
return c.json([]);
|
|
@@ -1044,13 +1048,20 @@ app.get("/api/images/:project", (c) => {
|
|
|
1044
1048
|
app.get("/api/images/:project/:filename", (c) => {
|
|
1045
1049
|
const project = c.req.param("project");
|
|
1046
1050
|
const filename = c.req.param("filename");
|
|
1051
|
+
if (!isSafeSegment(project) || !isSafeSegment(filename))
|
|
1052
|
+
return c.notFound();
|
|
1047
1053
|
const filePath = path.join(dataDir, "projects", project, "assets", filename);
|
|
1048
1054
|
if (!fs.existsSync(filePath))
|
|
1049
1055
|
return c.notFound();
|
|
1050
1056
|
const ext = path.extname(filename).toLowerCase().slice(1);
|
|
1051
1057
|
const mime = IMAGE_MIME[ext] ?? "application/octet-stream";
|
|
1052
1058
|
return new Response(fs.readFileSync(filePath), {
|
|
1053
|
-
headers: {
|
|
1059
|
+
headers: {
|
|
1060
|
+
"Content-Type": mime,
|
|
1061
|
+
"X-Content-Type-Options": "nosniff",
|
|
1062
|
+
// An SVG opened on its own must not run scripts under the hub's origin.
|
|
1063
|
+
"Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; sandbox",
|
|
1064
|
+
},
|
|
1054
1065
|
});
|
|
1055
1066
|
});
|
|
1056
1067
|
// Static files
|