npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mattermost-boards-mcp

v1.0.0

Published

MCP server for Mattermost Boards. Lets an agent create, read, change and watch boards and cards — and mention people in them — with the same permissions as the account whose token it carries.

Readme

mattermost-boards-mcp

Mattermost Boards, as tools an agent can actually use. Boards, cards, properties, comments, members, mentions and change-watching — over MCP, with the permissions of whichever account's token you hand it.

  agent  ──MCP──▶  mattermost-boards-mcp  ──HTTPS──▶  Mattermost + Boards plugin
                      (stdio or http)                  (your token, your rights)

It adds no permission model of its own. Mattermost already knows who may read a board and who may change it — the token is the scope.

Sixty seconds

npx mattermost-boards-mcp --url https://team.example.com --token "$MATTERMOST_TOKEN" --team main

Point any MCP client at it:

{
  "mcpServers": {
    "boards": {
      "command": "npx",
      "args": ["-y", "mattermost-boards-mcp"],
      "env": {
        "MATTERMOST_URL": "https://team.example.com",
        "MATTERMOST_TOKEN": "…",
        "MATTERMOST_TEAM": "main"
      }
    }
  }
}

The token is a personal access token or a bot token. It needs nothing special — no admin, no system console, no separate integration.

What you get

| | | | --- | --- | | Boards that open | create, list, search, change, duplicate, delete — and repair | | Cards by name | properties as {"Status": "Done"}, never as opaque ids | | Bodies and comments | markdown in, markdown out | | Mentions that arrive | and a warning when one would not | | Members | who may do what, per board | | Watching | subscribe a channel, or poll cheaply | | Scope | read-only, chosen toolsets, chosen teams and boards | | Two ways to host it | sidecar per agent, or one shared server |

Boards that open

A minimal POST /boards against the plugin succeeds, stores cardProperties: null, and produces a board the web client cannot render — "Sorry, something went wrong", while every API call against it still returns 200. board_create always sends properties and a view, so this never happens.

board_create { "title": "Roadmap" }
// → open board, a Status select (To do / In progress / Done), an Assignee person,
//   and a Kanban view grouped by Status

Already have a broken one, from some other script?

board_repair { "boardId": "b…" }   // one PATCH; the cards on it survive

Cards by name

Boards stores a card's fields as {propertyId: optionId}. You never see either.

card_create {
  "boardId": "b…",
  "title": "Ship the release",
  "properties": { "Status": "In progress", "Assignee": "andrey" },
  "body": "Blocked on the CI runner.\n\nRetry after the upgrade."
}

Get a name wrong and the error tells you what exists:

property "Staus" is not on this board. It has: Status, Assignee
property "Status" has no option "Blocked". It has: To do, In progress, Done.
Pass createOptions: true to add it.

Filter without reading every card:

cards_list { "boardId": "b…", "where": { "Status": "In progress" } }

Bodies and comments

Markdown goes in, splits into content blocks on blank lines (fenced code stays whole), and comes back as the same markdown.

card_comment  { "cardId": "c…", "text": "Runner is back, retrying." }
card_comments { "cardId": "c…" }   // oldest first, with who and when

Replacing a card's body does not touch its comments.

Mentions that arrive

A Boards mention is a side effect of storing a block whose title contains @name; the Boards bot then DMs that person a link to the card. On a private board a mention of a non-member is dropped — logged at debug, success returned, nobody told.

card_mention { "cardId": "c…", "users": ["andrey"], "message": "can you review this" }
// → { "notified": ["andrey"], "addedToBoard": ["andrey"], "text": "@andrey can you review this" }

With addToBoard: false on a private board you get the truth instead of silence:

// → { "notified": [], "warning": "andrey will NOT be notified: this is a private
//      board and they are not members. Call again with addToBoard: true, …" }

Members

board_members     { "boardId": "b…" }
board_member_add  { "boardId": "b…", "user": "andrey", "role": "editor" }
board_join        { "boardId": "b…" }   // open boards only, and it says so if not

