mspbots-agent-mcp
OfficialClick on "Deploy 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., "@mspbots-agent-mcpWhat connectors are installed and which ones are connected?"
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.
mspbots-agent-mcp
MCP server for the MSPbots Agent Platform API — exposes the Agent Platform's connector inventory, agent triggers (scheduled/event tasks), and per-agent policy (permissions, self-evaluation, human-in-the-loop approval) to MCP clients.
It follows the same design as the sibling ticketqa-mcp service: stateless, no
stored credentials, per-request header authentication over the
Model Context Protocol (Streamable HTTP/SSE
transport).
When would you use this
This MCP is meta: it doesn't wrap a customer-facing integration, it wraps the Agent Platform's own admin/config API for an agent. The "users" are platform admins/builders setting up an agent, or an agent introspecting/adjusting its own configuration:
"What connectors does this tenant have hooked up, and which are actually connected right now?" →
mspbotsagent_get_connectors"What connectors can this agent use / what does it pull data from?" →
mspbotsagent_get_sop_data_sources(agent-scoped — notmspbotsagent_get_connectors, which is tenant-wide)"Make this agent run every Monday morning to summarize open tickets" →
mspbotsagent_upsert_trigger(typerecurring)"Kick this agent off automatically whenever a new ConnectWise ticket comes in" →
mspbotsagent_upsert_trigger(typeevent, after checkingmspbotsagent_get_trigger_catalog)"Lock down what this agent is allowed to do without asking first" →
mspbotsagent_upsert_agent_permissions"Require a human to sign off before this agent issues a refund" →
mspbotsagent_upsert_agent_approval"Write up this agent's SOP so it has a documented, repeatable procedure" →
mspbotsagent_set_sop_purpose/mspbotsagent_set_sop_procedure/ the othermspbotsagent_set_sop_*tools"What SOPs does this tenant have / which are still drafts?" →
mspbotsagent_list_sops"Ask the onboarding SOP what it would do with this ticket" →
mspbotsagent_chat_with_sop(one blocking turn with that SOP's own agent)
Related MCP server: agent-tenant-user-mcp
Tools
Every tool takes its credentials from the request headers
(X-MSP-Token / X-MSP-Tenant-Id / X-MSP-Host) — no token is ever passed as a
tool argument.
Connectors
Tool | What it does | Parameters |
| List every connector on the current tenant, returning each one's name, whether it is installed, and its connection status | none |
mspbotsagent_get_connectors is tenant-scoped: it takes no agent_id and
returns the same inventory regardless of which agent is being configured. For
the connectors a specific agent is declared to use, see
mspbotsagent_get_sop_data_sources instead.
mspbotsagent_get_connectors returns one row per connector:
Field | Description |
| Connector display name |
| Connector/integration key (e.g. |
| Connector scope (e.g. |
| How it is managed (e.g. |
| Whether it is installed/enabled (bool, from the API's |
| Whether it is currently connected (bool, from the API's |
| Derived status: |
Every connector the API returns also carries a very large base64
logofield — this service strips it to keep responses lean.
Backing endpoint: GET /apps/mb-platform-agent/api/capabilities/connectors, which
returns {"success": true, "data": {"list": [ {connector}, ... ]}}.
Triggers (agent scheduled / event tasks)
A trigger runs an agent automatically: on a cron schedule (recurring) or when an
event fires in an external integration (event). Its primary key is taskId, and it
belongs to one agentId.
Tool | What it does | Parameters |
| List all of an agent's triggers (paginated) |
|
| Create or modify a trigger (with |
|
| Delete a trigger (cannot be undone) |
|
| List the integration + events combinations that are valid for event triggers | none |
| Run one trigger manually, right now (for testing or a catch-up run) |
|
Creating via
upsertrequiresagent_id/name/prompt/type;recurringalso requiresschedule(cron, 1h minimum interval), andeventrequirestrigger_integration+trigger_events, whose combination must appear in theget_trigger_catalogcatalog. When modifying, submit only the fields you want to change.Backing endpoints:
GET|POST /api/tasks,PUT|DELETE /api/tasks/:taskId,GET /api/tasks/trigger-catalog,POST /api/tasks/:taskId/run.
Agent policy (permissions / evaluation / approval)
All three blocks of configuration hang off the same agent record: the reads share
one GET, and the writes share one partial PUT. So do not write to the same agent
concurrently (partial patches overwrite each other); and when a read comes back with
policyError=true, the upsert tools refuse to write permission/interruptOn back
(owners-only updates are unaffected) to avoid persisting a broken policy. The primary
key is agentId throughout.
Tool | What it does | Parameters |
| Read the permission config: |
|
| Update tool permissions, interrupt settings and ownership (partial; the three keys are independent, so sending owners alone is valid) |
|
| Read the self-evaluation config |
|
| Set/update the self-evaluation rules (an empty |
|
| Read the human approval rules |
|
| Set/update the human approval rules (an empty array removes every approval gate) |
|
Rule object shapes:
evaluation
rules[]:{ rubric, name, description, triggers: [regex/keywords] }approval
rules[]:{ name, intent, triggers: [regex/keywords], tools: [...], decisions: [...] }
Backing endpoints:
GET /api/agents/:id(shared by all three getters),PUT /api/agents/:id(shared by all three upserts, partial: writingpermission/interruptOn,review, andapprovalrespectively).
Agent SOP author
An agent's SOP (standard operating procedure) draft has 5 independent fields, each with
its own read and write tool, and each field writes through its own separate endpoint
(unlike permission/evaluation/approval above, which share a single
PUT /api/agents/:id). Every write has the same shape: PUT {"value": ...}. The
primary key is agentId.
Even though each field has a different endpoint, all 5 still live on the same agent record (the
sopAuthorsub-document), so the rule from permission/evaluation/approval applies here too: do not write to the same agent's SOP fields concurrently, or they may overwrite each other. The SOP itself is a document — a planning draft describing what the agent "should do"; the permission/evaluation/approval config above is the behavior policy that actually takes effect at runtime, deciding what the agent "can actually do". The two are complementary but distinct kinds of configuration.
Tool | What it does | Parameters |
| Read the SOP name |
|
| Set the SOP name (non-empty, ≤60 chars, unique within the tenant, cannot be cleared) |
|
| Read the source (the original task description the SOP was written from) |
|
| Set the source (pass null to clear it) |
|
| Read the purpose (markdown) |
|
| Set the purpose (markdown) |
|
| Read the dataSources list (structured object) |
|
| Set the dataSources list (structured object) |
|
| Read the procedure (markdown) |
|
| Set the procedure (markdown) |
|
The dataSources value shape (each source stores only integration, no
preconditions):
{
"sources": [
{ "integration": "open-meteo" },
{ "integration": "ms-graph" }
]
}Every entry mspbotsagent_get_sop_data_sources reads back is a connector (an MCP
server available to the agent); besides integration it carries connection status and
a tool list:
Field | Type | Meaning |
| string | Data source key |
| bool, optional | Present only when the source was added at the organization level |
| bool |
|
| string | Connector display info and its MCP endpoint (present only when |
| string | Current connection status, see the table below. Agent level wins over org level |
| bool | Whether an organization/tenant-level connection exists |
| bool | Whether the connector is enabled at the organization level |
| string, optional |
|
| array | The tools this connector provides ( |
connection values:
Value | Meaning | How to treat it |
| This agent connected with its own account (agent-level connection) | Usable |
| It is using the organization/tenant-level connection | Usable |
| Not connected (or the connector is disabled for the organization) | Not usable; the user has to connect it first |
| The gateway status could not be read | ⚠️ Does not mean disconnected — it just could not be read at this moment. Do not call it "not connected" on this basis |
Two easy mistakes, both written into the tool descriptions:
connectionis a single value and agent wins. When both levels are connected,connectionshows only"agent", and the tenant half is only visible intenantConnected. There is currently no symmetric standaloneagentConnectedboolean, so agent level can only be detected viaconnection === "agent"."none"and"unavailable"must be handled differently: the former really is not connected (fine to prompt the user to go connect it), while the latter merely means the status could not be read (lean towards still-usable, and do not misreport it).
Summary test: usable = found === true and connection ∈ {agent, org} (with
unavailable leaning usable); unusable = found === false or connection === "none".
mspbotsagent_get_sop_data_sourcesis the agent-scoped answer to "what connectors does this agent use", and its rows carry connection status, so it usually answers "can this agent use X" on its own.mspbotsagent_get_connectorsis the tenant-wide inventory — reach for it when the question is not about a particular agent.
Backing endpoints:
GET|PUT /api/agents/:id/sop-author/{name,source,purpose,data-sources-list,procedure}.
Agent skills
🚫 Currently not exposed. These tools are commented out in
create_mcp_server()(src/mspbots_agent_mcp/server.py), so they do not appear intools/listand cannot be called by external clients. The implementation stays insrc/mspbots_agent_mcp/tools/skills.py; re-enable by uncommenting theskillsimport andskills.register(...)call there (and the matching entries intests/test_tools.py). The rest of this section describes them as implemented.
The skills available to an agent come in three kinds: mspbots (built into the
platform), org (shared across the organization), and agent (private to that agent).
Only private (agent) skills can be created/edited/deleted here; mspbots/org
skills can only be read, not changed. The primary key is capabilityId (the id on
each record from the list endpoint).
Tool | What it does | Parameters |
| List every skill available to an agent (org-shared + that agent's private ones + platform |
|
| Create a new private skill (scope=agent) for an agent, publish it as an npm version and install it into that agent's workspace |
|
| Edit an agent's private skill files and publish a new version (whole-package replacement). Private skills owned by that agent only |
|
| Delete an agent's private skill: soft-delete the capability + clear the opt-out row + uninstall it from the workspace |
|
| Turn one skill on or off for an agent without touching its files. Works on all three kinds, not just private ones |
|
mspbotsagent_list_agent_skills returns, for each skill,
id/type (mspbots|org|agent)/ref/scope/name/skillName/description/version/enabled/selected
(true when not opted out)/available, plus a selectedIds list. selected is
exactly the switch mspbotsagent_set_agent_skill_enabled writes, and each entry's
ref and type are what to pass back in as ref and skill_type.
⚠️ The field structure of each element of
files("SkillFile") is not specified: the API spec handed over only said"$ref": "#/definitions/SkillFile", without attaching that definitions block. This implementation assumes{"path": "<relative path, e.g. 'SKILL.md'>", "content": "<raw text content>"}— inferred from the common SKILL.md packaging convention, not verified against the real API. Confirm the actual field names with the backend before relying on it (whether there is also anencoding/executable, etc.).Backing endpoints:
GET /api/agents/:id/skills,POST /api/agents/:id/skills/create,PUT /api/agents/:id/skills/:capabilityId/files,PUT /api/agents/:id/skills,DELETE /api/agents/:id/skills(the last one's request body is{"id": "<capabilityId>"}— a DELETE with a body is unusual in HTTP, so this service sends it through httpx's low-levelrequest()rather thandelete()).
PUT /api/agents/:id/skillstakes{ref, enabled, type}and answers{agentId, ref, enabled, restartRequired, pending}.restartRequiredis alwaystrue.pending: truemeans the flag was recorded but syncing the install/uninstall to the runtime failed, so the agent's capability is marked pending and will be retried later — the change is not in effect yet. An unresolvablerefor an unknown agent comes back as HTTP 200 with{"success": false, "error": "…"}rather than a 4xx, so the tool checkssuccessexplicitly and reports it as an error.
SOP library (list + synchronous chat)
A SOP (standard operating procedure) is a tenant-level record, and creating one
also opens a dedicated agent for it. That agent is what
mspbotsagent_chat_with_sop talks to, which is why it takes only sop_id, never
agent_id. This is a different thing from Agent SOP author above:
those tools edit the SOP draft sections attached to an agent, while these read the
SOP library and actually run a turn of conversation.
Tool | What it does | Parameters |
| List the tenant's SOPs with pagination, returning each one's status and owning agent |
|
| Hold one synchronous turn of conversation with a SOP's agent: blocks until the whole turn finishes, then returns the reply |
|
mspbotsagent_list_sops returns per row
id/name/description/status/source/tags/agentId/agentLive/updatedAt.
agentLive being false means that agent record no longer exists — the SOP is still
readable, but it cannot be talked to, and no parameter on either tool can bring it back.
How "the user names a SOP in their own words" turns into a sop_id:
the backend's search only does name ILIKE '%…%' (service/sop/db.ts:88) — it does
not match description / tags / body, and there is no fuzzy, synonym or cross-language
matching. So a miss does not mean it does not exist; it usually just means the
wording differs (the user says "the refund one", the SOP is called Refund Handling).
Dropping the user's own words straight into search, getting 0 rows, and then
answering "there is no such SOP" is the most expensive mistake on this path. The right
move is to not pass search, read each row's name + description and pick it
yourself, using total to decide whether to page. Use search only for a name fragment
you already know for certain. This guidance lives in the search parameter's own
description (parameter descriptions do not count towards the 500-char limit), and is
guarded by test_search_param_warns_that_a_miss_is_not_an_absence in
tests/test_sops.py.
When you only have an agent_id: every list_sops row carries agentId, so list
them and match on it to get the sop_id; mspbotsagent_get_usage_overview rows carry
both sopId and agentId too. Do not try to parse the agent's name — although a SOP
agent is created as ${sop.name} #${sop.id} and renames propagate, PUT /api/agents/:id
lets a user change name directly, which decouples it immediately, and an ordinary
agent can be given the same name too.
mspbotsagent_chat_with_sop returns
sopId/sopName/agentId/threadId/status/reply, where status explains an
empty reply:
| Meaning |
| Ran normally; |
| Paused on a human approval gate, with |
| Finished, but produced no assistant message |
reply is extracted the same way the frontend's lastAssistantText does it in
components/assistant-ui/aegra/messages.ts: scan backwards for a
type=ai/role=assistant message, skipping the internal messages the UI hides
(rubric_grader, sop_context, sop_suffix, compact_summary, task_notification).
⚠️ This call path differs from the other tools in two ways — do not revert them to the defaults:
It turns retries off (
retries=0). A request that runs an agent is not idempotent — re-sending after a lost response runs the agent again instead of recovering the first result.It raises the read timeout to 300s (the 30s default cannot survive a real agent turn; Aegra's own backstop is 1 hour). The error returned on timeout carries
retryable=falseand says explicitly: do not re-send, ask again on the same conversation instead.Backing endpoints:
GET /api/sops(returns{list, total}when paginated),GET /api/sops/:id(to getagent_id),POST /api/agents/:id/run/wait. That last one signals a failed run with HTTP 200 +{"success": false}rather than a 4xx/5xx, so this tool checks thesuccessfield explicitly to avoid reading a failure as "succeeded but said nothing".
Quick Start
Docker (recommended)
docker compose up --buildThe server starts on http://localhost:8080.
Local (uv)
uv sync
python -m mspbots_agent_mcpHealth Check
curl http://localhost:8080/health
# {"status": "ok"}No credentials are required for the health endpoint.
Authentication
Every request to /mcp must include the following HTTP headers (provided by the
MCP caller — kept consistent with ticketqa-mcp):
Header | Type | Required | Description | Example |
| string | Yes | An access credential already issued by the Agent Platform (JWT bearer token). This service forwards it verbatim as the downstream request's |
|
| string | Yes | Tenant identifier. Renamed to the |
|
| string | Yes | The host the Agent API lives on. |
|
Missing any of the three headers returns 401 Unauthorized.
Environment Variables
Variable | Default | Description |
|
| Listening port |
|
| Listening host |
MCP Endpoint
POST http://localhost:8080/mcpConnect your MCP client with:
Transport:
http(Streamable HTTP / SSE)Headers:
X-MSP-Token,X-MSP-Tenant-Id,X-MSP-Host(all required)
Test Example
curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "X-MSP-Token: <token>" \
-H "X-MSP-Tenant-Id: <tenant-id>" \
-H "X-MSP-Host: https://agent.mspbots.ai" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { "name": "mspbotsagent_get_connectors", "arguments": {} }
}'⚠️ This is a public repository. Never write real tokens, tenant ids or other sensitive values into any committed file — the
<token>/<tenant-id>above are placeholders only.
This server cannot be deployed
Maintenance
Related MCP Connectors
Authenticated, user-scoped MCP connectors for 30+ business systems.
Track agent ROI and enrich companies via MCP. Remote, OAuth 2.1, no install or keys.
Model Context Protocol server for the Apideck Unified API. Connect any MCP-compatible agent framework to 100+ accounting systems, HRIS platforms, file storage providers, and more through one integration. More information https://www.apideck.com/mcp-server
- GentkeyOAuthcom.gentkey
One MCP URL for all your connectors — scoped writes, enforced constraints, and a full audit trail.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceExposes Azure Entra ID user and license management as MCP tools over HTTP-SSE, enabling operations such as user creation, group assignment, and license management via Microsoft Graph API.Apache 2.0
- FlicenseNot gradedqualityBmaintenanceMCP server for the MSPbots Agent Platform tenant/user API, allowing an Agent to list all platform tenants via a single tool.-
- FlicenseNot gradedqualityCmaintenanceExposes ChatATP Studio's backend API as MCP tools over Streamable HTTP, enabling clients to manage agents, knowledge bases, MCP connections, HTTP APIs, LLM providers, messaging platforms, and teams with forwarded authorization.-
- AlicenseNot gradedqualityCmaintenanceEnables connecting multiple business tools behind a single managed MCP endpoint with per-connector permissions, dispatch, and audit records.MIT