sipgate-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +213 -0
- package/dist/backend/sipgate-backend.d.ts +32 -0
- package/dist/backend/sipgate-backend.d.ts.map +1 -0
- package/dist/backend/sipgate-backend.js +244 -0
- package/dist/backend/sipgate-backend.js.map +1 -0
- package/dist/backend/sipgate-client.d.ts +24 -0
- package/dist/backend/sipgate-client.d.ts.map +1 -0
- package/dist/backend/sipgate-client.js +79 -0
- package/dist/backend/sipgate-client.js.map +1 -0
- package/dist/backend/telephony-backend.d.ts +59 -0
- package/dist/backend/telephony-backend.d.ts.map +1 -0
- package/dist/backend/telephony-backend.js +2 -0
- package/dist/backend/telephony-backend.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +58 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/definitions.d.ts +17 -0
- package/dist/tools/definitions.d.ts.map +1 -0
- package/dist/tools/definitions.js +192 -0
- package/dist/tools/definitions.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arne Wiese
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# sipgate-mcp
|
|
2
|
+
|
|
3
|
+
`sipgate-mcp` is an open-source, self-hosted Model Context Protocol server for inspecting and configuring a sipgate account. It exposes the sipgate REST API v2 as focused tools for agents such as Claude Code, Claude Desktop, and Codex.
|
|
4
|
+
|
|
5
|
+
The server uses stdio only. It does not start an HTTP server or route credentials
|
|
6
|
+
through a third-party service. It sends authentication only from the local MCP
|
|
7
|
+
process directly to `https://api.sipgate.com/v2`.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- Node.js 22 or newer
|
|
12
|
+
- A sipgate account with a Personal Access Token (PAT)
|
|
13
|
+
- An MCP client with stdio support
|
|
14
|
+
|
|
15
|
+
After the first npm release, install the command globally with any of the
|
|
16
|
+
supported package managers:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install --global sipgate-mcp
|
|
20
|
+
pnpm add --global sipgate-mcp
|
|
21
|
+
vp install --global sipgate-mcp
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Then start it with:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
export SIPGATE_TOKEN_ID="your-token-id"
|
|
28
|
+
export SIPGATE_TOKEN="your-token"
|
|
29
|
+
sipgate-mcp
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
For clients that manage MCP commands on demand, `npx -y sipgate-mcp` remains
|
|
33
|
+
supported without a global installation. The package name was available when
|
|
34
|
+
checked on 2026-08-30; this repository is configured for `sipgate-mcp`, but the
|
|
35
|
+
initial npm publish still needs to be completed.
|
|
36
|
+
|
|
37
|
+
## Create a Personal Access Token
|
|
38
|
+
|
|
39
|
+
1. Open [sipgate Personal Access Tokens](https://app.sipgate.com/personal-access-token).
|
|
40
|
+
2. Select **Add token**, give the token a recognizable name, and select the scopes needed for the tools you intend to use.
|
|
41
|
+
3. Copy both the token ID and token. sipgate displays the token itself only once.
|
|
42
|
+
4. Put them in the environment as `SIPGATE_TOKEN_ID` and `SIPGATE_TOKEN` before starting your MCP client.
|
|
43
|
+
|
|
44
|
+
sipgate PAT authentication uses HTTP Basic Auth with `token-id:token` as the credential pair. `sipgate-mcp` constructs that header in memory. See sipgate's [authentication guide](https://en.sipgate.io/rest-api/authentication).
|
|
45
|
+
|
|
46
|
+
Do not put either value in this repository, an MCP config committed to source control, command output, or an issue report.
|
|
47
|
+
|
|
48
|
+
## Tools and PAT scopes
|
|
49
|
+
|
|
50
|
+
The table lists the non-`all` scopes named by sipgate's live Swagger document, plus scopes required by this server's pre/post state reads. sipgate also exposes broader parent scopes such as `sessions:write`; select the listed specific and parent scopes offered by the PAT UI when in doubt.
|
|
51
|
+
|
|
52
|
+
| Tool | Access | sipgate API calls | PAT scopes |
|
|
53
|
+
| --- | --- | --- | --- |
|
|
54
|
+
| `account_info` | Read | `GET /account`, `GET /authorization/userinfo` | `account:read` (`userinfo` has no scope declaration in Swagger) |
|
|
55
|
+
| `list_users` | Read | `GET /users` | `users:read` |
|
|
56
|
+
| `list_numbers` | Read | `GET /numbers` | `numbers:read` |
|
|
57
|
+
| `list_devices` | Read | `GET /users`, `GET /{userId}/devices` | `devices:read`; `users:read` when `user_id` is omitted |
|
|
58
|
+
| `get_routing` | Read | `GET /numbers`, `GET /users`, `GET /{userId}/phonelines`, `GET /{userId}/phonelines/{phonelineId}/numbers`, `GET /{userId}/phonelines/{phonelineId}/forwardings` | `numbers:read`, `phonelines:read`, `phonelines:numbers:read`, `phonelines:forwardings:read`; `users:read` when `user_id` is omitted |
|
|
59
|
+
| `call_history` | Read | `GET /history` | `history:read` |
|
|
60
|
+
| `get_settings` | Read | `GET /users[/userId]`, `GET /{userId}/devices`, `GET /{userId}/phonelines[/phonelineId]` | `users:read`, `devices:read`, `phonelines:read` |
|
|
61
|
+
| `set_number_routing` | Write | pre/post `GET /numbers`, `PUT /numbers/{numberId}` | `numbers:read`, `numbers:write` |
|
|
62
|
+
| `set_forwarding` | Write | pre/post `GET /{userId}/phonelines/{phonelineId}/forwardings`, `PUT` to the same path | `phonelines:read`, `phonelines:write`, `phonelines:forwardings:read`, `phonelines:forwardings:write` |
|
|
63
|
+
| `set_dnd` | Write | pre/post `GET /devices/{deviceId}`, `PUT /devices/{deviceId}` | `devices:read`, `devices:write` |
|
|
64
|
+
| `send_sms` | Write/action | `GET /{userId}/sms`, pre/post `GET /history`, `POST /sessions/sms` | `sms:read`, `history:read`, `sessions:write`, `sessions:sms:write` |
|
|
65
|
+
| `initiate_call` | Write/action | pre/post `GET /calls`, `POST /sessions/calls` | `rtcm:read`, `sessions:write`, `sessions:calls:write` |
|
|
66
|
+
|
|
67
|
+
Every write tool reads current state first and returns a JSON object with `before` and `after`. SMS history can update asynchronously, and `/calls` only contains established calls, so those action snapshots also include an acceptance/session marker.
|
|
68
|
+
|
|
69
|
+
### Tool notes
|
|
70
|
+
|
|
71
|
+
- `list_devices` resolves devices through users because the documented account-wide route is `GET /{userId}/devices`; the live v2 Swagger document does not define `GET /devices`.
|
|
72
|
+
- Number routing uses sipgate's documented `endpointId`. Obtain existing IDs from the read tools; a phoneline ID such as `p0` is the documented example.
|
|
73
|
+
- `set_forwarding` replaces the complete phoneline forwarding list. Pass `forwardings: []` to remove all forwardings. A `timeout` of `0` represents immediate forwarding.
|
|
74
|
+
- `send_sms` refuses to post unless `GET /{userId}/sms` returns the requested (or first available) SMS extension.
|
|
75
|
+
- sipgate documents `POST /sessions/calls` as the classic-PBX Click2Dial route. The API documentation points Neo PBX accounts to `/calls`; supporting that distinct call workflow is left for a future compatibility pass.
|
|
76
|
+
|
|
77
|
+
## Read-only mode
|
|
78
|
+
|
|
79
|
+
Set `SIPGATE_MCP_READONLY=1` to register only the seven read tools. Write tools are absent from `tools/list`, rather than merely failing when called.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
export SIPGATE_MCP_READONLY=1
|
|
83
|
+
npx -y sipgate-mcp
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## MCP client configuration
|
|
87
|
+
|
|
88
|
+
Set `SIPGATE_TOKEN_ID` and `SIPGATE_TOKEN` in the environment that launches the MCP client. The examples keep secret values out of configuration files.
|
|
89
|
+
|
|
90
|
+
### Claude Code
|
|
91
|
+
|
|
92
|
+
Claude Code expands `${VAR}` references in MCP environment entries. Single quotes below prevent your shell from replacing the references before Claude stores them:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
claude mcp add \
|
|
96
|
+
--env 'SIPGATE_TOKEN_ID=${SIPGATE_TOKEN_ID}' \
|
|
97
|
+
--env 'SIPGATE_TOKEN=${SIPGATE_TOKEN}' \
|
|
98
|
+
--transport stdio \
|
|
99
|
+
--scope user \
|
|
100
|
+
sipgate -- npx -y sipgate-mcp
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Add `--env SIPGATE_MCP_READONLY=1` before `--transport` for read-only mode. Verify the connection with `claude mcp get sipgate`. See the official [Claude Code MCP documentation](https://code.claude.com/docs/en/mcp).
|
|
104
|
+
|
|
105
|
+
### Claude Desktop
|
|
106
|
+
|
|
107
|
+
Ensure the Claude Desktop process inherits `SIPGATE_TOKEN_ID` and `SIPGATE_TOKEN`, then add this entry to `claude_desktop_config.json`:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"mcpServers": {
|
|
112
|
+
"sipgate": {
|
|
113
|
+
"command": "npx",
|
|
114
|
+
"args": ["-y", "sipgate-mcp"]
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Restart Claude Desktop after editing the file. Do not paste PAT values into the JSON. If the app was launched from a desktop shell rather than a terminal, configure the variables in that app's launch environment first.
|
|
121
|
+
|
|
122
|
+
### Codex
|
|
123
|
+
|
|
124
|
+
Codex can forward named variables from its local environment without storing their values. Add this to `~/.codex/config.toml` (or a trusted project's `.codex/config.toml`):
|
|
125
|
+
|
|
126
|
+
```toml
|
|
127
|
+
[mcp_servers.sipgate]
|
|
128
|
+
command = "npx"
|
|
129
|
+
args = ["-y", "sipgate-mcp"]
|
|
130
|
+
env_vars = ["SIPGATE_TOKEN_ID", "SIPGATE_TOKEN", "SIPGATE_MCP_READONLY"]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Export the variables before starting Codex, then use `/mcp` or `codex mcp list` to confirm the server. The `env_vars` forwarding form is documented in the [official OpenAI MCP documentation](https://developers.openai.com/codex/mcp/).
|
|
134
|
+
|
|
135
|
+
## Manual smoke test with MCP Inspector
|
|
136
|
+
|
|
137
|
+
This test makes real sipgate API calls. Start with a PAT containing only `account:read` and `numbers:read`, plus the required environment variables:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
export SIPGATE_TOKEN_ID="your-token-id"
|
|
141
|
+
export SIPGATE_TOKEN="your-token"
|
|
142
|
+
npx @modelcontextprotocol/inspector npx -y sipgate-mcp
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The current Inspector v2 may require a more recent Node 22 minor release than the server itself. In the Inspector web UI:
|
|
146
|
+
|
|
147
|
+
1. Connect to the stdio server.
|
|
148
|
+
2. Open **Tools** and run `account_info` with `{}`.
|
|
149
|
+
3. Run `list_numbers` with `{"offset": 0, "limit": 100}`.
|
|
150
|
+
4. Confirm that the responses contain account/user metadata and numbers with `endpointId` assignments, and that no token or Authorization header is displayed.
|
|
151
|
+
|
|
152
|
+
The Inspector's ad-hoc stdio syntax is documented in its [server configuration guide](https://github.com/modelcontextprotocol/inspector/blob/main/docs/mcp-server-configuration.md).
|
|
153
|
+
|
|
154
|
+
## Architecture
|
|
155
|
+
|
|
156
|
+
The MCP layer depends only on the backend interface:
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
MCP stdio server
|
|
160
|
+
-> validated tool definitions (Zod)
|
|
161
|
+
-> TelephonyBackend
|
|
162
|
+
-> SipgateBackend (v1 implementation)
|
|
163
|
+
-> SipgateClient
|
|
164
|
+
-> native fetch -> https://api.sipgate.com/v2
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`TelephonyBackend` contains the stable, provider-neutral operations. `SipgateBackend` is the only v1 implementation, so a future second telephony provider can reuse the same MCP tool surface.
|
|
168
|
+
|
|
169
|
+
## Security
|
|
170
|
+
|
|
171
|
+
- PAT values are read only from `SIPGATE_TOKEN_ID` and `SIPGATE_TOKEN`.
|
|
172
|
+
- The Basic Auth header exists only in memory and is sent only to the fixed sipgate API base URL.
|
|
173
|
+
- API error bodies are discarded. User-facing errors never include request headers, response bodies, or credentials.
|
|
174
|
+
- Potentially sensitive response properties such as `credentials`, `password`, `token`, and `secret` are redacted before tool output.
|
|
175
|
+
- The server writes no application logs to stdout; stdout is reserved for MCP stdio frames.
|
|
176
|
+
- `.env*`, fetched research data, build output, and package tarballs are ignored by Git.
|
|
177
|
+
- Write tools clearly identify account changes and possible charges in their descriptions. Prefer read-only mode until write access is needed.
|
|
178
|
+
|
|
179
|
+
## Development
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
npm install
|
|
183
|
+
npm run build
|
|
184
|
+
npm test
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Tests use `node:test` and mocked `fetch`; they never call the real sipgate API. The suite includes client authentication/error behavior, exact critical write payloads, credential redaction, one test per MCP tool, and read-only registration.
|
|
188
|
+
|
|
189
|
+
## Releases
|
|
190
|
+
|
|
191
|
+
Normal CI tests Node.js 22 and 24. Publishing a GitHub Release whose tag matches
|
|
192
|
+
the version in `package.json` triggers an npm publish from a GitHub-hosted runner.
|
|
193
|
+
The release workflow uses npm Trusted Publishing with OpenID Connect, contains
|
|
194
|
+
no long-lived npm token, and produces npm provenance automatically. Stable
|
|
195
|
+
GitHub Releases publish under npm's `latest` tag; GitHub prereleases use `next`.
|
|
196
|
+
|
|
197
|
+
Maintainer setup and the one-time first-publish procedure are documented in
|
|
198
|
+
[RELEASING.md](RELEASING.md).
|
|
199
|
+
|
|
200
|
+
## API provenance and limitations
|
|
201
|
+
|
|
202
|
+
The endpoint paths, query parameters, request bodies, response models, and scope names were checked against sipgate's live public [REST API v2 Swagger document](https://api.sipgate.com/v2/swagger.json) and [Swagger UI](https://api.sipgate.com/v2/doc) on 2026-08-29. PAT Basic Auth was checked against sipgate's public authentication guide. No authenticated production account was available during development, so real-account behavior remains to be confirmed with the smoke test above—especially product-specific availability, eventual history updates, and classic versus Neo PBX calling.
|
|
203
|
+
|
|
204
|
+
## Roadmap
|
|
205
|
+
|
|
206
|
+
- v1: local self-hosted stdio server (this release)
|
|
207
|
+
- v2: optional remote deployment, including a Cloudflare Workers backend, without changing the MCP tool surface
|
|
208
|
+
- Additional `TelephonyBackend` implementation(s)
|
|
209
|
+
- Product-aware Click2Dial behavior for classic and Neo PBX accounts
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
MIT © 2026 Arne Wiese. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SipgateClient } from "./sipgate-client.js";
|
|
2
|
+
import type { DeviceType, ForwardingRule, HistoryQuery, JsonValue, MutationResult, PaginationInput, TelephonyBackend } from "./telephony-backend.js";
|
|
3
|
+
export declare class SipgateBackend implements TelephonyBackend {
|
|
4
|
+
private readonly client;
|
|
5
|
+
constructor(client: SipgateClient);
|
|
6
|
+
getAccountInfo(): Promise<JsonValue>;
|
|
7
|
+
listUsers(): Promise<JsonValue>;
|
|
8
|
+
listNumbers({ offset, limit }: PaginationInput): Promise<JsonValue>;
|
|
9
|
+
listDevices(userId?: string, types?: DeviceType[]): Promise<JsonValue>;
|
|
10
|
+
getRouting(userId?: string): Promise<JsonValue>;
|
|
11
|
+
getCallHistory(query: HistoryQuery): Promise<JsonValue>;
|
|
12
|
+
getSettings(userId?: string): Promise<JsonValue>;
|
|
13
|
+
setNumberRouting(numberId: string, endpointId: string): Promise<MutationResult>;
|
|
14
|
+
setForwarding(userId: string, phonelineId: string, forwardings: ForwardingRule[]): Promise<MutationResult>;
|
|
15
|
+
setDnd(deviceId: string, enabled: boolean): Promise<MutationResult>;
|
|
16
|
+
sendSms(input: {
|
|
17
|
+
userId: string;
|
|
18
|
+
smsId?: string;
|
|
19
|
+
recipient: string;
|
|
20
|
+
message: string;
|
|
21
|
+
sendAt?: number;
|
|
22
|
+
}): Promise<MutationResult>;
|
|
23
|
+
initiateCall(input: {
|
|
24
|
+
caller: string;
|
|
25
|
+
callee: string;
|
|
26
|
+
callerId?: string;
|
|
27
|
+
deviceId?: string;
|
|
28
|
+
}): Promise<MutationResult>;
|
|
29
|
+
private findNumber;
|
|
30
|
+
private latestHistory;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=sipgate-backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sipgate-backend.d.ts","sourceRoot":"","sources":["../../src/backend/sipgate-backend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,KAAK,EACV,UAAU,EACV,cAAc,EACd,YAAY,EAEZ,SAAS,EACT,cAAc,EACd,eAAe,EACf,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAqChC,qBAAa,cAAe,YAAW,gBAAgB;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAA1C,YAAoC,MAAM,EAAE,aAAa,EAAI;IAEhD,cAAc,IAAI,OAAO,CAAC,SAAS,CAAC,CAMhD;IAEY,SAAS,IAAI,OAAO,CAAC,SAAS,CAAC,CAG3C;IAEY,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAI/E;IAEY,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAgBlF;IAEY,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CA6B3D;IAEY,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC,CAqBnE;IAEY,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CA8B5D;IAEY,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAQ3F;IAEY,aAAa,CACxB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,cAAc,EAAE,GAC5B,OAAO,CAAC,cAAc,CAAC,CASzB;IAEY,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAM/E;IAEY,OAAO,CAAC,KAAK,EAAE;QAC1B,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,cAAc,CAAC,CAqC1B;IAEY,YAAY,CAAC,KAAK,EAAE;QAC/B,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,cAAc,CAAC,CAoB1B;YAEa,UAAU;YAOV,aAAa;CAY5B"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { SipgateApiError, SipgateClient } from "./sipgate-client.js";
|
|
2
|
+
const SENSITIVE_KEY = /authorization|credential|password|secret|token/i;
|
|
3
|
+
function sanitize(value) {
|
|
4
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
if (Array.isArray(value))
|
|
8
|
+
return value.map(sanitize);
|
|
9
|
+
if (typeof value === "object") {
|
|
10
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, SENSITIVE_KEY.test(key) ? "[REDACTED]" : sanitize(child)]));
|
|
11
|
+
}
|
|
12
|
+
return String(value);
|
|
13
|
+
}
|
|
14
|
+
function asItems(value) {
|
|
15
|
+
if (!value || Array.isArray(value) || typeof value !== "object")
|
|
16
|
+
return [];
|
|
17
|
+
const items = value.items;
|
|
18
|
+
return Array.isArray(items) ? items : [];
|
|
19
|
+
}
|
|
20
|
+
function stringField(value, key) {
|
|
21
|
+
const field = value[key];
|
|
22
|
+
return typeof field === "string" ? field : undefined;
|
|
23
|
+
}
|
|
24
|
+
function encodeId(id) {
|
|
25
|
+
return encodeURIComponent(id);
|
|
26
|
+
}
|
|
27
|
+
export class SipgateBackend {
|
|
28
|
+
client;
|
|
29
|
+
constructor(client) {
|
|
30
|
+
this.client = client;
|
|
31
|
+
}
|
|
32
|
+
async getAccountInfo() {
|
|
33
|
+
const [account, authenticatedUser] = await Promise.all([
|
|
34
|
+
this.client.request("/account"),
|
|
35
|
+
this.client.request("/authorization/userinfo"),
|
|
36
|
+
]);
|
|
37
|
+
return sanitize({ account, authenticatedUser });
|
|
38
|
+
}
|
|
39
|
+
async listUsers() {
|
|
40
|
+
const response = await this.client.request("/users");
|
|
41
|
+
return sanitize(response ?? { items: [] });
|
|
42
|
+
}
|
|
43
|
+
async listNumbers({ offset, limit }) {
|
|
44
|
+
const response = await this.client.request("/numbers", { query: { offset, limit } });
|
|
45
|
+
const items = asItems(response);
|
|
46
|
+
return sanitize({ items, pagination: { offset, limit, returned: items.length } });
|
|
47
|
+
}
|
|
48
|
+
async listDevices(userId, types) {
|
|
49
|
+
const userIds = userId ? [userId] : asItems(await this.client.request("/users"))
|
|
50
|
+
.map((user) => stringField(user, "id"))
|
|
51
|
+
.filter((id) => Boolean(id));
|
|
52
|
+
const type = types && types.length > 0 ? types.join(",") : undefined;
|
|
53
|
+
const responses = await Promise.all(userIds.map(async (id) => ({
|
|
54
|
+
userId: id,
|
|
55
|
+
response: await this.client.request(`/${encodeId(id)}/devices`, { query: { type } }),
|
|
56
|
+
})));
|
|
57
|
+
const items = responses.flatMap(({ userId: ownerId, response }) => asItems(response).map((device) => ({ ...device, userId: ownerId })));
|
|
58
|
+
return sanitize({ items });
|
|
59
|
+
}
|
|
60
|
+
async getRouting(userId) {
|
|
61
|
+
const [numbersResponse, usersResponse] = await Promise.all([
|
|
62
|
+
this.client.request("/numbers", { query: { offset: 0, limit: 1000 } }),
|
|
63
|
+
userId ? Promise.resolve(undefined) : this.client.request("/users"),
|
|
64
|
+
]);
|
|
65
|
+
const userIds = userId ? [userId] : asItems(usersResponse)
|
|
66
|
+
.map((user) => stringField(user, "id"))
|
|
67
|
+
.filter((id) => Boolean(id));
|
|
68
|
+
const users = await Promise.all(userIds.map(async (id) => {
|
|
69
|
+
const phonelinesResponse = await this.client.request(`/${encodeId(id)}/phonelines`);
|
|
70
|
+
const phonelines = await Promise.all(asItems(phonelinesResponse).map(async (phoneline) => {
|
|
71
|
+
const phonelineId = stringField(phoneline, "id");
|
|
72
|
+
if (!phonelineId)
|
|
73
|
+
return { ...phoneline, numbers: [], forwardings: [] };
|
|
74
|
+
const base = `/${encodeId(id)}/phonelines/${encodeId(phonelineId)}`;
|
|
75
|
+
const [lineNumbers, forwardings] = await Promise.all([
|
|
76
|
+
this.client.request(`${base}/numbers`),
|
|
77
|
+
this.client.request(`${base}/forwardings`),
|
|
78
|
+
]);
|
|
79
|
+
return {
|
|
80
|
+
...phoneline,
|
|
81
|
+
numbers: asItems(lineNumbers),
|
|
82
|
+
forwardings: asItems(forwardings),
|
|
83
|
+
};
|
|
84
|
+
}));
|
|
85
|
+
return { userId: id, phonelines };
|
|
86
|
+
}));
|
|
87
|
+
return sanitize({ numbers: asItems(numbersResponse), users });
|
|
88
|
+
}
|
|
89
|
+
async getCallHistory(query) {
|
|
90
|
+
const response = await this.client.request("/history", {
|
|
91
|
+
query: {
|
|
92
|
+
connectionIds: query.connectionIds,
|
|
93
|
+
types: query.types ?? ["CALL"],
|
|
94
|
+
directions: query.directions,
|
|
95
|
+
offset: query.offset,
|
|
96
|
+
limit: query.limit,
|
|
97
|
+
from: query.from,
|
|
98
|
+
to: query.to,
|
|
99
|
+
phonenumber: query.phoneNumber,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
const object = response && !Array.isArray(response) && typeof response === "object" ? response : {};
|
|
103
|
+
const items = asItems(response);
|
|
104
|
+
const totalCount = typeof object.totalCount === "number" ? object.totalCount : items.length;
|
|
105
|
+
const nextOffset = query.offset + items.length < totalCount ? query.offset + items.length : null;
|
|
106
|
+
return sanitize({
|
|
107
|
+
items,
|
|
108
|
+
pagination: { offset: query.offset, limit: query.limit, totalCount, nextOffset },
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
async getSettings(userId) {
|
|
112
|
+
const users = userId
|
|
113
|
+
? [await this.client.request(`/users/${encodeId(userId)}`)]
|
|
114
|
+
: asItems(await this.client.request("/users"));
|
|
115
|
+
const settings = await Promise.all(users.map(async (userValue) => {
|
|
116
|
+
if (!userValue || Array.isArray(userValue) || typeof userValue !== "object")
|
|
117
|
+
return null;
|
|
118
|
+
const user = userValue;
|
|
119
|
+
const id = stringField(user, "id");
|
|
120
|
+
if (!id)
|
|
121
|
+
return null;
|
|
122
|
+
const [devicesResponse, phonelinesResponse] = await Promise.all([
|
|
123
|
+
this.client.request(`/${encodeId(id)}/devices`),
|
|
124
|
+
this.client.request(`/${encodeId(id)}/phonelines`),
|
|
125
|
+
]);
|
|
126
|
+
const phonelines = await Promise.all(asItems(phonelinesResponse).map(async (line) => {
|
|
127
|
+
const lineId = stringField(line, "id");
|
|
128
|
+
if (!lineId)
|
|
129
|
+
return line;
|
|
130
|
+
return await this.client.request(`/${encodeId(id)}/phonelines/${encodeId(lineId)}`) ?? line;
|
|
131
|
+
}));
|
|
132
|
+
return {
|
|
133
|
+
user,
|
|
134
|
+
reachability: {
|
|
135
|
+
busyOnBusy: user.busyOnBusy ?? null,
|
|
136
|
+
defaultDevice: user.defaultDevice ?? null,
|
|
137
|
+
devices: asItems(devicesResponse),
|
|
138
|
+
},
|
|
139
|
+
phonelines,
|
|
140
|
+
};
|
|
141
|
+
}));
|
|
142
|
+
return sanitize({ users: settings.filter((item) => item !== null) });
|
|
143
|
+
}
|
|
144
|
+
async setNumberRouting(numberId, endpointId) {
|
|
145
|
+
const before = await this.findNumber(numberId);
|
|
146
|
+
await this.client.request(`/numbers/${encodeId(numberId)}`, {
|
|
147
|
+
method: "PUT",
|
|
148
|
+
body: { endpointId },
|
|
149
|
+
});
|
|
150
|
+
const after = await this.findNumber(numberId);
|
|
151
|
+
return { before: sanitize(before), after: sanitize(after) };
|
|
152
|
+
}
|
|
153
|
+
async setForwarding(userId, phonelineId, forwardings) {
|
|
154
|
+
const path = `/${encodeId(userId)}/phonelines/${encodeId(phonelineId)}/forwardings`;
|
|
155
|
+
const before = await this.client.request(path);
|
|
156
|
+
await this.client.request(path, {
|
|
157
|
+
method: "PUT",
|
|
158
|
+
body: { forwardings },
|
|
159
|
+
});
|
|
160
|
+
const after = await this.client.request(path);
|
|
161
|
+
return { before: sanitize(before ?? { items: [] }), after: sanitize(after ?? { items: [] }) };
|
|
162
|
+
}
|
|
163
|
+
async setDnd(deviceId, enabled) {
|
|
164
|
+
const path = `/devices/${encodeId(deviceId)}`;
|
|
165
|
+
const before = await this.client.request(path);
|
|
166
|
+
await this.client.request(path, { method: "PUT", body: { dnd: enabled } });
|
|
167
|
+
const after = await this.client.request(path);
|
|
168
|
+
return { before: sanitize(before ?? {}), after: sanitize(after ?? {}) };
|
|
169
|
+
}
|
|
170
|
+
async sendSms(input) {
|
|
171
|
+
const extensions = asItems(await this.client.request(`/${encodeId(input.userId)}/sms`));
|
|
172
|
+
const extension = input.smsId
|
|
173
|
+
? extensions.find((item) => stringField(item, "id") === input.smsId)
|
|
174
|
+
: extensions[0];
|
|
175
|
+
if (!extension) {
|
|
176
|
+
throw new SipgateApiError(input.smsId
|
|
177
|
+
? "The requested SMS extension is not available for this user."
|
|
178
|
+
: "No SMS-capable extension is available for this user.", 404);
|
|
179
|
+
}
|
|
180
|
+
const smsId = stringField(extension, "id");
|
|
181
|
+
if (!smsId)
|
|
182
|
+
throw new SipgateApiError("sipgate returned an SMS extension without an ID.");
|
|
183
|
+
const beforeHistory = await this.latestHistory("SMS", input.recipient);
|
|
184
|
+
await this.client.request("/sessions/sms", {
|
|
185
|
+
method: "POST",
|
|
186
|
+
body: {
|
|
187
|
+
smsId,
|
|
188
|
+
recipient: input.recipient,
|
|
189
|
+
message: input.message,
|
|
190
|
+
...(input.sendAt === undefined ? {} : { sendAt: input.sendAt }),
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
const afterHistory = await this.latestHistory("SMS", input.recipient);
|
|
194
|
+
return {
|
|
195
|
+
before: sanitize({ smsExtension: extension, latestMatchingHistory: beforeHistory }),
|
|
196
|
+
after: sanitize({
|
|
197
|
+
smsExtension: extension,
|
|
198
|
+
latestMatchingHistory: afterHistory,
|
|
199
|
+
requestAccepted: true,
|
|
200
|
+
}),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
async initiateCall(input) {
|
|
204
|
+
const before = await this.client.request("/calls");
|
|
205
|
+
const session = await this.client.request("/sessions/calls", {
|
|
206
|
+
method: "POST",
|
|
207
|
+
body: {
|
|
208
|
+
caller: input.caller,
|
|
209
|
+
callee: input.callee,
|
|
210
|
+
...(input.callerId === undefined ? {} : { callerId: input.callerId }),
|
|
211
|
+
...(input.deviceId === undefined ? {} : { deviceId: input.deviceId }),
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
const activeCalls = await this.client.request("/calls");
|
|
215
|
+
return {
|
|
216
|
+
before: sanitize(before ?? { data: [] }),
|
|
217
|
+
after: sanitize({
|
|
218
|
+
activeCalls: activeCalls ?? { data: [] },
|
|
219
|
+
session,
|
|
220
|
+
note: "The active-calls endpoint only includes established calls, so a newly ringing call may not appear immediately.",
|
|
221
|
+
}),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
async findNumber(numberId) {
|
|
225
|
+
const response = await this.client.request("/numbers", { query: { offset: 0, limit: 1000 } });
|
|
226
|
+
const number = asItems(response).find((item) => stringField(item, "id") === numberId);
|
|
227
|
+
if (!number)
|
|
228
|
+
throw new SipgateApiError("The requested sipgate phone number was not found.", 404);
|
|
229
|
+
return number;
|
|
230
|
+
}
|
|
231
|
+
async latestHistory(type, phoneNumber) {
|
|
232
|
+
const response = await this.client.request("/history", {
|
|
233
|
+
query: {
|
|
234
|
+
types: [type],
|
|
235
|
+
directions: ["OUTGOING"],
|
|
236
|
+
phonenumber: phoneNumber,
|
|
237
|
+
offset: 0,
|
|
238
|
+
limit: 1,
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
return asItems(response)[0] ?? null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=sipgate-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sipgate-backend.js","sourceRoot":"","sources":["../../src/backend/sipgate-backend.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { JsonValue } from "./telephony-backend.js";
|
|
2
|
+
type QueryValue = string | number | boolean | readonly (string | number | boolean)[] | undefined;
|
|
3
|
+
export interface SipgateClientOptions {
|
|
4
|
+
tokenId: string;
|
|
5
|
+
token: string;
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
fetch?: typeof globalThis.fetch;
|
|
8
|
+
}
|
|
9
|
+
export interface RequestOptions {
|
|
10
|
+
method?: "GET" | "POST" | "PUT" | "DELETE";
|
|
11
|
+
query?: Record<string, QueryValue>;
|
|
12
|
+
body?: JsonValue;
|
|
13
|
+
}
|
|
14
|
+
export declare class SipgateApiError extends Error {
|
|
15
|
+
readonly status: number | undefined;
|
|
16
|
+
constructor(message: string, status?: number);
|
|
17
|
+
}
|
|
18
|
+
export declare class SipgateClient {
|
|
19
|
+
#private;
|
|
20
|
+
constructor(options: SipgateClientOptions);
|
|
21
|
+
request<T extends JsonValue | undefined>(path: string, options?: RequestOptions): Promise<T>;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=sipgate-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sipgate-client.d.ts","sourceRoot":"","sources":["../../src/backend/sipgate-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAIxD,KAAK,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,EAAE,GAAG,SAAS,CAAC;AAEjG,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACnC,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED,qBAAa,eAAgB,SAAQ,KAAK;IACxC,SAAgB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAE3C,YAAmB,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAIlD;CACF;AA4BD,qBAAa,aAAa;;IAKxB,YAAmB,OAAO,EAAE,oBAAoB,EAU/C;IAEY,OAAO,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,EAClD,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,CAAC,CAAC,CAmCZ;CACF"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const DEFAULT_BASE_URL = "https://api.sipgate.com/v2";
|
|
2
|
+
export class SipgateApiError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
constructor(message, status) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "SipgateApiError";
|
|
7
|
+
this.status = status;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function errorForStatus(status, retryAfter) {
|
|
11
|
+
switch (status) {
|
|
12
|
+
case 401:
|
|
13
|
+
return new SipgateApiError("sipgate authentication failed (HTTP 401). Check SIPGATE_TOKEN_ID and SIPGATE_TOKEN.", status);
|
|
14
|
+
case 403:
|
|
15
|
+
return new SipgateApiError("sipgate denied this operation (HTTP 403). The Personal Access Token may be missing a required PAT scope or the account may not include this feature.", status);
|
|
16
|
+
case 404:
|
|
17
|
+
return new SipgateApiError("The requested sipgate resource was not found (HTTP 404). Check the supplied user, device, phoneline, number, or extension ID.", status);
|
|
18
|
+
case 429: {
|
|
19
|
+
const suffix = retryAfter ? ` Retry after ${retryAfter} seconds.` : " Retry later.";
|
|
20
|
+
return new SipgateApiError(`sipgate rate limit exceeded (HTTP 429).${suffix}`, status);
|
|
21
|
+
}
|
|
22
|
+
default:
|
|
23
|
+
return new SipgateApiError(`sipgate rejected the request (HTTP ${status}).`, status);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export class SipgateClient {
|
|
27
|
+
#baseUrl;
|
|
28
|
+
#authorization;
|
|
29
|
+
#fetch;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
if (!options.tokenId || !options.token) {
|
|
32
|
+
throw new SipgateApiError("Both SIPGATE_TOKEN_ID and SIPGATE_TOKEN must be set in the server environment.");
|
|
33
|
+
}
|
|
34
|
+
this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
35
|
+
this.#authorization = `Basic ${Buffer.from(`${options.tokenId}:${options.token}`, "utf8").toString("base64")}`;
|
|
36
|
+
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
37
|
+
}
|
|
38
|
+
async request(path, options = {}) {
|
|
39
|
+
const url = new URL(`${this.#baseUrl}${path.startsWith("/") ? path : `/${path}`}`);
|
|
40
|
+
for (const [name, value] of Object.entries(options.query ?? {})) {
|
|
41
|
+
if (value === undefined)
|
|
42
|
+
continue;
|
|
43
|
+
const values = Array.isArray(value) ? value : [value];
|
|
44
|
+
for (const item of values)
|
|
45
|
+
url.searchParams.append(name, String(item));
|
|
46
|
+
}
|
|
47
|
+
const headers = {
|
|
48
|
+
Accept: "application/json",
|
|
49
|
+
Authorization: this.#authorization,
|
|
50
|
+
};
|
|
51
|
+
if (options.body !== undefined)
|
|
52
|
+
headers["Content-Type"] = "application/json";
|
|
53
|
+
let response;
|
|
54
|
+
try {
|
|
55
|
+
response = await this.#fetch(url, {
|
|
56
|
+
method: options.method ?? "GET",
|
|
57
|
+
headers,
|
|
58
|
+
...(options.body === undefined ? {} : { body: JSON.stringify(options.body) }),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
throw new SipgateApiError("Could not reach the sipgate API. Check the network connection and try again.");
|
|
63
|
+
}
|
|
64
|
+
if (!response.ok)
|
|
65
|
+
throw errorForStatus(response.status, response.headers.get("retry-after"));
|
|
66
|
+
if (response.status === 204)
|
|
67
|
+
return undefined;
|
|
68
|
+
const text = await response.text();
|
|
69
|
+
if (!text)
|
|
70
|
+
return undefined;
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(text);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
throw new SipgateApiError("sipgate returned an unexpected non-JSON response.", response.status);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=sipgate-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sipgate-client.js","sourceRoot":"","sources":["../../src/backend/sipgate-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
2
|
+
export type JsonValue = JsonPrimitive | JsonValue[] | {
|
|
3
|
+
[key: string]: JsonValue;
|
|
4
|
+
};
|
|
5
|
+
export type JsonObject = {
|
|
6
|
+
[key: string]: JsonValue;
|
|
7
|
+
};
|
|
8
|
+
export type HistoryDirection = "INCOMING" | "OUTGOING" | "MISSED_INCOMING" | "MISSED_OUTGOING";
|
|
9
|
+
export type HistoryType = "CALL" | "VOICEMAIL" | "SMS" | "FAX";
|
|
10
|
+
export type DeviceType = "all" | "app" | "register" | "mobile" | "external";
|
|
11
|
+
export interface PaginationInput {
|
|
12
|
+
offset: number;
|
|
13
|
+
limit: number;
|
|
14
|
+
}
|
|
15
|
+
export interface HistoryQuery extends PaginationInput {
|
|
16
|
+
directions?: HistoryDirection[];
|
|
17
|
+
from?: string;
|
|
18
|
+
to?: string;
|
|
19
|
+
phoneNumber?: string;
|
|
20
|
+
types?: HistoryType[];
|
|
21
|
+
connectionIds?: string[];
|
|
22
|
+
}
|
|
23
|
+
export interface ForwardingRule {
|
|
24
|
+
[key: string]: JsonValue;
|
|
25
|
+
active: boolean;
|
|
26
|
+
destination: string;
|
|
27
|
+
timeout: number;
|
|
28
|
+
}
|
|
29
|
+
export interface MutationResult {
|
|
30
|
+
[key: string]: JsonValue;
|
|
31
|
+
before: JsonValue;
|
|
32
|
+
after: JsonValue;
|
|
33
|
+
}
|
|
34
|
+
export interface TelephonyBackend {
|
|
35
|
+
getAccountInfo(): Promise<JsonValue>;
|
|
36
|
+
listUsers(): Promise<JsonValue>;
|
|
37
|
+
listNumbers(pagination: PaginationInput): Promise<JsonValue>;
|
|
38
|
+
listDevices(userId?: string, types?: DeviceType[]): Promise<JsonValue>;
|
|
39
|
+
getRouting(userId?: string): Promise<JsonValue>;
|
|
40
|
+
getCallHistory(query: HistoryQuery): Promise<JsonValue>;
|
|
41
|
+
getSettings(userId?: string): Promise<JsonValue>;
|
|
42
|
+
setNumberRouting(numberId: string, endpointId: string): Promise<MutationResult>;
|
|
43
|
+
setForwarding(userId: string, phonelineId: string, forwardings: ForwardingRule[]): Promise<MutationResult>;
|
|
44
|
+
setDnd(deviceId: string, enabled: boolean): Promise<MutationResult>;
|
|
45
|
+
sendSms(input: {
|
|
46
|
+
userId: string;
|
|
47
|
+
smsId?: string;
|
|
48
|
+
recipient: string;
|
|
49
|
+
message: string;
|
|
50
|
+
sendAt?: number;
|
|
51
|
+
}): Promise<MutationResult>;
|
|
52
|
+
initiateCall(input: {
|
|
53
|
+
caller: string;
|
|
54
|
+
callee: string;
|
|
55
|
+
callerId?: string;
|
|
56
|
+
deviceId?: string;
|
|
57
|
+
}): Promise<MutationResult>;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=telephony-backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telephony-backend.d.ts","sourceRoot":"","sources":["../../src/backend/telephony-backend.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telephony-backend.js","sourceRoot":"","sources":["../../src/backend/telephony-backend.ts"],"names":[],"mappings":""}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,YAAY,CAO7E"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function loadConfig(env = process.env) {
|
|
2
|
+
const tokenId = env.SIPGATE_TOKEN_ID;
|
|
3
|
+
const token = env.SIPGATE_TOKEN;
|
|
4
|
+
if (!tokenId || !token) {
|
|
5
|
+
throw new Error("Both SIPGATE_TOKEN_ID and SIPGATE_TOKEN must be set.");
|
|
6
|
+
}
|
|
7
|
+
return { tokenId, token, readonly: env.SIPGATE_MCP_READONLY === "1" };
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,UAAU,CAAC,GAAG,GAAsB,OAAO,CAAC,GAAG;IAC7D,MAAM,OAAO,GAAG,GAAG,CAAC,gBAAgB,CAAC;IACrC,MAAM,KAAK,GAAG,GAAG,CAAC,aAAa,CAAC;IAChC,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,oBAAoB,KAAK,GAAG,EAAE,CAAC;AACxE,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export declare function main(): Promise<void>;
|
|
3
|
+
export declare function isMainModule(moduleUrl: string, entrypoint: string | undefined): boolean;
|
|
4
|
+
export { SipgateBackend } from "./backend/sipgate-backend.js";
|
|
5
|
+
export { SipgateApiError, SipgateClient } from "./backend/sipgate-client.js";
|
|
6
|
+
export type { TelephonyBackend } from "./backend/telephony-backend.js";
|
|
7
|
+
export { createServer } from "./server.js";
|
|
8
|
+
export { createToolDefinitions } from "./tools/definitions.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AASA,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAM1C;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAUvF;AASD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC7E,YAAY,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { SipgateBackend } from "./backend/sipgate-backend.js";
|
|
6
|
+
import { SipgateClient } from "./backend/sipgate-client.js";
|
|
7
|
+
import { loadConfig } from "./config.js";
|
|
8
|
+
import { createServer } from "./server.js";
|
|
9
|
+
export async function main() {
|
|
10
|
+
const config = loadConfig();
|
|
11
|
+
const client = new SipgateClient({ tokenId: config.tokenId, token: config.token });
|
|
12
|
+
const backend = new SipgateBackend(client);
|
|
13
|
+
const server = createServer(backend, config.readonly);
|
|
14
|
+
await server.connect(new StdioServerTransport());
|
|
15
|
+
}
|
|
16
|
+
export function isMainModule(moduleUrl, entrypoint) {
|
|
17
|
+
if (!entrypoint) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return realpathSync(fileURLToPath(moduleUrl)) === realpathSync(entrypoint);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (isMainModule(import.meta.url, process.argv[1])) {
|
|
28
|
+
main().catch(() => {
|
|
29
|
+
console.error("sipgate-mcp failed to start. Check the required environment variables and MCP client configuration.");
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export { SipgateBackend } from "./backend/sipgate-backend.js";
|
|
34
|
+
export { SipgateApiError, SipgateClient } from "./backend/sipgate-client.js";
|
|
35
|
+
export { createServer } from "./server.js";
|
|
36
|
+
export { createToolDefinitions } from "./tools/definitions.js";
|
|
37
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IACnF,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtD,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,SAAiB,EAAE,UAA8B;IAC5E,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,KAAK,YAAY,CAAC,UAAU,CAAC,CAAC;IAC7E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,IAAI,YAAY,CAAC,OAAO,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACnD,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;QAChB,OAAO,CAAC,KAAK,CAAC,qGAAqG,CAAC,CAAC;QACrH,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAE7E,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import type { TelephonyBackend } from "./backend/telephony-backend.js";
|
|
3
|
+
export declare function createServer(backend: TelephonyBackend, readonly?: boolean): Server;
|
|
4
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAOnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAuBvE,wBAAgB,YAAY,CAAC,OAAO,EAAE,gBAAgB,EAAE,QAAQ,UAAQ,GAAG,MAAM,CAuChF"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { createToolDefinitions } from "./tools/definitions.js";
|
|
5
|
+
function publicError(error) {
|
|
6
|
+
if (error instanceof z.ZodError) {
|
|
7
|
+
const details = error.issues.map((issue) => {
|
|
8
|
+
const path = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
|
|
9
|
+
return `${path}${issue.message}`;
|
|
10
|
+
});
|
|
11
|
+
return `Invalid tool input. ${details.join("; ")}`;
|
|
12
|
+
}
|
|
13
|
+
if (error instanceof Error && error.message) {
|
|
14
|
+
return error.message.replace(/Basic\s+[A-Za-z0-9+/=]+/gi, "Basic [REDACTED]");
|
|
15
|
+
}
|
|
16
|
+
return "The sipgate operation failed unexpectedly.";
|
|
17
|
+
}
|
|
18
|
+
function jsonSchemaFor(schema) {
|
|
19
|
+
const generated = z.toJSONSchema(schema, { target: "draft-7" });
|
|
20
|
+
const { $schema: _ignored, ...inputSchema } = generated;
|
|
21
|
+
return inputSchema;
|
|
22
|
+
}
|
|
23
|
+
export function createServer(backend, readonly = false) {
|
|
24
|
+
const definitions = createToolDefinitions(backend, readonly);
|
|
25
|
+
const byName = new Map(definitions.map((definition) => [definition.name, definition]));
|
|
26
|
+
const server = new Server({ name: "sipgate-mcp", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
27
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
28
|
+
tools: definitions.map((definition) => ({
|
|
29
|
+
name: definition.name,
|
|
30
|
+
description: definition.description,
|
|
31
|
+
inputSchema: jsonSchemaFor(definition.inputSchema),
|
|
32
|
+
annotations: definition.annotations,
|
|
33
|
+
})),
|
|
34
|
+
}));
|
|
35
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
36
|
+
const definition = byName.get(request.params.name);
|
|
37
|
+
if (!definition) {
|
|
38
|
+
return {
|
|
39
|
+
isError: true,
|
|
40
|
+
content: [{ type: "text", text: `Unknown tool: ${request.params.name}` }],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const result = await definition.execute(request.params.arguments ?? {});
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
return {
|
|
51
|
+
isError: true,
|
|
52
|
+
content: [{ type: "text", text: publicError(error) }],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return server;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { JsonValue, TelephonyBackend } from "../backend/telephony-backend.js";
|
|
3
|
+
export interface ToolAnnotations {
|
|
4
|
+
readOnlyHint: boolean;
|
|
5
|
+
destructiveHint: boolean;
|
|
6
|
+
idempotentHint: boolean;
|
|
7
|
+
openWorldHint: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface ToolDefinition {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
inputSchema: z.ZodType<Record<string, unknown>>;
|
|
13
|
+
annotations: ToolAnnotations;
|
|
14
|
+
execute(input: unknown): Promise<JsonValue>;
|
|
15
|
+
}
|
|
16
|
+
export declare function createToolDefinitions(backend: TelephonyBackend, readonly?: boolean): ToolDefinition[];
|
|
17
|
+
//# sourceMappingURL=definitions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/tools/definitions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAEnF,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,OAAO,CAAC;IACtB,eAAe,EAAE,OAAO,CAAC;IACzB,cAAc,EAAE,OAAO,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChD,WAAW,EAAE,eAAe,CAAC;IAC7B,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CAC7C;AA2CD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,gBAAgB,EACzB,QAAQ,UAAQ,GACf,cAAc,EAAE,CAmKlB"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const readAnnotations = {
|
|
3
|
+
readOnlyHint: true,
|
|
4
|
+
destructiveHint: false,
|
|
5
|
+
idempotentHint: true,
|
|
6
|
+
openWorldHint: true,
|
|
7
|
+
};
|
|
8
|
+
const writeAnnotations = {
|
|
9
|
+
readOnlyHint: false,
|
|
10
|
+
destructiveHint: true,
|
|
11
|
+
idempotentHint: true,
|
|
12
|
+
openWorldHint: true,
|
|
13
|
+
};
|
|
14
|
+
const actionAnnotations = {
|
|
15
|
+
readOnlyHint: false,
|
|
16
|
+
destructiveHint: true,
|
|
17
|
+
idempotentHint: false,
|
|
18
|
+
openWorldHint: true,
|
|
19
|
+
};
|
|
20
|
+
const id = z.string().trim().min(1).max(128);
|
|
21
|
+
const e164 = z.string().regex(/^\+[1-9]\d{6,14}$/, "Use an E.164 phone number such as +4915799912345");
|
|
22
|
+
const isoDateTime = z.string().refine((value) => !Number.isNaN(Date.parse(value)), "Use an ISO 8601 date-time");
|
|
23
|
+
function define(options) {
|
|
24
|
+
return {
|
|
25
|
+
name: options.name,
|
|
26
|
+
description: options.description,
|
|
27
|
+
inputSchema: options.schema,
|
|
28
|
+
annotations: options.annotations,
|
|
29
|
+
execute: async (input) => options.execute(options.schema.parse(input)),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function createToolDefinitions(backend, readonly = false) {
|
|
33
|
+
const readTools = [
|
|
34
|
+
define({
|
|
35
|
+
name: "account_info",
|
|
36
|
+
description: "Return sipgate account data and the authenticated user's identity.",
|
|
37
|
+
schema: z.object({}),
|
|
38
|
+
annotations: readAnnotations,
|
|
39
|
+
execute: async () => backend.getAccountInfo(),
|
|
40
|
+
}),
|
|
41
|
+
define({
|
|
42
|
+
name: "list_users",
|
|
43
|
+
description: "List all users in the sipgate account, including IDs used by other tools.",
|
|
44
|
+
schema: z.object({}),
|
|
45
|
+
annotations: readAnnotations,
|
|
46
|
+
execute: async () => backend.listUsers(),
|
|
47
|
+
}),
|
|
48
|
+
define({
|
|
49
|
+
name: "list_numbers",
|
|
50
|
+
description: "List sipgate phone numbers and their current endpoint assignments.",
|
|
51
|
+
schema: z.object({
|
|
52
|
+
offset: z.int().min(0).default(0).describe("Zero-based pagination offset"),
|
|
53
|
+
limit: z.int().min(1).max(1000).default(1000).describe("Maximum number of phone numbers"),
|
|
54
|
+
}),
|
|
55
|
+
annotations: readAnnotations,
|
|
56
|
+
execute: async ({ offset, limit }) => backend.listNumbers({ offset, limit }),
|
|
57
|
+
}),
|
|
58
|
+
define({
|
|
59
|
+
name: "list_devices",
|
|
60
|
+
description: "List phones and devices with owner, active routing, DND, and online/register status. Without user_id, all users are queried.",
|
|
61
|
+
schema: z.object({
|
|
62
|
+
user_id: id.optional().describe("Limit results to one sipgate user ID, for example w0"),
|
|
63
|
+
types: z.array(z.enum(["all", "app", "register", "mobile", "external"])).min(1).optional()
|
|
64
|
+
.describe("Optional sipgate device-type filters"),
|
|
65
|
+
}),
|
|
66
|
+
annotations: readAnnotations,
|
|
67
|
+
execute: async ({ user_id, types }) => backend.listDevices(user_id, types),
|
|
68
|
+
}),
|
|
69
|
+
define({
|
|
70
|
+
name: "get_routing",
|
|
71
|
+
description: "Return number-to-endpoint routing plus each user's phonelines, assigned numbers, and active or timeout forwardings.",
|
|
72
|
+
schema: z.object({
|
|
73
|
+
user_id: id.optional().describe("Limit phoneline forwarding details to one sipgate user ID"),
|
|
74
|
+
}),
|
|
75
|
+
annotations: readAnnotations,
|
|
76
|
+
execute: async ({ user_id }) => backend.getRouting(user_id),
|
|
77
|
+
}),
|
|
78
|
+
define({
|
|
79
|
+
name: "call_history",
|
|
80
|
+
description: "List paginated call history with optional direction, time-range, number, and connection filters.",
|
|
81
|
+
schema: z.object({
|
|
82
|
+
directions: z.array(z.enum(["INCOMING", "OUTGOING", "MISSED_INCOMING", "MISSED_OUTGOING"])).min(1).optional(),
|
|
83
|
+
from: isoDateTime.optional().describe("Inclusive ISO 8601 start date-time"),
|
|
84
|
+
to: isoDateTime.optional().describe("Inclusive ISO 8601 end date-time"),
|
|
85
|
+
phone_number: e164.optional().describe("Only calls to or from this phone number"),
|
|
86
|
+
connection_ids: z.array(id).min(1).optional().describe("Optional sipgate extension IDs"),
|
|
87
|
+
offset: z.int().min(0).default(0),
|
|
88
|
+
limit: z.int().min(1).max(1000).default(100),
|
|
89
|
+
}).refine((value) => !value.from || !value.to || Date.parse(value.from) <= Date.parse(value.to), {
|
|
90
|
+
message: "from must be before or equal to to",
|
|
91
|
+
path: ["from"],
|
|
92
|
+
}),
|
|
93
|
+
annotations: readAnnotations,
|
|
94
|
+
execute: async ({ directions, from, to, phone_number, connection_ids, offset, limit }) => backend.getCallHistory({
|
|
95
|
+
offset,
|
|
96
|
+
limit,
|
|
97
|
+
types: ["CALL"],
|
|
98
|
+
...(directions === undefined ? {} : { directions }),
|
|
99
|
+
...(from === undefined ? {} : { from }),
|
|
100
|
+
...(to === undefined ? {} : { to }),
|
|
101
|
+
...(phone_number === undefined ? {} : { phoneNumber: phone_number }),
|
|
102
|
+
...(connection_ids === undefined ? {} : { connectionIds: connection_ids }),
|
|
103
|
+
}),
|
|
104
|
+
}),
|
|
105
|
+
define({
|
|
106
|
+
name: "get_settings",
|
|
107
|
+
description: "Return relevant user reachability settings, device availability/DND state, and phoneline voicemail activation and timeout settings.",
|
|
108
|
+
schema: z.object({
|
|
109
|
+
user_id: id.optional().describe("Limit settings to one sipgate user ID"),
|
|
110
|
+
}),
|
|
111
|
+
annotations: readAnnotations,
|
|
112
|
+
execute: async ({ user_id }) => backend.getSettings(user_id),
|
|
113
|
+
}),
|
|
114
|
+
];
|
|
115
|
+
if (readonly)
|
|
116
|
+
return readTools;
|
|
117
|
+
const writeTools = [
|
|
118
|
+
define({
|
|
119
|
+
name: "set_number_routing",
|
|
120
|
+
description: "CHANGES THE SIPGATE ACCOUNT: route a phone number to a sipgate endpoint ID (for example a phoneline). Reads and returns the number's before/after state.",
|
|
121
|
+
schema: z.object({
|
|
122
|
+
number_id: id.describe("Phone-number ID returned by list_numbers"),
|
|
123
|
+
endpoint_id: id.describe("Destination endpoint ID accepted by sipgate, for example p0"),
|
|
124
|
+
}),
|
|
125
|
+
annotations: writeAnnotations,
|
|
126
|
+
execute: async ({ number_id, endpoint_id }) => backend.setNumberRouting(number_id, endpoint_id),
|
|
127
|
+
}),
|
|
128
|
+
define({
|
|
129
|
+
name: "set_forwarding",
|
|
130
|
+
description: "CHANGES THE SIPGATE ACCOUNT: replace all forwardings for a phoneline, including timeout routing. Pass an empty forwardings array to delete all forwardings. Reads and returns before/after state.",
|
|
131
|
+
schema: z.object({
|
|
132
|
+
user_id: id.describe("Owner user ID, for example w0"),
|
|
133
|
+
phoneline_id: id.describe("Phoneline ID, for example p0"),
|
|
134
|
+
forwardings: z.array(z.object({
|
|
135
|
+
active: z.boolean().default(true),
|
|
136
|
+
destination: e164,
|
|
137
|
+
timeout: z.int().min(0).describe("Seconds before forwarding; use 0 for immediate forwarding"),
|
|
138
|
+
})).describe("Complete replacement list; [] removes every forwarding"),
|
|
139
|
+
}),
|
|
140
|
+
annotations: writeAnnotations,
|
|
141
|
+
execute: async ({ user_id, phoneline_id, forwardings }) => backend.setForwarding(user_id, phoneline_id, forwardings),
|
|
142
|
+
}),
|
|
143
|
+
define({
|
|
144
|
+
name: "set_dnd",
|
|
145
|
+
description: "CHANGES THE SIPGATE ACCOUNT: enable or disable Do Not Disturb for one device. Reads and returns the device's before/after state.",
|
|
146
|
+
schema: z.object({
|
|
147
|
+
device_id: id.describe("Device ID returned by list_devices"),
|
|
148
|
+
enabled: z.boolean(),
|
|
149
|
+
}),
|
|
150
|
+
annotations: writeAnnotations,
|
|
151
|
+
execute: async ({ device_id, enabled }) => backend.setDnd(device_id, enabled),
|
|
152
|
+
}),
|
|
153
|
+
define({
|
|
154
|
+
name: "send_sms",
|
|
155
|
+
description: "CHANGES THE SIPGATE ACCOUNT AND MAY INCUR CHARGES: send an SMS after verifying an SMS-capable extension. Reads and returns the relevant before/after history snapshot; history can update asynchronously.",
|
|
156
|
+
schema: z.object({
|
|
157
|
+
user_id: id.describe("Owner of the SMS extension"),
|
|
158
|
+
sms_id: id.optional().describe("SMS extension ID; the first available extension is used when omitted"),
|
|
159
|
+
recipient: e164,
|
|
160
|
+
message: z.string().min(1),
|
|
161
|
+
send_at: z.int().min(0).optional().describe("Optional Unix timestamp in seconds"),
|
|
162
|
+
}),
|
|
163
|
+
annotations: actionAnnotations,
|
|
164
|
+
execute: async ({ user_id, sms_id, recipient, message, send_at }) => backend.sendSms({
|
|
165
|
+
userId: user_id,
|
|
166
|
+
recipient,
|
|
167
|
+
message,
|
|
168
|
+
...(sms_id === undefined ? {} : { smsId: sms_id }),
|
|
169
|
+
...(send_at === undefined ? {} : { sendAt: send_at }),
|
|
170
|
+
}),
|
|
171
|
+
}),
|
|
172
|
+
define({
|
|
173
|
+
name: "initiate_call",
|
|
174
|
+
description: "CHANGES THE SIPGATE ACCOUNT AND MAY INCUR CHARGES: start a Click2Dial call. Reads established calls before and after and returns the new session; ringing calls may not appear immediately.",
|
|
175
|
+
schema: z.object({
|
|
176
|
+
caller: id.describe("sipgate device ID or caller phone number"),
|
|
177
|
+
callee: e164,
|
|
178
|
+
caller_id: e164.optional().describe("Optional number displayed to the callee"),
|
|
179
|
+
device_id: id.optional().describe("Required by sipgate when caller is a phone number instead of a device ID"),
|
|
180
|
+
}),
|
|
181
|
+
annotations: actionAnnotations,
|
|
182
|
+
execute: async ({ caller, callee, caller_id, device_id }) => backend.initiateCall({
|
|
183
|
+
caller,
|
|
184
|
+
callee,
|
|
185
|
+
...(caller_id === undefined ? {} : { callerId: caller_id }),
|
|
186
|
+
...(device_id === undefined ? {} : { deviceId: device_id }),
|
|
187
|
+
}),
|
|
188
|
+
}),
|
|
189
|
+
];
|
|
190
|
+
return [...readTools, ...writeTools];
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=definitions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/tools/definitions.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sipgate-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A self-hosted MCP server for configuring sipgate accounts through the sipgate REST API v2",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sipgate-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -p tsconfig.json",
|
|
24
|
+
"test": "npm run build && tsc -p tsconfig.test.json && node --test .test-dist/test/*.test.js",
|
|
25
|
+
"prepublishOnly": "npm test"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"mcp",
|
|
29
|
+
"model-context-protocol",
|
|
30
|
+
"sipgate",
|
|
31
|
+
"telephony",
|
|
32
|
+
"claude",
|
|
33
|
+
"codex"
|
|
34
|
+
],
|
|
35
|
+
"author": "Arne Wiese",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/wiesson/sipgate-mcp.git"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/wiesson/sipgate-mcp#readme",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/wiesson/sipgate-mcp/issues"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=22"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public",
|
|
50
|
+
"registry": "https://registry.npmjs.org/"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
54
|
+
"zod": "^4.5.4"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^26.4.0",
|
|
58
|
+
"typescript": "^7.0.2"
|
|
59
|
+
}
|
|
60
|
+
}
|