Skip to main content
Glama
Destiny-Enterprises

Dashboard Builder MCP server

Dashboard Builder MCP server

Lets an AI client discover your datasets and author dashboards in Dashboard Builder.

It talks to the Next.js app over HTTP as an ordinary API client, so every permission guard, dependency policy and validation rule in the app still applies. Nothing in the main application changes.

Run it two ways:

Who runs it

Identity

Users need

Hosted

one server, whole org

each person's own account, bound to their key once

a URL and a key

Local

each person, own machine

that person's own account

Node and a copy of this folder

Hosted is the normal deployment and is what this document covers. Local mode is for developing the server itself, or for per-user identity, and lives in DEVELOPMENT.md.


For users: connecting to a hosted server

You need two things from whoever deployed it: the URL and your gate key. Nothing to clone, no files to point at, no .env.

Add this to claude_desktop_config.json (Claude Desktop) or .mcp.json (Claude Code):

{
  "mcpServers": {
    "dashboard-builder": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote",
        "https://mcp.yourcompany.com/mcp",
        "--header", "Authorization: Bearer YOUR_KEY_HERE",
        "--header", "X-Dashboard-Username: you",
        "--header", "X-Dashboard-Password: your-dashboard-password"
      ]
    }
  }
}

mcpServers is a top-level key, a sibling of preferences — not nested inside it. Quit Claude Desktop from the system tray and reopen; closing the window is not enough.

With the two X-Dashboard-* headers present, the server logs in as you automatically on first use and again whenever the session expires — nothing else to do, and every call acts as you: your permissions, your audit trail. The trade-off is that your dashboard password sits in this config file and travels (over HTTPS) with each request. If the password contains characters outside ASCII, use the curl bind below instead — HTTP headers do not carry them reliably.

Alternative: bind once with curl, keep the password out of the config

Omit the two X-Dashboard-* headers and instead bind your key once — the password is used for that single login and never stored anywhere; the server keeps only the resulting session tokens, exactly like a browser keeps cookies:

curl -X POST https://mcp.yourcompany.com/auth/bind \
  -H "Authorization: Bearer YOUR_KEY_HERE" \
  -H "content-type: application/json" \
  -d '{"username":"you","password":"your-dashboard-password"}'

The difference from the header route: when the session chain eventually expires you re-run this command, whereas the headers re-bind automatically. DELETE /auth/bind with the same Authorization header signs the key out in either case.

Alice's Claude ──[gate key]──> MCP server ──[Alice's session cookies]──> Dashboard API
                     ^                                ^
              client config              bound via credential headers or
                                         POST /auth/bind; refreshed
                                         automatically after that

Credential

Lives in

Answers

Gate key

each user's client config

may this person use the MCP server?

Session tokens

the server, one file per key

who does this key act as?

If a key is never bound, tool calls fail with an error that explains the bind step — or, when the server is configured with a legacy service account, they fall back to that shared identity.

mcp-remote is a small bridge that runs locally and forwards to the server, so Node must be installed on the user's machine. To avoid even that, Claude Desktop's Settings → Connectors → Add custom connector takes a URL directly with nothing local — that path expects OAuth rather than a static key, and availability varies by Desktop version.


Deploying the server

server.js is the startup file. It listens on PORT like a Next.js server.js, and puts an API-key gate in front of every MCP request so unauthenticated callers are rejected before anything reaches the dashboard system.

