@dialerr/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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/bin/server.js +176 -0
  4. package/package.json +36 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dialerr
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,55 @@
1
+ # @dialerr/mcp
2
+
3
+ MCP server for **Dialerr** — lets your AI coding assistant (Claude Code, Claude
4
+ Desktop, Cursor, …) add the Dialerr softphone widget to your project, including
5
+ **provisioning an embed key for you** via a browser-approved flow. You approve
6
+ in your own browser; no password or secret ever passes through a command line.
7
+
8
+ ## Add to Claude Code
9
+
10
+ ```bash
11
+ claude mcp add dialerr -- npx -y @dialerr/mcp
12
+ ```
13
+
14
+ ## Add to Claude Desktop / Cursor
15
+
16
+ ```json
17
+ {
18
+ "mcpServers": {
19
+ "dialerr": { "command": "npx", "args": ["-y", "@dialerr/mcp"] }
20
+ }
21
+ }
22
+ ```
23
+
24
+ ## Then just ask
25
+
26
+ > "Add a phone dialer to my app with Dialerr"
27
+
28
+ The assistant will:
29
+ 1. Call `dialerr_start_key_provisioning` → you get a link like
30
+ `https://dialerr.com/device?code=XXXX-XXXX`.
31
+ 2. You open it, sign in (or create an account), and click **Authorize** — that
32
+ provisions an embed key for your organization.
33
+ 3. The assistant picks up the key, adds the widget snippet to your project, and
34
+ puts the secret in `.env.local` (server-side only).
35
+
36
+ ## Tools
37
+
38
+ | Tool | What it does |
39
+ |------|--------------|
40
+ | `dialerr_get_install_guide` | The official install guide (script tag, frameworks, seamless sign-in route, security rules). |
41
+ | `dialerr_start_key_provisioning` | Starts the browser-approval flow; returns the link + polling code. |
42
+ | `dialerr_check_key_provisioning` | Polls for approval; returns the keys exactly once. |
43
+
44
+ ## Security notes
45
+
46
+ - This server only calls the Dialerr API (`https://dialerr.com`; override with
47
+ `DIALERR_BASE`). It reads no files and runs no commands.
48
+ - The **publishable** key (`pk_…`) is safe in client code (like Stripe's).
49
+ - The **secret** key (`sk_…`) belongs only in a server env var
50
+ (`DIALERR_SECRET`); the assistant is instructed to write it only to
51
+ `.env.local`.
52
+ - Keys are shown once; manage/revoke them at dialerr.com → Settings → API
53
+ Access, and restrict them to your domains before production.
54
+
55
+ Docs: https://dialerr.com/llms.txt · https://dialerr.com/embed
package/bin/server.js ADDED
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /*
4
+ * Dialerr MCP server — lets an AI coding assistant add the Dialerr softphone
5
+ * widget to a project, including provisioning an embed key via a
6
+ * browser-approved device flow (the human approves in their own browser; no
7
+ * password or secret ever passes through a command line).
8
+ *
9
+ * Tools:
10
+ * dialerr_get_install_guide — how to embed the widget (script tag first)
11
+ * dialerr_start_key_provisioning — begin device login; returns a URL the
12
+ * user opens + a deviceCode to poll with
13
+ * dialerr_check_key_provisioning — poll; on approval returns the keys once
14
+ *
15
+ * This server calls only the Dialerr API (default https://dialerr.com; override
16
+ * with DIALERR_BASE). It reads no files and runs no commands.
17
+ */
18
+ const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
19
+ const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
20
+ const { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js');
21
+
22
+ const BASE = (process.env.DIALERR_BASE || 'https://dialerr.com').replace(/\/$/, '');
23
+
24
+ async function api(path, body) {
25
+ const res = await fetch(BASE + path, {
26
+ method: 'POST',
27
+ headers: { 'content-type': 'application/json' },
28
+ body: JSON.stringify(body || {}),
29
+ });
30
+ let json = null;
31
+ try { json = await res.json(); } catch (e) {}
32
+ return { status: res.status, json };
33
+ }
34
+
35
+ const INSTALL_GUIDE = `# Installing the Dialerr softphone widget
36
+
37
+ The recommended install is ONE SCRIPT TAG — nothing to install or run. It loads
38
+ the official widget from dialerr.com in a sandboxed iframe. Add before </body>
39
+ (or the framework equivalent below):
40
+
41
+ <script src="${BASE}/embed.js" data-key="pk_..."></script>
42
+
43
+ The publishable key (pk_...) is designed for client-side code, like Stripe's
44
+ pk_ — safe in page HTML. Get one with the dialerr_start_key_provisioning tool,
45
+ or the user can copy one from ${BASE} -> Settings -> API Access.
46
+
47
+ Framework placement:
48
+ - Next.js (App Router): in app/layout.tsx via next/script, or add a small
49
+ client component that appends the script tag once.
50
+ - React / Vite / CRA: add the tag to index.html, or "npm i @dialerr/embed" and
51
+ render <DialerrToolbar dataKey="pk_..." /> (import from '@dialerr/embed/react').
52
+ - Plain HTML / Webflow / WordPress: paste the tag before </body>.
53
+
54
+ That alone works — the user signs into Dialerr inside the widget.
55
+
56
+ ## Seamless sign-in (recommended for apps with a backend)
57
+ Add a backend route that mints a short-lived token using the SECRET key, then
58
+ point the widget at it with data-token-url="/api/dialerr/token".
59
+
60
+ Next.js route (app/api/dialerr/token/route.ts):
61
+
62
+ export async function GET() {
63
+ const r = await fetch('${BASE}/api/v1/embed/token', {
64
+ method: 'POST',
65
+ headers: { Authorization: \`Bearer \${process.env.DIALERR_SECRET}\` },
66
+ body: JSON.stringify({ /* agentEmail: currentUser.email */ }),
67
+ });
68
+ return new Response(await r.text(), { headers: { 'content-type': 'application/json' } });
69
+ }
70
+
71
+ ## Hard rules
72
+ - The SECRET key (sk_...) goes ONLY in a server env var (DIALERR_SECRET in
73
+ .env.local). Never in client code, commands, or commits.
74
+ - The site must be HTTPS (or localhost) for the microphone.
75
+ - Host-page API: window.Dialerr.open()/.startCall(phone)/.on('call_ended', cb);
76
+ DOM events like 'dialerr:call_connected' also fire.
77
+ - The user should restrict the key to their domains in Settings -> API Access
78
+ before production.
79
+
80
+ Full reference: ${BASE}/llms.txt`;
81
+
82
+ const TOOLS = [
83
+ {
84
+ name: 'dialerr_get_install_guide',
85
+ description:
86
+ 'How to add the Dialerr softphone widget (browser calling) to a web project. ' +
87
+ 'Returns the official script-tag snippet, framework placement, the optional seamless ' +
88
+ 'sign-in route, and security rules. Call this before editing files.',
89
+ inputSchema: { type: 'object', properties: {}, additionalProperties: false },
90
+ },
91
+ {
92
+ name: 'dialerr_start_key_provisioning',
93
+ description:
94
+ 'Provision a Dialerr embed key (pk_/sk_) for the user via a browser-approved device flow. ' +
95
+ 'Returns a URL for the USER to open and approve in their own browser (they sign in or create ' +
96
+ 'an account there — no credentials pass through this tool), plus a deviceCode for polling. ' +
97
+ 'After the user says they approved, call dialerr_check_key_provisioning.',
98
+ inputSchema: { type: 'object', properties: {}, additionalProperties: false },
99
+ },
100
+ {
101
+ name: 'dialerr_check_key_provisioning',
102
+ description:
103
+ 'Check whether the user approved the device flow started by dialerr_start_key_provisioning. ' +
104
+ 'Returns pending, or the provisioned keys exactly once.',
105
+ inputSchema: {
106
+ type: 'object',
107
+ properties: {
108
+ deviceCode: { type: 'string', description: 'The deviceCode returned by dialerr_start_key_provisioning (dc_...)' },
109
+ },
110
+ required: ['deviceCode'],
111
+ additionalProperties: false,
112
+ },
113
+ },
114
+ ];
115
+
116
+ function text(t) { return { content: [{ type: 'text', text: t }] }; }
117
+
118
+ async function handleCall(name, args) {
119
+ if (name === 'dialerr_get_install_guide') return text(INSTALL_GUIDE);
120
+
121
+ if (name === 'dialerr_start_key_provisioning') {
122
+ const r = await api('/api/v1/embed/device/code');
123
+ if (r.status !== 200 || !r.json || !r.json.deviceCode) {
124
+ return text(`Could not start key provisioning (HTTP ${r.status}). The user can instead copy a key from ${BASE} -> Settings -> API Access.`);
125
+ }
126
+ const d = r.json;
127
+ return text(
128
+ `Ask the user to open this link in their browser and click Authorize (sign in or create a Dialerr account if needed):\n\n` +
129
+ ` ${d.verificationUriComplete}\n\n` +
130
+ `Code shown on the page: ${d.userCode} (expires in ${Math.round((d.expiresIn || 600) / 60)} minutes)\n\n` +
131
+ `When the user says they approved, call dialerr_check_key_provisioning with deviceCode: ${d.deviceCode}`,
132
+ );
133
+ }
134
+
135
+ if (name === 'dialerr_check_key_provisioning') {
136
+ const deviceCode = String((args && args.deviceCode) || '');
137
+ if (!deviceCode.startsWith('dc_')) return text('Invalid deviceCode — it should start with dc_. Start over with dialerr_start_key_provisioning.');
138
+ const r = await api('/api/v1/embed/device/token', { deviceCode });
139
+ if (r.status === 410) return text('That device flow expired or its keys were already claimed. Start over with dialerr_start_key_provisioning.');
140
+ if (r.json && r.json.status === 'pending') return text('Not approved yet. Ask the user to open the link and click Authorize, then call this tool again.');
141
+ if (r.json && r.json.status === 'approved') {
142
+ return text(
143
+ `Approved! Keys provisioned:\n\n` +
144
+ `Publishable key (safe in client code): ${r.json.publicKey}\n` +
145
+ `Secret key (SERVER-SIDE ONLY): ${r.json.secretKey}\n\n` +
146
+ `Handling rules:\n` +
147
+ `- Use the publishable key in the embed snippet: <script src="${BASE}/embed.js" data-key="${r.json.publicKey}"></script>\n` +
148
+ `- Write the secret ONLY to .env.local as DIALERR_SECRET=... (never client code, commands, or commits).\n` +
149
+ `- These keys are shown once. Tell the user they can manage/revoke the key at ${BASE} -> Settings -> API Access, and should restrict it to their domains before production.\n\n` +
150
+ `Now call dialerr_get_install_guide for the exact snippet + optional seamless sign-in route.`,
151
+ );
152
+ }
153
+ return text(`Unexpected response (HTTP ${r.status}). Try again, or start over with dialerr_start_key_provisioning.`);
154
+ }
155
+
156
+ return text(`Unknown tool: ${name}`);
157
+ }
158
+
159
+ const server = new Server(
160
+ { name: 'dialerr', version: '0.1.0' },
161
+ { capabilities: { tools: {} } },
162
+ );
163
+
164
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
165
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
166
+ try {
167
+ return await handleCall(req.params.name, req.params.arguments || {});
168
+ } catch (e) {
169
+ return text(`Error: ${e.message}`);
170
+ }
171
+ });
172
+
173
+ (async () => {
174
+ const transport = new StdioServerTransport();
175
+ await server.connect(transport);
176
+ })();
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@dialerr/mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Dialerr — lets AI coding assistants provision an embed key (browser-approved) and install the Dialerr softphone widget.",
5
+ "bin": {
6
+ "dialerr-mcp": "bin/server.js"
7
+ },
8
+ "type": "commonjs",
9
+ "files": [
10
+ "bin",
11
+ "LICENSE",
12
+ "README.md"
13
+ ],
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.0.0"
16
+ },
17
+ "engines": {
18
+ "node": ">=18"
19
+ },
20
+ "keywords": [
21
+ "dialerr",
22
+ "mcp",
23
+ "modelcontextprotocol",
24
+ "softphone",
25
+ "webrtc",
26
+ "dialer",
27
+ "embed",
28
+ "widget"
29
+ ],
30
+ "author": "Dialerr <steven@dialerr.com> (https://dialerr.com)",
31
+ "homepage": "https://dialerr.com/embed",
32
+ "bugs": {
33
+ "email": "support@dialerr.com"
34
+ },
35
+ "license": "MIT"
36
+ }