oitvoip-mcp
by MSPbotsAI
README.md
# oitvoip-mcp
MCP server for **Oitvoip** (hosted VoIP/UCaaS reseller platform, built on
**NetSapiens** — API host pattern is `{tenant-pbx-host}/ns-api/`). Exposes
the NetSapiens ns-api's domain, reseller, device, subscriber, and CDR
methods as MCP tools.
> Naming note: MSPbots' own integration is registered as "Oitvoip"
> (`subjectCode=NS` — short for NetSapiens); the underlying API and all
> official documentation reference "NetSapiens" / "ns-api". This MCP
> covers exactly the 5 methods MSPbots itself has configured.
## Overview
- Stateless HTTP service. No credentials are ever persisted — each request
supplies its own credentials via headers, used only for the lifetime of
that single request.
- Supports concurrent requests; per-request credential isolation is done via
Python `contextvars`, not a global/shared client instance.
- Entry points: `POST /mcp` (MCP protocol) and `GET /health` (health check).
- Default port: `8080` (configurable via `MCP_HTTP_PORT`).
## Authentication
NetSapiens uses a standard OAuth2 **password grant**:
```
POST https://{site}/ns-api/oauth2/token/
grant_type=password&client_id=...&client_secret=...&username=...&password=...
-> {"access_token": "...", "expires_in": 3600, "token_type": "Bearer", ...}
```
The resulting `access_token` is valid for 1 hour, but this server
re-authenticates fresh on every single tool call rather than caching it
across MCP requests — nothing is cached or persisted. Every real ns-api
call then sends `Authorization: Bearer <access_token>`.
### HEADER 授权参数说明
| Header | 类型 | 是否必填 | 默认值 | 枚举值 | 字段描述 | Example |
|---|---|---|---|---|---|---|
| `X-Oitvoip-Site` | string | 是 | 无 | 无 | 租户 PBX 主机名(不含协议前缀) | `pbx.example.com` |
| `X-Oitvoip-Client-Id` | string | 是 | 无 | 无 | NetSapiens OAuth2 API Client ID | `58900.mspbot` |
| `X-Oitvoip-Client-Secret` | string | 是 | 无 | 无 | NetSapiens OAuth2 API Client Secret | `a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6` |
| `X-Oitvoip-Username` | string | 是 | 无 | 无 | Subscriber 登录名(含域名后缀) | `1000@example` |
| `X-Oitvoip-Password` | string | 是 | 无 | 无 | 对应密码 | `••••••••` |
Missing any header returns `401`:
```json
{
"error": "Missing credentials",
"message": "This server requires the X-Oitvoip-Site, X-Oitvoip-Client-Id, X-Oitvoip-Client-Secret, X-Oitvoip-Username, X-Oitvoip-Password headers",
"required_headers": ["X-Oitvoip-Site", "X-Oitvoip-Client-Id", "X-Oitvoip-Client-Secret", "X-Oitvoip-Username", "X-Oitvoip-Password"],
"optional_headers": []
}
```
An invalid credential or an authenticated-but-under-scoped subscriber
account surfaces as a tool-level `unauthorized` error envelope (message
includes the vendor's own detail, e.g. `Invalid Scope [APP001]`), not an
HTTP-level error from this server — see Known Gaps.
## Environment Variables
| Variable | 类型 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|---|
| `MCP_HTTP_PORT` | int | 否 | `8080` | HTTP 监听端口 |
| `MCP_HTTP_HOST` | string | 否 | `0.0.0.0` | HTTP 监听地址 |
## MCP Endpoint
- `POST /mcp` — MCP protocol (streamable HTTP transport)
- `GET /health` — health check, returns `{"status": "ok"}` (pure local probe, does not call the vendor API)
## Tool List
| Tool | 功能 | 参数 |
|---|---|---|
| `oitvoip_get_domains` | 列出该 reseller 账号下所有已开通的域名(租户) | 无 |
| `oitvoip_get_resellers` | 获取指定域名的 reseller 级别详情 | `domain`(必填) |
| `oitvoip_get_devices` | 列出指定域名下已注册的 SIP 设备/终端 | `domain`(必填) |
| `oitvoip_get_subscribers` | 列出指定域名下的用户/分机 | `domain`(必填) |
| `oitvoip_get_cdr2` | 获取指定域名、指定日期范围内的通话详单(CDR) | `domain`、`start_date`、`end_date`(均必填) |
Responses are the vendor's JSON (array or object depending on the method),
serialized compactly (no indentation, `ensure_ascii=False`). If a response
would exceed ~20,000 characters, the largest list field is truncated and
the result includes `truncated: true` plus the original count, rather than
returning an unbounded blob. All 5 tools are read-only (`readOnlyHint`) —
there are no write/delete tools in this service.
On error, tools return a structured JSON error envelope instead of raising:
```json
{"error": {"code": "unauthorized", "message": "...", "retryable": false}}
```
`code` is one of `not_configured` / `unauthorized` / `not_found` /
`invalid_argument` / `rate_limited` / `upstream_error`; `retryable`
indicates whether the Agent may safely retry (true for `rate_limited` and
`upstream_error`).
## 测试示例
```bash
# Health check
curl -s http://localhost:8080/health
# Call a tool via the MCP protocol (streamable HTTP) — requires an
# initialize handshake first per the MCP spec; abbreviated example below
# shows the tool-call request body only:
curl -s -X POST http://localhost:8080/mcp \
-H "X-Oitvoip-Site: pbx.example.com" \
-H "X-Oitvoip-Client-Id: 58900.mspbot" \
-H "X-Oitvoip-Client-Secret: <your-client-secret>" \
-H "X-Oitvoip-Username: 1000@example" \
-H "X-Oitvoip-Password: <your-password>" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: <session-id-from-initialize>" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "oitvoip_get_subscribers",
"arguments": {"domain": "example.58900.service"}
}
}'
```
**Live-verified** (2026-07-30) against a real tenant, all 5 tools called
end-to-end through this running server: `oitvoip_get_subscribers` returned
real subscriber/extension records; `oitvoip_get_devices` returned real
registered SIP devices (Polycom endpoints, live registration state);
`oitvoip_get_cdr2` returned real call detail records for the given date
range. `oitvoip_get_domains` and `oitvoip_get_resellers` correctly reached
the API and surfaced a clean, expected `401 Invalid Scope [APP001]`
tool-level error — the provided test credentials are a subscriber-level
account (`scope: "Office Manager"`), which does not carry domain/reseller
admin privileges on this particular NetSapiens deployment; see Known Gaps.
## API Reference
- Public, no login required: https://api.ucaasnetwork.com/ns-api/apidoc/
(full ns-api reference, including OAuth2, Domain, Reseller, Device,
Subscriber, and CDR objects)
## Known Gaps
- **Scope is exactly MSPbots' 5 configured endpoints, not the vendor's
full API surface** — ns-api also covers Callqueue, Agent, Phonenumber,
Dialplan, Contacts, Presence, Call Queue Report/Stat, real-time Call
control, and more (per the public docs' own object list); those are out
of scope here.
- **`oitvoip_get_domains` and `oitvoip_get_resellers` could not be fully
live-verified with real data** — the provided test account authenticates
successfully (proving the OAuth2 flow and this implementation are
correct) but is scoped as a subscriber-level "Office Manager" role,
which NetSapiens rejects for these two admin-level objects with `401
Invalid Scope [APP001]`. This is a credential-privilege limitation of
the specific test account, not a bug in this server — `oitvoip_get_
subscribers`, `oitvoip_get_devices`, and `oitvoip_get_cdr2` all
succeeded with real data using the exact same access token from the
exact same login.
- **CDR date range fields (`start_date`/`end_date`) are unvalidated
strings** — passed through to the vendor as-is in `YYYY-MM-DD HH:MM:SS`
format, matching MSPbots' own stored usage; no client-side date parsing
is performed.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues