@pitcher/cli-scripts 2.8.0 → 2.10.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 +56 -1
- package/dist/index.js +2 -0
- package/dist/src/commands/canvases.js +11 -8
- package/dist/src/commands/dsrLanding.d.ts +2 -0
- package/dist/src/commands/dsrLanding.js +18 -10
- package/dist/src/commands/emailTemplate.d.ts +1 -0
- package/dist/src/commands/emailTemplate.js +8 -4
- package/dist/src/commands/mcp.d.ts +2 -0
- package/dist/src/commands/mcp.js +18 -0
- package/dist/src/core/readInput.d.ts +2 -0
- package/dist/src/core/readInput.js +24 -0
- package/dist/src/mcp/bundled.d.ts +4 -0
- package/dist/src/mcp/bundled.js +25 -0
- package/dist/src/mcp/env.d.ts +7 -0
- package/dist/src/mcp/env.js +13 -0
- package/dist/src/mcp/prompts/dsrLanding.d.ts +2 -0
- package/dist/src/mcp/prompts/dsrLanding.js +40 -0
- package/dist/src/mcp/prompts/emailTemplate.d.ts +2 -0
- package/dist/src/mcp/prompts/emailTemplate.js +34 -0
- package/dist/src/mcp/prompts/registry.d.ts +2 -0
- package/dist/src/mcp/prompts/registry.js +4 -0
- package/dist/src/mcp/prompts/shared.d.ts +2 -0
- package/dist/src/mcp/prompts/shared.js +26 -0
- package/dist/src/mcp/prompts/types.d.ts +9 -0
- package/dist/src/mcp/prompts/types.js +1 -0
- package/dist/src/mcp/registry.d.ts +2 -0
- package/dist/src/mcp/registry.js +46 -0
- package/dist/src/mcp/resources/contentDesign.d.ts +3 -0
- package/dist/src/mcp/resources/contentDesign.js +62 -0
- package/dist/src/mcp/resources/registry.d.ts +2 -0
- package/dist/src/mcp/resources/registry.js +3 -0
- package/dist/src/mcp/resources/types.d.ts +8 -0
- package/dist/src/mcp/resources/types.js +1 -0
- package/dist/src/mcp/result.d.ts +14 -0
- package/dist/src/mcp/result.js +80 -0
- package/dist/src/mcp/serve.d.ts +4 -0
- package/dist/src/mcp/serve.js +18 -0
- package/dist/src/mcp/server.d.ts +6 -0
- package/dist/src/mcp/server.js +88 -0
- package/dist/src/mcp/tools/analytics.d.ts +2 -0
- package/dist/src/mcp/tools/analytics.js +20 -0
- package/dist/src/mcp/tools/announcements.d.ts +2 -0
- package/dist/src/mcp/tools/announcements.js +32 -0
- package/dist/src/mcp/tools/canvases.d.ts +2 -0
- package/dist/src/mcp/tools/canvases.js +97 -0
- package/dist/src/mcp/tools/devSync.d.ts +2 -0
- package/dist/src/mcp/tools/devSync.js +53 -0
- package/dist/src/mcp/tools/dsr.d.ts +2 -0
- package/dist/src/mcp/tools/dsr.js +47 -0
- package/dist/src/mcp/tools/dsrLanding.d.ts +2 -0
- package/dist/src/mcp/tools/dsrLanding.js +53 -0
- package/dist/src/mcp/tools/emailTemplate.d.ts +2 -0
- package/dist/src/mcp/tools/emailTemplate.js +51 -0
- package/dist/src/mcp/tools/files.d.ts +2 -0
- package/dist/src/mcp/tools/files.js +65 -0
- package/dist/src/mcp/tools/folders.d.ts +2 -0
- package/dist/src/mcp/tools/folders.js +56 -0
- package/dist/src/mcp/tools/groups.d.ts +2 -0
- package/dist/src/mcp/tools/groups.js +60 -0
- package/dist/src/mcp/tools/instances.d.ts +2 -0
- package/dist/src/mcp/tools/instances.js +82 -0
- package/dist/src/mcp/tools/invitations.d.ts +2 -0
- package/dist/src/mcp/tools/invitations.js +23 -0
- package/dist/src/mcp/tools/memberships.d.ts +2 -0
- package/dist/src/mcp/tools/memberships.js +47 -0
- package/dist/src/mcp/tools/metadataTemplate.d.ts +2 -0
- package/dist/src/mcp/tools/metadataTemplate.js +42 -0
- package/dist/src/mcp/tools/org.d.ts +2 -0
- package/dist/src/mcp/tools/org.js +12 -0
- package/dist/src/mcp/tools/publishApp.d.ts +2 -0
- package/dist/src/mcp/tools/publishApp.js +39 -0
- package/dist/src/mcp/tools/roles.d.ts +2 -0
- package/dist/src/mcp/tools/roles.js +15 -0
- package/dist/src/mcp/tools/search.d.ts +2 -0
- package/dist/src/mcp/tools/search.js +16 -0
- package/dist/src/mcp/tools/sharedLinks.d.ts +2 -0
- package/dist/src/mcp/tools/sharedLinks.js +27 -0
- package/dist/src/mcp/tools/tags.d.ts +2 -0
- package/dist/src/mcp/tools/tags.js +20 -0
- package/dist/src/mcp/tools/themes.d.ts +2 -0
- package/dist/src/mcp/tools/themes.js +28 -0
- package/dist/src/mcp/tools/types.d.ts +15 -0
- package/dist/src/mcp/tools/types.js +1 -0
- package/dist/src/mcp/tools/users.d.ts +2 -0
- package/dist/src/mcp/tools/users.js +31 -0
- package/package.json +9 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Envelope } from '../core/envelope.js';
|
|
2
|
+
export interface ToolTextContent {
|
|
3
|
+
type: 'text';
|
|
4
|
+
text: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CallToolResultShape {
|
|
7
|
+
content: ToolTextContent[];
|
|
8
|
+
structuredContent?: Record<string, unknown>;
|
|
9
|
+
isError: boolean;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
export declare const MAX_RESULT_BYTES = 900000;
|
|
13
|
+
export declare function toErrorResult(code: string, message: string): CallToolResultShape;
|
|
14
|
+
export declare function envelopeToToolResult(env: Envelope<unknown>, maxBytes?: number): CallToolResultShape;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// MCP hosts cap a single tool result (Claude Desktop: 1 MB). We stay well under it,
|
|
2
|
+
// leaving headroom for JSON-RPC framing, warning blocks, and multi-byte characters.
|
|
3
|
+
export const MAX_RESULT_BYTES = 900000;
|
|
4
|
+
// Cap any single variable-length string (a server error message, a warning) so one huge value
|
|
5
|
+
// can't blow the size budget regardless of which tier it lands in.
|
|
6
|
+
const MAX_TEXT_BYTES = 8000;
|
|
7
|
+
function clampText(s) {
|
|
8
|
+
return Buffer.byteLength(s, 'utf8') <= MAX_TEXT_BYTES ? s : `${s.slice(0, MAX_TEXT_BYTES)}… (truncated)`;
|
|
9
|
+
}
|
|
10
|
+
function isPlainObject(v) {
|
|
11
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
12
|
+
}
|
|
13
|
+
// Best-effort item count for the "too large" guidance, without assuming a payload shape.
|
|
14
|
+
function countItems(data) {
|
|
15
|
+
if (Array.isArray(data))
|
|
16
|
+
return data.length;
|
|
17
|
+
if (isPlainObject(data) && Array.isArray(data.results))
|
|
18
|
+
return data.results.length;
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
export function toErrorResult(code, message) {
|
|
22
|
+
return { content: [{ type: 'text', text: clampText(`${code}: ${message}`) }], isError: true };
|
|
23
|
+
}
|
|
24
|
+
export function envelopeToToolResult(env, maxBytes = MAX_RESULT_BYTES) {
|
|
25
|
+
if (!env.ok) {
|
|
26
|
+
return toErrorResult(env.error?.code ?? 'UNKNOWN', env.error?.message ?? 'unknown error');
|
|
27
|
+
}
|
|
28
|
+
const full = env.data === undefined ? 'null' : JSON.stringify(env.data);
|
|
29
|
+
const bytes = Buffer.byteLength(full, 'utf8');
|
|
30
|
+
// structuredContent must be an object; arrays/primitives ride in the text block only.
|
|
31
|
+
const obj = isPlainObject(env.data) ? env.data : undefined;
|
|
32
|
+
const withWarnings = (content) => {
|
|
33
|
+
for (const w of env.warnings ?? [])
|
|
34
|
+
content.push({ type: 'text', text: clampText(`warning: ${w}`) });
|
|
35
|
+
return content;
|
|
36
|
+
};
|
|
37
|
+
// Measure the ACTUAL serialized size of a candidate result — JSON re-escaping (a payload copied
|
|
38
|
+
// into content.text is escaped a second time), warning blocks, keys, and structure are all
|
|
39
|
+
// counted — rather than projecting from the raw data size. maxBytes keeps headroom under the
|
|
40
|
+
// host cap for the outer JSON-RPC framing around this result.
|
|
41
|
+
const fits = (candidate) => Buffer.byteLength(JSON.stringify(candidate), 'utf8') <= maxBytes;
|
|
42
|
+
// Tier 1: full payload with the default dual representation (text + structuredContent).
|
|
43
|
+
const dual = { content: withWarnings([{ type: 'text', text: full }]), isError: false };
|
|
44
|
+
if (obj)
|
|
45
|
+
dual.structuredContent = obj;
|
|
46
|
+
if (fits(dual))
|
|
47
|
+
return dual;
|
|
48
|
+
const kb = Math.round(bytes / 1024);
|
|
49
|
+
const count = countItems(env.data);
|
|
50
|
+
const countPhrase = count === undefined ? '' : ` (${count} item${count === 1 ? '' : 's'})`;
|
|
51
|
+
// Tier 2: the payload fits once but not twice — keep it in structuredContent, drop the text dup.
|
|
52
|
+
if (obj) {
|
|
53
|
+
const note = `Large result${countPhrase} (~${kb} KB) returned in structuredContent; not duplicated as text to stay under the host's tool-result size limit.`;
|
|
54
|
+
const structuredOnly = {
|
|
55
|
+
content: withWarnings([{ type: 'text', text: note }]),
|
|
56
|
+
structuredContent: obj,
|
|
57
|
+
isError: false,
|
|
58
|
+
};
|
|
59
|
+
if (fits(structuredOnly))
|
|
60
|
+
return structuredOnly;
|
|
61
|
+
}
|
|
62
|
+
// Tier 3: even a single copy exceeds the limit — return a summary + guidance instead of an
|
|
63
|
+
// unusable dump the host would reject outright.
|
|
64
|
+
const limitKb = Math.round(maxBytes / 1024);
|
|
65
|
+
const guidance = `Result too large to return${countPhrase}: ~${kb} KB exceeds the ~${limitKb} KB tool-result limit. ` +
|
|
66
|
+
`Narrow the request — add filters, fetch a specific item by id, or use a smaller page-size — then retry.`;
|
|
67
|
+
const summary = { truncated: true, sizeBytes: bytes, limitBytes: maxBytes };
|
|
68
|
+
if (count !== undefined)
|
|
69
|
+
summary.count = count;
|
|
70
|
+
const summaryResult = {
|
|
71
|
+
content: withWarnings([{ type: 'text', text: guidance }]),
|
|
72
|
+
structuredContent: summary,
|
|
73
|
+
isError: false,
|
|
74
|
+
};
|
|
75
|
+
if (fits(summaryResult))
|
|
76
|
+
return summaryResult;
|
|
77
|
+
// Last resort (e.g. many warnings): drop the warnings — guidance + summary are always tiny, so
|
|
78
|
+
// this is guaranteed size-compliant rather than returning a result the host would reject.
|
|
79
|
+
return { content: [{ type: 'text', text: guidance }], structuredContent: summary, isError: false };
|
|
80
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ToolDescriptor } from './tools/types.js';
|
|
2
|
+
import type { PromptDescriptor } from './prompts/types.js';
|
|
3
|
+
import type { ResourceDescriptor } from './resources/types.js';
|
|
4
|
+
export declare function runMcpServe(env?: NodeJS.ProcessEnv, tools?: ToolDescriptor[], prompts?: PromptDescriptor[], resources?: ResourceDescriptor[]): Promise<void>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
2
|
+
import { resolveMcpEnv } from './env.js';
|
|
3
|
+
import { buildServer } from './server.js';
|
|
4
|
+
import { allTools } from './registry.js';
|
|
5
|
+
import { allPrompts } from './prompts/registry.js';
|
|
6
|
+
import { allResources } from './resources/registry.js';
|
|
7
|
+
// Resolves auth/env, builds the server, connects stdio. Never writes to stdout except protocol frames.
|
|
8
|
+
// `tools` defaults to the full set (the CLI `p mcp`); the .mcpb bundle passes a curated subset.
|
|
9
|
+
// Prompts/resources default to the full set for every frontend (no subsetting).
|
|
10
|
+
export async function runMcpServe(env = process.env, tools = allTools, prompts = allPrompts, resources = allResources) {
|
|
11
|
+
const mcpEnv = resolveMcpEnv(env);
|
|
12
|
+
const server = buildServer(mcpEnv, tools, prompts, resources);
|
|
13
|
+
const transport = new StdioServerTransport();
|
|
14
|
+
await server.connect(transport);
|
|
15
|
+
const target = mcpEnv.url ?? `https://${mcpEnv.org}.my.pitcher.com`;
|
|
16
|
+
console.error(`[pitcher-mcp] serving ${tools.length} tools, ${prompts.length} prompts, ${resources.length} resources ` +
|
|
17
|
+
`against ${target}${mcpEnv.prod ? ' (prod: destructive-hints on)' : ''}`);
|
|
18
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { McpEnv } from './env.js';
|
|
3
|
+
import type { ToolDescriptor } from './tools/types.js';
|
|
4
|
+
import type { PromptDescriptor } from './prompts/types.js';
|
|
5
|
+
import type { ResourceDescriptor } from './resources/types.js';
|
|
6
|
+
export declare function buildServer(env: McpEnv, tools?: ToolDescriptor[], prompts?: PromptDescriptor[], resources?: ResourceDescriptor[]): McpServer;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { allTools } from './registry.js';
|
|
6
|
+
import { allPrompts } from './prompts/registry.js';
|
|
7
|
+
import { allResources } from './resources/registry.js';
|
|
8
|
+
import { envelopeToToolResult, toErrorResult } from './result.js';
|
|
9
|
+
import { isCliError } from '../core/errors.js';
|
|
10
|
+
const version = (() => {
|
|
11
|
+
// The .mcpb bundle inlines this at build time (esbuild define) because the bundled single
|
|
12
|
+
// file has no package.json to walk to. Undefined at CLI runtime → the readFileSync path below.
|
|
13
|
+
const injected = process.env.PITCHER_CLI_VERSION;
|
|
14
|
+
if (injected)
|
|
15
|
+
return injected;
|
|
16
|
+
try {
|
|
17
|
+
const dir = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
return JSON.parse(readFileSync(join(dir, '..', '..', '..', 'package.json'), 'utf8')).version ?? '0.0.0';
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// cosmetic server version; fall back if package.json is unreadable
|
|
22
|
+
return '0.0.0';
|
|
23
|
+
}
|
|
24
|
+
})();
|
|
25
|
+
// Connector branding shown by MCP hosts (Claude Desktop, Cursor). Self-contained
|
|
26
|
+
// data URI so it needs no network/asset resolution: a navy (#29335C) tile with a
|
|
27
|
+
// white "P" and the Pitcher-red (#CC0000) accent from the wordmark.
|
|
28
|
+
const icon = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiI+PHJlY3Qgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHJ4PSIxMTYiIGZpbGw9IiMyOTMzNUMiLz48Y2lyY2xlIGN4PSIzNzIiIGN5PSIxNTAiIHI9IjMwIiBmaWxsPSIjQ0MwMDAwIi8+PHBhdGggZmlsbD0iI2ZmZmZmZiIgZD0iTTE1MCAxMTZoOTZhOTIgOTIgMCAwIDEgMCAxODRoLTUwdjk2aC00NnoiLz48cGF0aCBmaWxsPSIjMjkzMzVDIiBkPSJNMTk2IDE2MHY5Nmg1MGE0OCA0OCAwIDAgMCAwLTk2eiIvPjwvc3ZnPg==';
|
|
29
|
+
// Compact, sanitized error string (no raw stack) — shared by the prompt/resource callbacks.
|
|
30
|
+
function errorText(e) {
|
|
31
|
+
if (isCliError(e))
|
|
32
|
+
return `${e.code}: ${e.message}`;
|
|
33
|
+
return e instanceof Error ? e.message : String(e);
|
|
34
|
+
}
|
|
35
|
+
export function buildServer(env, tools = allTools, prompts = allPrompts, resources = allResources) {
|
|
36
|
+
const server = new McpServer({
|
|
37
|
+
name: 'pitcher',
|
|
38
|
+
version,
|
|
39
|
+
title: 'Pitcher',
|
|
40
|
+
description: 'Search, manage, and share Pitcher content, canvases, files, DSRs, and instances.',
|
|
41
|
+
websiteUrl: 'https://www.pitcher.com',
|
|
42
|
+
icons: [{ src: icon, mimeType: 'image/svg+xml', sizes: ['any'] }],
|
|
43
|
+
});
|
|
44
|
+
for (const tool of tools) {
|
|
45
|
+
const destructive = tool.alwaysDestructive === true || (tool.write === true && env.prod);
|
|
46
|
+
server.registerTool(tool.name, {
|
|
47
|
+
title: tool.title,
|
|
48
|
+
description: tool.description,
|
|
49
|
+
inputSchema: tool.inputShape,
|
|
50
|
+
annotations: { readOnlyHint: tool.readOnly, destructiveHint: destructive },
|
|
51
|
+
}, async (args) => {
|
|
52
|
+
try {
|
|
53
|
+
// spread args FIRST so injected auth/env always wins — never reorder to { apiKey, ...args }
|
|
54
|
+
const envelope = await tool.run({ ...args, apiKey: env.apiKey, org: env.org, url: env.url });
|
|
55
|
+
return envelopeToToolResult(envelope);
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
const code = isCliError(e) ? e.code : 'UNKNOWN';
|
|
59
|
+
const message = isCliError(e) ? e.message : e instanceof Error ? e.message : String(e);
|
|
60
|
+
return toErrorResult(code, message);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
for (const prompt of prompts) {
|
|
65
|
+
server.registerPrompt(prompt.name, { title: prompt.title, description: prompt.description, argsSchema: prompt.argsSchema }, (args) => {
|
|
66
|
+
try {
|
|
67
|
+
return { messages: prompt.build(args) };
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
// build() is pure string assembly, but guard defensively: prompts have no result-level
|
|
71
|
+
// error channel (unlike tools), so surface failures by throwing — the SDK converts a
|
|
72
|
+
// thrown error into a JSON-RPC error. Re-throw a sanitized message (no raw stack).
|
|
73
|
+
throw new Error(`prompt "${prompt.name}" failed: ${errorText(e)}`);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
for (const resource of resources) {
|
|
78
|
+
server.registerResource(resource.name, resource.uri, { title: resource.title, description: resource.description, mimeType: resource.mimeType }, (uri) => {
|
|
79
|
+
try {
|
|
80
|
+
return { contents: [{ uri: uri.href, mimeType: resource.mimeType, text: resource.read() }] };
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
throw new Error(`resource "${resource.name}" failed: ${errorText(e)}`);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return server;
|
|
88
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { runAnalyticsOrg, runAnalyticsInstance } from '../../commands/analytics.js';
|
|
3
|
+
export const analyticsTools = [
|
|
4
|
+
{
|
|
5
|
+
name: 'pitcher_analytics_org',
|
|
6
|
+
title: 'Org analytics rollups',
|
|
7
|
+
description: 'Read organization-wide analytics rollup totals. Requires an org-admin API key.',
|
|
8
|
+
inputShape: {},
|
|
9
|
+
run: runAnalyticsOrg,
|
|
10
|
+
readOnly: true,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'pitcher_analytics_instance',
|
|
14
|
+
title: 'Instance analytics rollups',
|
|
15
|
+
description: 'Read analytics rollup totals for a single instance. Requires an instance-admin or org-admin API key.',
|
|
16
|
+
inputShape: { instanceId: z.string().describe('Instance ID (required)') },
|
|
17
|
+
run: runAnalyticsInstance,
|
|
18
|
+
readOnly: true,
|
|
19
|
+
},
|
|
20
|
+
];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { runAnnouncementsCreate, runAnnouncementsList } from '../../commands/announcements.js';
|
|
3
|
+
export const announcementsTools = [
|
|
4
|
+
{
|
|
5
|
+
name: 'pitcher_announcements_create',
|
|
6
|
+
title: 'Create an announcement',
|
|
7
|
+
description: 'Create a wa-announcements appsDB record (title/body, optional media, CTA link, scheduling, group targeting). ' +
|
|
8
|
+
'Requires either fromFile (a JSON object with the announcement data) or both title and body. Additive.',
|
|
9
|
+
inputShape: {
|
|
10
|
+
instanceId: z.string().describe('Instance ID (required)'),
|
|
11
|
+
fromFile: z.string().optional().describe('Path to a JSON file with the announcement data object'),
|
|
12
|
+
title: z.string().optional().describe('Announcement title (overrides a matching fromFile key)'),
|
|
13
|
+
body: z.string().optional().describe('Announcement body HTML (overrides a matching fromFile key)'),
|
|
14
|
+
priority: z.string().optional().describe("Priority, e.g. 'normal' or 'important' (overrides a matching fromFile key)"),
|
|
15
|
+
},
|
|
16
|
+
run: runAnnouncementsCreate,
|
|
17
|
+
readOnly: false,
|
|
18
|
+
write: true,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'pitcher_announcements_list',
|
|
22
|
+
title: 'List announcements',
|
|
23
|
+
description: 'List wa-announcements appsDB records for an instance.',
|
|
24
|
+
inputShape: {
|
|
25
|
+
instanceId: z.string().describe('Instance ID (required)'),
|
|
26
|
+
limit: z.number().int().optional(),
|
|
27
|
+
offset: z.number().int().optional(),
|
|
28
|
+
},
|
|
29
|
+
run: runAnnouncementsList,
|
|
30
|
+
readOnly: true,
|
|
31
|
+
},
|
|
32
|
+
];
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { runCanvasesList, runCanvasesGet } from '../../commands/canvases.js';
|
|
3
|
+
// The five real canvas types, derived from the (is_section, is_template, is_block) flags.
|
|
4
|
+
// One stable enum the model can filter and reason on — independent of any per-instance display
|
|
5
|
+
// label an admin may configure for these in the UI.
|
|
6
|
+
const CANVAS_TYPES = ['presentation', 'template', 'section', 'section_template', 'block'];
|
|
7
|
+
function canvasType(c) {
|
|
8
|
+
if (c.is_block === true)
|
|
9
|
+
return 'block';
|
|
10
|
+
if (c.is_section === true && c.is_template === true)
|
|
11
|
+
return 'section_template';
|
|
12
|
+
if (c.is_template === true)
|
|
13
|
+
return 'template';
|
|
14
|
+
if (c.is_section === true)
|
|
15
|
+
return 'section';
|
|
16
|
+
return 'presentation';
|
|
17
|
+
}
|
|
18
|
+
// type → the explicit flag filter the Django canvases endpoint understands (false is honored server-side).
|
|
19
|
+
// Non-block types pin isBlock:false so the canonical filter is unambiguous even if a caller also
|
|
20
|
+
// passed a conflicting legacy flag.
|
|
21
|
+
const TYPE_FLAGS = {
|
|
22
|
+
presentation: { isSection: false, isTemplate: false, isBlock: false },
|
|
23
|
+
template: { isSection: false, isTemplate: true, isBlock: false },
|
|
24
|
+
section: { isSection: true, isTemplate: false, isBlock: false },
|
|
25
|
+
section_template: { isSection: true, isTemplate: true, isBlock: false },
|
|
26
|
+
block: { isBlock: true },
|
|
27
|
+
};
|
|
28
|
+
// Tag a canvas object with its derived `type`; leave non-objects as-is. Spread `...c` FIRST so
|
|
29
|
+
// the derived value always wins — if the API payload ever carries its own `type`, our canonical
|
|
30
|
+
// enum must not be clobbered by it (JSON key order is cosmetic; correctness needs derived-last).
|
|
31
|
+
function withType(c) {
|
|
32
|
+
if (c && typeof c === 'object' && !Array.isArray(c))
|
|
33
|
+
return { ...c, type: canvasType(c) };
|
|
34
|
+
return c;
|
|
35
|
+
}
|
|
36
|
+
// Wrapper: maps the `type` filter to explicit flags, then tags each returned canvas with its derived
|
|
37
|
+
// `type`. Non-ok envelopes pass through untouched. `wraps` lets the coverage guard match by identity.
|
|
38
|
+
async function runCanvasesListTyped(opts, deps) {
|
|
39
|
+
// Pull the legacy flags out of rest so a canonical `type` can override them entirely — otherwise
|
|
40
|
+
// e.g. `type: 'presentation'` + `isBlock: true` would still send is_block=true and return blocks.
|
|
41
|
+
const { type, isSection, isTemplate, isBlock, ...rest } = opts;
|
|
42
|
+
const flags = typeof type === 'string' && type in TYPE_FLAGS ? TYPE_FLAGS[type] : { isSection, isTemplate, isBlock };
|
|
43
|
+
const env = await runCanvasesList({ ...rest, ...flags }, deps);
|
|
44
|
+
if (!env.ok)
|
|
45
|
+
return env;
|
|
46
|
+
const data = env.data;
|
|
47
|
+
if (Array.isArray(data?.results))
|
|
48
|
+
return { ...env, data: { ...data, results: data.results.map(withType) } };
|
|
49
|
+
return env;
|
|
50
|
+
}
|
|
51
|
+
async function runCanvasesGetTyped(opts, deps) {
|
|
52
|
+
const env = await runCanvasesGet(opts, deps);
|
|
53
|
+
if (!env.ok)
|
|
54
|
+
return env;
|
|
55
|
+
return { ...env, data: withType(env.data) };
|
|
56
|
+
}
|
|
57
|
+
export const canvasesTools = [
|
|
58
|
+
{
|
|
59
|
+
name: 'pitcher_canvases_list',
|
|
60
|
+
title: 'List canvases',
|
|
61
|
+
description: 'List canvases in an instance and discover their IDs. Each canvas is one of five types (returned as `type`): ' +
|
|
62
|
+
"presentation (a rep's finished deck), template (a reusable starter deck), section (a single slide / product " +
|
|
63
|
+
'module), section_template (a reusable section), or block (a reusable component group). ' +
|
|
64
|
+
'Filter to one with `type` (preferred) — e.g. type="presentation" for finished decks, type="template" for templates. ' +
|
|
65
|
+
'With no filter, presentations + templates + sections come back together and blocks are hidden. ' +
|
|
66
|
+
'Some instances rename these types in their UI (e.g. section → "Slide"); pitcher_instances_info returns a ' +
|
|
67
|
+
'`terminology` map — use those labels when addressing the user, but keep filtering on the canonical `type`.',
|
|
68
|
+
inputShape: {
|
|
69
|
+
instanceId: z.string().describe('Instance ID (required)'),
|
|
70
|
+
type: z
|
|
71
|
+
.enum(CANVAS_TYPES)
|
|
72
|
+
.optional()
|
|
73
|
+
.describe('Filter to one canvas type (preferred over the is* flags): presentation | template | section | section_template | block'),
|
|
74
|
+
isTemplate: z.boolean().optional().describe('Legacy flag filter; prefer `type`'),
|
|
75
|
+
isSection: z.boolean().optional().describe('Legacy flag filter; prefer `type`'),
|
|
76
|
+
isBlock: z.boolean().optional().describe('Legacy flag filter; prefer `type`. Blocks are hidden unless set'),
|
|
77
|
+
tags: z.string().optional().describe('Comma-separated tags'),
|
|
78
|
+
idIn: z.string().optional().describe('Comma-separated canvas IDs'),
|
|
79
|
+
accessType: z.enum(['public', 'restricted', 'personal']).optional(),
|
|
80
|
+
page: z.number().int().optional(),
|
|
81
|
+
pageSize: z.number().int().optional().describe('Page size (max 500)'),
|
|
82
|
+
},
|
|
83
|
+
run: runCanvasesListTyped,
|
|
84
|
+
wraps: runCanvasesList,
|
|
85
|
+
readOnly: true,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'pitcher_canvases_get',
|
|
89
|
+
title: 'Get a canvas',
|
|
90
|
+
description: 'Retrieve a single canvas by its ID. The returned object includes a derived `type` ' +
|
|
91
|
+
'(presentation, template, section, section_template, or block).',
|
|
92
|
+
inputShape: { id: z.string().describe('Canvas ID') },
|
|
93
|
+
run: runCanvasesGetTyped,
|
|
94
|
+
wraps: runCanvasesGet,
|
|
95
|
+
readOnly: true,
|
|
96
|
+
},
|
|
97
|
+
];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { runDevSync, runRevert } from '../../commands/devSync.js';
|
|
3
|
+
import { fail } from '../../core/envelope.js';
|
|
4
|
+
import { createCliError } from '../../core/errors.js';
|
|
5
|
+
// devSync.ts is legacy-shaped: `DevSyncOptions.fileId` is a required (non-optional)
|
|
6
|
+
// string, unlike every other command's `CommonOpts & { field?: ... }` + in-run
|
|
7
|
+
// VALIDATION-throw pattern. That mismatch means `runDevSync`/`runRevert` cannot be
|
|
8
|
+
// assigned directly to `ToolDescriptor.run` (typed `Record<string, unknown> &
|
|
9
|
+
// InjectedAuth`) — TS reports `fileId` as missing. These thin wrappers narrow the
|
|
10
|
+
// runtime value (the MCP zod schema already guarantees it's a string) and forward
|
|
11
|
+
// a properly-typed object literal, instead of casting the whole `opts` blob.
|
|
12
|
+
function requireFileId(opts) {
|
|
13
|
+
return typeof opts.fileId === 'string' ? opts.fileId : undefined;
|
|
14
|
+
}
|
|
15
|
+
export const devSyncTools = [
|
|
16
|
+
{
|
|
17
|
+
name: 'pitcher_dev_sync',
|
|
18
|
+
title: 'Dev-sync an app (one-shot)',
|
|
19
|
+
description: 'One-time sync of a local app build (a folder with app.json, on the MCP server host — cwd or ./dist) ' +
|
|
20
|
+
'to S3 as a live dev override for an installed app file, for testing without publishing. ' +
|
|
21
|
+
'This is a single sync, not the continuous watcher — call again to re-sync after further changes. ' +
|
|
22
|
+
'Reversible with pitcher_dev_sync_revert.',
|
|
23
|
+
inputShape: {
|
|
24
|
+
fileId: z.string().describe('File ID of the installed app (pitcher_file.id) to override'),
|
|
25
|
+
},
|
|
26
|
+
run: (opts, deps) => {
|
|
27
|
+
const fileId = requireFileId(opts);
|
|
28
|
+
if (!fileId)
|
|
29
|
+
return Promise.resolve(fail(createCliError('VALIDATION', 'fileId is required')));
|
|
30
|
+
return runDevSync({ fileId, apiKey: opts.apiKey, org: opts.org }, deps);
|
|
31
|
+
},
|
|
32
|
+
wraps: runDevSync,
|
|
33
|
+
readOnly: false,
|
|
34
|
+
write: true,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'pitcher_dev_sync_revert',
|
|
38
|
+
title: 'Revert a dev-sync override',
|
|
39
|
+
description: 'Revert a dev-sync override on an installed app file, restoring its original published version.',
|
|
40
|
+
inputShape: {
|
|
41
|
+
fileId: z.string().describe('File ID of the installed app (pitcher_file.id) to revert'),
|
|
42
|
+
},
|
|
43
|
+
run: (opts, deps) => {
|
|
44
|
+
const fileId = requireFileId(opts);
|
|
45
|
+
if (!fileId)
|
|
46
|
+
return Promise.resolve(fail(createCliError('VALIDATION', 'fileId is required')));
|
|
47
|
+
return runRevert({ fileId, apiKey: opts.apiKey, org: opts.org }, deps);
|
|
48
|
+
},
|
|
49
|
+
wraps: runRevert,
|
|
50
|
+
readOnly: false,
|
|
51
|
+
write: true,
|
|
52
|
+
},
|
|
53
|
+
];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { runDsrCreate, runDsrBindLanding } from '../../commands/dsr.js';
|
|
3
|
+
const AUTH_STRATEGIES = [
|
|
4
|
+
'none', 'any_email', 'specified_emails', 'specified_domains', 'verified_emails', 'verified_domains',
|
|
5
|
+
];
|
|
6
|
+
export const dsrTools = [
|
|
7
|
+
{
|
|
8
|
+
name: 'pitcher_dsr_create',
|
|
9
|
+
title: 'Create and share a DSR',
|
|
10
|
+
description: 'Create a Digital Sales Room (a canvas share link) and get a clickable link back. Source is a canvas (canvasId) OR files (shareboxFileIds) - exactly one. Optional email/domain gating and a bound landing-page template. Never hard-fails once a link exists: a failed landing bind returns the link with a warning. Additive.',
|
|
11
|
+
inputShape: {
|
|
12
|
+
instanceId: z.string().optional().describe('Instance ID (required for the shareboxFileIds source)'),
|
|
13
|
+
canvasId: z.string().optional().describe('Canvas to share (exactly one of canvasId / shareboxFileIds)'),
|
|
14
|
+
shareboxFileIds: z.array(z.string()).optional().describe('File IDs to build a share from (exactly one of canvasId / shareboxFileIds)'),
|
|
15
|
+
canvasName: z.string().optional().describe('Name for the ShareBox-built canvas'),
|
|
16
|
+
accountId: z.string().optional(),
|
|
17
|
+
templateId: z.string().optional(),
|
|
18
|
+
authStrategy: z.enum(AUTH_STRATEGIES).optional().describe('Access gating (server default any_email)'),
|
|
19
|
+
allowedEmails: z.array(z.string()).optional().describe('Required for specified_emails / verified_emails'),
|
|
20
|
+
allowedDomains: z.array(z.string()).optional().describe('Required for specified_domains / verified_domains'),
|
|
21
|
+
validUntil: z.string().optional().describe('Expiry, ISO datetime'),
|
|
22
|
+
allowForward: z.boolean().optional(),
|
|
23
|
+
allowDownload: z.boolean().optional(),
|
|
24
|
+
tags: z.array(z.string()).optional(),
|
|
25
|
+
landingTemplateId: z.string().optional().describe('Bind a landing-page template (discover via pitcher_dsr_landing_list)'),
|
|
26
|
+
landingContext: z.string().optional().describe('Landing-page merge fields, a JSON object as a string'),
|
|
27
|
+
configFile: z.string().optional().describe('Path on the MCP server host to a full {source,security,landing} spec JSON; mutually exclusive with the individual flags'),
|
|
28
|
+
},
|
|
29
|
+
run: runDsrCreate,
|
|
30
|
+
readOnly: false,
|
|
31
|
+
write: true,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'pitcher_dsr_bind_landing',
|
|
35
|
+
title: 'Bind a DSR landing page',
|
|
36
|
+
description: 'Attach, re-brand, or unbind a landing-page template on an existing DSR share. Use landingTemplateId "none" to unbind. Requires an editor-level key (hard-fails AUTH otherwise).',
|
|
37
|
+
inputShape: {
|
|
38
|
+
id: z.string().describe('Shared-link ID'),
|
|
39
|
+
landingTemplateId: z.string().describe('Landing template ID, or "none" to unbind'),
|
|
40
|
+
landingContext: z.string().optional().describe('Landing-page merge fields, a JSON object as a string'),
|
|
41
|
+
instanceId: z.string().optional(),
|
|
42
|
+
},
|
|
43
|
+
run: runDsrBindLanding,
|
|
44
|
+
readOnly: false,
|
|
45
|
+
write: true,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { runDsrLandingCreate, runDsrLandingSetDefault, runDsrLandingList } from '../../commands/dsrLanding.js';
|
|
3
|
+
export const dsrLandingTools = [
|
|
4
|
+
{
|
|
5
|
+
name: 'pitcher_dsr_landing_create',
|
|
6
|
+
title: 'Create a DSR landing template',
|
|
7
|
+
description: 'Create a DSR (Digital Sales Room) landing-page template from HTML (must contain the data-pitcher-email-gate ' +
|
|
8
|
+
'marker) and optional CSS/merge-schema/thumbnail. Optionally set it as the instance default in the same call. Additive.',
|
|
9
|
+
inputShape: {
|
|
10
|
+
instanceId: z.string().describe('Instance ID (required)'),
|
|
11
|
+
name: z.string().describe('Template name (required)'),
|
|
12
|
+
html: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe('Inline landing HTML (must contain data-pitcher-email-gate). Use this OR htmlFile — prefer this in hosts without a shared filesystem, e.g. Claude Desktop.'),
|
|
16
|
+
htmlFile: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('Path to the landing HTML (must contain data-pitcher-email-gate). Use this OR html.'),
|
|
20
|
+
css: z.string().optional().describe('Inline CSS for the template. Use this OR cssFile.'),
|
|
21
|
+
cssFile: z.string().optional().describe('Path to a CSS file for the template. Use this OR css.'),
|
|
22
|
+
mergeSchema: z.string().optional().describe('Merge schema JSON array as a string'),
|
|
23
|
+
thumbnailUrl: z.string().optional().describe('Thumbnail image URL'),
|
|
24
|
+
language: z.string().optional().describe('Language code (max 10 chars)'),
|
|
25
|
+
default: z.boolean().optional().describe('Also set this template as the instance-scope default after creating it'),
|
|
26
|
+
},
|
|
27
|
+
run: runDsrLandingCreate,
|
|
28
|
+
readOnly: false,
|
|
29
|
+
write: true,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'pitcher_dsr_landing_set_default',
|
|
33
|
+
title: 'Set default DSR landing template',
|
|
34
|
+
description: 'Set an existing DSR landing-page template as the instance-scope default.',
|
|
35
|
+
inputShape: {
|
|
36
|
+
instanceId: z.string().describe('Instance ID (required)'),
|
|
37
|
+
id: z.string().describe('Template ID (required)'),
|
|
38
|
+
},
|
|
39
|
+
run: runDsrLandingSetDefault,
|
|
40
|
+
readOnly: false,
|
|
41
|
+
write: true,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'pitcher_dsr_landing_list',
|
|
45
|
+
title: 'List DSR landing templates',
|
|
46
|
+
description: 'List DSR (Digital Sales Room) landing-page templates in an instance.',
|
|
47
|
+
inputShape: {
|
|
48
|
+
instanceId: z.string().describe('Instance ID (required)'),
|
|
49
|
+
},
|
|
50
|
+
run: runDsrLandingList,
|
|
51
|
+
readOnly: true,
|
|
52
|
+
},
|
|
53
|
+
];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { runEmailTemplateCreate, runEmailTemplateList, runEmailTemplateGet } from '../../commands/emailTemplate.js';
|
|
3
|
+
export const emailTemplateTools = [
|
|
4
|
+
{
|
|
5
|
+
name: 'pitcher_email_template_create',
|
|
6
|
+
title: 'Create an email template',
|
|
7
|
+
description: 'Create an HTML email template for the emails app (Rep Triggered Emails). Additive.',
|
|
8
|
+
inputShape: {
|
|
9
|
+
instanceId: z.string().describe('Instance ID (required)'),
|
|
10
|
+
name: z.string().describe('Template name (required)'),
|
|
11
|
+
bodyHtml: z
|
|
12
|
+
.string()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe('Inline HTML body (inline CSS). Use this OR bodyFile — prefer this in hosts without a shared filesystem, e.g. Claude Desktop.'),
|
|
15
|
+
bodyFile: z.string().optional().describe('Path to an HTML file (inline CSS) used as the template body'),
|
|
16
|
+
subject: z.string().optional().describe('Email subject (supports {{tokens}})'),
|
|
17
|
+
tags: z.string().optional().describe('Tags JSON object as a string'),
|
|
18
|
+
metadata: z.string().optional().describe('Metadata JSON object as a string'),
|
|
19
|
+
},
|
|
20
|
+
run: runEmailTemplateCreate,
|
|
21
|
+
readOnly: false,
|
|
22
|
+
write: true,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'pitcher_email_template_list',
|
|
26
|
+
title: 'List email templates',
|
|
27
|
+
description: 'List HTML email templates for the emails app in an instance. Filter by name, status, category, or target.',
|
|
28
|
+
inputShape: {
|
|
29
|
+
instanceId: z.string().describe('Instance ID (required)'),
|
|
30
|
+
name: z.string().optional().describe('Case-insensitive substring filter, matched against name OR subject'),
|
|
31
|
+
status: z.string().optional().describe('Status filter'),
|
|
32
|
+
category: z.string().optional().describe('Category filter'),
|
|
33
|
+
target: z.string().optional().describe('Target filter'),
|
|
34
|
+
limit: z.number().int().optional(),
|
|
35
|
+
offset: z.number().int().optional(),
|
|
36
|
+
},
|
|
37
|
+
run: runEmailTemplateList,
|
|
38
|
+
readOnly: true,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'pitcher_email_template_get',
|
|
42
|
+
title: 'Get an email template',
|
|
43
|
+
description: 'Retrieve a single HTML email template by its ID.',
|
|
44
|
+
inputShape: {
|
|
45
|
+
instanceId: z.string().describe('Instance ID (required)'),
|
|
46
|
+
id: z.string().describe('Template ID (required)'),
|
|
47
|
+
},
|
|
48
|
+
run: runEmailTemplateGet,
|
|
49
|
+
readOnly: true,
|
|
50
|
+
},
|
|
51
|
+
];
|