@pitcher/cli-scripts 2.14.0 → 2.15.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 +4 -2
- package/dist/src/mcp/bundled.js +1 -1
- package/dist/src/mcp/prompts/analytics.d.ts +2 -0
- package/dist/src/mcp/prompts/analytics.js +112 -0
- package/dist/src/mcp/prompts/registry.js +2 -0
- package/dist/src/mcp/prompts/shared.d.ts +1 -0
- package/dist/src/mcp/prompts/shared.js +11 -0
- package/dist/src/mcp/renderPolicy.d.ts +1 -0
- package/dist/src/mcp/renderPolicy.js +23 -0
- package/dist/src/mcp/resources/analyticsCatalog.d.ts +3 -0
- package/dist/src/mcp/resources/analyticsCatalog.js +60 -0
- package/dist/src/mcp/resources/registry.js +6 -1
- package/dist/src/mcp/server.js +6 -1
- package/dist/src/mcp/tools/analytics.js +138 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ p command --help
|
|
|
12
12
|
|
|
13
13
|
## MCP server
|
|
14
14
|
|
|
15
|
-
Run `p mcp` to start a stdio [MCP](https://modelcontextprotocol.io) server that exposes this CLI's commands as tools to an MCP-capable host (Claude Desktop, Claude Code, etc). It wraps
|
|
15
|
+
Run `p mcp` to start a stdio [MCP](https://modelcontextprotocol.io) server that exposes this CLI's commands as tools to an MCP-capable host (Claude Desktop, Claude Code, etc). It wraps 121 tools, one per CLI `run*` function: every command above (including `dsr create` + `dsr bind-landing` and the full `ld` surface) except the five local `scaffold` subcommands (which just print/write a starter file and make no request). `dev-sync` is exposed as a one-shot sync only; the continuous `watch` mode is not available as a tool.
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
p mcp
|
|
@@ -55,7 +55,7 @@ yarn mcpb:pack # → pitcher.mcpb (validated + packed)
|
|
|
55
55
|
|
|
56
56
|
Install by double-clicking `pitcher.mcpb` (or Settings → Extensions → Install Extension). Claude Desktop prompts for the API key (stored in the OS keychain) and organization; both are editable afterward, so switching orgs needs no reinstall.
|
|
57
57
|
|
|
58
|
-
The bundle reuses the same server as `p mcp` but registers a **content-tool subset (
|
|
58
|
+
The bundle reuses the same server as `p mcp` but registers a **content-tool subset (118)** — it omits the developer-only `publish-app` and `dev-sync`/`dev-sync-revert`. The `organization` field accepts a subdomain (`acme`), a bare hostname (`acme.my.pitcher.com`), or a full `https://` URL. Build details (esbuild single-file, version injection) live in `mcpb/` and `CLAUDE.md`.
|
|
59
59
|
|
|
60
60
|
This section is a reference only. For app-development conventions (modules, settings, mount points, SFDC), see the `pitcher-apps` skill, not this README.
|
|
61
61
|
|
|
@@ -394,6 +394,8 @@ p analytics viewers --instance-id 01... --canvas-id 01CANVASID... --viewer-type
|
|
|
394
394
|
p analytics sessions --instance-id 01... --canvas-id 01CANVASID... --user rep@example.com --api-key myapikey
|
|
395
395
|
```
|
|
396
396
|
|
|
397
|
+
MCP: all ten verbs are exposed as read-only `pitcher_analytics_*` tools, alongside a `pitcher://analytics/catalog` resource (which verb answers which question — returns, time model, key tier) and four query prompts — `top_content`, `content_engagement_summary`, `who_viewed_canvas`, `adoption_snapshot`.
|
|
398
|
+
|
|
397
399
|
## To run the `users` command:
|
|
398
400
|
|
|
399
401
|
Read users in the org (read-only). Org scope comes from the API key via the proxy `x-org-id` header — there is no `--org-id` flag.
|
package/dist/src/mcp/bundled.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { allTools } from './registry.js';
|
|
2
2
|
// Developer-only tools excluded from the consumer .mcpb bundle. `publish_app` is alwaysDestructive
|
|
3
3
|
// (creates a live, org-visible app release); `dev_sync`/`dev_sync_revert` mutate remote S3 dev state.
|
|
4
|
-
// None are relevant to a non-technical adopter installing the extension. The CLI `p mcp` keeps all
|
|
4
|
+
// None are relevant to a non-technical adopter installing the extension. The CLI `p mcp` keeps all 121.
|
|
5
5
|
export const BUNDLE_EXCLUDED_TOOLS = new Set(['pitcher_publish_app', 'pitcher_dev_sync', 'pitcher_dev_sync_revert']);
|
|
6
6
|
// The tool set the .mcpb bundle registers.
|
|
7
7
|
export const bundledTools = allTools.filter((t) => !BUNDLE_EXCLUDED_TOOLS.has(t.name));
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { instanceLine, readSafetyNote } from './shared.js';
|
|
3
|
+
// Read-only analytics query recipes. Unlike the authoring prompts, these create nothing — each just
|
|
4
|
+
// routes a common question to the right pitcher_analytics_* verb(s) and asks for a concise summary.
|
|
5
|
+
// They carry readSafetyNote() (confirm instance + treat identities as customer data), NOT the
|
|
6
|
+
// content-creation safetyPreamble (no markup is authored, so the {{token}} anti-XSS guidance is N/A).
|
|
7
|
+
export const analyticsPrompts = [
|
|
8
|
+
{
|
|
9
|
+
name: 'top_content',
|
|
10
|
+
title: 'Top-performing content',
|
|
11
|
+
description: 'Rank an instance’s most-viewed canvases (and optionally files) over a window.',
|
|
12
|
+
argsSchema: {
|
|
13
|
+
instance_id: z.string().optional().describe('Target instance ID (leave blank to resolve interactively)'),
|
|
14
|
+
period: z.string().optional().describe('Time window in words, e.g. "last 30 days", "this quarter"'),
|
|
15
|
+
limit: z.string().optional().describe('How many top items to return (default 10)'),
|
|
16
|
+
},
|
|
17
|
+
build: (args) => {
|
|
18
|
+
const period = args.period || 'the last 30 days';
|
|
19
|
+
const limit = args.limit || '10';
|
|
20
|
+
const text = [
|
|
21
|
+
readSafetyNote(),
|
|
22
|
+
'',
|
|
23
|
+
'## Task: top-performing content',
|
|
24
|
+
instanceLine(args.instance_id),
|
|
25
|
+
`Window: ${period}.`,
|
|
26
|
+
'',
|
|
27
|
+
'Steps:',
|
|
28
|
+
`1. Translate the window into ISO \`startDate\`/\`endDate\` (omit both ONLY when the window is the default last 30 days). Then call \`pitcher_analytics_popularity\` for the instance with those dates and \`limit\`=${limit} to get the most-viewed canvases, ranked.`,
|
|
29
|
+
'2. (Optional) Call `pitcher_analytics_files` with the same dates to add the most-used files.',
|
|
30
|
+
'3. Summarize the top items with their view/engagement numbers, and state the exact window used. Requires an org-admin key.',
|
|
31
|
+
].join('\n');
|
|
32
|
+
return [{ role: 'user', content: { type: 'text', text } }];
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'content_engagement_summary',
|
|
37
|
+
title: 'Content engagement summary',
|
|
38
|
+
description: 'Summarize per-content/DSR engagement for an instance over a window.',
|
|
39
|
+
argsSchema: {
|
|
40
|
+
instance_id: z.string().optional().describe('Target instance ID (leave blank to resolve interactively)'),
|
|
41
|
+
period: z.string().optional().describe('Time window in words, e.g. "last 30 days"'),
|
|
42
|
+
},
|
|
43
|
+
build: (args) => {
|
|
44
|
+
const period = args.period || 'the last 30 days';
|
|
45
|
+
const text = [
|
|
46
|
+
readSafetyNote(),
|
|
47
|
+
'',
|
|
48
|
+
'## Task: content engagement summary',
|
|
49
|
+
instanceLine(args.instance_id),
|
|
50
|
+
`Window: ${period}.`,
|
|
51
|
+
'',
|
|
52
|
+
'Steps:',
|
|
53
|
+
'1. Translate the window into ISO `startDate`/`endDate` (omit both ONLY when the window is the default last 30 days), then call `pitcher_analytics_engagement` for the instance with those dates (raise `limit` if you need more than the top 20 rows).',
|
|
54
|
+
'2. Read the `summary` for headline metrics and the `rows` for per-item engagement; note `has_more`/`total_count` if the list is capped.',
|
|
55
|
+
'3. Report which content is engaged with most and the overall trend for the window. Works with a member key.',
|
|
56
|
+
].join('\n');
|
|
57
|
+
return [{ role: 'user', content: { type: 'text', text } }];
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'who_viewed_canvas',
|
|
62
|
+
title: 'Who viewed a DSR canvas',
|
|
63
|
+
description: 'List the viewers of a specific DSR canvas, then drill into one viewer’s session timeline.',
|
|
64
|
+
argsSchema: {
|
|
65
|
+
instance_id: z.string().optional().describe('Target instance ID (leave blank to resolve interactively)'),
|
|
66
|
+
canvas_id: z.string().optional().describe('Canvas to inspect (leave blank to pick one interactively)'),
|
|
67
|
+
},
|
|
68
|
+
build: (args) => {
|
|
69
|
+
const text = [
|
|
70
|
+
readSafetyNote(),
|
|
71
|
+
'',
|
|
72
|
+
'## Task: who viewed a DSR canvas',
|
|
73
|
+
instanceLine(args.instance_id),
|
|
74
|
+
args.canvas_id
|
|
75
|
+
? `Canvas to inspect: ${args.canvas_id}.`
|
|
76
|
+
: 'No canvas given — help the user pick one via `pitcher_canvases_list` (or `pitcher_search`).',
|
|
77
|
+
'',
|
|
78
|
+
'Steps:',
|
|
79
|
+
'1. Call `pitcher_analytics_viewers` with the `canvasId` to list viewers and their per-viewer engagement.',
|
|
80
|
+
'2. To drill into a specific viewer, call `pitcher_analytics_sessions` with the same `canvasId` plus that viewer’s ' +
|
|
81
|
+
'`userEmail` (both come from step 1) to get their raw session event timeline.',
|
|
82
|
+
'3. Summarize who viewed, how much they engaged, and — if you drilled in — what that viewer did. Works with a member key.',
|
|
83
|
+
].join('\n');
|
|
84
|
+
return [{ role: 'user', content: { type: 'text', text } }];
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'adoption_snapshot',
|
|
89
|
+
title: 'Adoption snapshot',
|
|
90
|
+
description: 'Combine active-user stats and KPIs into a short adoption snapshot for an instance.',
|
|
91
|
+
argsSchema: {
|
|
92
|
+
instance_id: z.string().optional().describe('Target instance ID (leave blank to resolve interactively)'),
|
|
93
|
+
period: z.string().optional().describe('Time window in words, e.g. "last 30 days"'),
|
|
94
|
+
},
|
|
95
|
+
build: (args) => {
|
|
96
|
+
const period = args.period || 'the last 30 days';
|
|
97
|
+
const text = [
|
|
98
|
+
readSafetyNote(),
|
|
99
|
+
'',
|
|
100
|
+
'## Task: adoption snapshot',
|
|
101
|
+
instanceLine(args.instance_id),
|
|
102
|
+
`Window: ${period}.`,
|
|
103
|
+
'',
|
|
104
|
+
'Steps:',
|
|
105
|
+
'1. Translate the window into ISO `startDate`/`endDate` (omit both ONLY when the window is the default last 30 days), then call `pitcher_analytics_users` with `stats`=true and those dates for active-user statistics.',
|
|
106
|
+
'2. Call `pitcher_analytics_kpi` with the same dates for headline counts (e.g. `type`=total-active-users, then total-members for the denominator).',
|
|
107
|
+
'3. Combine into a short adoption snapshot — active users, totals, and the active/total ratio for the window. Requires an org-admin key.',
|
|
108
|
+
].join('\n');
|
|
109
|
+
return [{ role: 'user', content: { type: 'text', text } }];
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
];
|
|
@@ -3,6 +3,7 @@ import { dsrLandingPrompts } from './dsrLanding.js';
|
|
|
3
3
|
import { dsrPrompts } from './dsr.js';
|
|
4
4
|
import { announcementsPrompts } from './announcements.js';
|
|
5
5
|
import { ldCanvasCoursePrompts } from './ldCanvasCourse.js';
|
|
6
|
+
import { analyticsPrompts } from './analytics.js';
|
|
6
7
|
// Every recipe prompt the server can expose. Mirrors mcp/registry.ts (allTools).
|
|
7
8
|
export const allPrompts = [
|
|
8
9
|
...emailTemplatePrompts,
|
|
@@ -10,4 +11,5 @@ export const allPrompts = [
|
|
|
10
11
|
...dsrPrompts,
|
|
11
12
|
...announcementsPrompts,
|
|
12
13
|
...ldCanvasCoursePrompts,
|
|
14
|
+
...analyticsPrompts,
|
|
13
15
|
];
|
|
@@ -24,6 +24,17 @@ export function safetyPreamble() {
|
|
|
24
24
|
'the generated markup.',
|
|
25
25
|
].join('\n');
|
|
26
26
|
}
|
|
27
|
+
// Lighter note for READ-ONLY recipes (analytics queries): they create nothing, so they don't carry
|
|
28
|
+
// the content-creation prod-warning or the {{token}}/anti-XSS guidance (there is no markup to author).
|
|
29
|
+
// Instead: confirm the instance is the intended one, and treat returned identities as customer data.
|
|
30
|
+
export function readSafetyNote() {
|
|
31
|
+
return [
|
|
32
|
+
'## Before you query',
|
|
33
|
+
'These are **read-only** analytics tools — they create nothing and mutate nothing. Confirm you are ' +
|
|
34
|
+
'pointed at the intended instance before reporting numbers, and treat any returned user ' +
|
|
35
|
+
'identities/emails as customer data (handle per policy).',
|
|
36
|
+
].join('\n');
|
|
37
|
+
}
|
|
27
38
|
// Full preamble for HTML-authoring recipes: the design principles + the safety guidance.
|
|
28
39
|
// Authored once so the principles, prod-awareness, and anti-XSS guidance can't drift.
|
|
29
40
|
export function recipePreamble() {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ANALYTICS_RENDER_POLICY = "When answering Pitcher content-analytics questions (the pitcher_analytics_* tools), return the smallest sufficient representation. Answer in words first; a visual supports the sentence, it never replaces it. Default to prose and escalate ONLY when a trigger below fires.\n\nRendering tiers (evaluate top-down, stop at the first match):\n- A single fact / yes-no / \"who/what/how many is the single X\" (e.g. \"which canvas did I view?\", \"how many viewers?\") -> 1-2 sentences with the number inline. No table, no chart.\n- A ranking request (\"top\", \"most\", \"best\", \"rank\", or \"list the highest/lowest ...\") about content, files, users, or canvases -> a markdown TABLE of the ranked rows (default the top 5-8); columns = only the metric(s) asked for. Treat \"what's my top content?\" as a ranking (a table), NOT a single-item sentence. Lead with a one-line takeaway, then the table.\n- \"over time\" / \"trend\" / \"by week/month\" -> ONE line/area chart artifact (this rule wins over the generic comparison rule below for time series).\n- An explicit visualization request (\"show a chart\", \"chart\", \"visualize\", \"graph\", \"plot\") OR a comparison across a non-time dimension -> ONE focused chart as a single self-contained HTML artifact, only the asked-for series.\n- A broad overview (\"how's everything / my content / adoption doing\", \"give me the picture\", \"dashboard\", QBR) -> a multi-panel dashboard artifact. This is the ceiling \u2014 use it rarely, only for genuinely broad asks.\n\nGuardrails (every tier):\n1. Words first \u2014 even with a chart, lead with a one-sentence answer to the literal question.\n2. Mask names, emails, and user IDs by default (User A/B/...). Reveal an identity only when the analytics tool actually returned it for the authenticated role (the verbs are role-gated); never infer authorization from the user's wording.\n3. If data is empty or trivially small, say so in prose \u2014 do not render an empty chart.\n4. If results are capped/truncated, state the cap (\"top 15 of 118\").\n5. Never show a \"vs prior period\" delta unless the data actually contains prior-period numbers.\n6. Cite the source verb (\"via analytics popularity\").\n\nDo not jump a tier without its trigger. The dashboard is opt-in by intent, never a default.";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Connector-level MCP instructions surfaced to the host model (Claude Desktop, Cursor) via
|
|
2
|
+
// InitializeResult.instructions. Governs how analytics answers are RENDERED so a natural-language
|
|
3
|
+
// question gets a right-sized reply (a sentence, a table, one chart, or — rarely — a dashboard)
|
|
4
|
+
// instead of a rigid full dashboard on every question. This constant is the single source of truth;
|
|
5
|
+
// the Docs Portal spec (Product/Specs/2026-07-24-analytics-answer-rendering-policy.md) mirrors it.
|
|
6
|
+
export const ANALYTICS_RENDER_POLICY = `When answering Pitcher content-analytics questions (the pitcher_analytics_* tools), return the smallest sufficient representation. Answer in words first; a visual supports the sentence, it never replaces it. Default to prose and escalate ONLY when a trigger below fires.
|
|
7
|
+
|
|
8
|
+
Rendering tiers (evaluate top-down, stop at the first match):
|
|
9
|
+
- A single fact / yes-no / "who/what/how many is the single X" (e.g. "which canvas did I view?", "how many viewers?") -> 1-2 sentences with the number inline. No table, no chart.
|
|
10
|
+
- A ranking request ("top", "most", "best", "rank", or "list the highest/lowest ...") about content, files, users, or canvases -> a markdown TABLE of the ranked rows (default the top 5-8); columns = only the metric(s) asked for. Treat "what's my top content?" as a ranking (a table), NOT a single-item sentence. Lead with a one-line takeaway, then the table.
|
|
11
|
+
- "over time" / "trend" / "by week/month" -> ONE line/area chart artifact (this rule wins over the generic comparison rule below for time series).
|
|
12
|
+
- An explicit visualization request ("show a chart", "chart", "visualize", "graph", "plot") OR a comparison across a non-time dimension -> ONE focused chart as a single self-contained HTML artifact, only the asked-for series.
|
|
13
|
+
- A broad overview ("how's everything / my content / adoption doing", "give me the picture", "dashboard", QBR) -> a multi-panel dashboard artifact. This is the ceiling — use it rarely, only for genuinely broad asks.
|
|
14
|
+
|
|
15
|
+
Guardrails (every tier):
|
|
16
|
+
1. Words first — even with a chart, lead with a one-sentence answer to the literal question.
|
|
17
|
+
2. Mask names, emails, and user IDs by default (User A/B/...). Reveal an identity only when the analytics tool actually returned it for the authenticated role (the verbs are role-gated); never infer authorization from the user's wording.
|
|
18
|
+
3. If data is empty or trivially small, say so in prose — do not render an empty chart.
|
|
19
|
+
4. If results are capped/truncated, state the cap ("top 15 of 118").
|
|
20
|
+
5. Never show a "vs prior period" delta unless the data actually contains prior-period numbers.
|
|
21
|
+
6. Cite the source verb ("via analytics popularity").
|
|
22
|
+
|
|
23
|
+
Do not jump a tier without its trigger. The dashboard is opt-in by intent, never a default.`;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ResourceDescriptor } from './types.js';
|
|
2
|
+
export declare const ANALYTICS_CATALOG = "# Pitcher analytics catalog\n\nTen read-only verbs, each an MCP tool named `pitcher_analytics_<verb>`. Pick the verb that matches the\nquestion, then call its tool. All are read-only; org scope + auth come from the server's API key (never a\ntool input) \u2014 you only pass `instanceId` (and, where noted, `canvasId`/`userEmail`).\n\n## Key tiers\n- **org-admin key required:** org, popularity, users, files, canvases\n- **instance-admin or org-admin:** instance\n- **member key OK (access follows the key user's real role):** kpi, engagement, viewers, sessions\n\n## Time model\n- **Rollup (no window):** org, instance \u2014 lifetime/current totals.\n- **Date-range, 30-day default:** kpi (activity types need dates), engagement, popularity, `users` with\n `stats: true`, files, canvases \u2014 omit dates to get the last 30 days. (`users` without `stats` is a\n full roster with no window.)\n- **Date-range, server default window:** viewers, sessions \u2014 omit dates and the service picks the window.\n\n## Verbs\n\n| Verb (tool) | Returns | Time | Key | Example question |\n|---|---|---|---|---|\n| `org` | Organization-wide rollup totals (Django) | rollup | org-admin | \"How is the whole org doing overall?\" |\n| `instance` | Single-instance rollup totals (Django) | rollup | instance-admin | \"Totals for this one workspace?\" |\n| `kpi` | KPI metrics for an instance (members, active users, \u2026) | date-range | member | \"How many active users last month?\" |\n| `engagement` | Per-DSR/content engagement rows (capped list + summary) | date-range | member | \"Which content is being engaged with?\" |\n| `popularity` | Most-viewed canvases, ranked (view-analysis) | date-range | org-admin | \"Top 10 most-viewed decks this quarter?\" |\n| `users` | User roster, or active-user stats with `stats: true` | roster: none / stats: date-range | org-admin | \"Who are my active users this month?\" |\n| `files` | File usage analytics (views/opens/engagement per file) | date-range | org-admin | \"Which files get opened the most?\" |\n| `canvases` | Per-section canvas engagement list | date-range | org-admin | \"How are my canvas sections performing?\" |\n| `viewers` | Who viewed a specific DSR canvas (per-viewer engagement) | date-range | member | \"Who viewed this DSR, and how much?\" |\n| `sessions` | One viewer's session event timeline for a DSR canvas (raw) | date-range | member | \"What did this viewer do inside the DSR?\" |\n\n## Shaping\nRow-heavy verbs (engagement, popularity, users, files, canvases, viewers) return a capped envelope:\n`summary` + `rows` (default 20, max 100 \u2014 raise with `limit`) + `total_count`/`returned`/`has_more`,\nand a `truncated` note when more rows exist. `kpi` and `sessions` return the raw payload (a KPI scalar set\nand an uncapped event timeline, respectively) \u2014 never truncated.\n\n## DSR drill-down\nFor \"who viewed X and what did they do\": call `viewers` (with `canvasId`) to list viewers, then `sessions`\n(with the same `canvasId` + the viewer's `userEmail`) to see that viewer's event timeline.\n\n## Notes\n- `popularity` maps to the canvases view-analysis endpoint (there is no separate popularity route).\n- Results can include user identities/emails \u2014 treat as customer data and handle per policy.\n";
|
|
3
|
+
export declare const analyticsCatalogResource: ResourceDescriptor;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// A model-facing catalog of the analytics verbs exposed as pitcher_analytics_* tools. It exists so a
|
|
2
|
+
// host can read ONE resource to learn which verb answers which question, the time model each uses, and
|
|
3
|
+
// the key tier each needs — instead of guessing from 10 tool descriptions. Distilled from the analytics
|
|
4
|
+
// runner contracts in src/commands/analytics.ts (the authoritative source); keep in sync on route/gate
|
|
5
|
+
// changes. Definitions mirror the platform analytics vocabulary.
|
|
6
|
+
export const ANALYTICS_CATALOG = `# Pitcher analytics catalog
|
|
7
|
+
|
|
8
|
+
Ten read-only verbs, each an MCP tool named \`pitcher_analytics_<verb>\`. Pick the verb that matches the
|
|
9
|
+
question, then call its tool. All are read-only; org scope + auth come from the server's API key (never a
|
|
10
|
+
tool input) — you only pass \`instanceId\` (and, where noted, \`canvasId\`/\`userEmail\`).
|
|
11
|
+
|
|
12
|
+
## Key tiers
|
|
13
|
+
- **org-admin key required:** org, popularity, users, files, canvases
|
|
14
|
+
- **instance-admin or org-admin:** instance
|
|
15
|
+
- **member key OK (access follows the key user's real role):** kpi, engagement, viewers, sessions
|
|
16
|
+
|
|
17
|
+
## Time model
|
|
18
|
+
- **Rollup (no window):** org, instance — lifetime/current totals.
|
|
19
|
+
- **Date-range, 30-day default:** kpi (activity types need dates), engagement, popularity, \`users\` with
|
|
20
|
+
\`stats: true\`, files, canvases — omit dates to get the last 30 days. (\`users\` without \`stats\` is a
|
|
21
|
+
full roster with no window.)
|
|
22
|
+
- **Date-range, server default window:** viewers, sessions — omit dates and the service picks the window.
|
|
23
|
+
|
|
24
|
+
## Verbs
|
|
25
|
+
|
|
26
|
+
| Verb (tool) | Returns | Time | Key | Example question |
|
|
27
|
+
|---|---|---|---|---|
|
|
28
|
+
| \`org\` | Organization-wide rollup totals (Django) | rollup | org-admin | "How is the whole org doing overall?" |
|
|
29
|
+
| \`instance\` | Single-instance rollup totals (Django) | rollup | instance-admin | "Totals for this one workspace?" |
|
|
30
|
+
| \`kpi\` | KPI metrics for an instance (members, active users, …) | date-range | member | "How many active users last month?" |
|
|
31
|
+
| \`engagement\` | Per-DSR/content engagement rows (capped list + summary) | date-range | member | "Which content is being engaged with?" |
|
|
32
|
+
| \`popularity\` | Most-viewed canvases, ranked (view-analysis) | date-range | org-admin | "Top 10 most-viewed decks this quarter?" |
|
|
33
|
+
| \`users\` | User roster, or active-user stats with \`stats: true\` | roster: none / stats: date-range | org-admin | "Who are my active users this month?" |
|
|
34
|
+
| \`files\` | File usage analytics (views/opens/engagement per file) | date-range | org-admin | "Which files get opened the most?" |
|
|
35
|
+
| \`canvases\` | Per-section canvas engagement list | date-range | org-admin | "How are my canvas sections performing?" |
|
|
36
|
+
| \`viewers\` | Who viewed a specific DSR canvas (per-viewer engagement) | date-range | member | "Who viewed this DSR, and how much?" |
|
|
37
|
+
| \`sessions\` | One viewer's session event timeline for a DSR canvas (raw) | date-range | member | "What did this viewer do inside the DSR?" |
|
|
38
|
+
|
|
39
|
+
## Shaping
|
|
40
|
+
Row-heavy verbs (engagement, popularity, users, files, canvases, viewers) return a capped envelope:
|
|
41
|
+
\`summary\` + \`rows\` (default 20, max 100 — raise with \`limit\`) + \`total_count\`/\`returned\`/\`has_more\`,
|
|
42
|
+
and a \`truncated\` note when more rows exist. \`kpi\` and \`sessions\` return the raw payload (a KPI scalar set
|
|
43
|
+
and an uncapped event timeline, respectively) — never truncated.
|
|
44
|
+
|
|
45
|
+
## DSR drill-down
|
|
46
|
+
For "who viewed X and what did they do": call \`viewers\` (with \`canvasId\`) to list viewers, then \`sessions\`
|
|
47
|
+
(with the same \`canvasId\` + the viewer's \`userEmail\`) to see that viewer's event timeline.
|
|
48
|
+
|
|
49
|
+
## Notes
|
|
50
|
+
- \`popularity\` maps to the canvases view-analysis endpoint (there is no separate popularity route).
|
|
51
|
+
- Results can include user identities/emails — treat as customer data and handle per policy.
|
|
52
|
+
`;
|
|
53
|
+
export const analyticsCatalogResource = {
|
|
54
|
+
name: 'analytics-catalog',
|
|
55
|
+
uri: 'pitcher://analytics/catalog',
|
|
56
|
+
title: 'Analytics verb catalog',
|
|
57
|
+
description: 'Which analytics verb answers which question — returns, time model, key tier, examples.',
|
|
58
|
+
mimeType: 'text/markdown',
|
|
59
|
+
read: () => ANALYTICS_CATALOG,
|
|
60
|
+
};
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { contentDesignResource } from './contentDesign.js';
|
|
2
2
|
import { ldCanvasDesignResource } from './ldCanvasDesign.js';
|
|
3
|
+
import { analyticsCatalogResource } from './analyticsCatalog.js';
|
|
3
4
|
// Every resource the server can expose. Mirrors mcp/registry.ts (allTools).
|
|
4
|
-
export const allResources = [
|
|
5
|
+
export const allResources = [
|
|
6
|
+
contentDesignResource,
|
|
7
|
+
ldCanvasDesignResource,
|
|
8
|
+
analyticsCatalogResource,
|
|
9
|
+
];
|
package/dist/src/mcp/server.js
CHANGED
|
@@ -6,6 +6,7 @@ import { allTools } from './registry.js';
|
|
|
6
6
|
import { allPrompts } from './prompts/registry.js';
|
|
7
7
|
import { allResources } from './resources/registry.js';
|
|
8
8
|
import { envelopeToToolResult, toErrorResult } from './result.js';
|
|
9
|
+
import { ANALYTICS_RENDER_POLICY } from './renderPolicy.js';
|
|
9
10
|
import { isCliError } from '../core/errors.js';
|
|
10
11
|
const version = (() => {
|
|
11
12
|
// The .mcpb bundle inlines this at build time (esbuild define) because the bundled single
|
|
@@ -40,7 +41,11 @@ export function buildServer(env, tools = allTools, prompts = allPrompts, resourc
|
|
|
40
41
|
description: 'Search, manage, and share Pitcher content, canvases, files, DSRs, and instances.',
|
|
41
42
|
websiteUrl: 'https://www.pitcher.com',
|
|
42
43
|
icons: [{ src: icon, mimeType: 'image/svg+xml', sizes: ['any'] }],
|
|
43
|
-
}
|
|
44
|
+
},
|
|
45
|
+
// Surfaced to the host model via InitializeResult.instructions — the analytics answer-rendering
|
|
46
|
+
// policy so a natural-language question gets a right-sized reply (sentence / table / chart /
|
|
47
|
+
// dashboard) instead of a rigid full dashboard every time. See renderPolicy.ts.
|
|
48
|
+
{ instructions: ANALYTICS_RENDER_POLICY });
|
|
44
49
|
for (const tool of tools) {
|
|
45
50
|
const destructive = tool.alwaysDestructive === true || (tool.write === true && env.prod);
|
|
46
51
|
server.registerTool(tool.name, {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { runAnalyticsOrg, runAnalyticsInstance, runAnalyticsKpi } from '../../commands/analytics.js';
|
|
2
|
+
import { runAnalyticsOrg, runAnalyticsInstance, runAnalyticsKpi, runAnalyticsEngagement, runAnalyticsPopularity, runAnalyticsUsers, runAnalyticsFiles, runAnalyticsCanvases, runAnalyticsViewers, runAnalyticsSessions, } from '../../commands/analytics.js';
|
|
3
3
|
export const analyticsTools = [
|
|
4
4
|
{
|
|
5
5
|
name: 'pitcher_analytics_org',
|
|
@@ -34,4 +34,141 @@ export const analyticsTools = [
|
|
|
34
34
|
run: runAnalyticsKpi,
|
|
35
35
|
readOnly: true,
|
|
36
36
|
},
|
|
37
|
+
{
|
|
38
|
+
name: 'pitcher_analytics_engagement',
|
|
39
|
+
title: 'DSR/content engagement',
|
|
40
|
+
description: 'Per-DSR/content engagement rows for an instance from the clickhouse analytics service. Returns a ' +
|
|
41
|
+
'summary plus a list capped to `limit` (with total_count/has_more). Works with a member key. ' +
|
|
42
|
+
'Defaults to a 30-day window when dates are omitted.',
|
|
43
|
+
inputShape: {
|
|
44
|
+
instanceId: z.string().describe('Instance ID (required)'),
|
|
45
|
+
startDate: z.string().optional().describe('Window start, ISO date (defaults to 30 days ago)'),
|
|
46
|
+
endDate: z.string().optional().describe('Window end, ISO date (defaults to today)'),
|
|
47
|
+
engagementLevel: z.string().optional().describe("Engagement level filter (default 'all')"),
|
|
48
|
+
view: z.string().optional().describe("View context (default 'dsr')"),
|
|
49
|
+
search: z.string().optional().describe('Search term'),
|
|
50
|
+
groupIds: z.string().optional().describe('Comma-separated distribution-group IDs'),
|
|
51
|
+
sortBy: z.string().optional().describe('Sort field'),
|
|
52
|
+
sortOrder: z.string().optional().describe('Sort direction (asc/desc)'),
|
|
53
|
+
hideMemberFiles: z.boolean().optional().describe('Exclude rep-uploaded files'),
|
|
54
|
+
page: z.number().int().optional().describe('Page number'),
|
|
55
|
+
limit: z.number().int().optional().describe('Max rows to return (default 20, max 100)'),
|
|
56
|
+
},
|
|
57
|
+
run: runAnalyticsEngagement,
|
|
58
|
+
readOnly: true,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'pitcher_analytics_popularity',
|
|
62
|
+
title: 'Most-viewed canvases',
|
|
63
|
+
description: 'Most-viewed canvases (view-analysis) for an instance, ranked and capped to `limit`. Requires an ' +
|
|
64
|
+
'org-admin key. Defaults to a 30-day window when dates are omitted.',
|
|
65
|
+
inputShape: {
|
|
66
|
+
instanceId: z.string().describe('Instance ID (required)'),
|
|
67
|
+
startDate: z.string().optional().describe('Window start, ISO date (defaults to 30 days ago)'),
|
|
68
|
+
endDate: z.string().optional().describe('Window end, ISO date (defaults to today)'),
|
|
69
|
+
sortBy: z.string().optional().describe("Sort field (default 'total_views')"),
|
|
70
|
+
sortOrder: z.string().optional().describe("Sort direction (default 'DESC')"),
|
|
71
|
+
groupIds: z.string().optional().describe('Comma-separated distribution-group IDs'),
|
|
72
|
+
page: z.number().int().optional().describe('Page number'),
|
|
73
|
+
limit: z.number().int().optional().describe('Max rows to return (default 20, max 100)'),
|
|
74
|
+
},
|
|
75
|
+
run: runAnalyticsPopularity,
|
|
76
|
+
readOnly: true,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'pitcher_analytics_users',
|
|
80
|
+
title: 'User roster / active-user stats',
|
|
81
|
+
description: 'User roster (default) or active-user statistics (`stats: true`) for an instance. Requires an ' +
|
|
82
|
+
'org-admin key. The stats branch defaults to a 30-day window when dates are omitted.',
|
|
83
|
+
inputShape: {
|
|
84
|
+
instanceId: z.string().describe('Instance ID (required)'),
|
|
85
|
+
stats: z.boolean().optional().describe('Return active-user statistics instead of the roster'),
|
|
86
|
+
startDate: z.string().optional().describe('Window start, ISO date (stats only, defaults to 30 days ago)'),
|
|
87
|
+
endDate: z.string().optional().describe('Window end, ISO date (stats only, defaults to today)'),
|
|
88
|
+
groupIds: z.string().optional().describe('Comma-separated distribution-group IDs (stats only)'),
|
|
89
|
+
page: z.number().int().optional().describe('Page number (stats only)'),
|
|
90
|
+
limit: z.number().int().optional().describe('Max rows to return (default 20, max 100)'),
|
|
91
|
+
},
|
|
92
|
+
run: runAnalyticsUsers,
|
|
93
|
+
readOnly: true,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'pitcher_analytics_files',
|
|
97
|
+
title: 'File usage analytics',
|
|
98
|
+
description: 'File usage analytics for an instance (views/opens/engagement per file), summarized and capped to ' +
|
|
99
|
+
'`limit`. Requires an org-admin key. Defaults to a 30-day window when dates are omitted.',
|
|
100
|
+
inputShape: {
|
|
101
|
+
instanceId: z.string().describe('Instance ID (required)'),
|
|
102
|
+
startDate: z.string().optional().describe('Window start, ISO date (defaults to 30 days ago)'),
|
|
103
|
+
endDate: z.string().optional().describe('Window end, ISO date (defaults to today)'),
|
|
104
|
+
fileType: z.string().optional().describe('Filter by file type'),
|
|
105
|
+
filter: z.string().optional().describe('Search term'),
|
|
106
|
+
sortBy: z.string().optional().describe('Sort field'),
|
|
107
|
+
sortOrder: z.string().optional().describe('Sort direction (asc/desc; normalized to ASC/DESC)'),
|
|
108
|
+
metadataFilters: z.string().optional().describe('Metadata filters (JSON string)'),
|
|
109
|
+
groupIds: z.string().optional().describe('Comma-separated distribution-group IDs'),
|
|
110
|
+
userEmails: z.string().optional().describe('Comma-separated user emails to scope by'),
|
|
111
|
+
hideMemberFiles: z.boolean().optional().describe('Exclude rep-uploaded files'),
|
|
112
|
+
page: z.number().int().optional().describe('Page number'),
|
|
113
|
+
limit: z.number().int().optional().describe('Max rows to return (default 20, max 100)'),
|
|
114
|
+
},
|
|
115
|
+
run: runAnalyticsFiles,
|
|
116
|
+
readOnly: true,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'pitcher_analytics_canvases',
|
|
120
|
+
title: 'Per-section canvas engagement',
|
|
121
|
+
description: 'Per-section canvas engagement list for an instance, summarized and capped to `limit`. Requires an ' +
|
|
122
|
+
'org-admin key. Defaults to a 30-day window when dates are omitted.',
|
|
123
|
+
inputShape: {
|
|
124
|
+
instanceId: z.string().describe('Instance ID (required)'),
|
|
125
|
+
startDate: z.string().optional().describe('Window start, ISO date (defaults to 30 days ago)'),
|
|
126
|
+
endDate: z.string().optional().describe('Window end, ISO date (defaults to today)'),
|
|
127
|
+
groupIds: z.string().optional().describe('Comma-separated distribution-group IDs'),
|
|
128
|
+
page: z.number().int().optional().describe('Page number'),
|
|
129
|
+
limit: z.number().int().optional().describe('Max rows to return (default 20, max 100)'),
|
|
130
|
+
},
|
|
131
|
+
run: runAnalyticsCanvases,
|
|
132
|
+
readOnly: true,
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'pitcher_analytics_viewers',
|
|
136
|
+
title: 'DSR canvas viewers',
|
|
137
|
+
description: 'Who viewed a specific DSR canvas — a viewer list with per-viewer engagement, capped to `limit`. ' +
|
|
138
|
+
'Works with a member key. Requires canvasId. The server applies its own default window when dates ' +
|
|
139
|
+
'are omitted. Drill into one viewer with pitcher_analytics_sessions.',
|
|
140
|
+
inputShape: {
|
|
141
|
+
instanceId: z.string().describe('Instance ID (required)'),
|
|
142
|
+
canvasId: z.string().describe('Canvas ID (required)'),
|
|
143
|
+
viewerType: z.string().optional().describe("Viewer type filter: all, internal, external (default 'all')"),
|
|
144
|
+
sortBy: z
|
|
145
|
+
.string()
|
|
146
|
+
.optional()
|
|
147
|
+
.describe('Sort field: email, session_count, total_engagement_time, last_active, sections_viewed, files_viewed'),
|
|
148
|
+
sortOrder: z.string().optional().describe("Sort direction (asc/desc, default 'desc')"),
|
|
149
|
+
search: z.string().optional().describe('Search term'),
|
|
150
|
+
startDate: z.string().optional().describe('Window start, ISO date (server default applies if omitted)'),
|
|
151
|
+
endDate: z.string().optional().describe('Window end, ISO date (server default applies if omitted)'),
|
|
152
|
+
page: z.number().int().optional().describe('Page number'),
|
|
153
|
+
limit: z.number().int().optional().describe('Max rows to return (default 20, max 100)'),
|
|
154
|
+
},
|
|
155
|
+
run: runAnalyticsViewers,
|
|
156
|
+
readOnly: true,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'pitcher_analytics_sessions',
|
|
160
|
+
title: 'DSR per-viewer session timeline',
|
|
161
|
+
description: 'Per-viewer session event timeline for a DSR canvas (drill-down for one viewer). Returns the raw, ' +
|
|
162
|
+
'uncapped event list (not summarized). Works with a member key. Requires canvasId and userEmail — ' +
|
|
163
|
+
'get both from pitcher_analytics_viewers.',
|
|
164
|
+
inputShape: {
|
|
165
|
+
instanceId: z.string().describe('Instance ID (required)'),
|
|
166
|
+
canvasId: z.string().describe('Canvas ID (required)'),
|
|
167
|
+
userEmail: z.string().describe('Viewer user email (required)'),
|
|
168
|
+
startDate: z.string().optional().describe('Window start, ISO date (server default applies if omitted)'),
|
|
169
|
+
endDate: z.string().optional().describe('Window end, ISO date (server default applies if omitted)'),
|
|
170
|
+
},
|
|
171
|
+
run: runAnalyticsSessions,
|
|
172
|
+
readOnly: true,
|
|
173
|
+
},
|
|
37
174
|
];
|