Roles are viewer, commenter, editor, admin — the four flags the plugin stores, expressed as one name.

Watching

Two ways, for two situations.

Subscribe a channel. A Boards subscriber may be a user or a channel. Point a board at the channel your agent already listens in, and changes arrive through the gateway that is already running — no polling, no turn spent waiting.

watch_add { "boardId": "b…", "channel": "project-alpha" }

Poll cheaply. One call tells you which boards moved; only then read them.

board_activity      { "since": "2026-08-13T09:00:00Z" }   // → the boards that changed
cards_changed_since { "boardId": "b…", "since": "2026-08-13T09:00:00Z" }

Boards' own metadata endpoint needs a Mattermost licence and answers 501 appropriate license required without one. board_activity falls back to the newest card on each board and tells you it did — everything else here works unlicensed.

There is no blocking wait tool, on purpose: a tool that sleeps holds an agent's whole turn open doing nothing.

Scope

Everything below only ever narrows what the token already permits.

scope:
  readOnly: true                    # every writing tool disappears, and is refused if called
  toolsets: [directory, cards]      # of: directory, boards, cards, members, monitor
  teams: [main]                     # acting elsewhere is refused
  boards: [b1234…]                  # only these boards
  denyTools: [board_delete]
mattermost-boards-mcp --read-only --toolsets directory,boards,cards --teams main

Two ways to host it

Sidecar (default). One server per agent, in the agent's own container, reusing the MATTERMOST_TOKEN that is already there. The agent's reach is exactly that bot account's reach, and there is nothing new to provision.

Shared. One server for several agents and people:

mattermost-boards-mcp --http --host 0.0.0.0 --port 9100

Every request must carry its own Authorization: Bearer <mattermost-token>, and a token in the config is refused at startup rather than used as a fallback — otherwise one stored credential would flatten every caller into one account. It is stateless: no session id, no server-side store, GET refused because there is no stream to open, and Origin checked.

Configuration

Flags, environment, or a YAML/JSON file — later wins. ${VAR} expands inside the file, so a tracked config holds no secret.

mattermost:
  url: https://team.example.com
  token: ${MATTERMOST_TOKEN}
  team: main

server:
  transport: stdio        # or http
  host: 127.0.0.1
  port: 9100
  path: /mcp

scope:
  readOnly: false
  toolsets: [directory, boards, cards, members, monitor]

log:
  level: warn             # error, warn, info, debug — always on stderr
mattermost-boards-mcp --config boards.yaml --check    # validate and exit
mattermost-boards-mcp --help

Environment: MATTERMOST_URL, MATTERMOST_TOKEN, MATTERMOST_TEAM, BOARDS_MCP_CONFIG, BOARDS_MCP_TRANSPORT, BOARDS_MCP_HOST, BOARDS_MCP_PORT, BOARDS_MCP_PATH, BOARDS_MCP_READ_ONLY, BOARDS_MCP_TOOLSETS, BOARDS_MCP_TEAMS, BOARDS_MCP_BOARDS, BOARDS_MCP_DENY_TOOLS, BOARDS_MCP_LOG_LEVEL.

Requirements

Node 22+. Mattermost with the Boards plugin (focalboard) installed and enabled — it is no longer bundled; it lives at mattermost/mattermost-plugin-boards. Pick a release by the min_server_version inside its bundle, not by version number: two release lines ship in parallel and the tags do not order by date.

Developed against plugin 9.3.1 on Mattermost 11.10.0.

Develop

npm test        # node --test; no Mattermost needed, and none is contacted

The suite runs against test/fixtures/fake-mattermost.js — a real HTTP server that enforces the plugin's actual rules, including the CSRF header, the "O"/"P" board types and the non-zero createAt. A stubbed fetch would agree with whatever the client did and prove nothing.

Durable knowledge about this codebase lives in .hint files next to the code and is queried with HINThint src/tools/cards.js prints what applies before you change it.

Licence

Apache-2.0.