posterly-mcp-server 0.43.8 → 0.45.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 +310 -109
- package/dist/index.js +190 -874
- package/dist/lib/alias-transport.d.ts +84 -0
- package/dist/lib/alias-transport.js +193 -0
- package/dist/lib/api-client.d.ts +97 -0
- package/dist/lib/api-client.js +81 -0
- package/dist/lib/preview-token-core.d.ts +62 -0
- package/dist/lib/preview-token-core.js +131 -0
- package/dist/lib/preview-token.d.ts +23 -0
- package/dist/lib/preview-token.js +44 -0
- package/dist/lib/tool-registry.d.ts +246 -0
- package/dist/lib/tool-registry.js +394 -0
- package/dist/lib/tool-runtime.d.ts +68 -0
- package/dist/lib/tool-runtime.js +85 -0
- package/dist/lib/version.d.ts +1 -1
- package/dist/lib/version.js +1 -1
- package/dist/tools/audit-google-business-profile.js +1 -1
- package/dist/tools/connect-account.d.ts +4 -4
- package/dist/tools/connect-account.js +2 -2
- package/dist/tools/create-api-key.js +1 -1
- package/dist/tools/create-connect-session.d.ts +2 -2
- package/dist/tools/create-connect-session.js +2 -2
- package/dist/tools/create-post.d.ts +6 -6
- package/dist/tools/create-post.js +3 -3
- package/dist/tools/create-posts-batch.d.ts +4 -4
- package/dist/tools/create-webhook.d.ts +4 -4
- package/dist/tools/delete-post.d.ts +34 -10
- package/dist/tools/delete-post.js +52 -16
- package/dist/tools/disconnect-account.d.ts +2 -2
- package/dist/tools/generate-captions.d.ts +2 -2
- package/dist/tools/generate-image.d.ts +4 -4
- package/dist/tools/generate-image.js +2 -2
- package/dist/tools/generate-video.d.ts +6 -6
- package/dist/tools/generate-video.js +1 -1
- package/dist/tools/get-agent-signup-info.js +2 -2
- package/dist/tools/get-post.d.ts +16 -1
- package/dist/tools/get-post.js +35 -19
- package/dist/tools/list-accounts.d.ts +49 -3
- package/dist/tools/list-accounts.js +148 -33
- package/dist/tools/list-analytics.d.ts +111 -0
- package/dist/tools/list-analytics.js +393 -0
- package/dist/tools/list-brands.d.ts +38 -3
- package/dist/tools/list-brands.js +138 -20
- package/dist/tools/list-comments.d.ts +29 -9
- package/dist/tools/list-comments.js +75 -34
- package/dist/tools/list-conversations.d.ts +32 -9
- package/dist/tools/list-conversations.js +63 -27
- package/dist/tools/list-google-business-media.js +1 -1
- package/dist/tools/list-google-business-reviews.d.ts +32 -9
- package/dist/tools/list-google-business-reviews.js +77 -52
- package/dist/tools/list-jobs.d.ts +60 -0
- package/dist/tools/list-jobs.js +81 -0
- package/dist/tools/list-platforms.d.ts +62 -3
- package/dist/tools/list-platforms.js +118 -9
- package/dist/tools/list-posts.d.ts +2 -2
- package/dist/tools/list-workspace-members.d.ts +17 -0
- package/dist/tools/list-workspace-members.js +51 -0
- package/dist/tools/manage-comment.d.ts +102 -0
- package/dist/tools/manage-comment.js +125 -0
- package/dist/tools/manage-conversation.d.ts +79 -0
- package/dist/tools/manage-conversation.js +75 -0
- package/dist/tools/manage-google-business-media.d.ts +103 -0
- package/dist/tools/manage-google-business-media.js +126 -0
- package/dist/tools/manage-google-business-review.d.ts +146 -0
- package/dist/tools/manage-google-business-review.js +115 -0
- package/dist/tools/manage-oauth-client.d.ts +121 -0
- package/dist/tools/manage-oauth-client.js +94 -0
- package/dist/tools/manage-webhook.d.ts +99 -0
- package/dist/tools/manage-webhook.js +87 -0
- package/dist/tools/manage-workspace-member.d.ts +57 -0
- package/dist/tools/manage-workspace-member.js +131 -0
- package/dist/tools/reply-to-comment.d.ts +2 -2
- package/dist/tools/send-message.d.ts +2 -2
- package/dist/tools/start-signup.d.ts +2 -2
- package/dist/tools/submit-agent-feedback.d.ts +4 -4
- package/dist/tools/submit-product-feedback.d.ts +4 -4
- package/dist/tools/trigger-platform-helper.d.ts +2 -2
- package/dist/tools/update-post-release-id.d.ts +2 -2
- package/dist/tools/update-post-status.d.ts +2 -2
- package/dist/tools/update-post.d.ts +64 -13
- package/dist/tools/update-post.js +79 -26
- package/dist/tools/upload-media.d.ts +32 -9
- package/dist/tools/upload-media.js +54 -18
- package/dist/tools/validate-post.d.ts +4 -4
- package/dist/tools/whoami.d.ts +15 -2
- package/dist/tools/whoami.js +221 -25
- package/package.json +5 -3
- package/scripts/test-alias-transport.mjs +368 -0
- package/scripts/test-client-header-sanitization.mjs +78 -0
- package/scripts/test-tool-annotations.mjs +38 -17
- package/server.json +9 -2
- package/src/index.ts +227 -1411
- package/src/lib/alias-transport.ts +233 -0
- package/src/lib/api-client.ts +168 -0
- package/src/lib/preview-token-core.ts +157 -0
- package/src/lib/preview-token.ts +54 -0
- package/src/lib/tool-registry.ts +538 -0
- package/src/lib/tool-runtime.ts +117 -0
- package/src/lib/version.ts +1 -1
- package/src/tools/audit-google-business-profile.ts +1 -1
- package/src/tools/connect-account.ts +2 -2
- package/src/tools/create-api-key.ts +1 -1
- package/src/tools/create-connect-session.ts +2 -2
- package/src/tools/create-post.ts +3 -3
- package/src/tools/delete-post.ts +60 -19
- package/src/tools/generate-image.ts +2 -2
- package/src/tools/generate-video.ts +1 -1
- package/src/tools/get-agent-signup-info.ts +2 -2
- package/src/tools/get-post.ts +35 -21
- package/src/tools/list-accounts.ts +187 -39
- package/src/tools/list-analytics.ts +444 -0
- package/src/tools/list-brands.ts +162 -26
- package/src/tools/list-comments.ts +86 -43
- package/src/tools/list-conversations.ts +81 -39
- package/src/tools/list-google-business-media.ts +1 -1
- package/src/tools/list-google-business-reviews.ts +93 -64
- package/src/tools/list-jobs.ts +91 -0
- package/src/tools/list-platforms.ts +135 -11
- package/src/tools/list-workspace-members.ts +61 -0
- package/src/tools/manage-comment.ts +142 -0
- package/src/tools/manage-conversation.ts +89 -0
- package/src/tools/manage-google-business-media.ts +147 -0
- package/src/tools/manage-google-business-review.ts +139 -0
- package/src/tools/manage-oauth-client.ts +116 -0
- package/src/tools/manage-webhook.ts +103 -0
- package/src/tools/manage-workspace-member.ts +151 -0
- package/src/tools/update-post.ts +106 -39
- package/src/tools/upload-media.ts +76 -28
- package/src/tools/whoami.ts +253 -30
- package/tool-annotations.json +76 -144
- package/tool-registry.json +2085 -0
- package/src/tools/add-google-business-media.ts +0 -50
- package/src/tools/create-oauth-client.ts +0 -36
- package/src/tools/delete-comment.ts +0 -29
- package/src/tools/delete-google-business-media.ts +0 -30
- package/src/tools/delete-google-business-review-reply.ts +0 -30
- package/src/tools/delete-oauth-client.ts +0 -23
- package/src/tools/delete-post-group.ts +0 -17
- package/src/tools/delete-webhook.ts +0 -17
- package/src/tools/get-account-analytics.ts +0 -171
- package/src/tools/get-brand-profile.ts +0 -86
- package/src/tools/get-brand.ts +0 -34
- package/src/tools/get-comment.ts +0 -47
- package/src/tools/get-connect-link.ts +0 -61
- package/src/tools/get-connect-session.ts +0 -63
- package/src/tools/get-conversation.ts +0 -44
- package/src/tools/get-google-business-review-link.ts +0 -30
- package/src/tools/get-image-job.ts +0 -36
- package/src/tools/get-learned-voice.ts +0 -31
- package/src/tools/get-mcp-status.ts +0 -212
- package/src/tools/get-performance-profile.ts +0 -47
- package/src/tools/get-platform-schema.ts +0 -56
- package/src/tools/get-post-analytics.ts +0 -243
- package/src/tools/get-post-insights.ts +0 -60
- package/src/tools/get-post-missing.ts +0 -16
- package/src/tools/get-video-job.ts +0 -36
- package/src/tools/list-brand-accounts.ts +0 -36
- package/src/tools/reply-google-business-review.ts +0 -32
- package/src/tools/reply-to-comment.ts +0 -30
- package/src/tools/send-message.ts +0 -28
- package/src/tools/suggest-google-business-review-reply.ts +0 -44
- package/src/tools/sync-inbox.ts +0 -28
- package/src/tools/test-webhook.ts +0 -18
- package/src/tools/update-comment.ts +0 -31
- package/src/tools/update-oauth-client.ts +0 -38
- package/src/tools/update-post-release-id.ts +0 -25
- package/src/tools/update-post-status.ts +0 -31
- package/src/tools/update-webhook.ts +0 -36
- package/src/tools/upload-media-from-url.ts +0 -33
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview-then-confirm tokens for MCP write actions, shared by both surfaces.
|
|
3
|
+
* Depends on node:crypto only.
|
|
4
|
+
*
|
|
5
|
+
* Format: pv1.<exp>.<nonce>.<mac>
|
|
6
|
+
* exp unix seconds when the preview stops being confirmable
|
|
7
|
+
* nonce random, base64url; also the one-time-use key for nonce actions
|
|
8
|
+
* mac base64url HMAC-SHA256(key,
|
|
9
|
+
* "posterly-mcp-preview-v1|" + exp + "|" + nonce + "|" + userId
|
|
10
|
+
* + "|" + tool + "|" + action + "|" + canonicalJson(args))
|
|
11
|
+
*
|
|
12
|
+
* The token binds the preview to one user, one tool, one action, and the
|
|
13
|
+
* exact (normalised) arguments, so a confirm call with different args, from a
|
|
14
|
+
* different user, or after expiry is refused. It is UX protection that a
|
|
15
|
+
* human saw what will change, not authorization: REST scopes and ownership
|
|
16
|
+
* checks still decide what is allowed.
|
|
17
|
+
*
|
|
18
|
+
* Callers own the key (hosted: derived from a server secret; stdio: random
|
|
19
|
+
* per process) and the one-time-use store (hosted: Redis; stdio: memory).
|
|
20
|
+
*/
|
|
21
|
+
import { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
22
|
+
export const PREVIEW_TOKEN_VERSION = 'pv1';
|
|
23
|
+
const MAC_DOMAIN = 'posterly-mcp-preview-v1';
|
|
24
|
+
function isPlainObject(value) {
|
|
25
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
function sortKeysDeep(value) {
|
|
28
|
+
if (Array.isArray(value))
|
|
29
|
+
return value.map((item) => (item === undefined ? null : sortKeysDeep(item)));
|
|
30
|
+
if (isPlainObject(value)) {
|
|
31
|
+
const out = {};
|
|
32
|
+
for (const key of Object.keys(value).sort()) {
|
|
33
|
+
const item = value[key];
|
|
34
|
+
if (item === undefined)
|
|
35
|
+
continue;
|
|
36
|
+
out[key] = sortKeysDeep(item);
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Canonical JSON of a call's arguments: `confirm` and `preview_id` removed
|
|
44
|
+
* (top level), undefined dropped, keys sorted recursively, arrays kept in
|
|
45
|
+
* order, no whitespace. Same rules as canonicalArgs in tool-registry.ts.
|
|
46
|
+
*/
|
|
47
|
+
export function canonicalJson(args) {
|
|
48
|
+
const source = isPlainObject(args) ? args : {};
|
|
49
|
+
const stripped = {};
|
|
50
|
+
for (const [key, value] of Object.entries(source)) {
|
|
51
|
+
if (key === 'confirm' || key === 'preview_id')
|
|
52
|
+
continue;
|
|
53
|
+
stripped[key] = value;
|
|
54
|
+
}
|
|
55
|
+
return JSON.stringify(sortKeysDeep(stripped));
|
|
56
|
+
}
|
|
57
|
+
/** Derives the MAC key from a server secret with a domain prefix, so the raw secret is never the key. */
|
|
58
|
+
export function derivePreviewKey(secret, domain = 'posterly-mcp-preview-key-v1') {
|
|
59
|
+
return createHmac('sha256', secret).update(domain).digest();
|
|
60
|
+
}
|
|
61
|
+
function field(value) {
|
|
62
|
+
// Pipes separate fields; ids never contain them, but never let one shift a
|
|
63
|
+
// boundary. Percent is escaped first so the encoding is injective: a value
|
|
64
|
+
// that already contains "%7C" cannot collide with one that contains "|".
|
|
65
|
+
return value.replace(/%/g, '%25').replace(/\|/g, '%7C');
|
|
66
|
+
}
|
|
67
|
+
function computeMac(key, exp, nonce, binding) {
|
|
68
|
+
const payload = [
|
|
69
|
+
MAC_DOMAIN,
|
|
70
|
+
String(exp),
|
|
71
|
+
nonce,
|
|
72
|
+
field(binding.userId),
|
|
73
|
+
field(binding.tool),
|
|
74
|
+
field(binding.action),
|
|
75
|
+
canonicalJson(binding.args),
|
|
76
|
+
].join('|');
|
|
77
|
+
return createHmac('sha256', key).update(payload).digest('base64url');
|
|
78
|
+
}
|
|
79
|
+
export function createPreviewToken(key, binding, options) {
|
|
80
|
+
const now = Math.floor((options.nowMs ?? Date.now()) / 1000);
|
|
81
|
+
const exp = now + Math.max(1, Math.floor(options.ttlSeconds));
|
|
82
|
+
const nonce = options.nonce ?? randomBytes(16).toString('base64url');
|
|
83
|
+
const mac = computeMac(key, exp, nonce, binding);
|
|
84
|
+
return `${PREVIEW_TOKEN_VERSION}.${exp}.${nonce}.${mac}`;
|
|
85
|
+
}
|
|
86
|
+
export function parsePreviewToken(token) {
|
|
87
|
+
if (typeof token !== 'string')
|
|
88
|
+
return null;
|
|
89
|
+
const parts = token.split('.');
|
|
90
|
+
if (parts.length !== 4 || parts[0] !== PREVIEW_TOKEN_VERSION)
|
|
91
|
+
return null;
|
|
92
|
+
const [, expRaw, nonce, mac] = parts;
|
|
93
|
+
if (!/^\d{1,12}$/.test(expRaw) || !/^[A-Za-z0-9_-]{8,64}$/.test(nonce) || !/^[A-Za-z0-9_-]{20,100}$/.test(mac))
|
|
94
|
+
return null;
|
|
95
|
+
return { exp: Number(expRaw), nonce, mac };
|
|
96
|
+
}
|
|
97
|
+
export function verifyPreviewToken(key, token, binding, options = {}) {
|
|
98
|
+
const parsed = parsePreviewToken(token);
|
|
99
|
+
if (!parsed)
|
|
100
|
+
return { ok: false, reason: 'malformed' };
|
|
101
|
+
const expected = Buffer.from(computeMac(key, parsed.exp, parsed.nonce, binding));
|
|
102
|
+
const actual = Buffer.from(parsed.mac);
|
|
103
|
+
if (expected.length !== actual.length || !timingSafeEqual(expected, actual)) {
|
|
104
|
+
return { ok: false, reason: 'mismatch' };
|
|
105
|
+
}
|
|
106
|
+
const now = Math.floor((options.nowMs ?? Date.now()) / 1000);
|
|
107
|
+
if (parsed.exp <= now)
|
|
108
|
+
return { ok: false, reason: 'expired' };
|
|
109
|
+
return { ok: true, exp: parsed.exp, nonce: parsed.nonce, remainingSeconds: parsed.exp - now };
|
|
110
|
+
}
|
|
111
|
+
/** Stable fingerprint of a token for one-time-use bookkeeping (never store the raw token). */
|
|
112
|
+
export function previewTokenFingerprint(token) {
|
|
113
|
+
return createHash('sha256').update(token).digest('hex');
|
|
114
|
+
}
|
|
115
|
+
/** Plain-language refusal for each verification failure. */
|
|
116
|
+
export function previewFailureMessage(reason) {
|
|
117
|
+
switch (reason) {
|
|
118
|
+
case 'missing':
|
|
119
|
+
return 'confirm: true needs the preview_id from a preview call. Call once without confirm, show the preview to the user, then confirm with its preview_id.';
|
|
120
|
+
case 'expired':
|
|
121
|
+
return 'This preview expired, so nothing was changed. Call again without confirm to get a fresh preview.';
|
|
122
|
+
case 'mismatch':
|
|
123
|
+
return 'This preview does not match these arguments, so nothing was changed. Call again without confirm to get a fresh preview for exactly what should happen.';
|
|
124
|
+
case 'used':
|
|
125
|
+
return 'This preview was already used, so nothing was changed. Call again without confirm to get a fresh preview.';
|
|
126
|
+
case 'unavailable':
|
|
127
|
+
return 'posterly could not check this preview right now, so nothing was changed. Please retry in a moment.';
|
|
128
|
+
default:
|
|
129
|
+
return 'preview_id is not valid, so nothing was changed. Call again without confirm to get a fresh preview.';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stdio (npm package) preview tokens: a random key per process and an
|
|
3
|
+
* in-memory one-time-use set. A preview only survives as long as the local
|
|
4
|
+
* MCP process does, which matches how a local client uses it (preview and
|
|
5
|
+
* confirm happen in the same session). This is UX protection only: REST
|
|
6
|
+
* authorization on the posterly API is unchanged.
|
|
7
|
+
*/
|
|
8
|
+
import { type PreviewBinding, type VerifyPreviewResult } from './preview-token-core.js';
|
|
9
|
+
export declare const STDIO_PREVIEW_TTL_SECONDS: number;
|
|
10
|
+
export declare class StdioPreviewTokens {
|
|
11
|
+
private readonly ttlSeconds;
|
|
12
|
+
private readonly key;
|
|
13
|
+
private readonly used;
|
|
14
|
+
constructor(key?: Buffer, ttlSeconds?: number);
|
|
15
|
+
create(binding: PreviewBinding, nowMs?: number): string;
|
|
16
|
+
verify(token: unknown, binding: PreviewBinding, nowMs?: number): VerifyPreviewResult;
|
|
17
|
+
/**
|
|
18
|
+
* Marks a verified token as used. Returns false when it was already used
|
|
19
|
+
* (a second confirm with the same preview_id). Call only after verify().
|
|
20
|
+
*/
|
|
21
|
+
claimOnce(token: string, expSeconds: number, nowMs?: number): boolean;
|
|
22
|
+
private sweep;
|
|
23
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stdio (npm package) preview tokens: a random key per process and an
|
|
3
|
+
* in-memory one-time-use set. A preview only survives as long as the local
|
|
4
|
+
* MCP process does, which matches how a local client uses it (preview and
|
|
5
|
+
* confirm happen in the same session). This is UX protection only: REST
|
|
6
|
+
* authorization on the posterly API is unchanged.
|
|
7
|
+
*/
|
|
8
|
+
import { randomBytes } from 'node:crypto';
|
|
9
|
+
import { createPreviewToken, previewTokenFingerprint, verifyPreviewToken, } from './preview-token-core.js';
|
|
10
|
+
export const STDIO_PREVIEW_TTL_SECONDS = 10 * 60;
|
|
11
|
+
export class StdioPreviewTokens {
|
|
12
|
+
ttlSeconds;
|
|
13
|
+
key;
|
|
14
|
+
used = new Map();
|
|
15
|
+
constructor(key = randomBytes(32), ttlSeconds = STDIO_PREVIEW_TTL_SECONDS) {
|
|
16
|
+
this.ttlSeconds = ttlSeconds;
|
|
17
|
+
this.key = key;
|
|
18
|
+
}
|
|
19
|
+
create(binding, nowMs) {
|
|
20
|
+
return createPreviewToken(this.key, binding, { ttlSeconds: this.ttlSeconds, nowMs });
|
|
21
|
+
}
|
|
22
|
+
verify(token, binding, nowMs) {
|
|
23
|
+
return verifyPreviewToken(this.key, token, binding, { nowMs });
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Marks a verified token as used. Returns false when it was already used
|
|
27
|
+
* (a second confirm with the same preview_id). Call only after verify().
|
|
28
|
+
*/
|
|
29
|
+
claimOnce(token, expSeconds, nowMs = Date.now()) {
|
|
30
|
+
this.sweep(nowMs);
|
|
31
|
+
const fingerprint = previewTokenFingerprint(token);
|
|
32
|
+
if (this.used.has(fingerprint))
|
|
33
|
+
return false;
|
|
34
|
+
this.used.set(fingerprint, expSeconds);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
sweep(nowMs) {
|
|
38
|
+
const now = Math.floor(nowMs / 1000);
|
|
39
|
+
for (const [fingerprint, exp] of this.used) {
|
|
40
|
+
if (exp <= now)
|
|
41
|
+
this.used.delete(fingerprint);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, dependency-free helpers over the MCP tool registry
|
|
3
|
+
* (mcp-server/tool-registry.json). Shared by BOTH surfaces:
|
|
4
|
+
* - the npm stdio package (mcp-server/src/index.ts, alias-transport.ts)
|
|
5
|
+
* - the hosted endpoint (lib/mcp/tool-registry.ts re-exports these with the
|
|
6
|
+
* registry bound)
|
|
7
|
+
*
|
|
8
|
+
* This module never imports the JSON itself: each surface loads the registry
|
|
9
|
+
* its own way (createRequire on stdio, a static JSON import on hosted) and
|
|
10
|
+
* passes it in. Keep it free of Node, Next, and zod imports so it compiles
|
|
11
|
+
* unchanged under both the package tsconfig and the app tsconfig.
|
|
12
|
+
*/
|
|
13
|
+
export type ToolsetName = string;
|
|
14
|
+
export type ConfirmLevel = 'none' | 'confirm' | 'preview';
|
|
15
|
+
export type ToolSurface = 'hosted' | 'stdio';
|
|
16
|
+
export type ModeSelector = 'view' | 'action' | 'type' | 'params';
|
|
17
|
+
export type ExceptionCategory = 'money' | 'secret' | 'signup' | 'publishing' | 'ai_generation' | 'feedback' | 'connect' | 'single_action' | 'session' | 'dispatcher' | 'report';
|
|
18
|
+
export interface ModeParamSpec {
|
|
19
|
+
type?: 'string' | 'integer' | 'number' | 'boolean' | 'array' | 'object';
|
|
20
|
+
required?: boolean;
|
|
21
|
+
default?: unknown;
|
|
22
|
+
min?: number;
|
|
23
|
+
max?: number;
|
|
24
|
+
enum?: Array<string | number>;
|
|
25
|
+
/** Keep an explicit null (for example workspace_id: null clears a webhook's workspace). */
|
|
26
|
+
nullable?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** One view (read) or action (write) inside a multi-mode tool. */
|
|
29
|
+
export interface ToolModeEntry {
|
|
30
|
+
label: string;
|
|
31
|
+
destructive?: boolean;
|
|
32
|
+
idempotent?: boolean;
|
|
33
|
+
openWorld?: boolean;
|
|
34
|
+
confirm?: ConfirmLevel;
|
|
35
|
+
/** Preview actions whose confirm may be used once only (public writes). */
|
|
36
|
+
nonce?: boolean;
|
|
37
|
+
/** When present, the only params this mode accepts (besides the selector, confirm, preview_id). */
|
|
38
|
+
params?: Record<string, ModeParamSpec>;
|
|
39
|
+
/**
|
|
40
|
+
* Param-driven tools (selector "params") only: this mode is chosen when any
|
|
41
|
+
* of these params is present. The one mode without `when` is the default.
|
|
42
|
+
*/
|
|
43
|
+
when?: string[];
|
|
44
|
+
}
|
|
45
|
+
export interface ToolRegistryTool {
|
|
46
|
+
set: ToolsetName;
|
|
47
|
+
label: string;
|
|
48
|
+
/** Surfaces that register this tool. Defaults to both. */
|
|
49
|
+
surfaces?: ToolSurface[];
|
|
50
|
+
exception?: {
|
|
51
|
+
category: ExceptionCategory;
|
|
52
|
+
reason: string;
|
|
53
|
+
};
|
|
54
|
+
/** Single-mode tools carry their hints here; multi-mode tools derive them from views/actions. */
|
|
55
|
+
readOnly?: boolean;
|
|
56
|
+
destructive?: boolean;
|
|
57
|
+
idempotent?: boolean;
|
|
58
|
+
openWorld?: boolean;
|
|
59
|
+
confirm?: ConfirmLevel;
|
|
60
|
+
/**
|
|
61
|
+
* How a multi-mode tool picks its mode: an enum param (view/action/type), or
|
|
62
|
+
* "params" when the mode follows from which params are present (for example
|
|
63
|
+
* update_post with status vs content fields). Param-driven modes are still
|
|
64
|
+
* recorded as views/actions so the action count is derived from the registry.
|
|
65
|
+
*/
|
|
66
|
+
selector?: ModeSelector;
|
|
67
|
+
/** Default view/action when the selector param is omitted. */
|
|
68
|
+
defaultMode?: string;
|
|
69
|
+
views?: Record<string, ToolModeEntry>;
|
|
70
|
+
actions?: Record<string, ToolModeEntry>;
|
|
71
|
+
}
|
|
72
|
+
export interface ToolRegistryAlias {
|
|
73
|
+
target: string;
|
|
74
|
+
/** Params forced onto the translated call (inject wins over caller args). */
|
|
75
|
+
inject?: Record<string, unknown>;
|
|
76
|
+
/** Old param name -> new param name. */
|
|
77
|
+
rename?: Record<string, string>;
|
|
78
|
+
/** Accept confirm:true without preview_id until sunset (per call, never stored on shared context). */
|
|
79
|
+
legacyConfirm?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Param-driven targets (selector "params") only: the mode the old tool
|
|
82
|
+
* always ran in, so for example get_comment without comment_id is still
|
|
83
|
+
* rejected instead of silently listing every comment.
|
|
84
|
+
*/
|
|
85
|
+
mode?: string;
|
|
86
|
+
added: string;
|
|
87
|
+
sunset: string;
|
|
88
|
+
}
|
|
89
|
+
export interface ToolRegistry {
|
|
90
|
+
$comment?: string;
|
|
91
|
+
version: number;
|
|
92
|
+
designRules: 'off' | 'on';
|
|
93
|
+
budgets: {
|
|
94
|
+
maxTotal: number;
|
|
95
|
+
maxCore: number;
|
|
96
|
+
};
|
|
97
|
+
sunset: string | null;
|
|
98
|
+
toolsets: Record<ToolsetName, {
|
|
99
|
+
label: string;
|
|
100
|
+
description: string;
|
|
101
|
+
alwaysOn?: boolean;
|
|
102
|
+
surfaces?: ToolSurface[];
|
|
103
|
+
}>;
|
|
104
|
+
tools: Record<string, ToolRegistryTool>;
|
|
105
|
+
aliases: Record<string, ToolRegistryAlias>;
|
|
106
|
+
}
|
|
107
|
+
export interface ResolvedAlias extends ToolRegistryAlias {
|
|
108
|
+
name: string;
|
|
109
|
+
}
|
|
110
|
+
/** The alias entry for `name`, or null when `name` is not an alias. */
|
|
111
|
+
export declare function resolveAlias(registry: ToolRegistry, name: string): ResolvedAlias | null;
|
|
112
|
+
/**
|
|
113
|
+
* Translates an alias call's arguments into the target tool's arguments.
|
|
114
|
+
* Always returns a fresh object (never mutates the caller's args): renames
|
|
115
|
+
* first, then injected params, which win over anything the caller passed.
|
|
116
|
+
*/
|
|
117
|
+
export declare function translateArgs(alias: ToolRegistryAlias, args: unknown): Record<string, unknown>;
|
|
118
|
+
export type ValidateModeResult = {
|
|
119
|
+
ok: true;
|
|
120
|
+
mode: string | null;
|
|
121
|
+
args: Record<string, unknown>;
|
|
122
|
+
} | {
|
|
123
|
+
ok: false;
|
|
124
|
+
error: string;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* The mode a param-driven tool (selector "params") runs in for these args:
|
|
128
|
+
* the first mode whose `when` params are present, otherwise the tool's
|
|
129
|
+
* default mode (the one without `when`). Undefined for other tools.
|
|
130
|
+
*/
|
|
131
|
+
export declare function detectParamsMode(tool: ToolRegistryTool, args: unknown): string | undefined;
|
|
132
|
+
/**
|
|
133
|
+
* Validates and normalises a call's arguments against the registry's per-mode
|
|
134
|
+
* rules, BEFORE any request is made. It is the normaliser on both surfaces
|
|
135
|
+
* (the hosted endpoint has no zod), so a preview call and its confirm call
|
|
136
|
+
* hash identically: numeric strings become numbers, defaults are applied, and
|
|
137
|
+
* params outside the selected mode are rejected with a plain error.
|
|
138
|
+
*
|
|
139
|
+
* `explicitMode` forces a mode for param-driven tools (selector "params"), for
|
|
140
|
+
* example an alias of the old get_comment tool always runs in the comment
|
|
141
|
+
* mode. Without it, a param-driven tool's mode is detected from which params
|
|
142
|
+
* are present (see detectParamsMode).
|
|
143
|
+
*
|
|
144
|
+
* Tools without views/actions, and modes without a `params` spec, pass
|
|
145
|
+
* through unchanged (a fresh copy).
|
|
146
|
+
*/
|
|
147
|
+
export declare function validateMode(registry: ToolRegistry, toolName: string, args: unknown, explicitMode?: string): ValidateModeResult;
|
|
148
|
+
/**
|
|
149
|
+
* The canonical form of a call's arguments for hashing: `confirm` and
|
|
150
|
+
* `preview_id` removed, undefined dropped, object keys sorted recursively,
|
|
151
|
+
* arrays kept in order. Pass args AFTER validateMode so defaults and number
|
|
152
|
+
* coercion are already applied.
|
|
153
|
+
*/
|
|
154
|
+
export declare function canonicalArgs(args: unknown): Record<string, unknown>;
|
|
155
|
+
export interface DeprecationInfo {
|
|
156
|
+
old: string;
|
|
157
|
+
new: string;
|
|
158
|
+
action: string | null;
|
|
159
|
+
sunset: string;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The note appended (as a final text content item, never touching content[0])
|
|
163
|
+
* to every alias call's result, plus the matching _meta payload. `action` in
|
|
164
|
+
* the payload is the injected view/action/type value (null when none).
|
|
165
|
+
*/
|
|
166
|
+
export declare function deprecationNote(alias: ResolvedAlias): {
|
|
167
|
+
text: string;
|
|
168
|
+
meta: DeprecationInfo;
|
|
169
|
+
};
|
|
170
|
+
export declare const DEPRECATION_META_KEY = "posterly/deprecation";
|
|
171
|
+
export interface ParsedToolsets {
|
|
172
|
+
/** Enabled set names, or every set when "all" (the default). */
|
|
173
|
+
enabled: ToolsetName[];
|
|
174
|
+
/** Requested names that are not known sets (ignored). */
|
|
175
|
+
unknown: string[];
|
|
176
|
+
/** True when every set is enabled. */
|
|
177
|
+
all: boolean;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Parses a toolset selection such as "core,inbox" or "all". Empty, missing,
|
|
181
|
+
* or all-unknown input means every set (nobody loses tools by mistake).
|
|
182
|
+
* With a surface, sets not available on it (for example signup, stdio only,
|
|
183
|
+
* on the hosted endpoint) are treated as unknown.
|
|
184
|
+
*/
|
|
185
|
+
export declare function parseToolsets(registry: ToolRegistry, value: string | null | undefined, surface?: ToolSurface): ParsedToolsets;
|
|
186
|
+
/**
|
|
187
|
+
* Names of the canonical tools visible on `surface` for the enabled sets.
|
|
188
|
+
* Aliases are never listed. Sets marked alwaysOn (signup on stdio) are always
|
|
189
|
+
* visible. Surfaces keep their own registration order by filtering with this.
|
|
190
|
+
*/
|
|
191
|
+
export declare function visibleTools(registry: ToolRegistry, sets: ToolsetName[], surface: ToolSurface): Set<string>;
|
|
192
|
+
export interface DerivedAnnotations {
|
|
193
|
+
readOnlyHint: boolean;
|
|
194
|
+
destructiveHint: boolean;
|
|
195
|
+
idempotentHint: boolean;
|
|
196
|
+
openWorldHint: boolean;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Annotation hints derived from the registry: a multi-action tool is
|
|
200
|
+
* destructive if any action is, idempotent only if all are, openWorld if any
|
|
201
|
+
* is; multi-view reads are readOnly + idempotent, openWorld if any view is.
|
|
202
|
+
*/
|
|
203
|
+
export declare function deriveAnnotations(tool: ToolRegistryTool): DerivedAnnotations;
|
|
204
|
+
/** Actions (sub tools) a tool counts for: its views/actions, otherwise 1. */
|
|
205
|
+
export declare function countActions(tool: ToolRegistryTool): number;
|
|
206
|
+
export interface ToolListingAction {
|
|
207
|
+
key: string;
|
|
208
|
+
label: string;
|
|
209
|
+
}
|
|
210
|
+
export interface ToolListingTool {
|
|
211
|
+
name: string;
|
|
212
|
+
label: string;
|
|
213
|
+
/** The view/action/type param name, or null for param-driven or single-mode tools. */
|
|
214
|
+
selector: string | null;
|
|
215
|
+
/** Views/actions (the "sub tools") with plain-English labels; empty for single-mode tools. */
|
|
216
|
+
actions: ToolListingAction[];
|
|
217
|
+
}
|
|
218
|
+
export interface ToolListingSet {
|
|
219
|
+
set: ToolsetName;
|
|
220
|
+
label: string;
|
|
221
|
+
description: string;
|
|
222
|
+
tools: ToolListingTool[];
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Every canonical tool on `surface`, grouped by tool set in registry order,
|
|
226
|
+
* each with its views/actions and their labels. This is what every tool list
|
|
227
|
+
* in the docs, marketing pages, and server card renders from, so the "sub
|
|
228
|
+
* tools" inside a merged tool are always shown. Aliases are never listed.
|
|
229
|
+
*/
|
|
230
|
+
export declare function listToolsBySet(registry: ToolRegistry, surface: ToolSurface): ToolListingSet[];
|
|
231
|
+
/**
|
|
232
|
+
* What a called name resolves to: the canonical tool plus the view/action it
|
|
233
|
+
* runs (for an alias), or the name itself. Used by /admin/mcp to show old
|
|
234
|
+
* names next to the tool they now reach, without any new telemetry column.
|
|
235
|
+
*/
|
|
236
|
+
export declare function resolveCalledTool(registry: ToolRegistry, calledName: string): {
|
|
237
|
+
called: string;
|
|
238
|
+
tool: string;
|
|
239
|
+
mode: string | null;
|
|
240
|
+
alias: boolean;
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* The initialize `instructions` text: which tool sets exist, that they are
|
|
244
|
+
* all on by default, how to load fewer, and how preview then confirm works.
|
|
245
|
+
*/
|
|
246
|
+
export declare function toolsetInstructions(registry: ToolRegistry, surface: ToolSurface): string;
|