Dashboard Builder MCP server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Dashboard Builder MCP servershow me available datasets"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 thatCredential | 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 |
|
|
|
|
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 |
| a service account |
| 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 |
|
| start read-only until identities are bound |
|
| enables DNS-rebinding protection |
| 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 |
| 3001 |
|
|
| 120 requests per key per window |
| 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 |
Application startup file |
|
Application mode | production |
Environment variables | the tables above, in the Node.js panel |
Before starting |
|
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 |
| read | Dataset ids, labels and scopes |
| read | Exact field names, inferred types, one sample value each |
| read | A capped sample of real rows |
| read | Dashboard ids, labels and scopes |
| read | Dashboard details plus one line per widget; one config on request |
| read | The authorable widget kinds |
| read | Config contract for one kind, plus a real example from your workspace |
| write | Create a dashboard and attach its datasets |
| write | Replace the dashboard's dataset list |
| write | Add one widget, auto-placed on the grid |
| write | Change title, dataset or config keys |
| write | Remove a widget |
| 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.tsmirrorssrc/features/dashboard/widgets/registry.ts— including each kind'sdefaultConfig— 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 adefaultConfigvalue 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 bydescribe_widget_kindis 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
ApiKeywired intosrc/lib/api-guard.tsin 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-draftsinstead and keep the account's grants read-only.
This server cannot be installed
Maintenance
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
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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