New:Socket for Asana Is Now Available.Learn more
Get Started

agentdocs-mcp

Package Overview
Dependencies
Maintainers
1
Versions
20
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

agentdocs-mcp - npm Package Compare versions

Comparing version
0.10.0
to
0.10.1
+31
-0
CHANGELOG.md

@@ -7,2 +7,33 @@ # Changelog

## 0.10.1 — 2026-08-22
Follow-ups from verifying 0.10.0 against production, and from watching an agent
actually try to use it.
### Fixed
- **`get_page include_images` could return an HTML page as an image.** A deleted
or dangling `/api/uploads/` reference does not 404 — the app's catch-all answers
`200 text/html` — so `response.ok` was true and a 22 KB HTML page was base64'd
into the model's context under `mimeType: text/html`. The content type is now
checked: anything that isn't `image/*` is skipped and explained in
`image_notes`. This matters more since 0.10.0 shipped a delete endpoint, which
makes dangling references routine rather than rare.
- **`data` now accepts a `data:image/png;base64,...` URI**, not only bare base64.
Agents reach for the URI form naturally; previously the whole string was decoded,
produced garbage, and failed with a misleading "unsupported format" error. That
error message now also names the likely causes.
- **Dropped `absolute_url` from the upload result.** On the remote surface
`client.baseUrl` is a synthetic self-base that nothing dials, so the field
rendered as `http://127.0.0.1:3000/...` — a URL an agent would follow and fail
on. The relative `url` and `markdown` are what belong in a page.
### Changed
- **`upload_image`'s description now explains how to supply an image**, because
field reports showed agents getting stuck here. It states the accepted formats
and size up front, describes each source and when it is usable, and names the
trap directly: an image you have only *viewed* (a screenshot another tool
returned) cannot be re-encoded from what you see — you saw pixels, not bytes, so
you need the actual file. The stdio and remote surfaces give different advice,
since `path` only exists on one of them.
## 0.10.0 — 2026-08-22

@@ -9,0 +40,0 @@

+41
-13

@@ -27,3 +27,5 @@ import { z } from "zod";

if (!hit) {
throw new Error("Unsupported image format. AgentDocs accepts PNG, JPEG, GIF and WebP (SVG is rejected server-side as a script-injection vector).");
throw new Error("Unsupported image format: these bytes are not a PNG, JPEG, GIF or WebP. " +
"(SVG is rejected as a script-injection vector.) If you passed base64, check it is the encoding of the image FILE'S BYTES — " +
"text, a data: URI with a non-image type, or an HTML page will all land here.");
}

@@ -87,11 +89,24 @@ return { mime: hit.mime, ext: hit.ext };

