Skip to main content

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 variableRequiredPurpose
ASR_API_BASE_URLyesBase URL of the running API, e.g. https://assessor.bimexcellence.org
MCP_SERVICE_TOKENyesThe service token the server authenticates with
AIO_API_BASE_URLdeprecatedThe 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:

WasIsRemoved
AIO_API_BASE_URLASR_API_BASE_URL2026-12-31
aio_* tool namesasr_* tool names2026-12-31
aio_… service tokensasr_… service tokensthe 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_URL wins, 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​

ToolDeprecated aliasCallsScope required
asr_list_campaignsaio_list_campaignsGET …/campaignscampaigns:read
asr_get_campaignaio_get_campaignGET …/campaigns/{id}campaigns:read
asr_list_participantsaio_list_participantsGET …/participantsparticipants:read
asr_upsert_participantaio_upsert_participantPOST …/participantsparticipants:write
asr_list_submissionsaio_list_submissionsGET …/submissionssubmissions:read (answers also need submissions:read:answers)
asr_export_resultsaio_export_resultsPOST …/exportsresults:export
asr_list_webhooksaio_list_webhooksGET …/webhookswebhooks:manage
asr_create_webhookaio_create_webhookPOST …/webhookswebhooks:manage
asr_delete_webhookaio_delete_webhookDELETE …/webhooks/{id}webhooks:manage
asr_search_statementsaio_search_statementsGET …/retrieval (types=statement)statements:read
asr_search_itemsaio_search_itemsGET …/retrieval (types=item,template_question)statements:read
asr_list_templatesaio_list_templatesGET …/templatestemplates:read
asr_get_templateaio_get_templateGET …/templates/{id}templates:read
asr_validate_templateaio_validate_templatePOST …/templates/validatetemplates:read
asr_list_scalesaio_list_scalesGET …/scalestemplates:read
asr_list_labelsaio_list_labelsGET …/labelstemplates:read
asr_create_blueprintaio_create_blueprintPOST …/blueprintsblueprints:write
asr_run_blueprint_assistantaio_run_blueprint_assistantPOST …/blueprints/{id}/aiablueprints:write
asr_get_blueprintaio_get_blueprintGET …/blueprints/{id}templates:read
asr_validate_blueprintaio_validate_blueprintPOST …/blueprints/{id}/validatetemplates:read
asr_compile_blueprintaio_compile_blueprintPOST …/blueprints/{id}/compileblueprints: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:read and submissions:read and 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:answers unlocks the answers object on asr_list_submissions; results:export always 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 read answersIncluded: false for ever. It is on the checkbox list now, marked sensitive, and a listing that withholds answers says why in meta.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's run(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 an McpServer over stdio.
  • Public API (v2) — the HTTP surface these tools wrap, including the response envelope, pagination, and webhook signature verification.