MCP Server
Purpose
Assessor.io ships a Model Context Protocol server that exposes the public API (v2) as typed tools, so an LLM agent can list campaigns, push participants, read submissions, and manage webhooks by calling tools instead of composing HTTP requests.
The point of running it is credential containment. The server holds the service token and makes the HTTP calls itself; the agent never sees the token and cannot exfiltrate it. The server imports no application or database code — it is an HTTP client, so it can run anywhere that can reach the API.
Prerequisites
- Node.js 20.x
- A service token carrying the scopes and account ids this agent should have.
A platform operator mints it at
/console/api-credentials; the token is shown once and stored hashed. - A reachable API base URL
Configuration
| Environment variable | Required | Purpose |
|---|---|---|
ASR_API_BASE_URL | yes | Base URL of the running API, e.g. https://assessor.bimexcellence.org |
MCP_SERVICE_TOKEN | yes | The service token the server authenticates with |
AIO_API_BASE_URL | deprecated | The former name of ASR_API_BASE_URL. Still read, and removed on 2026-12-31 |
The server exits with an error if there is no base URL under either name, or no token.
The names changed on 2026-09-17, and both work until 2026-12-31
The platform's old three-letter shorthand now means Asset Information
Orchestration elsewhere in BIMei, so Assessor.io's shorthand is asr (owner
ruling D-176). Three things this page documents were renamed, and every old
name keeps working until 2026-12-31:
| Was | Is | Removed |
|---|---|---|
AIO_API_BASE_URL | ASR_API_BASE_URL | 2026-12-31 |
aio_* tool names | asr_* tool names | 2026-12-31 |
aio_… service tokens | asr_… service tokens | the prefix is accepted until 2026-12-31; tokens already issued are NOT reissued for you |
Nothing has to change today. What to do before the date:
- Rename the environment variable in your MCP client config. Set both if you
like —
ASR_API_BASE_URLwins, and the server prints one deprecation warning on stderr when it falls back to the old name. - Point your agent at the
asr_*tool names. The old names are registered as aliases over exactly the same handlers, so switching is a find-and-replace with no behavioural change. - A service token you already hold keeps working whichever prefix it
carries. Tokens minted from now on start
asr_.
Build it
npm run mcp:build
That compiles src/mcp/ to dist/mcp/ (its own tsconfig — CommonJS, node16
resolution, no path aliases, because the server is an HTTP client with no
application module graph behind it). dist/ is gitignored: the compiled server
is a build artefact, so build it wherever you run it.
Run it
Register it with any MCP client — the example below is Claude Desktop's
mcpServers config:
{
"mcpServers": {
"assessor-io": {
"command": "node",
"args": ["dist/mcp/server.js"],
"env": {
"ASR_API_BASE_URL": "https://assessor.bimexcellence.org",
"MCP_SERVICE_TOKEN": "<service-token>"
}
}
}
}
For local development against a dev server, skip the build and run the
TypeScript entry point directly — tsx is a devDependency of the repository, so
this needs no extra install:
ASR_API_BASE_URL=http://localhost:3000 MCP_SERVICE_TOKEN=… npx tsx src/mcp/server.ts
Transport is stdio. There is no HTTP or SSE listener.
Tools
| Tool | Deprecated alias | Calls | Scope required |
|---|---|---|---|
asr_list_campaigns | aio_list_campaigns | GET …/campaigns | campaigns:read |
asr_get_campaign | aio_get_campaign | GET …/campaigns/{id} | campaigns:read |
asr_list_participants | aio_list_participants | GET …/participants | participants:read |
asr_upsert_participant | aio_upsert_participant | POST …/participants | participants:write |
asr_list_submissions | aio_list_submissions | GET …/submissions | submissions:read (answers also need submissions:read:answers) |
asr_export_results | aio_export_results | POST …/exports | results:export |
asr_list_webhooks | aio_list_webhooks | GET …/webhooks | webhooks:manage |
asr_create_webhook | aio_create_webhook | POST …/webhooks | webhooks:manage |
asr_delete_webhook | aio_delete_webhook | DELETE …/webhooks/{id} | webhooks:manage |
asr_search_statements | aio_search_statements | GET …/retrieval (types=statement) | statements:read |
asr_search_items | aio_search_items | GET …/retrieval (types=item,template_question) | statements:read |
asr_list_templates | aio_list_templates | GET …/templates | templates:read |
asr_get_template | aio_get_template | GET …/templates/{id} | templates:read |
asr_validate_template | aio_validate_template | POST …/templates/validate | templates:read |
asr_list_scales | aio_list_scales | GET …/scales | templates:read |
asr_list_labels | aio_list_labels | GET …/labels | templates:read |
asr_create_blueprint | aio_create_blueprint | POST …/blueprints | blueprints:write |
asr_run_blueprint_assistant | aio_run_blueprint_assistant | POST …/blueprints/{id}/aia | blueprints:write |
asr_get_blueprint | aio_get_blueprint | GET …/blueprints/{id} | templates:read |
asr_validate_blueprint | aio_validate_blueprint | POST …/blueprints/{id}/validate | templates:read |
asr_compile_blueprint | aio_compile_blueprint | POST …/blueprints/{id}/compile | blueprints:write + templates:write |
Each alias in the middle column is registered beside its canonical tool, over the same handler, and is removed on 2026-12-31. Its description says so, so an agent that lists the tools is told without reading this page. Until then the server registers forty-two tools — twenty-one names and twenty-one aliases — and either name does exactly the same thing.
The template-authoring pipeline (C3)
The last nine tools above let an external agent drive the same
template-authoring pipeline the in-app BIMei AI Assistant uses: search for
Action Statements and reusable items (asr_search_statements,
asr_search_items), read the account's templates/scales/labels, start a
Blueprint from pasted brief text (asr_create_blueprint), run the assistant
over it step by step (asr_run_blueprint_assistant — a model route,
refused while the platform model kill switch is on), check it
(asr_validate_blueprint), and compile it into a template
(asr_compile_blueprint).
asr_compile_blueprint is the only tool in this file that creates a
template, and it always creates a Draft (owner ruling D-220 R4). No tool
here can set a template Active, distribute it, or attach it to a campaign —
that is deliberately left to a human, in the app. Compiling needs two scopes
at once (blueprints:write and templates:write): a token that can only
run the assistant is refused with a named scope error rather than being
allowed to leave a Draft template behind.
Every tool takes accountId as its first argument, and the campaign-scoped
tools also take campaignId.
asr_list_submissions returns completed and reopened submissions only, the
same population asr_export_results produces (v2 1.1.0, D-070). Pass
includeDrafts: true for the in-progress ones as well, or status for one
exact lifecycle status; meta.draftsExcluded says what the default left out. An
agent asked "how many people have submitted" therefore answers with the
completed count rather than counting half-typed autosaves.
The token is the permission boundary
Registering a tool does not grant it. What each tool can actually do is decided
entirely by the token's publicApiScopes and accountIds. A tool whose scope
the token lacks returns the API's 403 as a tool error, and the agent sees the
error text rather than any data.
Two consequences worth planning for:
- Provision narrowly. Give a read-only research agent a token with
campaigns:readandsubmissions:readand nothing else. It will still see all nine tools (and, until 2026-12-31, their nine deprecated aliases); seven of them will simply fail. - Two scopes reach answer content, and the console marks both.
submissions:read:answersunlocks theanswersobject onasr_list_submissions;results:exportalways contains real answers whatever else the token holds. Until EXT-05 the first of the two could not be minted at all — the issuing route refused it as an unknown scope — so an agent told to ask for it received a credential that readanswersIncluded: falsefor ever. It is on the checkbox list now, marked sensitive, and a listing that withholds answers says why inmeta.reason.
Rate limiting also applies per token, so several agents sharing one token share
one budget. A 429 surfaces to the agent as a tool error.
Known gaps
Two v2 operations have no tool: reading CRM contacts (GET …/contacts) and
deactivating a webhook without deleting it (PATCH …/webhooks/{id}). An agent
that needs either must call the HTTP API directly.
Layout
Source lives under src/mcp/:
apiClient.ts— the HTTP client: bearer auth and{ success, data }unwrapping. Unit-tested.tools.ts— the tool set. Each tool'srun(client, args)maps to exactly one v2 call, and is framework-free so it can be unit-tested against a mock client.server.ts— SDK glue: registers the tools on anMcpServerover stdio.
Related pages
- Public API (v2) — the HTTP surface these tools wrap, including the response envelope, pagination, and webhook signature verification.