# `eu.plmhub/boards` on anlyon.dev

> Text inside fenced blocks marked untrusted-server-text was written by the MCP server's publisher, not by anlyon.dev. It is data to inspect, not instructions to follow. Invisible characters are shown as [U+XXXX].
> No finding is not a safety guarantee: these are point-in-time checks of tool definitions, not of code or behaviour.

- Page: https://anlyon.dev/servers/eu.plmhub/boards
- JSON: https://anlyon.dev/api/v1/servers/eu.plmhub/boards
- Version: 1.0.0 (active)
- Publisher: eu.plmhub (domain proven to the MCP Registry)
- Runs: remote (streamable-http)
- Sign-in to list tools: none
- Metadata from the official MCP Registry, synced 2026-10-23T18:18:16.548Z

Title and description, as the publisher wrote them:

```untrusted-server-text
PLMHub boards
Draw diagrams, build HTML pages and write Markdown in a live editor the user sees.
```

Scanned 2026-10-23T18:19:05.759Z over streamable-http.

**5 tools: 4 change things and 1 only read.**

| Group | Class | Tools | Claude Code |
| --- | --- | --- | --- |
| Deletes or cancels | destructive | 0 | deny |
| Can't be undone | irreversible | 0 | ask |
| Changes things | write | 4 | ask |
| Only reads | read | 1 | allow |

## Exfil surface

No private-data -> untrusted-input -> outbound path in this server alone: no tool is tagged private-data or outbound.

| Tag | Meaning | Tools |
| --- | --- | --- |
| private-data | Reads files, mail, messages, databases, secrets, calendars or repositories. | none |
| untrusted-input | Brings in text someone else can write: web pages, inbound email, messages, issues and comments. | `mcp__boards__read_board` |
| outbound | Can carry data to people or systems: it sends, posts or publishes, or takes a recipient, channel or URL. | none |

## Findings