Endpoints: POST /mcp (gated), POST /auth/bind and DELETE /auth/bind (gated — bind or unbind the calling key's dashboard identity), and GET /health (open, for the platform health check). Everything else returns 404.

Environment variables

Required — the server will not start without these

Variable

Value

DASHBOARD_API_URL

https://dashboard.yourcompany.com

MCP_API_KEYS

alice:<secret>,bob:<secret> — one per person, min 24 chars

Generate keys with openssl rand -hex 24. The label before the colon appears in logs and rate-limit buckets; the secret itself is never logged. Revoke one person by removing their entry and restarting — and delete their session file under ~/.dashboard-mcp/sessions/ to drop the bound identity too.

Each key is then bound to a dashboard account by its holder via POST /auth/bind — see the user section above. No dashboard credential lives in the server's environment.

Optional legacy fallback — a shared service account

Variable

Value

DASHBOARD_MCP_USERNAME

a service account

DASHBOARD_MCP_PASSWORD

that account's password

When set, keys that have not been bound act as this shared account instead of failing — as does a key whose binding has expired, until it is re-bound. Useful during migration; skip it for new deployments so every caller has their own identity.

Strongly recommended

Variable

Value

Why

DASHBOARD_MCP_ALLOW_WRITES

false

start read-only until identities are bound

MCP_ALLOWED_HOSTS

mcp.yourcompany.com

enables DNS-rebinding protection

MCP_ALLOWED_ORIGINS

your client origin

same

Leave DASHBOARD_MCP_PERSIST_SESSION at its default (true): bindings are stored one file per key and survive restarts. Setting it to false keeps bindings in memory only, so every restart — and every worker in a multi-worker host — needs its own re-bind.

MCP_ALLOWED_HOSTS and MCP_ALLOWED_ORIGINS are optional — the server runs without them and the API-key gate still applies. Setting either switches on the transport's DNS-rebinding protection. Leave both unset and the startup log says so explicitly.

Optional

Variable

Default

PORT

3001

HOST

127.0.0.1 — keeps the port off the public interface; the reverse proxy reaches it locally

MCP_RATE_LIMIT

120 requests per key per window

MCP_RATE_LIMIT_WINDOW_MS

60000

Further tuning variables — session file path, request timeout, response caps and the dashboard kind id override — are documented inline in .env.example, which is organised by mode and lists every variable the server reads.

Multi-worker note

Bindings are one file per key, and a worker whose in-memory token has been rotated out by another worker recovers by re-reading that file, which the winning worker has already updated. The failure window is two workers refreshing the same token at the same instant; the loser recovers on its next attempt, and in the worst case the key must be re-bound. The MCP transport itself is stateless, so requests can land on any worker.

Plesk setup

Setting

Value

Application root

the mcp-server directory

Application startup file

server.js

Application mode

production

Environment variables

the tables above, in the Node.js panel

Before starting

npm install, then npm run build

Add to the domain's Additional nginx directives:

proxy_buffering off;
proxy_read_timeout 300s;

MCP replies as Server-Sent Events and nginx buffers proxied responses by default. Without proxy_buffering off requests appear to hang rather than fail, which is a confusing way to lose an afternoon.

Keep the Node port off the public firewall. Plesk's nginx proxies to it and sets X-Forwarded-For, which is what makes the logged client IPs trustworthy.

Access versus identity

The gate key controls access; identity comes from the binding. The key gets a caller past the gate, and the session bound to that key decides who the dashboard sees — their permissions, their audit trail. The two are deliberately separate: rotating a key's secret drops its binding (the session is filed under the key's digest), and revoking a key removes access without touching the account.

The binding works the way a browser login does. POST /auth/bind runs the app's real /api/auth/login once, the password is discarded after the exchange, and only the rotating refresh-token session is kept — one file per key, mode 0600. Because the app rotates the refresh token on every use, a leaked session file dies quickly; because the password is never stored, there is nothing long-lived to leak. The trade-off: when a refresh chain expires or breaks, that key re-binds with one curl.

A stable per-request credential (ApiKey in the main system, or OAuth) would remove even that re-bind, but requires changes in the main application. This design deliberately needs none.


Developing or running it locally

Running the server on your own machine — for development, or for per-user identity without hosting — is documented separately in DEVELOPMENT.md.

Tools

Tool

Mode

Purpose

list_datasets

read

Dataset ids, labels and scopes

describe_dataset

read

Exact field names, inferred types, one sample value each

sample_dataset

read

A capped sample of real rows

list_dashboards

read

Dashboard ids, labels and scopes

get_dashboard

read

Dashboard details plus one line per widget; one config on request

list_widget_kinds

read

The authorable widget kinds

describe_widget_kind

read

Config contract for one kind, plus a real example from your workspace

create_dashboard

write