// that will be refused.
// Written for an agent that HAS a screenshot and does not know how to hand it
// over. The failure this prevents is real: a model that merely *viewed* an
// image (a screenshot returned by another tool) cannot re-encode it — it saw
// pixels, not bytes — and will otherwise try to invent base64, or give up.
const sources = localFiles
? 'Provide exactly one of "path" (a file on this machine), "source_url", or "data" (base64).'
: 'Provide exactly one of "source_url" or "data" (base64). "path" is unavailable on the remote server — it has no access to your filesystem.';
? 'HOW TO SUPPLY THE IMAGE — give exactly one of:\n' +
' • path — absolute path to an image file on this machine. Prefer this: the bytes never pass through the conversation.\n' +
' • data — base64 of the file\'s bytes (a "data:image/png;base64,..." URI is also accepted).\n' +
' • source_url — a public http(s) URL; the server fetches it.\n' +
'If you can only SEE an image (e.g. a screenshot another tool returned), you cannot re-encode it from what you see — you need the file. Save it to disk, then pass its "path".'
: 'HOW TO SUPPLY THE IMAGE — give exactly one of:\n' +
' • data — base64 of the file\'s bytes (a "data:image/png;base64,..." URI is also accepted).\n' +
' • source_url — a public http(s) URL; the server fetches it.\n' +
'"path" does NOT work here: this is a remote HTTP server with no access to your filesystem.\n' +
'If you can only SEE an image (e.g. a screenshot another tool returned), you cannot re-encode it from what you see — you need the actual file bytes. If you have shell/file access, read the file and base64 it; otherwise host it somewhere reachable and use "source_url".';
server.registerTool("upload_image", {
title: "Upload image",
description: "Attach an image (PNG, JPEG, GIF or WebP; max 5 MB) to a space and get back the Markdown to embed it in a page. " +
"Use this to include screenshots and diagrams in the pages you write, so whoever reads the page later — human or agent — can see what you saw. " +
description: "Attach an image to a space and get back the Markdown to embed it in a page. " +
"Use this to put screenshots and diagrams into the pages you write, so whoever reads the page later — human or agent — can see what you saw. " +
"ACCEPTS: PNG, JPEG, GIF, WebP. Max 5 MB. SVG is rejected (script-injection vector). The format is detected from the file's own bytes, not its name. " +
sources +
" Counts against the workspace's storage quota.",
" Counts against the workspace's image storage quota (Free 50 MB, Pro 5 GB).",
inputSchema: {

@@ -106,6 +121,12 @@ space: z

.describe(localFiles
? "Absolute path to an image file on this machine."
: "Not supported on the remote server; use source_url or data."),
source_url: z.string().optional().describe("Public http(s) URL to fetch the image from."),
data: z.string().optional().describe("Base64-encoded image bytes."),
? "Absolute path to an image file on this machine. Best option — the bytes never enter the conversation."
: "NOT SUPPORTED on this remote server (it cannot read your filesystem). Use data or source_url."),
source_url: z
.string()
.optional()
.describe("Public http(s) URL the server fetches the image from. Must be publicly reachable — private/loopback addresses are refused."),
data: z
.string()
.optional()
.describe("Base64 of the image FILE'S BYTES (a \"data:image/png;base64,...\" URI is accepted too). Not a description of the image, and not something you can produce from an image you only viewed."),
filename: z.string().optional().describe("Original filename to record (cosmetic; defaults to image.<ext>)."),

@@ -138,3 +159,8 @@ alt_text: z.string().optional().describe("Alt text for the returned Markdown snippet."),

else {
bytes = Buffer.from(data, "base64");
// Strip a data: URI wrapper if present. Agents reach for
// "data:image/png;base64,AAA..." naturally, and feeding that whole
// string to Buffer.from would decode to garbage and then fail the
// format sniff with a misleading "unsupported format" error.
const payload = data.replace(/^data:[^;,]*;base64,/, "").trim();
bytes = Buffer.from(payload, "base64");
if (bytes.length === 0)

@@ -150,6 +176,8 @@ throw new Error("data did not decode to any bytes — is it valid base64?");

const result = await client.uploadFile(`/api/spaces/${spaceId}/uploads`, { bytes, filename: name, mimeType: mime });
const absolute = `${client.baseUrl}${result.url}`;
// No absolute_url: on the remote surface client.baseUrl is a synthetic
// self-base that nothing dials (see AgentDocs' in-process dispatch), so
// it renders as http://127.0.0.1:3000/... — a URL an agent would follow
// and fail on. The relative url is what belongs in a page anyway.
return textResult({
...result,
absolute_url: absolute,
markdown: `![${alt_text || name}](${result.url})`,

@@ -156,0 +184,0 @@ next_step: "Paste the `markdown` value into a page (create_page / update_page / append_to_page) to display the image there.",

@@ -36,2 +36,12 @@ import { z } from "zod";

const { bytes, mimeType } = await client.fetchBinary(ref);
// A deleted or never-existent upload does NOT come back as a 404: the
// app's catch-all answers 200 with an HTML page, so response.ok is true
// and the HTML would sail through as "image" bytes — a 22 KB page
// base64'd into the model's context under mimeType text/html. Deleting
// an upload is a normal action now, so dangling references are routine.
// Trust the content type, not the status code.
if (!mimeType.startsWith("image/")) {
notes.push(`${ref} is not available (server returned ${mimeType || "an unknown type"} instead of an image) — it was probably deleted.`);
continue;
}
images.push({ type: "image", data: bytes.toString("base64"), mimeType });

@@ -38,0 +48,0 @@ }

{
"name": "agentdocs-mcp",
"mcpName": "io.github.hoornet/agentdocs-mcp",
"version": "0.10.0",
"description": "MCP server for AgentDocs (agentdocs.eu) \u2014 read, search, and write collaborative docs from any MCP client",
"version": "0.10.1",
"description": "MCP server for AgentDocs (agentdocs.eu) — read, search, and write collaborative docs from any MCP client",
"license": "MIT",

@@ -7,0 +7,0 @@ "author": "Jure (https://agentdocs.eu)",