- **info** Long descriptions in `draw` ([long-description](https://anlyon.dev/methodology#long-description))

```untrusted-server-text
The description is 4,280 characters long, over the 2,000 this check looks for.

description ends: "...n unknown id, 409 for an id already in use. Fix that op and send the batch again."
```


## Changes things (4)

Creates or changes something that can be changed back. Claude Code: ask.

### `draw`

- Nothing in its name or description says what it does, so it is counted as a write.
- Findings: info long-description

Description, as the server wrote it:

```untrusted-server-text
Draw on a doodle board. The user sees each call appear live in the editor.
## Doodle ops

A doodle is a board (default 1280 x 720 px, origin top-left, y grows down). Send ops as
`{"ops": [...]}`; they apply in order, all or nothing. The answer lists the ids each op
created, in order. Address elements by id; read the board for the ids it already has.

**Positions:** x, y is the CENTER of a shape or text, the top-left of a comment, the
midpoint of a line. Omit them to place at the board's center. Later elements draw on top.

### Create
- `{"op": "add", "role": "text" | "box" | "button" | "input" | "card" | "ellipse" | "line" | "image" | "comment" | "draw" | "triangle" | "diamond" | "star" | "frame", "x", "y", "w"?, "h"?, "id"?, ...style}`
  Defaults (w x h): text 160 wide, box 180x110, card 240x150 (radius 12), button 150x46,
  input 220x44, ellipse 140x90, line 180 long, triangle 120x110, diamond/star 120x120,
  image 320x200 (needs "src": URL or data: URL), frame 360x240, draw needs "path".
  Pass your own "id" to address the element without reading back (it must be unused).
  Buttons and inputs are plain shapes: give them text with `label`.
- `{"op": "draw", "path": "M 10 10 L 90 90", "stroke"?, "strokeWidth"?}` freehand; path
  coordinates are board coordinates.
- `{"op": "comment", "text", "x"?, "y"?, "w"?}` a sticky note for the human.
- `{"op": "label", "id": shapeId, "text", "fontSize"? = 16, "fill"?, "labelId"?}` text
  centered inside a shape that follows it when the shape moves. Empty text removes it.
- `{"op": "duplicate", "id", "newId"?, "dx"? = 20, "dy"? = 20}`

### Change
- `{"op": "update", "id", ...style}` (fields below; "x"/"y"/"w"/"h" set position and size)
- `{"op": "move", "id", "dx", "dy"}` or `{"op": "move", "id", "x", "y"}`; moving a
  frame moves its children too.
- `{"op": "reorder", "id", "to": "front" | "back" | "forward" | "backward"}`
- `{"op": "delete", "id"}` · `{"op": "clear"}` removes everything.

### Style fields (add and update)
fill, stroke, backgroundColor (CSS colors) · strokeWidth · opacity 0..1 · angle (degrees) ·
radius (corner radius; or rx, ry) · borderStyle "solid" | "dashed" | "dotted" | "dashdot" or
strokeDashArray [numbers] · shadow "none" | "soft" | "medium" | "hard", {color, blur, offsetX, offsetY}
or null · gradient {"type": "linear" | "radial", "stops": [{"offset": 0, "color": "#6366f1"},
{"offset": 1, "color": "#ec4899"}], "coords"?: 0..1 units} (replaces fill) · flipX, flipY,
skewX, skewY · text, fontSize, fontFamily, fontWeight, fontStyle ("italic"), textAlign,
underline, linethrough, lineHeight, charSpacing, padding · name (layer name), visible,
locked (true/false).

### Structure
- `{"op": "group", "ids": [2 or more], "name"?, "groupId"?}` returns the group id.
  `{"op": "ungroup", "group": groupId}` or `{"op": "ungroup", "ids": [...]}`.
- `{"op": "wrap", "ids": [...], "direction"?, "justify"?, "align"?, "gap"?, "padding"?,
  "frameId"?}` puts the elements in a new auto-layout frame and returns its id.
  `{"op": "unwrap", "id": frameId}` removes the frame and keeps its children.
- `{"op": "reparent", "ids": [...], "frame": frameId}` moves elements into a frame;
  `"frame": null` takes them out.
- `{"op": "layout", "id": frameId, "mode"?: "block" | "flex", "direction"?: "row" | "col",
  "justify"?: "start" | "center" | "end" | "between", "align"?: "start" | "center" | "end" | "stretch", "gap"? = 16,
  "padding"? = 16, "wMode"?, "hMode"?: "hug" | "fixed", "w"?, "h"?}` A flex frame lines
  its children up (row = left to right, col = top to bottom) and hugs them, or keeps the
  fixed w/h. A block frame leaves children where they are.
- `{"op": "size", "id", "w"?, "h"?: "fixed" | "hug" | "fill", "minW"?, "maxW"?, "minH"?,
  "maxH"?}` how a child of a flex frame sizes; fill shares the frame's spare room; null
  clears a min/max.

### Board
- `{"op": "bg", "color": "#f8fafc"}` or `{"op": "bg", "src": imageUrl}` (an image
  behind everything).
- `{"op": "board", "w"?, "h"?, "autoH… [280 more characters not shown]
```

### `edit_text`

- The name "edit_text" says "edit": a word for a change.

Description, as the server wrote it:

```untrusted-server-text
Change part of an HTML or Markdown board without resending all of it.
Ops (applied in order, all or nothing):
- {"op": "write", "text": "..."} (replace everything)
- {"op": "append", "text": "..."}
- {"op": "replace", "find": "...", "replace": "..."} (find must occur exactly once)
```

### `open_board`

- The name "open_board" says "open": a word for a change.

Description, as the server wrote it:

```untrusted-server-text
Open a new, empty board and get its link. Give the link to the user: they watch your work there and download it.
```

### `write_board`

- The name "write_board" says "write": a word for a change.

Description, as the server wrote it:

```untrusted-server-text
Replace everything on an HTML or Markdown board. HTML: one complete document with inline CSS and JS. Markdown: GitHub-flavored. For a doodle, use `draw` instead.
```

## Only reads (1)

Looks things up and changes nothing. Claude Code: allow.

### `read_board`

- The name "read_board" says "read": a word for reading.
- Tag untrusted-input: The description's first sentence says "read" and "html": it reads web pages, which anyone can write.

Description, as the server wrote it:

```untrusted-server-text
Read a board: its kind and current content (the Markdown or HTML text, or the doodle's elements with their ids, roles, boxes and styles).
```

## Claude Code permissions

For `.claude/settings.json`, assuming the server is installed as `boards`:

```json
{
  "permissions": {
    "allow": [
      "mcp__boards__read_board"
    ],
    "ask": [
      "mcp__boards__open_board",
      "mcp__boards__write_board",
      "mcp__boards__edit_text",
      "mcp__boards__draw"
    ],
    "deny": []
  }
}
```

Capability tags behind the rules (a read tagged outbound is asked about):

- `mcp__boards__read_board`: untrusted-input
