@mailkite/mcp 0.1.0 → 0.2.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/package.json +3 -3
- package/server.mjs +33 -1
- package/spec/api.json +16 -1
- package/spec/cases.json +75 -0
- package/spec/schemas/register-domain-request.json +40 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mailkite/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Model Context Protocol server for MailKite — exposes the MailKite API to LLM agents as tools. A thin layer over the MailKite Node SDK and the shared sdks/spec contract.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,13 +34,13 @@
|
|
|
34
34
|
"homepage": "https://mailkite.dev/docs/libraries",
|
|
35
35
|
"repository": {
|
|
36
36
|
"type": "git",
|
|
37
|
-
"url": "https://github.com/fijiwebdesign/mailkite.git",
|
|
37
|
+
"url": "git+https://github.com/fijiwebdesign/mailkite.git",
|
|
38
38
|
"directory": "sdks/mcp"
|
|
39
39
|
},
|
|
40
40
|
"license": "MIT",
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
43
43
|
"ajv": "^8.17.1",
|
|
44
|
-
"mailkite": "^0.
|
|
44
|
+
"mailkite": "^0.2.0"
|
|
45
45
|
}
|
|
46
46
|
}
|
package/server.mjs
CHANGED
|
@@ -58,6 +58,9 @@ function buildInputSchema(method) {
|
|
|
58
58
|
if (arg.in === "path") {
|
|
59
59
|
properties[arg.name] = { type: "string", description: `Path parameter \`${arg.name}\`.` };
|
|
60
60
|
required.push(arg.name);
|
|
61
|
+
} else if (arg.in === "query") {
|
|
62
|
+
properties[arg.name] = { type: "string", description: `Query parameter \`${arg.name}\`.` };
|
|
63
|
+
required.push(arg.name);
|
|
61
64
|
} else if (arg.in === "body" && arg.schema && schemas[arg.schema]) {
|
|
62
65
|
const body = schemas[arg.schema];
|
|
63
66
|
Object.assign(properties, body.properties || {});
|
|
@@ -77,7 +80,11 @@ const credentialNote = (method) => {
|
|
|
77
80
|
|
|
78
81
|
const tools = api.methods.map((method) => ({
|
|
79
82
|
name: toolName(method),
|
|
80
|
-
description: `${method.summary} ${credentialNote(method)}
|
|
83
|
+
description: `${method.summary} ${credentialNote(method)}${
|
|
84
|
+
method.agentConfirm
|
|
85
|
+
? " Does NOT register automatically — returns a dashboard link for the user to review the price and confirm. An assistant can never purchase a domain on its own."
|
|
86
|
+
: ""
|
|
87
|
+
}`,
|
|
81
88
|
inputSchema: buildInputSchema(method),
|
|
82
89
|
_method: method, // kept server-side for dispatch; not sent to the client
|
|
83
90
|
}));
|
|
@@ -94,16 +101,22 @@ function resolveCall(method, input) {
|
|
|
94
101
|
input = input || {};
|
|
95
102
|
let urlPath = method.http.path;
|
|
96
103
|
let bodySchemaId = null;
|
|
104
|
+
const query = [];
|
|
97
105
|
|
|
98
106
|
for (const arg of method.args || []) {
|
|
99
107
|
if (arg.in === "path") {
|
|
100
108
|
const value = input[arg.name];
|
|
101
109
|
if (value == null || value === "") throw new Error(`Missing required path parameter: ${arg.name}`);
|
|
102
110
|
urlPath = urlPath.replace(`{${arg.name}}`, encodeURIComponent(String(value)));
|
|
111
|
+
} else if (arg.in === "query") {
|
|
112
|
+
const value = input[arg.name];
|
|
113
|
+
if (value == null || value === "") throw new Error(`Missing required query parameter: ${arg.name}`);
|
|
114
|
+
query.push(`${encodeURIComponent(arg.name)}=${encodeURIComponent(String(value))}`);
|
|
103
115
|
} else if (arg.in === "body" && arg.schema) {
|
|
104
116
|
bodySchemaId = arg.schema;
|
|
105
117
|
}
|
|
106
118
|
}
|
|
119
|
+
if (query.length) urlPath += (urlPath.includes("?") ? "&" : "?") + query.join("&");
|
|
107
120
|
|
|
108
121
|
let body;
|
|
109
122
|
if (bodySchemaId) {
|
|
@@ -163,6 +176,25 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
163
176
|
}
|
|
164
177
|
}
|
|
165
178
|
|
|
179
|
+
// Gated methods (e.g. domain registration) must be approved by a human and are never run
|
|
180
|
+
// automatically by an AI agent. Return the dashboard URL to confirm instead of calling the API.
|
|
181
|
+
if (tool._method.agentConfirm) {
|
|
182
|
+
const dash = (process.env.MAILKITE_DASHBOARD_URL || "https://app.mailkite.dev").replace(/\/+$/, "");
|
|
183
|
+
const domain = (req.params.arguments && req.params.arguments.domain) || "the domain";
|
|
184
|
+
return {
|
|
185
|
+
content: [
|
|
186
|
+
{
|
|
187
|
+
type: "text",
|
|
188
|
+
text:
|
|
189
|
+
`Domain registration can't be completed automatically — it has to be approved by you.\n\n` +
|
|
190
|
+
`Open the dashboard to review the price and confirm the purchase of ${domain}:\n` +
|
|
191
|
+
`${dash}/domains\n\n` +
|
|
192
|
+
`Once it's registered there, tell me to continue.`,
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
166
198
|
if (!apiKey) {
|
|
167
199
|
return {
|
|
168
200
|
isError: true,
|
package/spec/api.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mailkite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Canonical interface contract for every MailKite SDK. One low-level request() plus one function per endpoint. All languages expose the same shape; only naming adapts to each language's convention (e.g. Go exports PascalCase).",
|
|
5
5
|
"baseUrl": "https://api.mailkite.dev",
|
|
6
6
|
"auth": {
|
|
@@ -75,6 +75,21 @@
|
|
|
75
75
|
"args": [{ "name": "id", "in": "path" }],
|
|
76
76
|
"returns": "any"
|
|
77
77
|
},
|
|
78
|
+
{
|
|
79
|
+
"name": "checkDomainAvailability",
|
|
80
|
+
"summary": "Check whether a domain is available to register, and at what price. Read-only — no charge.",
|
|
81
|
+
"http": { "method": "GET", "path": "/api/domains/register/check" },
|
|
82
|
+
"args": [{ "name": "domain", "in": "query" }],
|
|
83
|
+
"returns": "any"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"name": "registerDomain",
|
|
87
|
+
"summary": "Register (buy) a domain on the customer's behalf; provisions mail DNS and adds it to the account in one call. Charges the registrar.",
|
|
88
|
+
"http": { "method": "POST", "path": "/api/domains/register" },
|
|
89
|
+
"args": [{ "name": "body", "in": "body", "schema": "register-domain-request" }],
|
|
90
|
+
"returns": "any",
|
|
91
|
+
"agentConfirm": true
|
|
92
|
+
},
|
|
78
93
|
{
|
|
79
94
|
"name": "listRoutes",
|
|
80
95
|
"summary": "List inbound routing rules.",
|
package/spec/cases.json
CHANGED
|
@@ -122,6 +122,81 @@
|
|
|
122
122
|
"response": { "status": 200, "body": { "ok": true } },
|
|
123
123
|
"result": { "ok": true }
|
|
124
124
|
},
|
|
125
|
+
{
|
|
126
|
+
"name": "check_domain_availability",
|
|
127
|
+
"method": "checkDomainAvailability",
|
|
128
|
+
"args": { "domain": "acme.com" },
|
|
129
|
+
"request": { "method": "GET", "path": "/api/domains/register/check" },
|
|
130
|
+
"response": {
|
|
131
|
+
"status": 200,
|
|
132
|
+
"body": {
|
|
133
|
+
"configured": true,
|
|
134
|
+
"domain": "acme.com",
|
|
135
|
+
"available": true,
|
|
136
|
+
"premium": false,
|
|
137
|
+
"price": { "amount": 12.99, "currency": "USD", "period": 1, "periodUnit": "y" }
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"result": {
|
|
141
|
+
"configured": true,
|
|
142
|
+
"domain": "acme.com",
|
|
143
|
+
"available": true,
|
|
144
|
+
"premium": false,
|
|
145
|
+
"price": { "amount": 12.99, "currency": "USD", "period": 1, "periodUnit": "y" }
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"name": "register_domain",
|
|
150
|
+
"method": "registerDomain",
|
|
151
|
+
"args": {
|
|
152
|
+
"domain": "acme.com",
|
|
153
|
+
"contact": {
|
|
154
|
+
"firstName": "Jane",
|
|
155
|
+
"lastName": "Doe",
|
|
156
|
+
"email": "jane@example.com",
|
|
157
|
+
"phone": "+1.4155551234",
|
|
158
|
+
"address": "123 Main St",
|
|
159
|
+
"city": "SF",
|
|
160
|
+
"zip": "94016",
|
|
161
|
+
"country": "US"
|
|
162
|
+
},
|
|
163
|
+
"years": 1
|
|
164
|
+
},
|
|
165
|
+
"request": {
|
|
166
|
+
"method": "POST",
|
|
167
|
+
"path": "/api/domains/register",
|
|
168
|
+
"bodySchema": "register-domain-request",
|
|
169
|
+
"body": {
|
|
170
|
+
"domain": "acme.com",
|
|
171
|
+
"contact": {
|
|
172
|
+
"firstName": "Jane",
|
|
173
|
+
"lastName": "Doe",
|
|
174
|
+
"email": "jane@example.com",
|
|
175
|
+
"phone": "+1.4155551234",
|
|
176
|
+
"address": "123 Main St",
|
|
177
|
+
"city": "SF",
|
|
178
|
+
"zip": "94016",
|
|
179
|
+
"country": "US"
|
|
180
|
+
},
|
|
181
|
+
"years": 1
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
"response": {
|
|
185
|
+
"status": 201,
|
|
186
|
+
"body": {
|
|
187
|
+
"domain": { "id": "dom_1", "domain": "acme.com", "status": "verified" },
|
|
188
|
+
"dns": [],
|
|
189
|
+
"registration": { "status": "registered", "reference": "ref_1" },
|
|
190
|
+
"dnsProvisioned": true
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
"result": {
|
|
194
|
+
"domain": { "id": "dom_1", "domain": "acme.com", "status": "verified" },
|
|
195
|
+
"dns": [],
|
|
196
|
+
"registration": { "status": "registered", "reference": "ref_1" },
|
|
197
|
+
"dnsProvisioned": true
|
|
198
|
+
}
|
|
199
|
+
},
|
|
125
200
|
{
|
|
126
201
|
"name": "list_routes",
|
|
127
202
|
"method": "listRoutes",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "register-domain-request",
|
|
4
|
+
"title": "Register domain request body",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["domain", "contact"],
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"domain": { "type": "string" },
|
|
10
|
+
"contact": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"required": [
|
|
13
|
+
"firstName",
|
|
14
|
+
"lastName",
|
|
15
|
+
"email",
|
|
16
|
+
"phone",
|
|
17
|
+
"address",
|
|
18
|
+
"city",
|
|
19
|
+
"zip",
|
|
20
|
+
"country"
|
|
21
|
+
],
|
|
22
|
+
"additionalProperties": false,
|
|
23
|
+
"properties": {
|
|
24
|
+
"firstName": { "type": "string" },
|
|
25
|
+
"lastName": { "type": "string" },
|
|
26
|
+
"email": { "type": "string" },
|
|
27
|
+
"phone": { "type": "string", "description": "E.164-ish, +<cc>.<number>, e.g. +1.4155551234" },
|
|
28
|
+
"address": { "type": "string" },
|
|
29
|
+
"city": { "type": "string" },
|
|
30
|
+
"zip": { "type": "string" },
|
|
31
|
+
"country": { "type": "string", "description": "ISO 3166-1 alpha-2, e.g. US" },
|
|
32
|
+
"state": { "type": "string", "description": "ISO 3166-2 subdivision, e.g. US-CA" },
|
|
33
|
+
"organization": { "type": "string" },
|
|
34
|
+
"type": { "type": "string", "enum": ["individual", "company", "association", "publicbody"] }
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"years": { "type": "integer", "minimum": 1 },
|
|
38
|
+
"dryRun": { "type": "boolean" }
|
|
39
|
+
}
|
|
40
|
+
}
|