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,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport wrapper that keeps renamed MCP tools working on the stdio package
|
|
3
|
+
* without registering the old names with McpServer (so they never appear in
|
|
4
|
+
* tools/list).
|
|
5
|
+
*
|
|
6
|
+
* Incoming: a tools/call whose name is an alias is rewritten BEFORE McpServer
|
|
7
|
+
* sees it: params.name becomes the target tool and params.arguments become
|
|
8
|
+
* the translated arguments, so the SDK validates them against the canonical
|
|
9
|
+
* schema. The request id is remembered with the alias details.
|
|
10
|
+
*
|
|
11
|
+
* Handlers read that per-request info through getCallInfo(extra.requestId):
|
|
12
|
+
* the called name (for the X-Posterly-Tool telemetry header) and whether the
|
|
13
|
+
* alias may still confirm in one step. Nothing is stored in the arguments.
|
|
14
|
+
*
|
|
15
|
+
* Outgoing: the response for that id gets the rename note APPENDED as a final
|
|
16
|
+
* text item (content[0] is never touched) plus _meta["posterly/deprecation"],
|
|
17
|
+
* and the remembered entry is deleted. A notifications/cancelled for that id
|
|
18
|
+
* also deletes it (the SDK sends no response for a cancelled request).
|
|
19
|
+
*
|
|
20
|
+
* Legacy input schemas: when a legacyValidator is given, an alias call's
|
|
21
|
+
* arguments are first checked against the OLD tool's exact input schema, so a
|
|
22
|
+
* call the old tool rejected gets the same SDK validation error text (and a
|
|
23
|
+
* call it accepted gets the same parsed arguments, defaults included) before
|
|
24
|
+
* translation. The npm package passes the old zod schemas here.
|
|
25
|
+
*
|
|
26
|
+
* Tool sets: every tool stays registered (so a call to any tool always
|
|
27
|
+
* works); when a visible set is given, only the outgoing tools/list result is
|
|
28
|
+
* filtered to it.
|
|
29
|
+
*
|
|
30
|
+
* Only the public Transport interface is used; no SDK private fields.
|
|
31
|
+
*/
|
|
32
|
+
import type { Transport, TransportSendOptions } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
33
|
+
import type { JSONRPCMessage, MessageExtraInfo, RequestId } from '@modelcontextprotocol/sdk/types.js';
|
|
34
|
+
import { type ResolvedAlias, type ToolRegistry } from './tool-registry.js';
|
|
35
|
+
export interface AliasCallInfo {
|
|
36
|
+
/** The name the client called (the alias). */
|
|
37
|
+
calledName: string;
|
|
38
|
+
/** The canonical tool the call was routed to. */
|
|
39
|
+
target: string;
|
|
40
|
+
/** The alias may confirm with confirm:true alone until its sunset date. */
|
|
41
|
+
legacyConfirm: boolean;
|
|
42
|
+
alias: ResolvedAlias;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Checks an alias call's raw arguments against the old tool's input schema.
|
|
46
|
+
* Return null when the alias has no legacy schema (no check), the parsed
|
|
47
|
+
* arguments when they are valid, or the exact error text the old tool gave.
|
|
48
|
+
*/
|
|
49
|
+
export type LegacyAliasValidator = (aliasName: string, args: unknown) => {
|
|
50
|
+
ok: true;
|
|
51
|
+
args: Record<string, unknown>;
|
|
52
|
+
} | {
|
|
53
|
+
ok: false;
|
|
54
|
+
message: string;
|
|
55
|
+
} | null;
|
|
56
|
+
export declare class AliasTransport implements Transport {
|
|
57
|
+
private readonly inner;
|
|
58
|
+
private readonly registry;
|
|
59
|
+
private readonly visibleTools;
|
|
60
|
+
private readonly legacyValidator;
|
|
61
|
+
onclose?: () => void;
|
|
62
|
+
onerror?: (error: Error) => void;
|
|
63
|
+
onmessage?: <T extends JSONRPCMessage>(message: T, extra?: MessageExtraInfo) => void;
|
|
64
|
+
private readonly calls;
|
|
65
|
+
/** In-flight tools/list request ids, so only their responses are filtered. */
|
|
66
|
+
private readonly listRequests;
|
|
67
|
+
/**
|
|
68
|
+
* @param visibleTools names to list in tools/list, or null to list every
|
|
69
|
+
* registered tool unchanged (the default: every set).
|
|
70
|
+
*/
|
|
71
|
+
constructor(inner: Transport, registry: ToolRegistry, visibleTools?: Set<string> | null, legacyValidator?: LegacyAliasValidator | null);
|
|
72
|
+
get sessionId(): string | undefined;
|
|
73
|
+
setProtocolVersion(version: string): void;
|
|
74
|
+
start(): Promise<void>;
|
|
75
|
+
close(): Promise<void>;
|
|
76
|
+
/** Alias details for an in-flight tools/call, or undefined for a canonical call. */
|
|
77
|
+
getCallInfo(requestId: RequestId | undefined): AliasCallInfo | undefined;
|
|
78
|
+
send(message: JSONRPCMessage, options?: TransportSendOptions): Promise<void>;
|
|
79
|
+
private handleIncoming;
|
|
80
|
+
/** Answers an alias call directly with a plain isError result; send() appends the rename note. */
|
|
81
|
+
private respondWithError;
|
|
82
|
+
private filterToolsList;
|
|
83
|
+
private decorateResponse;
|
|
84
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport wrapper that keeps renamed MCP tools working on the stdio package
|
|
3
|
+
* without registering the old names with McpServer (so they never appear in
|
|
4
|
+
* tools/list).
|
|
5
|
+
*
|
|
6
|
+
* Incoming: a tools/call whose name is an alias is rewritten BEFORE McpServer
|
|
7
|
+
* sees it: params.name becomes the target tool and params.arguments become
|
|
8
|
+
* the translated arguments, so the SDK validates them against the canonical
|
|
9
|
+
* schema. The request id is remembered with the alias details.
|
|
10
|
+
*
|
|
11
|
+
* Handlers read that per-request info through getCallInfo(extra.requestId):
|
|
12
|
+
* the called name (for the X-Posterly-Tool telemetry header) and whether the
|
|
13
|
+
* alias may still confirm in one step. Nothing is stored in the arguments.
|
|
14
|
+
*
|
|
15
|
+
* Outgoing: the response for that id gets the rename note APPENDED as a final
|
|
16
|
+
* text item (content[0] is never touched) plus _meta["posterly/deprecation"],
|
|
17
|
+
* and the remembered entry is deleted. A notifications/cancelled for that id
|
|
18
|
+
* also deletes it (the SDK sends no response for a cancelled request).
|
|
19
|
+
*
|
|
20
|
+
* Legacy input schemas: when a legacyValidator is given, an alias call's
|
|
21
|
+
* arguments are first checked against the OLD tool's exact input schema, so a
|
|
22
|
+
* call the old tool rejected gets the same SDK validation error text (and a
|
|
23
|
+
* call it accepted gets the same parsed arguments, defaults included) before
|
|
24
|
+
* translation. The npm package passes the old zod schemas here.
|
|
25
|
+
*
|
|
26
|
+
* Tool sets: every tool stays registered (so a call to any tool always
|
|
27
|
+
* works); when a visible set is given, only the outgoing tools/list result is
|
|
28
|
+
* filtered to it.
|
|
29
|
+
*
|
|
30
|
+
* Only the public Transport interface is used; no SDK private fields.
|
|
31
|
+
*/
|
|
32
|
+
import { DEPRECATION_META_KEY, deprecationNote, resolveAlias, translateArgs, validateMode, } from './tool-registry.js';
|
|
33
|
+
function requestKey(id) {
|
|
34
|
+
return `${typeof id}:${String(id)}`;
|
|
35
|
+
}
|
|
36
|
+
function isObject(value) {
|
|
37
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
38
|
+
}
|
|
39
|
+
export class AliasTransport {
|
|
40
|
+
inner;
|
|
41
|
+
registry;
|
|
42
|
+
visibleTools;
|
|
43
|
+
legacyValidator;
|
|
44
|
+
onclose;
|
|
45
|
+
onerror;
|
|
46
|
+
onmessage;
|
|
47
|
+
calls = new Map();
|
|
48
|
+
/** In-flight tools/list request ids, so only their responses are filtered. */
|
|
49
|
+
listRequests = new Set();
|
|
50
|
+
/**
|
|
51
|
+
* @param visibleTools names to list in tools/list, or null to list every
|
|
52
|
+
* registered tool unchanged (the default: every set).
|
|
53
|
+
*/
|
|
54
|
+
constructor(inner, registry, visibleTools = null, legacyValidator = null) {
|
|
55
|
+
this.inner = inner;
|
|
56
|
+
this.registry = registry;
|
|
57
|
+
this.visibleTools = visibleTools;
|
|
58
|
+
this.legacyValidator = legacyValidator;
|
|
59
|
+
inner.onmessage = (message, extra) => this.handleIncoming(message, extra);
|
|
60
|
+
inner.onclose = () => this.onclose?.();
|
|
61
|
+
inner.onerror = (error) => this.onerror?.(error);
|
|
62
|
+
}
|
|
63
|
+
get sessionId() {
|
|
64
|
+
return this.inner.sessionId;
|
|
65
|
+
}
|
|
66
|
+
setProtocolVersion(version) {
|
|
67
|
+
this.inner.setProtocolVersion?.(version);
|
|
68
|
+
}
|
|
69
|
+
start() {
|
|
70
|
+
return this.inner.start();
|
|
71
|
+
}
|
|
72
|
+
close() {
|
|
73
|
+
this.calls.clear();
|
|
74
|
+
this.listRequests.clear();
|
|
75
|
+
return this.inner.close();
|
|
76
|
+
}
|
|
77
|
+
/** Alias details for an in-flight tools/call, or undefined for a canonical call. */
|
|
78
|
+
getCallInfo(requestId) {
|
|
79
|
+
if (requestId === undefined || requestId === null)
|
|
80
|
+
return undefined;
|
|
81
|
+
return this.calls.get(requestKey(requestId));
|
|
82
|
+
}
|
|
83
|
+
async send(message, options) {
|
|
84
|
+
const outgoing = this.filterToolsList(this.decorateResponse(message));
|
|
85
|
+
return this.inner.send(outgoing, options);
|
|
86
|
+
}
|
|
87
|
+
handleIncoming(message, extra) {
|
|
88
|
+
const request = message;
|
|
89
|
+
if (request.method === 'notifications/cancelled') {
|
|
90
|
+
const cancelled = isObject(request.params) ? request.params.requestId : undefined;
|
|
91
|
+
if (typeof cancelled === 'string' || typeof cancelled === 'number') {
|
|
92
|
+
this.calls.delete(requestKey(cancelled));
|
|
93
|
+
this.listRequests.delete(requestKey(cancelled));
|
|
94
|
+
}
|
|
95
|
+
this.onmessage?.(message, extra);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (request.method === 'tools/list' && this.visibleTools && request.id !== undefined && request.id !== null) {
|
|
99
|
+
this.listRequests.add(requestKey(request.id));
|
|
100
|
+
this.onmessage?.(message, extra);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (request.method !== 'tools/call' || request.id === undefined || request.id === null || !isObject(request.params)) {
|
|
104
|
+
this.onmessage?.(message, extra);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const name = request.params.name;
|
|
108
|
+
const alias = typeof name === 'string' ? resolveAlias(this.registry, name) : null;
|
|
109
|
+
if (!alias) {
|
|
110
|
+
this.onmessage?.(message, extra);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const info = {
|
|
114
|
+
calledName: alias.name,
|
|
115
|
+
target: alias.target,
|
|
116
|
+
legacyConfirm: alias.legacyConfirm === true,
|
|
117
|
+
alias,
|
|
118
|
+
};
|
|
119
|
+
this.calls.set(requestKey(request.id), info);
|
|
120
|
+
let callerArgs = request.params.arguments;
|
|
121
|
+
const legacy = this.legacyValidator ? this.legacyValidator(alias.name, callerArgs) : null;
|
|
122
|
+
if (legacy && !legacy.ok) {
|
|
123
|
+
this.respondWithError(request.id, legacy.message);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (legacy?.ok)
|
|
127
|
+
callerArgs = legacy.args;
|
|
128
|
+
// alias.mode pins a param-driven target to the old tool's mode.
|
|
129
|
+
const validated = validateMode(this.registry, alias.target, translateArgs(alias, callerArgs), alias.mode);
|
|
130
|
+
if (!validated.ok) {
|
|
131
|
+
// A plain error before any request, same as the hosted endpoint; the
|
|
132
|
+
// rename note is still appended by send() so the caller learns the new
|
|
133
|
+
// name either way.
|
|
134
|
+
this.respondWithError(request.id, validated.error);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const rewritten = {
|
|
138
|
+
...message,
|
|
139
|
+
params: { ...request.params, name: alias.target, arguments: validated.args },
|
|
140
|
+
};
|
|
141
|
+
this.onmessage?.(rewritten, extra);
|
|
142
|
+
}
|
|
143
|
+
/** Answers an alias call directly with a plain isError result; send() appends the rename note. */
|
|
144
|
+
respondWithError(id, text) {
|
|
145
|
+
void this.send({
|
|
146
|
+
jsonrpc: '2.0',
|
|
147
|
+
id,
|
|
148
|
+
result: { content: [{ type: 'text', text }], isError: true },
|
|
149
|
+
}).catch((error) => this.onerror?.(error instanceof Error ? error : new Error(String(error))));
|
|
150
|
+
}
|
|
151
|
+
filterToolsList(message) {
|
|
152
|
+
const response = message;
|
|
153
|
+
if (!this.visibleTools || response.method !== undefined || response.id === undefined || response.id === null)
|
|
154
|
+
return message;
|
|
155
|
+
const key = requestKey(response.id);
|
|
156
|
+
if (!this.listRequests.has(key))
|
|
157
|
+
return message;
|
|
158
|
+
this.listRequests.delete(key);
|
|
159
|
+
if (!isObject(response.result) || !Array.isArray(response.result.tools))
|
|
160
|
+
return message;
|
|
161
|
+
const visible = this.visibleTools;
|
|
162
|
+
const filtered = {
|
|
163
|
+
...message,
|
|
164
|
+
result: {
|
|
165
|
+
...response.result,
|
|
166
|
+
tools: response.result.tools.filter((tool) => typeof tool?.name === 'string' && visible.has(tool.name)),
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
return filtered;
|
|
170
|
+
}
|
|
171
|
+
decorateResponse(message) {
|
|
172
|
+
const response = message;
|
|
173
|
+
if (response.method !== undefined || response.id === undefined || response.id === null)
|
|
174
|
+
return message;
|
|
175
|
+
const key = requestKey(response.id);
|
|
176
|
+
const info = this.calls.get(key);
|
|
177
|
+
if (!info)
|
|
178
|
+
return message;
|
|
179
|
+
this.calls.delete(key);
|
|
180
|
+
if (!isObject(response.result) || !Array.isArray(response.result.content))
|
|
181
|
+
return message;
|
|
182
|
+
const note = deprecationNote(info.alias);
|
|
183
|
+
const decorated = {
|
|
184
|
+
...message,
|
|
185
|
+
result: {
|
|
186
|
+
...response.result,
|
|
187
|
+
content: [...response.result.content, { type: 'text', text: note.text }],
|
|
188
|
+
_meta: { ...(isObject(response.result._meta) ? response.result._meta : {}), [DEPRECATION_META_KEY]: note.meta },
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
return decorated;
|
|
192
|
+
}
|
|
193
|
+
}
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
export declare function setMcpClientNameResolver(resolver: () => string | undefined): void;
|
|
2
|
+
/**
|
|
3
|
+
* HTTP header values must be printable Latin-1 with no control characters;
|
|
4
|
+
* Node's fetch throws (not returns an error, THROWS) on a header value
|
|
5
|
+
* containing anything else, so an MCP client reporting a non-ASCII name
|
|
6
|
+
* (e.g. "通义灵码") or one with an en/em dash would otherwise break every
|
|
7
|
+
* single tool call. Strips anything outside printable ASCII rather than
|
|
8
|
+
* encoding it, since this is just a display label, not data that needs to
|
|
9
|
+
* round-trip losslessly.
|
|
10
|
+
*/
|
|
11
|
+
export declare function sanitizeClientHeaderValue(value: string): string;
|
|
1
12
|
export interface Account {
|
|
2
13
|
id: string;
|
|
3
14
|
platform: string;
|
|
@@ -320,6 +331,67 @@ export type AskSupportPayload = {
|
|
|
320
331
|
confirm_escalation?: boolean;
|
|
321
332
|
};
|
|
322
333
|
export type ApiKeyScope = 'accounts:read' | 'accounts:write' | 'posts:read' | 'posts:write' | 'media:write' | 'analytics:read' | 'billing:read' | 'billing:write';
|
|
334
|
+
export type WorkspaceMemberRole = 'publisher' | 'editor' | 'viewer';
|
|
335
|
+
export type WorkspaceMemberSummary = {
|
|
336
|
+
id: string;
|
|
337
|
+
user_id: string | null;
|
|
338
|
+
email: string | null;
|
|
339
|
+
name: string | null;
|
|
340
|
+
role: string | null;
|
|
341
|
+
status: string | null;
|
|
342
|
+
/** [] = every brand; missing = not visible to a brand-limited caller. */
|
|
343
|
+
brand_ids?: string[];
|
|
344
|
+
joined_at: string | null;
|
|
345
|
+
created_at: string | null;
|
|
346
|
+
};
|
|
347
|
+
export type WorkspaceInviteSummary = {
|
|
348
|
+
id: string;
|
|
349
|
+
email: string | null;
|
|
350
|
+
role: string | null;
|
|
351
|
+
status: string | null;
|
|
352
|
+
brand_ids?: string[];
|
|
353
|
+
expires_at: string | null;
|
|
354
|
+
created_at: string | null;
|
|
355
|
+
};
|
|
356
|
+
export type WorkspaceMembersResponse = {
|
|
357
|
+
workspace_id: string;
|
|
358
|
+
members: WorkspaceMemberSummary[];
|
|
359
|
+
invites: WorkspaceInviteSummary[];
|
|
360
|
+
usage: {
|
|
361
|
+
active_members: number;
|
|
362
|
+
pending_invites: number;
|
|
363
|
+
seats_used: number;
|
|
364
|
+
};
|
|
365
|
+
};
|
|
366
|
+
export type WorkspaceMemberPreview = {
|
|
367
|
+
action: string;
|
|
368
|
+
summary: string;
|
|
369
|
+
email: string | null;
|
|
370
|
+
role: string | null;
|
|
371
|
+
previous_role: string | null;
|
|
372
|
+
brand_ids: string[] | null;
|
|
373
|
+
previous_brand_ids: string[] | null;
|
|
374
|
+
sends_email: boolean;
|
|
375
|
+
counts_toward_seats: boolean;
|
|
376
|
+
seats_used: number | null;
|
|
377
|
+
seat_limit: number | null;
|
|
378
|
+
};
|
|
379
|
+
export type WorkspaceMemberChangeResponse = {
|
|
380
|
+
requires_confirmation?: boolean;
|
|
381
|
+
preview?: WorkspaceMemberPreview;
|
|
382
|
+
preview_id?: string;
|
|
383
|
+
preview_expires_at?: string;
|
|
384
|
+
status?: string;
|
|
385
|
+
message?: string;
|
|
386
|
+
invite?: WorkspaceInviteSummary;
|
|
387
|
+
member?: WorkspaceMemberSummary;
|
|
388
|
+
email_sent?: boolean;
|
|
389
|
+
owner_notified?: boolean;
|
|
390
|
+
};
|
|
391
|
+
export type WorkspaceMemberConfirmFields = {
|
|
392
|
+
confirm?: boolean;
|
|
393
|
+
preview_id?: string;
|
|
394
|
+
};
|
|
323
395
|
export type CreateApiKeyPayload = {
|
|
324
396
|
name?: string;
|
|
325
397
|
scopes?: ApiKeyScope[];
|
|
@@ -988,6 +1060,13 @@ export declare class PosterlyClient {
|
|
|
988
1060
|
constructor(apiKey?: string, baseUrl?: string);
|
|
989
1061
|
hasApiKey(): boolean;
|
|
990
1062
|
getBaseUrl(): string;
|
|
1063
|
+
/**
|
|
1064
|
+
* Runs `fn` with a fresh per-call tool name/id scoped via AsyncLocalStorage,
|
|
1065
|
+
* so any request() calls made inside it (directly or nested) are tagged
|
|
1066
|
+
* with X-Posterly-Tool / X-Posterly-Tool-Call. Called once per tool
|
|
1067
|
+
* invocation from index.ts's server.tool() handlers.
|
|
1068
|
+
*/
|
|
1069
|
+
runToolCall<T>(toolName: string, fn: () => Promise<T>): Promise<T>;
|
|
991
1070
|
probeWhoami(timeoutMs?: number): Promise<{
|
|
992
1071
|
ok: boolean;
|
|
993
1072
|
status?: number;
|
|
@@ -1005,6 +1084,24 @@ export declare class PosterlyClient {
|
|
|
1005
1084
|
deleteApiKey(keyId: string, data: {
|
|
1006
1085
|
confirm: true;
|
|
1007
1086
|
}): Promise<DeleteApiKeyResponse>;
|
|
1087
|
+
listWorkspaceMembers(workspaceId: string): Promise<WorkspaceMembersResponse>;
|
|
1088
|
+
inviteWorkspaceMember(workspaceId: string, data: {
|
|
1089
|
+
email: string;
|
|
1090
|
+
role: WorkspaceMemberRole;
|
|
1091
|
+
brand_ids?: string[];
|
|
1092
|
+
resend?: boolean;
|
|
1093
|
+
} & WorkspaceMemberConfirmFields): Promise<WorkspaceMemberChangeResponse>;
|
|
1094
|
+
updateWorkspaceMember(workspaceId: string, target: {
|
|
1095
|
+
member_id?: string;
|
|
1096
|
+
invite_id?: string;
|
|
1097
|
+
}, data: {
|
|
1098
|
+
role?: WorkspaceMemberRole;
|
|
1099
|
+
brand_ids?: string[];
|
|
1100
|
+
} & WorkspaceMemberConfirmFields): Promise<WorkspaceMemberChangeResponse>;
|
|
1101
|
+
removeWorkspaceMember(workspaceId: string, target: {
|
|
1102
|
+
member_id?: string;
|
|
1103
|
+
invite_id?: string;
|
|
1104
|
+
}, data: WorkspaceMemberConfirmFields): Promise<WorkspaceMemberChangeResponse>;
|
|
1008
1105
|
getSubscription(): Promise<SubscriptionSummaryResponse>;
|
|
1009
1106
|
getCredits(): Promise<CreditsSummaryResponse>;
|
|
1010
1107
|
cancelSubscription(data: CancelSubscriptionPayload): Promise<CancelSubscriptionResponse>;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
import { readFile } from 'fs/promises';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
/**
|
|
6
|
+
* Scopes the current tool name/call id to the async execution of a single
|
|
7
|
+
* tool call, so request() can tag its headers without a mutable "current
|
|
8
|
+
* tool" field on the shared PosterlyClient singleton (index.ts registers one
|
|
9
|
+
* client instance for every tool). AsyncLocalStorage keeps overlapping calls
|
|
10
|
+
* from clobbering each other's context if the stdio transport ever
|
|
11
|
+
* dispatches tool calls concurrently.
|
|
12
|
+
*/
|
|
13
|
+
const toolCallStorage = new AsyncLocalStorage();
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the connected MCP client's name (e.g. "claude-ai", "chatgpt"), set
|
|
16
|
+
* once by index.ts after the server is constructed. A resolver function
|
|
17
|
+
* rather than a static value because the SDK only learns the client's
|
|
18
|
+
* identity once the initialize handshake completes, which happens after this
|
|
19
|
+
* module is first loaded; reading through the resolver on every request
|
|
20
|
+
* always gets the current value instead of whatever was available at import
|
|
21
|
+
* time (undefined).
|
|
22
|
+
*/
|
|
23
|
+
let clientNameResolver = null;
|
|
24
|
+
export function setMcpClientNameResolver(resolver) {
|
|
25
|
+
clientNameResolver = resolver;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* HTTP header values must be printable Latin-1 with no control characters;
|
|
29
|
+
* Node's fetch throws (not returns an error, THROWS) on a header value
|
|
30
|
+
* containing anything else, so an MCP client reporting a non-ASCII name
|
|
31
|
+
* (e.g. "通义灵码") or one with an en/em dash would otherwise break every
|
|
32
|
+
* single tool call. Strips anything outside printable ASCII rather than
|
|
33
|
+
* encoding it, since this is just a display label, not data that needs to
|
|
34
|
+
* round-trip losslessly.
|
|
35
|
+
*/
|
|
36
|
+
export function sanitizeClientHeaderValue(value) {
|
|
37
|
+
return value.replace(/[^\x20-\x7e]/g, '').trim().slice(0, 200);
|
|
38
|
+
}
|
|
3
39
|
export class PosterlyClient {
|
|
4
40
|
baseUrl;
|
|
5
41
|
apiKey;
|
|
@@ -16,6 +52,15 @@ export class PosterlyClient {
|
|
|
16
52
|
getBaseUrl() {
|
|
17
53
|
return this.baseUrl;
|
|
18
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Runs `fn` with a fresh per-call tool name/id scoped via AsyncLocalStorage,
|
|
57
|
+
* so any request() calls made inside it (directly or nested) are tagged
|
|
58
|
+
* with X-Posterly-Tool / X-Posterly-Tool-Call. Called once per tool
|
|
59
|
+
* invocation from index.ts's server.tool() handlers.
|
|
60
|
+
*/
|
|
61
|
+
async runToolCall(toolName, fn) {
|
|
62
|
+
return toolCallStorage.run({ toolName, toolCallId: randomUUID() }, fn);
|
|
63
|
+
}
|
|
19
64
|
async probeWhoami(timeoutMs = 2500) {
|
|
20
65
|
const startedAt = Date.now();
|
|
21
66
|
if (!this.apiKey) {
|
|
@@ -78,6 +123,16 @@ export class PosterlyClient {
|
|
|
78
123
|
Authorization: `Bearer ${this.apiKey}`,
|
|
79
124
|
'X-Posterly-Source': 'mcp_stdio',
|
|
80
125
|
};
|
|
126
|
+
const toolCall = toolCallStorage.getStore();
|
|
127
|
+
if (toolCall) {
|
|
128
|
+
headers['X-Posterly-Tool'] = toolCall.toolName;
|
|
129
|
+
headers['X-Posterly-Tool-Call'] = toolCall.toolCallId;
|
|
130
|
+
}
|
|
131
|
+
const clientName = clientNameResolver?.();
|
|
132
|
+
const clientLabel = clientName ? sanitizeClientHeaderValue(clientName) : '';
|
|
133
|
+
if (clientLabel) {
|
|
134
|
+
headers['X-Posterly-Client'] = clientLabel;
|
|
135
|
+
}
|
|
81
136
|
const init = { method, headers };
|
|
82
137
|
if (body instanceof FormData) {
|
|
83
138
|
init.body = body;
|
|
@@ -123,6 +178,32 @@ export class PosterlyClient {
|
|
|
123
178
|
async deleteApiKey(keyId, data) {
|
|
124
179
|
return this.request('DELETE', `/api-keys/${encodeURIComponent(keyId)}`, data);
|
|
125
180
|
}
|
|
181
|
+
async listWorkspaceMembers(workspaceId) {
|
|
182
|
+
return this.request('GET', `/workspaces/${encodeURIComponent(workspaceId)}/members`);
|
|
183
|
+
}
|
|
184
|
+
async inviteWorkspaceMember(workspaceId, data) {
|
|
185
|
+
return this.request('POST', `/workspaces/${encodeURIComponent(workspaceId)}/members`, data);
|
|
186
|
+
}
|
|
187
|
+
async updateWorkspaceMember(workspaceId, target, data) {
|
|
188
|
+
const base = `/workspaces/${encodeURIComponent(workspaceId)}`;
|
|
189
|
+
const path = target.member_id
|
|
190
|
+
? `${base}/members/${encodeURIComponent(target.member_id)}`
|
|
191
|
+
: `${base}/invites/${encodeURIComponent(target.invite_id || '')}`;
|
|
192
|
+
return this.request('PATCH', path, data);
|
|
193
|
+
}
|
|
194
|
+
async removeWorkspaceMember(workspaceId, target, data) {
|
|
195
|
+
const base = `/workspaces/${encodeURIComponent(workspaceId)}`;
|
|
196
|
+
const path = target.member_id
|
|
197
|
+
? `${base}/members/${encodeURIComponent(target.member_id)}`
|
|
198
|
+
: `${base}/invites/${encodeURIComponent(target.invite_id || '')}`;
|
|
199
|
+
const params = new URLSearchParams();
|
|
200
|
+
if (data.confirm === true)
|
|
201
|
+
params.set('confirm', 'true');
|
|
202
|
+
if (data.preview_id)
|
|
203
|
+
params.set('preview_id', data.preview_id);
|
|
204
|
+
const qs = params.toString();
|
|
205
|
+
return this.request('DELETE', `${path}${qs ? `?${qs}` : ''}`);
|
|
206
|
+
}
|
|
126
207
|
async getSubscription() {
|
|
127
208
|
return this.request('GET', '/subscription');
|
|
128
209
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
export declare const PREVIEW_TOKEN_VERSION = "pv1";
|
|
22
|
+
export interface PreviewBinding {
|
|
23
|
+
userId: string;
|
|
24
|
+
tool: string;
|
|
25
|
+
action: string;
|
|
26
|
+
/** Arguments AFTER validation/normalisation (defaults applied, numbers coerced). */
|
|
27
|
+
args: unknown;
|
|
28
|
+
}
|
|
29
|
+
export type VerifyPreviewResult = {
|
|
30
|
+
ok: true;
|
|
31
|
+
exp: number;
|
|
32
|
+
nonce: string;
|
|
33
|
+
remainingSeconds: number;
|
|
34
|
+
} | {
|
|
35
|
+
ok: false;
|
|
36
|
+
reason: 'malformed' | 'expired' | 'mismatch';
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Canonical JSON of a call's arguments: `confirm` and `preview_id` removed
|
|
40
|
+
* (top level), undefined dropped, keys sorted recursively, arrays kept in
|
|
41
|
+
* order, no whitespace. Same rules as canonicalArgs in tool-registry.ts.
|
|
42
|
+
*/
|
|
43
|
+
export declare function canonicalJson(args: unknown): string;
|
|
44
|
+
/** Derives the MAC key from a server secret with a domain prefix, so the raw secret is never the key. */
|
|
45
|
+
export declare function derivePreviewKey(secret: string | Buffer, domain?: string): Buffer;
|
|
46
|
+
export declare function createPreviewToken(key: Buffer, binding: PreviewBinding, options: {
|
|
47
|
+
ttlSeconds: number;
|
|
48
|
+
nowMs?: number;
|
|
49
|
+
nonce?: string;
|
|
50
|
+
}): string;
|
|
51
|
+
export declare function parsePreviewToken(token: unknown): {
|
|
52
|
+
exp: number;
|
|
53
|
+
nonce: string;
|
|
54
|
+
mac: string;
|
|
55
|
+
} | null;
|
|
56
|
+
export declare function verifyPreviewToken(key: Buffer, token: unknown, binding: PreviewBinding, options?: {
|
|
57
|
+
nowMs?: number;
|
|
58
|
+
}): VerifyPreviewResult;
|
|
59
|
+
/** Stable fingerprint of a token for one-time-use bookkeeping (never store the raw token). */
|
|
60
|
+
export declare function previewTokenFingerprint(token: string): string;
|
|
61
|
+
/** Plain-language refusal for each verification failure. */
|
|
62
|
+
export declare function previewFailureMessage(reason: 'malformed' | 'expired' | 'mismatch' | 'used' | 'missing' | 'unavailable'): string;
|