@honkio/mcp 1.2.0 → 1.5.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/README.md +145 -48
- package/dist/auth.d.ts +40 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +98 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +21 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +57 -19
- package/dist/client.js.map +1 -1
- package/dist/http.d.ts +51 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +248 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +0 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -46
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts +10 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +10 -0
- package/dist/log.js.map +1 -0
- package/dist/prompts.d.ts +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +19 -18
- package/dist/prompts.js.map +1 -1
- package/dist/rateLimit.d.ts +50 -0
- package/dist/rateLimit.d.ts.map +1 -0
- package/dist/rateLimit.js +85 -0
- package/dist/rateLimit.js.map +1 -0
- package/dist/resources.d.ts +1 -1
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +4 -4
- package/dist/resources.js.map +1 -1
- package/dist/server.d.ts +7 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +46 -0
- package/dist/server.js.map +1 -0
- package/dist/toolMeta.d.ts +20 -0
- package/dist/toolMeta.d.ts.map +1 -0
- package/dist/toolMeta.js +75 -0
- package/dist/toolMeta.js.map +1 -0
- package/dist/tools.d.ts +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +222 -181
- package/dist/tools.js.map +1 -1
- package/package.json +16 -7
package/dist/log.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** One JSON object per line on stdout. Used by the HTTP entry only; stdio must keep stdout for the protocol. */
|
|
2
|
+
export type LogFields = Record<string, unknown>;
|
|
3
|
+
export interface Logger {
|
|
4
|
+
info(msg: string, fields?: LogFields): void;
|
|
5
|
+
error(msg: string, fields?: LogFields): void;
|
|
6
|
+
}
|
|
7
|
+
export declare function createLogger(stream?: {
|
|
8
|
+
write(chunk: string): unknown;
|
|
9
|
+
}): Logger;
|
|
10
|
+
//# sourceMappingURL=log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA,gHAAgH;AAChH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEhD,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;CAC9C;AAED,wBAAgB,YAAY,CAAC,MAAM,GAAE;IAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;CAAmB,GAAG,MAAM,CAQ/F"}
|
package/dist/log.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function createLogger(stream = process.stdout) {
|
|
2
|
+
const emit = (level, msg, fields) => {
|
|
3
|
+
stream.write(`${JSON.stringify({ ts: new Date().toISOString(), level, msg, ...fields })}\n`);
|
|
4
|
+
};
|
|
5
|
+
return {
|
|
6
|
+
info: (msg, fields) => emit('info', msg, fields),
|
|
7
|
+
error: (msg, fields) => emit('error', msg, fields),
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=log.js.map
|
package/dist/log.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,YAAY,CAAC,SAA4C,OAAO,CAAC,MAAM;IACrF,MAAM,IAAI,GAAG,CAAC,KAAuB,EAAE,GAAW,EAAE,MAAkB,EAAE,EAAE;QACxE,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;IAC/F,CAAC,CAAC;IACF,OAAO;QACL,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC;QAChD,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC;KACnD,CAAC;AACJ,CAAC"}
|
package/dist/prompts.d.ts
CHANGED
package/dist/prompts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAGzD,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAiHvD"}
|
package/dist/prompts.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export function registerPrompts(server) {
|
|
3
|
-
server.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
server.registerPrompt('send_compliant_sms', { description: 'Walk through the full compliant SMS send workflow: check CASL consent and send the message. (CRTC DNCL checking is coming soon and not yet enforced.)', argsSchema: z.object({
|
|
4
|
+
phone_number: z.string().describe('Recipient phone number in E.164 format (+1 and ten digits). Use the real recipient: 555-XXXX exchanges are reserved and a send to one is refused.'),
|
|
5
|
+
from_number: z.string().describe('Your sending phone number in E.164 format.'),
|
|
6
|
+
message: z.string().describe('The message body you want to send.'),
|
|
7
|
+
}) }, ({ phone_number, from_number, message }) => ({
|
|
8
8
|
messages: [{
|
|
9
9
|
role: 'user',
|
|
10
10
|
content: {
|
|
@@ -26,9 +26,9 @@ Show me the result of each step.`,
|
|
|
26
26
|
},
|
|
27
27
|
}],
|
|
28
28
|
}));
|
|
29
|
-
server.
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
server.registerPrompt('provision_canadian_number', { description: 'Search for and provision a Canadian phone number, optionally filtered by area code.', argsSchema: z.object({
|
|
30
|
+
area_code: z.string().optional().describe('Preferred area code (e.g. "416" for Toronto, "604" for Vancouver). Leave blank to search all available Canadian numbers.'),
|
|
31
|
+
}) }, ({ area_code }) => ({
|
|
32
32
|
messages: [{
|
|
33
33
|
role: 'user',
|
|
34
34
|
content: {
|
|
@@ -44,9 +44,9 @@ Please:
|
|
|
44
44
|
},
|
|
45
45
|
}],
|
|
46
46
|
}));
|
|
47
|
-
server.
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
server.registerPrompt('setup_webhooks', { description: 'Set up webhook endpoints to receive HonkIO event notifications.', argsSchema: z.object({
|
|
48
|
+
url: z.string().describe('Your HTTPS endpoint URL that will receive webhook events.'),
|
|
49
|
+
}) }, ({ url }) => ({
|
|
50
50
|
messages: [{
|
|
51
51
|
role: 'user',
|
|
52
52
|
content: {
|
|
@@ -60,14 +60,15 @@ Please:
|
|
|
60
60
|
- message.failed (to handle delivery failures)
|
|
61
61
|
- message.received (to handle inbound SMS replies)
|
|
62
62
|
- opt_out.recorded (to sync opt-outs to your CRM)
|
|
63
|
+
- account.sending_paused and account.delivery_warning (to hear about a pause or a failure spike before customers do)
|
|
63
64
|
3. Display the signing_secret from the response — remind me to store it securely as it won't be shown again. Explain that I should use it to verify the X-HonkIO-Signature header on incoming webhook requests.
|
|
64
65
|
4. If webhooks already exist, show me the existing ones and ask if I want to create an additional one or update an existing one.`,
|
|
65
66
|
},
|
|
66
67
|
}],
|
|
67
68
|
}));
|
|
68
|
-
server.
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
server.registerPrompt('compliance_audit', { description: 'Run a compliance check on a phone number: consent status and opt-out status. (CRTC DNCL status is coming soon.)', argsSchema: z.object({
|
|
70
|
+
phone_number: z.string().describe('Phone number to audit in E.164 format.'),
|
|
71
|
+
}) }, ({ phone_number }) => ({
|
|
71
72
|
messages: [{
|
|
72
73
|
role: 'user',
|
|
73
74
|
content: {
|
|
@@ -86,10 +87,10 @@ Summarize with:
|
|
|
86
87
|
},
|
|
87
88
|
}],
|
|
88
89
|
}));
|
|
89
|
-
server.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
server.registerPrompt('handle_erasure_request', { description: 'Process a PIPEDA right-to-erasure request for a subscriber.', argsSchema: z.object({
|
|
91
|
+
phone_number: z.string().describe('Phone number to erase data for in E.164 format.'),
|
|
92
|
+
reason: z.string().optional().describe('Reason provided by the subscriber for the erasure request.'),
|
|
93
|
+
}) }, ({ phone_number, reason }) => ({
|
|
93
94
|
messages: [{
|
|
94
95
|
role: 'user',
|
|
95
96
|
content: {
|
package/dist/prompts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,UAAU,eAAe,CAAC,MAAiB;IAC/C,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-address rate limiting for the hosted endpoint. Fixed one-minute
|
|
3
|
+
* windows in memory: the service runs as one replica, and the API behind it
|
|
4
|
+
* has its own per-key limits, so this only has to stop floods and key
|
|
5
|
+
* spraying from reaching the process and the API.
|
|
6
|
+
*/
|
|
7
|
+
import type { IncomingMessage } from 'node:http';
|
|
8
|
+
/**
|
|
9
|
+
* The caller's address as seen through `trustedProxies` hops. Each trusted
|
|
10
|
+
* proxy appends its own address to X-Forwarded-For (Railway's edge appends
|
|
11
|
+
* one, verified 2026-09-05: a request arrives as "<client>, <edge>"), so the
|
|
12
|
+
* client is the entry that many places left of the right end. Anything a
|
|
13
|
+
* client writes into the header itself lands further left and is ignored.
|
|
14
|
+
* With fewer entries than hops (a local run with no proxy) the leftmost
|
|
15
|
+
* entry is used; with no header at all, the socket address.
|
|
16
|
+
*/
|
|
17
|
+
export declare function clientIp(req: Pick<IncomingMessage, 'headers' | 'socket'>, trustedProxies?: number): string;
|
|
18
|
+
export interface RateLimiterOptions {
|
|
19
|
+
/** Events allowed per key per window. */
|
|
20
|
+
limit: number;
|
|
21
|
+
/** Window length. Default one minute. */
|
|
22
|
+
windowMs?: number;
|
|
23
|
+
/** Cap on tracked keys; the oldest is dropped past it. Default 10,000. */
|
|
24
|
+
maxEntries?: number;
|
|
25
|
+
/** Clock, injectable for tests. */
|
|
26
|
+
now?: () => number;
|
|
27
|
+
}
|
|
28
|
+
export interface RateLimitVerdict {
|
|
29
|
+
allowed: boolean;
|
|
30
|
+
/** Seconds until the window resets; what to send as Retry-After. */
|
|
31
|
+
retryAfterSec: number;
|
|
32
|
+
}
|
|
33
|
+
export declare class RateLimiter {
|
|
34
|
+
private readonly buckets;
|
|
35
|
+
private readonly limit;
|
|
36
|
+
private readonly windowMs;
|
|
37
|
+
private readonly maxEntries;
|
|
38
|
+
private readonly now;
|
|
39
|
+
private lastPrune;
|
|
40
|
+
constructor(opts: RateLimiterOptions);
|
|
41
|
+
get size(): number;
|
|
42
|
+
/** Counts one event for the key and says whether it stayed within the limit. */
|
|
43
|
+
hit(key: string): RateLimitVerdict;
|
|
44
|
+
/** Whether the key is already at the limit, without counting anything. */
|
|
45
|
+
blocked(key: string): RateLimitVerdict;
|
|
46
|
+
private verdict;
|
|
47
|
+
private bucket;
|
|
48
|
+
private prune;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=rateLimit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rateLimit.d.ts","sourceRoot":"","sources":["../src/rateLimit.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,QAAQ,CAAC,EAAE,cAAc,SAAI,GAAG,MAAM,CAWrG;AAED,MAAM,WAAW,kBAAkB;IACjC,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;CACvB;AAID,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,SAAS,CAAS;gBAEd,IAAI,EAAE,kBAAkB;IAQpC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,gFAAgF;IAChF,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB;IAOlC,0EAA0E;IAC1E,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB;IAOtC,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,MAAM;IAkBd,OAAO,CAAC,KAAK;CAMd"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The caller's address as seen through `trustedProxies` hops. Each trusted
|
|
3
|
+
* proxy appends its own address to X-Forwarded-For (Railway's edge appends
|
|
4
|
+
* one, verified 2026-09-05: a request arrives as "<client>, <edge>"), so the
|
|
5
|
+
* client is the entry that many places left of the right end. Anything a
|
|
6
|
+
* client writes into the header itself lands further left and is ignored.
|
|
7
|
+
* With fewer entries than hops (a local run with no proxy) the leftmost
|
|
8
|
+
* entry is used; with no header at all, the socket address.
|
|
9
|
+
*/
|
|
10
|
+
export function clientIp(req, trustedProxies = 1) {
|
|
11
|
+
const xff = req.headers['x-forwarded-for'];
|
|
12
|
+
const raw = Array.isArray(xff) ? xff.join(',') : xff;
|
|
13
|
+
if (typeof raw === 'string') {
|
|
14
|
+
const parts = raw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
15
|
+
if (parts.length > 0) {
|
|
16
|
+
const index = Math.max(0, parts.length - 1 - trustedProxies);
|
|
17
|
+
return parts[index] ?? parts[0];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return req.socket?.remoteAddress ?? 'unknown';
|
|
21
|
+
}
|
|
22
|
+
export class RateLimiter {
|
|
23
|
+
buckets = new Map();
|
|
24
|
+
limit;
|
|
25
|
+
windowMs;
|
|
26
|
+
maxEntries;
|
|
27
|
+
now;
|
|
28
|
+
lastPrune;
|
|
29
|
+
constructor(opts) {
|
|
30
|
+
this.limit = opts.limit;
|
|
31
|
+
this.windowMs = opts.windowMs ?? 60_000;
|
|
32
|
+
this.maxEntries = opts.maxEntries ?? 10_000;
|
|
33
|
+
this.now = opts.now ?? Date.now;
|
|
34
|
+
this.lastPrune = this.now();
|
|
35
|
+
}
|
|
36
|
+
get size() {
|
|
37
|
+
return this.buckets.size;
|
|
38
|
+
}
|
|
39
|
+
/** Counts one event for the key and says whether it stayed within the limit. */
|
|
40
|
+
hit(key) {
|
|
41
|
+
const now = this.now();
|
|
42
|
+
const bucket = this.bucket(key, now);
|
|
43
|
+
bucket.count += 1;
|
|
44
|
+
return this.verdict(bucket, bucket.count <= this.limit, now);
|
|
45
|
+
}
|
|
46
|
+
/** Whether the key is already at the limit, without counting anything. */
|
|
47
|
+
blocked(key) {
|
|
48
|
+
const now = this.now();
|
|
49
|
+
const existing = this.buckets.get(key);
|
|
50
|
+
if (!existing || now - existing.windowStart >= this.windowMs)
|
|
51
|
+
return { allowed: true, retryAfterSec: 0 };
|
|
52
|
+
return this.verdict(existing, existing.count < this.limit, now);
|
|
53
|
+
}
|
|
54
|
+
verdict(bucket, allowed, now) {
|
|
55
|
+
const retryAfterSec = allowed ? 0 : Math.max(1, Math.ceil((bucket.windowStart + this.windowMs - now) / 1000));
|
|
56
|
+
return { allowed, retryAfterSec };
|
|
57
|
+
}
|
|
58
|
+
bucket(key, now) {
|
|
59
|
+
if (now - this.lastPrune >= this.windowMs)
|
|
60
|
+
this.prune(now);
|
|
61
|
+
let bucket = this.buckets.get(key);
|
|
62
|
+
if (bucket && now - bucket.windowStart >= this.windowMs) {
|
|
63
|
+
bucket.windowStart = now;
|
|
64
|
+
bucket.count = 0;
|
|
65
|
+
}
|
|
66
|
+
if (!bucket) {
|
|
67
|
+
if (this.buckets.size >= this.maxEntries) {
|
|
68
|
+
const oldest = this.buckets.keys().next().value;
|
|
69
|
+
if (oldest !== undefined)
|
|
70
|
+
this.buckets.delete(oldest);
|
|
71
|
+
}
|
|
72
|
+
bucket = { windowStart: now, count: 0 };
|
|
73
|
+
this.buckets.set(key, bucket);
|
|
74
|
+
}
|
|
75
|
+
return bucket;
|
|
76
|
+
}
|
|
77
|
+
prune(now) {
|
|
78
|
+
this.lastPrune = now;
|
|
79
|
+
for (const [key, bucket] of this.buckets) {
|
|
80
|
+
if (now - bucket.windowStart >= this.windowMs)
|
|
81
|
+
this.buckets.delete(key);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=rateLimit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rateLimit.js","sourceRoot":"","sources":["../src/rateLimit.ts"],"names":[],"mappings":"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"}
|
package/dist/resources.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { McpServer } from
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
2
|
import { type HonkioClient } from './client.js';
|
|
3
3
|
export declare function registerResources(server: McpServer, client: HonkioClient): void;
|
|
4
4
|
//# sourceMappingURL=resources.d.ts.map
|
package/dist/resources.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AACzD,OAAO,EAAE,KAAK,YAAY,EAAe,MAAM,aAAa,CAAC;AAE7D,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAgH/E"}
|
package/dist/resources.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { HonkioError } from './client.js';
|
|
2
2
|
export function registerResources(server, client) {
|
|
3
3
|
// honkio://messages — paginated list of messages
|
|
4
|
-
server.
|
|
4
|
+
server.registerResource('messages', 'honkio://messages', { description: 'Recent SMS messages (up to 20)' }, async (uri) => {
|
|
5
5
|
try {
|
|
6
6
|
const result = await client.listMessages({ limit: 20 });
|
|
7
7
|
return {
|
|
@@ -24,7 +24,7 @@ export function registerResources(server, client) {
|
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
26
|
// honkio://phone-numbers — list of provisioned numbers
|
|
27
|
-
server.
|
|
27
|
+
server.registerResource('phone-numbers', 'honkio://phone-numbers', { description: 'Phone numbers provisioned on your account' }, async (uri) => {
|
|
28
28
|
try {
|
|
29
29
|
const result = await client.listPhoneNumbers();
|
|
30
30
|
return {
|
|
@@ -47,7 +47,7 @@ export function registerResources(server, client) {
|
|
|
47
47
|
}
|
|
48
48
|
});
|
|
49
49
|
// honkio://consents — active CASL consents
|
|
50
|
-
server.
|
|
50
|
+
server.registerResource('consents', 'honkio://consents', { description: 'Active CASL consent records (up to 20)' }, async (uri) => {
|
|
51
51
|
try {
|
|
52
52
|
const result = await client.listConsents({ status: 'ACTIVE', limit: 20 });
|
|
53
53
|
return {
|
|
@@ -70,7 +70,7 @@ export function registerResources(server, client) {
|
|
|
70
70
|
}
|
|
71
71
|
});
|
|
72
72
|
// honkio://webhooks — registered webhooks
|
|
73
|
-
server.
|
|
73
|
+
server.registerResource('webhooks', 'honkio://webhooks', { description: 'Registered webhook endpoints for your account' }, async (uri) => {
|
|
74
74
|
try {
|
|
75
75
|
const result = await client.listWebhooks();
|
|
76
76
|
return {
|
package/dist/resources.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,WAAW,EAAE,MAAM,aAAa,CAAC;AAE7D,MAAM,UAAU,iBAAiB,CAAC,MAAiB,EAAE,MAAoB;IACvE,iDAAiD;IACjD,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/server';
|
|
2
|
+
import type { HonkioClient } from './client.js';
|
|
3
|
+
export declare const PACKAGE_VERSION: string;
|
|
4
|
+
/** Sent to clients that surface server instructions (Gemini CLI, Claude). Plain facts an agent needs before its first call. */
|
|
5
|
+
export declare const INSTRUCTIONS: string;
|
|
6
|
+
export declare function buildServer(client: HonkioClient): McpServer;
|
|
7
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAOhD,eAAO,MAAM,eAAe,EAAE,MAO1B,CAAC;AAKL,+HAA+H;AAC/H,eAAO,MAAM,YAAY,QAMd,CAAC;AAEZ,wBAAgB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,CAgB3D"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One factory for every transport. The stdio bin and the HTTP entry both call
|
|
3
|
+
* buildServer, so the two can never advertise different tools.
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { dirname, resolve } from 'node:path';
|
|
8
|
+
import { McpServer } from '@modelcontextprotocol/server';
|
|
9
|
+
import { registerTools } from './tools.js';
|
|
10
|
+
import { registerResources } from './resources.js';
|
|
11
|
+
import { registerPrompts } from './prompts.js';
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
export const PACKAGE_VERSION = (() => {
|
|
14
|
+
try {
|
|
15
|
+
const pkg = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf8'));
|
|
16
|
+
return pkg.version ?? 'unknown';
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return 'unknown';
|
|
20
|
+
}
|
|
21
|
+
})();
|
|
22
|
+
/** Lists change only on a deploy; let clients keep them for five minutes. */
|
|
23
|
+
const LIST_CACHE = { ttlMs: 5 * 60_000, cacheScope: 'private' };
|
|
24
|
+
/** Sent to clients that surface server instructions (Gemini CLI, Claude). Plain facts an agent needs before its first call. */
|
|
25
|
+
export const INSTRUCTIONS = [
|
|
26
|
+
'HonkIO is a Canadian SMS API. These tools act on the HonkIO account you signed in with, or whose API key you were configured with.',
|
|
27
|
+
'Keys starting with mk_test_ simulate everything for free: sends come back DELIVERED but nothing leaves the building and nothing is charged. Keys starting with mk_live_ send real SMS and spend real money.',
|
|
28
|
+
'Three tools spend money on a live key: send_sms and start_verification charge per SMS part, and provision_phone_number charges a first month plus a one-time activation fee and starts monthly rent. Call get_pricing for current figures, and confirm with the user before buying a number or sending to many recipients.',
|
|
29
|
+
'Commercial messages need CASL consent on file. Use check_consent before send_sms; record_consent stores express or implied consent. A refused send returns an error code such as NO_CONSENT, OPT_OUT_BLOCKED or DAILY_LIMIT_REACHED that explains what to fix.',
|
|
30
|
+
'Phone numbers are E.164 (+1 and ten digits). 555-XXXX exchanges are reserved and refused. Omit account_id on account tools; it is resolved from the key.',
|
|
31
|
+
].join(' ');
|
|
32
|
+
export function buildServer(client) {
|
|
33
|
+
const server = new McpServer({ name: 'honkio', version: PACKAGE_VERSION }, {
|
|
34
|
+
instructions: INSTRUCTIONS,
|
|
35
|
+
cacheHints: {
|
|
36
|
+
'tools/list': LIST_CACHE,
|
|
37
|
+
'prompts/list': LIST_CACHE,
|
|
38
|
+
'resources/list': LIST_CACHE,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
registerTools(server, client);
|
|
42
|
+
registerResources(server, client);
|
|
43
|
+
registerPrompts(server);
|
|
44
|
+
return server;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D,MAAM,CAAC,MAAM,eAAe,GAAW,CAAC,GAAG,EAAE;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAAyB,CAAC;QAC5G,OAAO,GAAG,CAAC,OAAO,IAAI,SAAS,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC,CAAC,EAAE,CAAC;AAEL,6EAA6E;AAC7E,MAAM,UAAU,GAAG,EAAE,KAAK,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,SAAS,EAAW,CAAC;AAEzE,+HAA+H;AAC/H,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,oIAAoI;IACpI,6MAA6M;IAC7M,4TAA4T;IAC5T,gQAAgQ;IAChQ,0JAA0J;CAC3J,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEZ,MAAM,UAAU,WAAW,CAAC,MAAoB;IAC9C,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,eAAe,EAAE,EAC5C;QACE,YAAY,EAAE,YAAY;QAC1B,UAAU,EAAE;YACV,YAAY,EAAE,UAAU;YACxB,cAAc,EAAE,UAAU;YAC1B,gBAAgB,EAAE,UAAU;SAC7B;KACF,CACF,CAAC;IACF,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,eAAe,CAAC,MAAM,CAAC,CAAC;IACxB,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Title and safety hints for every tool. Directory reviews (Anthropic, OpenAI)
|
|
3
|
+
* require them, and clients use the hints to decide when to ask the user
|
|
4
|
+
* before calling. Keep in step with tools.ts; tests/annotations.test.ts fails
|
|
5
|
+
* if a tool is missing here or here without a registration.
|
|
6
|
+
*
|
|
7
|
+
* readOnlyHint reads nothing changes
|
|
8
|
+
* destructiveHint cannot be undone (delete, revoke, release, erase)
|
|
9
|
+
* idempotentHint calling twice with the same arguments has no extra effect
|
|
10
|
+
* openWorldHint reaches a phone in the outside world; spends money on a live key
|
|
11
|
+
*/
|
|
12
|
+
import type { ToolAnnotations } from '@modelcontextprotocol/server';
|
|
13
|
+
export interface ToolMeta {
|
|
14
|
+
title: string;
|
|
15
|
+
annotations: ToolAnnotations;
|
|
16
|
+
}
|
|
17
|
+
export declare const TOOL_META: Record<string, ToolMeta>;
|
|
18
|
+
/** Metadata for a tool; throws at registration time so a missing entry cannot ship. */
|
|
19
|
+
export declare function meta(name: string): ToolMeta;
|
|
20
|
+
//# sourceMappingURL=toolMeta.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolMeta.d.ts","sourceRoot":"","sources":["../src/toolMeta.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,eAAe,CAAC;CAC9B;AAmBD,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAkD9C,CAAC;AAEF,uFAAuF;AACvF,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAI3C"}
|
package/dist/toolMeta.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const read = (title) => ({
|
|
2
|
+
title,
|
|
3
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
4
|
+
});
|
|
5
|
+
const write = (title, opts = {}) => ({
|
|
6
|
+
title,
|
|
7
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: opts.idempotent ?? false, openWorldHint: false },
|
|
8
|
+
});
|
|
9
|
+
const destructive = (title) => ({
|
|
10
|
+
title,
|
|
11
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
12
|
+
});
|
|
13
|
+
const reachesPhone = (title) => ({
|
|
14
|
+
title,
|
|
15
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
16
|
+
});
|
|
17
|
+
export const TOOL_META = {
|
|
18
|
+
// Messages
|
|
19
|
+
send_sms: reachesPhone('Send SMS'),
|
|
20
|
+
list_messages: read('List messages'),
|
|
21
|
+
get_message: read('Get message'),
|
|
22
|
+
// Phone numbers
|
|
23
|
+
search_phone_numbers: read('Search available numbers'),
|
|
24
|
+
provision_phone_number: reachesPhone('Buy phone number'),
|
|
25
|
+
list_phone_numbers: read('List phone numbers'),
|
|
26
|
+
get_phone_number: read('Get phone number'),
|
|
27
|
+
release_phone_number: destructive('Release phone number'),
|
|
28
|
+
// CASL compliance
|
|
29
|
+
record_consent: write('Record consent'),
|
|
30
|
+
list_consents: read('List consents'),
|
|
31
|
+
check_consent: read('Check consent'),
|
|
32
|
+
revoke_consent: destructive('Revoke consent'),
|
|
33
|
+
record_opt_out: write('Record opt-out', { idempotent: true }),
|
|
34
|
+
list_opt_outs: read('List opt-outs'),
|
|
35
|
+
check_dncl: read('Check Do Not Call List'),
|
|
36
|
+
batch_check_dncl: read('Batch check Do Not Call List'),
|
|
37
|
+
request_erasure: destructive('Erase subscriber data'),
|
|
38
|
+
// Pricing
|
|
39
|
+
get_pricing: read('Get pricing'),
|
|
40
|
+
// Verification
|
|
41
|
+
start_verification: reachesPhone('Start phone verification'),
|
|
42
|
+
check_verification: write('Check verification code'),
|
|
43
|
+
get_verification: read('Get verification'),
|
|
44
|
+
list_verifications: read('List verifications'),
|
|
45
|
+
// Webhooks
|
|
46
|
+
create_webhook: write('Create webhook'),
|
|
47
|
+
list_webhooks: read('List webhooks'),
|
|
48
|
+
update_webhook: write('Update webhook', { idempotent: true }),
|
|
49
|
+
delete_webhook: destructive('Delete webhook'),
|
|
50
|
+
list_webhook_deliveries: read('List webhook deliveries'),
|
|
51
|
+
reactivate_webhook: write('Reactivate webhook', { idempotent: true }),
|
|
52
|
+
list_webhook_dead_letters: read('List webhook dead letters'),
|
|
53
|
+
replay_webhook_dead_letter: write('Replay webhook dead letter'),
|
|
54
|
+
discard_webhook_dead_letter: destructive('Discard webhook dead letter'),
|
|
55
|
+
// Sending limits
|
|
56
|
+
get_send_limit: read('Get sending limits'),
|
|
57
|
+
request_send_limit: write('Request higher volume'),
|
|
58
|
+
get_topup_allowance: read('Get top-up allowance'),
|
|
59
|
+
// Account and keys
|
|
60
|
+
whoami: read('Who am I'),
|
|
61
|
+
get_account: read('Get account'),
|
|
62
|
+
update_account: write('Update account', { idempotent: true }),
|
|
63
|
+
get_usage: read('Get usage'),
|
|
64
|
+
create_api_key: write('Create API key'),
|
|
65
|
+
revoke_api_key: destructive('Revoke API key'),
|
|
66
|
+
rotate_api_key: destructive('Rotate API key'),
|
|
67
|
+
};
|
|
68
|
+
/** Metadata for a tool; throws at registration time so a missing entry cannot ship. */
|
|
69
|
+
export function meta(name) {
|
|
70
|
+
const m = TOOL_META[name];
|
|
71
|
+
if (!m)
|
|
72
|
+
throw new Error(`No tool metadata for ${name}; add it to toolMeta.ts`);
|
|
73
|
+
return m;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=toolMeta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolMeta.js","sourceRoot":"","sources":["../src/toolMeta.ts"],"names":[],"mappings":"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"}
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { McpServer } from
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
2
|
import { type HonkioClient } from './client.js';
|
|
3
3
|
export declare function registerTools(server: McpServer, client: HonkioClient): void;
|
|
4
4
|
//# sourceMappingURL=tools.d.ts.map
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAEzD,OAAO,EAAE,KAAK,YAAY,EAAe,MAAM,aAAa,CAAC;AA4C7D,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAwiB3E"}
|