Create a dashboard and attach its datasets

set_dashboard_datasets

write

Replace the dashboard's dataset list

add_widget

write

Add one widget, auto-placed on the grid

update_widget

write

Change title, dataset or config keys

delete_widget

write

Remove a widget

arrange_dashboard

write

Repack the grid or apply explicit positions

Design notes

Context discipline. The whole tool surface costs about 3.6 KB — 13 descriptions plus the server instructions — so it stays cheap to keep loaded. Responses are compact text rather than raw JSON, and every list is capped with an explicit note about what was omitted. get_dashboard deliberately omits widget configs; you ask for one widget by id when you need its config.

Progressive disclosure. A chart config has roughly 59 fields. Putting that in a tool description would dominate the client's context on every request, so describe_widget_kind serves the contract on demand instead: field names, types, notes, a minimal working example, and — the useful part — a real config harvested from an existing widget of that kind in your own workspace. Copying a shape that already renders beats inventing one from field names.

The server does the geometry. Models are unreliable at 2D packing. add_widget takes a size hint (small, medium, large, full) and finds the first free non-overlapping cell on the 12-column grid itself. arrange_dashboard in auto mode repacks an entire dashboard.

Fail before the API, not after. Widget configs are stored as opaque JSON by the app, so a misspelled key produces a blank widget rather than an error. add_widget validates the config against the kind's contract first — required keys, valid aggregation names, field present when the aggregation needs one — and returns a specific list of what is missing.

Widgets match what the UI would create. The app's palette seeds every new widget with the kind's defaultConfig from the registry (config === undefined ? def.defaultConfig : config). add_widget mirrors that: the kind defaults are layered underneath whatever the caller supplies, so an MCP-authored chart carries the same paginationMode and maxPoints baseline as a hand-built one instead of a sparse config the renderer has to fall back on. The merged object is what gets validated.

Merge instead of resend. PATCH /widgets/:id replaces the config object wholesale. update_widget defaults to merging your keys into the existing config, so changing one setting does not mean resending all of it.

Fail closed on startup. The HTTP server refuses to start without at least one MCP_API_KEYS entry, and rejects keys under 24 characters. An unauthenticated MCP endpoint should never be possible by accident. Keys are compared as SHA-256 digests with timingSafeEqual, and only labels are logged.

Known limits

  • The widget kind catalog is a copy. src/catalog/widget-kinds.ts mirrors src/features/dashboard/widgets/registry.ts — including each kind's defaultConfig — and the per-kind config interfaces. The app's registry is a client component and imports React, so it cannot be imported here. If a widget kind gains a field, or a defaultConfig value changes, update the catalog too, or MCP-created widgets will drift from UI-created ones.

  • Catalog coverage is high but not total. Documented vs. actual config fields: table 18/21, stat 22/25, chart 39/59, select 9/12, text 16/17. What is left out is mostly cosmetic variants (pie/line/bar style options, right-hand axis overrides) and legacy interaction keys superseded by highlightBindings. The live example returned by describe_widget_kind is the reference for those. Fields are grouped into core / display / interaction so the data contract reads first.

  • Bindings expire with the refresh chain. A key's session lasts as long as the app keeps its rotating refresh token alive. When it lapses, calls fail with an error naming the fix and the key's holder re-binds with one curl. A never-expiring identity needs ApiKey wired into src/lib/api-guard.ts in the main system, which is not done.

  • Writes are direct. The app has a change-draft and approval workflow (ChangeDraft, ApprovalRequest). These tools write straight through with the signed-in account's permissions. If AI-authored dashboards should be reviewed before going live, route the write tools at /api/change-drafts instead and keep the account's grants read-only.

-
license - not tested
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • Enterprise AI Control Plane: governance, guardrails, spend tracking, compliance & smart routing.

  • Secure Docusign Navigator integration for AI assistants to access and analyze agreement data.

  • A paid remote MCP for AI SDK eval dashboard, built to return verdicts, receipts, usage logs, and aud

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Destiny-Enterprises/mcp-dashboard-builder-tool'

If you have feedback or need assistance with the MCP directory API, please join our Discord server