@pitcher/cli-scripts 3.2.0 → 3.3.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.
Files changed (60) hide show
  1. package/dist/src/commands/dsrLanding.js +1 -1
  2. package/dist/src/commands/roleplay.d.ts +154 -3
  3. package/dist/src/commands/roleplay.js +276 -23
  4. package/dist/src/core/dsr-engagement.d.ts +152 -0
  5. package/dist/src/core/dsr-engagement.js +301 -0
  6. package/dist/src/core/envelope.d.ts +1 -0
  7. package/dist/src/core/envelope.js +6 -1
  8. package/dist/src/core/errors.d.ts +3 -1
  9. package/dist/src/core/errors.js +2 -2
  10. package/dist/src/core/http.js +7 -5
  11. package/dist/src/core/ld.js +2 -1
  12. package/dist/src/core/me.d.ts +3 -0
  13. package/dist/src/core/me.js +10 -0
  14. package/dist/src/core/roleplay-stats.d.ts +48 -7
  15. package/dist/src/core/roleplay-stats.js +68 -11
  16. package/dist/src/mcp/apps/confirm/define.d.ts +66 -0
  17. package/dist/src/mcp/apps/confirm/define.js +208 -0
  18. package/dist/src/mcp/apps/confirm/hash.d.ts +4 -0
  19. package/dist/src/mcp/apps/confirm/hash.js +23 -0
  20. package/dist/src/mcp/apps/confirm/types.d.ts +96 -0
  21. package/dist/src/mcp/apps/confirm/types.js +15 -0
  22. package/dist/src/mcp/apps/contentPicker.d.ts +104 -0
  23. package/dist/src/mcp/apps/contentPicker.js +268 -0
  24. package/dist/src/mcp/apps/dsrEngagement.d.ts +194 -0
  25. package/dist/src/mcp/apps/dsrEngagement.js +737 -0
  26. package/dist/src/mcp/apps/registry.d.ts +14 -0
  27. package/dist/src/mcp/apps/registry.js +25 -0
  28. package/dist/src/mcp/apps/roleplayManager.d.ts +228 -0
  29. package/dist/src/mcp/apps/roleplayManager.js +606 -0
  30. package/dist/src/mcp/apps/roleplayReps.d.ts +79 -0
  31. package/dist/src/mcp/apps/roleplayReps.js +328 -0
  32. package/dist/src/mcp/apps/roleplayWrites.d.ts +6 -0
  33. package/dist/src/mcp/apps/roleplayWrites.js +509 -0
  34. package/dist/src/mcp/apps/text.d.ts +41 -0
  35. package/dist/src/mcp/apps/text.js +58 -0
  36. package/dist/src/mcp/apps/types.d.ts +23 -0
  37. package/dist/src/mcp/apps/types.js +12 -0
  38. package/dist/src/mcp/apps/writeConfirm.d.ts +3 -0
  39. package/dist/src/mcp/apps/writeConfirm.js +8 -0
  40. package/dist/src/mcp/groups.d.ts +42 -0
  41. package/dist/src/mcp/groups.js +89 -0
  42. package/dist/src/mcp/hosted.d.ts +36 -0
  43. package/dist/src/mcp/hosted.js +47 -1
  44. package/dist/src/mcp/index.d.ts +9 -3
  45. package/dist/src/mcp/index.js +9 -2
  46. package/dist/src/mcp/prompts/registry.js +8 -7
  47. package/dist/src/mcp/prompts/types.d.ts +3 -0
  48. package/dist/src/mcp/registry.d.ts +4 -0
  49. package/dist/src/mcp/registry.js +29 -24
  50. package/dist/src/mcp/renderPolicy.d.ts +1 -0
  51. package/dist/src/mcp/renderPolicy.js +8 -2
  52. package/dist/src/mcp/resources/registry.js +2 -2
  53. package/dist/src/mcp/resources/types.d.ts +3 -0
  54. package/dist/src/mcp/result.d.ts +2 -1
  55. package/dist/src/mcp/result.js +8 -6
  56. package/dist/src/mcp/server.d.ts +62 -0
  57. package/dist/src/mcp/server.js +161 -10
  58. package/dist/src/mcp/tools/roleplay.js +34 -5
  59. package/dist/src/mcp/tools/types.d.ts +35 -0
  60. package/package.json +2 -1
@@ -0,0 +1,42 @@
1
+ /**
2
+ * PIT-10114: tool groups, the per-org policy and the access class of every tool.
3
+ *
4
+ * An org sets each group to off | read | read_write in `pitcher_organization.settings.mcp.groups`; `settings.mcp.access`
5
+ * stays the master switch and the ceiling (read_only caps every group at read). L&D is off unless the org turns it on.
6
+ * Inactive tools are absent from tools/list, and a call to one is refused and recorded.
7
+ */
8
+ import type { ToolDescriptor } from './tools/types.js';
9
+ export declare const TOOL_GROUPS: readonly ["content", "sharing", "analytics", "roleplay", "training_studio", "ld", "admin", "search", "developer"];
10
+ export type ToolGroup = (typeof TOOL_GROUPS)[number];
11
+ /** Policy class: 'write' = changes data OR exists only to lead to a change (PIT-10113 _propose and _commit). */
12
+ export type ToolAccess = 'read' | 'write';
13
+ export type GroupLevel = 'off' | 'read' | 'read_write';
14
+ export type ToolPolicy = Readonly<Record<ToolGroup, GroupLevel>>;
15
+ /**
16
+ * Groups an org may configure (developer is local-only). Django mirrors this list (`MCP_GROUPS` in
17
+ * ng-rest-api `pitcher/utils/validators.py`, which refuses unknown groups): add a group THERE FIRST.
18
+ */
19
+ export declare const CONFIGURABLE_GROUPS: readonly ToolGroup[];
20
+ export interface ResolvedPolicy {
21
+ policy: ToolPolicy;
22
+ /** Invalid or unknown keys ('groups' when malformed); logged once per fetch, never shown to users. */
23
+ warnings: string[];
24
+ }
25
+ export declare const OFF_POLICY: ToolPolicy;
26
+ /** `settings.mcp` → per-group levels. Fails closed: no valid `access`, malformed `groups`, an invalid level. */
27
+ export declare function resolveToolPolicy(mcp: unknown): ResolvedPolicy;
28
+ type AccessFields = Pick<ToolDescriptor, 'access' | 'readOnly' | 'write' | 'alwaysDestructive'>;
29
+ /** The policy class of a tool: explicit `access`, else the legacy read-only predicate (frozen for today's tools). */
30
+ export declare function accessOf(t: AccessFields): ToolAccess;
31
+ export type ToolVerdict = 'allowed' | 'GROUP_INACTIVE' | 'GROUP_READ_ONLY' | 'UNGROUPED';
32
+ export declare function toolVerdict(policy: ToolPolicy, t: AccessFields & Pick<ToolDescriptor, 'group'>): ToolVerdict;
33
+ /** A prompt drives a write tool unless flagged `readOnly`, so a group at `read` serves only the read-only ones. */
34
+ export declare function allowsPrompt(policy: ToolPolicy, p: {
35
+ group?: ToolGroup;
36
+ readOnly?: true;
37
+ }): boolean;
38
+ /** Hard rules on every registered descriptor: a group, and no write tool classed `read`. */
39
+ export declare function checkDescriptor(t: ToolDescriptor): ToolDescriptor;
40
+ /** Assign a whole module to a group; a descriptor that already names another group throws. */
41
+ export declare function inGroup(group: ToolGroup, tools: ToolDescriptor[]): ToolDescriptor[];
42
+ export {};
@@ -0,0 +1,89 @@
1
+ export const TOOL_GROUPS = [
2
+ 'content',
3
+ 'sharing',
4
+ 'analytics',
5
+ 'roleplay',
6
+ 'training_studio',
7
+ 'ld',
8
+ 'admin',
9
+ 'search',
10
+ 'developer',
11
+ ];
12
+ /**
13
+ * Groups an org may configure (developer is local-only). Django mirrors this list (`MCP_GROUPS` in
14
+ * ng-rest-api `pitcher/utils/validators.py`, which refuses unknown groups): add a group THERE FIRST.
15
+ */
16
+ export const CONFIGURABLE_GROUPS = TOOL_GROUPS.filter((g) => g !== 'developer');
17
+ const DEFAULT_OFF = new Set(['ld']); // N1 ruling, 2026-11-10
18
+ const RANK = { off: 0, read: 1, read_write: 2 };
19
+ const LEVELS = new Set(['off', 'read', 'read_write']);
20
+ /** The keys `settings.mcp` may hold; mirrors `MCP_KEYS` in ng-rest-api `pitcher/utils/validators.py`. */
21
+ const MCP_KEYS = new Set(['access', 'groups']);
22
+ export const OFF_POLICY = Object.freeze(Object.fromEntries(TOOL_GROUPS.map((g) => [g, 'off'])));
23
+ /** `settings.mcp` → per-group levels. Fails closed: no valid `access`, malformed `groups`, an invalid level. */
24
+ export function resolveToolPolicy(mcp) {
25
+ const raw = (mcp && typeof mcp === 'object' && !Array.isArray(mcp) ? mcp : {});
26
+ if (raw.access !== 'full' && raw.access !== 'read_only')
27
+ return { policy: OFF_POLICY, warnings: [] };
28
+ const ceiling = raw.access === 'full' ? 'read_write' : 'read';
29
+ const g0 = raw.groups ?? {};
30
+ if (typeof g0 !== 'object' || Array.isArray(g0))
31
+ return { policy: OFF_POLICY, warnings: ['groups'] };
32
+ const groups = g0;
33
+ const warnings = [
34
+ // A top-level typo (`group`, `Groups`) leaves `groups` absent, so every group takes its default. Django refuses
35
+ // unknown keys at save (ng-rest-api#1809, `MCP_KEYS`); report any that reach us anyway, as `mcp.<key>`.
36
+ ...Object.keys(raw).filter((k) => !MCP_KEYS.has(k)).map((k) => `mcp.${k}`),
37
+ ...Object.keys(groups).filter((k) => !CONFIGURABLE_GROUPS.includes(k)),
38
+ ];
39
+ const policy = { ...OFF_POLICY };
40
+ for (const g of CONFIGURABLE_GROUPS) {
41
+ const v = groups[g];
42
+ let requested;
43
+ if (v === undefined)
44
+ requested = DEFAULT_OFF.has(g) ? 'off' : ceiling;
45
+ else if (typeof v === 'string' && LEVELS.has(v))
46
+ requested = v;
47
+ else {
48
+ requested = 'off';
49
+ warnings.push(g);
50
+ }
51
+ policy[g] = RANK[requested] <= RANK[ceiling] ? requested : ceiling;
52
+ }
53
+ return { policy: Object.freeze(policy), warnings };
54
+ }
55
+ /** The policy class of a tool: explicit `access`, else the legacy read-only predicate (frozen for today's tools). */
56
+ export function accessOf(t) {
57
+ return t.access ?? (t.readOnly && !t.write && !t.alwaysDestructive ? 'read' : 'write');
58
+ }
59
+ export function toolVerdict(policy, t) {
60
+ if (!t.group)
61
+ return 'UNGROUPED'; // fail closed
62
+ const level = policy[t.group];
63
+ if (level === 'off')
64
+ return 'GROUP_INACTIVE';
65
+ return accessOf(t) === 'read' || level === 'read_write' ? 'allowed' : 'GROUP_READ_ONLY';
66
+ }
67
+ /** A prompt drives a write tool unless flagged `readOnly`, so a group at `read` serves only the read-only ones. */
68
+ export function allowsPrompt(policy, p) {
69
+ if (!p.group)
70
+ return false;
71
+ const level = policy[p.group];
72
+ return level === 'read_write' || (level === 'read' && p.readOnly === true);
73
+ }
74
+ /** Hard rules on every registered descriptor: a group, and no write tool classed `read`. */
75
+ export function checkDescriptor(t) {
76
+ if (!t.group)
77
+ throw new Error(`${t.name}: no tool group`);
78
+ if ((t.write || t.alwaysDestructive) && t.access === 'read')
79
+ throw new Error(`${t.name}: a write tool cannot be access 'read'`);
80
+ return t;
81
+ }
82
+ /** Assign a whole module to a group; a descriptor that already names another group throws. */
83
+ export function inGroup(group, tools) {
84
+ return tools.map((t) => {
85
+ if (t.group && t.group !== group)
86
+ throw new Error(`${t.name}: group ${t.group} conflicts with ${group}`);
87
+ return checkDescriptor({ ...t, group });
88
+ });
89
+ }
@@ -1,3 +1,6 @@
1
+ import type { AppViewDescriptor } from './apps/types.js';
2
+ import { type ToolPolicy } from './groups.js';
3
+ import type { ResourceDescriptor } from './resources/types.js';
1
4
  import type { PromptDescriptor } from './prompts/types.js';
2
5
  import type { BuildServerOptions } from './server.js';
3
6
  import type { ToolDescriptor } from './tools/types.js';
@@ -32,5 +35,38 @@ export declare const hostedTools: ToolDescriptor[];
32
35
  * A write tool is never registered, so calling one is "unknown tool", not a refused write.
33
36
  */
34
37
  export declare const hostedReadOnlyTools: ToolDescriptor[];
38
+ /** MCP Apps tools (PIT-10066), hosted-shaped: the same file/id runtime guards as every hosted tool. */
39
+ export declare const hostedAppTools: ToolDescriptor[];
40
+ /** A read-only server's app tool: read class, and never a PIT-10113 confirm tool, whatever its `access` says. */
41
+ export declare const isHostedReadOnlyAppTool: (t: ToolDescriptor) => boolean;
42
+ /** The app tools a read-only server serves (same rule as hostedReadOnlyTools). */
43
+ export declare const hostedReadOnlyAppTools: ToolDescriptor[];
35
44
  /** The prompts a read-only server serves: only those flagged `readOnly` (the rest drive a write tool). */
36
45
  export declare const readOnlyPrompts: PromptDescriptor[];
46
+ /** The CLI-backed hosted tools this policy serves. */
47
+ export declare const hostedToolsFor: (p: ToolPolicy) => ToolDescriptor[];
48
+ /** What decides app-tool registration besides the policy: the enabled views, and whether a PIT-10113 signer exists. */
49
+ export interface HostedFeatures {
50
+ views: AppViewDescriptor[];
51
+ proposals: boolean;
52
+ }
53
+ /** The app tools this policy serves: allowed, their view enabled, and a confirm tool only with a proposal signer. */
54
+ export declare const hostedAppToolsFor: (p: ToolPolicy, f: HostedFeatures) => ToolDescriptor[];
55
+ /**
56
+ * Views worth serving: only those with at least one surviving app tool. App tools are filtered by their own group
57
+ * first, so a view whose group is off (analytics off hides dsr-engagement) is never served.
58
+ */
59
+ export declare const viewsFor: (p: ToolPolicy, f: HostedFeatures) => AppViewDescriptor[];
60
+ /** Exactly the names buildServer registers for this policy and these features (one filter, used by build and recording). */
61
+ export declare const hostedRegisteredNames: (p: ToolPolicy, f: HostedFeatures) => Set<string>;
62
+ export declare const promptsFor: (p: ToolPolicy) => PromptDescriptor[];
63
+ export declare const resourcesFor: (p: ToolPolicy) => ResourceDescriptor[];
64
+ /** The HOSTED descriptor (hosted inputShape, never a LOCAL_ONLY name) by name. */
65
+ export declare function hostedToolByName(name: string): ToolDescriptor | undefined;
66
+ /**
67
+ * Whether `args` pass the hosted tool's input schema: the same `inputShape` object `registerTool` receives, so the
68
+ * mcp-server pre-inspection sees exactly the calls the SDK rejects before the callback (recorded as INVALID_ARGS).
69
+ */
70
+ export declare function hostedArgsValid(t: ToolDescriptor, args: unknown): boolean;
71
+ /** On = at least one hosted tool survives; an empty group (training_studio) does not keep the connector on. */
72
+ export declare const policyIsOn: (p: ToolPolicy) => boolean;
@@ -1,6 +1,10 @@
1
+ import { z } from 'zod';
1
2
  import { fail } from '../core/envelope.js';
2
3
  import { createCliError } from '../core/errors.js';
4
+ import { allAppTools } from './apps/registry.js';
5
+ import { accessOf, allowsPrompt, toolVerdict } from './groups.js';
3
6
  import { allPrompts } from './prompts/registry.js';
7
+ import { allResources } from './resources/registry.js';
4
8
  import { allTools } from './registry.js';
5
9
  /**
6
10
  * The tool set served by the hosted (remote, OAuth) MCP server — PIT-8912.
@@ -110,6 +114,48 @@ export const hostedTools = allTools.filter((t) => !LOCAL_ONLY.has(t.name)).map(t
110
114
  * Read-only mode (the org's `settings.mcp.access` is `read_only`, PIT-9768): only the hosted tools that read.
111
115
  * A write tool is never registered, so calling one is "unknown tool", not a refused write.
112
116
  */
113
- export const hostedReadOnlyTools = hostedTools.filter((t) => t.readOnly && !t.write && !t.alwaysDestructive);
117
+ export const hostedReadOnlyTools = hostedTools.filter((t) => accessOf(t) === 'read');
118
+ /** MCP Apps tools (PIT-10066), hosted-shaped: the same file/id runtime guards as every hosted tool. */
119
+ export const hostedAppTools = allAppTools.map(toHosted);
120
+ /** A read-only server's app tool: read class, and never a PIT-10113 confirm tool, whatever its `access` says. */
121
+ export const isHostedReadOnlyAppTool = (t) => accessOf(t) === 'read' && !t.confirm;
122
+ /** The app tools a read-only server serves (same rule as hostedReadOnlyTools). */
123
+ export const hostedReadOnlyAppTools = hostedAppTools.filter(isHostedReadOnlyAppTool);
114
124
  /** The prompts a read-only server serves: only those flagged `readOnly` (the rest drive a write tool). */
115
125
  export const readOnlyPrompts = allPrompts.filter((p) => p.readOnly === true);
126
+ // ---------------------------------------------------------------------------------------------------------------------
127
+ // PIT-10114: per-org tool groups. Every hosted list below is derived from the org's policy (`resolveToolPolicy`).
128
+ const allowed = (p) => (t) => toolVerdict(p, t) === 'allowed';
129
+ /** The CLI-backed hosted tools this policy serves. */
130
+ export const hostedToolsFor = (p) => hostedTools.filter(allowed(p));
131
+ /** The app tools this policy serves: allowed, their view enabled, and a confirm tool only with a proposal signer. */
132
+ export const hostedAppToolsFor = (p, f) => {
133
+ const on = new Set(f.views.map((v) => v.name));
134
+ return hostedAppTools.filter((t) => allowed(p)(t) && (!t.ui || on.has(t.ui.view)) && (!t.confirm || f.proposals));
135
+ };
136
+ /**
137
+ * Views worth serving: only those with at least one surviving app tool. App tools are filtered by their own group
138
+ * first, so a view whose group is off (analytics off hides dsr-engagement) is never served.
139
+ */
140
+ export const viewsFor = (p, f) => {
141
+ const used = new Set(hostedAppToolsFor(p, f).map((t) => t.ui.view));
142
+ return f.views.filter((v) => used.has(v.name));
143
+ };
144
+ /** Exactly the names buildServer registers for this policy and these features (one filter, used by build and recording). */
145
+ export const hostedRegisteredNames = (p, f) => new Set([...hostedToolsFor(p), ...hostedAppToolsFor(p, f)].map((t) => t.name));
146
+ export const promptsFor = (p) => allPrompts.filter((x) => allowsPrompt(p, x));
147
+ export const resourcesFor = (p) => allResources.filter((r) => !r.group || p[r.group] !== 'off');
148
+ const HOSTED_BY_NAME = new Map([...hostedTools, ...hostedAppTools].map((t) => [t.name, t]));
149
+ /** The HOSTED descriptor (hosted inputShape, never a LOCAL_ONLY name) by name. */
150
+ export function hostedToolByName(name) {
151
+ return HOSTED_BY_NAME.get(name);
152
+ }
153
+ /**
154
+ * Whether `args` pass the hosted tool's input schema: the same `inputShape` object `registerTool` receives, so the
155
+ * mcp-server pre-inspection sees exactly the calls the SDK rejects before the callback (recorded as INVALID_ARGS).
156
+ */
157
+ export function hostedArgsValid(t, args) {
158
+ return z.object(t.inputShape).safeParse(args ?? {}).success;
159
+ }
160
+ /** On = at least one hosted tool survives; an empty group (training_studio) does not keep the connector on. */
161
+ export const policyIsOn = (p) => hostedToolsFor(p).length > 0;
@@ -1,7 +1,13 @@
1
- export { buildServer, PITCHER_ICON, type McpRunContext, type BuildServerOptions } from './server.js';
2
- export { hostedTools, hostedReadOnlyTools, readOnlyPrompts, LOCAL_ONLY, FILE_KEY, ID_KEY, HOSTED_BUILD_OPTIONS, } from './hosted.js';
1
+ export { buildServer, PITCHER_ICON, CLI_SCRIPTS_VERSION, toolCallEvent, type McpRunContext, type BuildServerOptions, type ToolCallEvent, type ToolCallSink, } from './server.js';
2
+ export { TOOL_GROUPS, CONFIGURABLE_GROUPS, OFF_POLICY, resolveToolPolicy, toolVerdict, accessOf, allowsPrompt, type ToolGroup, type ToolAccess, type GroupLevel, type ToolPolicy, type ResolvedPolicy, type ToolVerdict, } from './groups.js';
3
+ export { hostedTools, hostedReadOnlyTools, readOnlyPrompts, LOCAL_ONLY, FILE_KEY, ID_KEY, HOSTED_BUILD_OPTIONS, hostedAppTools, hostedReadOnlyAppTools, hostedToolsFor, hostedAppToolsFor, viewsFor, hostedRegisteredNames, promptsFor, resourcesFor, hostedToolByName, hostedArgsValid, policyIsOn, type HostedFeatures, } from './hosted.js';
4
+ export { allAppViews, allAppTools } from './apps/registry.js';
5
+ export { PROPOSAL_TTL_S, PROPOSAL_MAX_BYTES, PROPOSAL_TEXT_MAX_BYTES, TEXT_HOST_MODE, type ProposalClaims, type ProposalSigner, type HostClient, type Proposal, type CommitResult, } from './apps/confirm/types.js';
6
+ export { APPS_RENDER_POLICY } from './renderPolicy.js';
7
+ export { appViewUri, type AppViewDescriptor, type AppCsp } from './apps/types.js';
3
8
  export { allTools } from './registry.js';
4
9
  export { allPrompts } from './prompts/registry.js';
5
10
  export { allResources } from './resources/registry.js';
6
- export type { ToolDescriptor } from './tools/types.js';
11
+ export type { ToolDescriptor, ToolUi, AppInjected } from './tools/types.js';
7
12
  export type { Credential } from '../core/credential.js';
13
+ export { createCliError, isCliError, type CliError } from '../core/errors.js';
@@ -1,6 +1,13 @@
1
1
  // Public entry `@pitcher/cli-scripts/mcp` — what the hosted MCP server (web/packages/mcp-server) consumes.
2
- export { buildServer, PITCHER_ICON } from './server.js';
3
- export { hostedTools, hostedReadOnlyTools, readOnlyPrompts, LOCAL_ONLY, FILE_KEY, ID_KEY, HOSTED_BUILD_OPTIONS, } from './hosted.js';
2
+ export { buildServer, PITCHER_ICON, CLI_SCRIPTS_VERSION, toolCallEvent, } from './server.js';
3
+ export { TOOL_GROUPS, CONFIGURABLE_GROUPS, OFF_POLICY, resolveToolPolicy, toolVerdict, accessOf, allowsPrompt, } from './groups.js';
4
+ export { hostedTools, hostedReadOnlyTools, readOnlyPrompts, LOCAL_ONLY, FILE_KEY, ID_KEY, HOSTED_BUILD_OPTIONS, hostedAppTools, hostedReadOnlyAppTools, hostedToolsFor, hostedAppToolsFor, viewsFor, hostedRegisteredNames, promptsFor, resourcesFor, hostedToolByName, hostedArgsValid, policyIsOn, } from './hosted.js';
5
+ export { allAppViews, allAppTools } from './apps/registry.js';
6
+ export { PROPOSAL_TTL_S, PROPOSAL_MAX_BYTES, PROPOSAL_TEXT_MAX_BYTES, TEXT_HOST_MODE, } from './apps/confirm/types.js';
7
+ export { APPS_RENDER_POLICY } from './renderPolicy.js';
8
+ export { appViewUri } from './apps/types.js';
4
9
  export { allTools } from './registry.js';
5
10
  export { allPrompts } from './prompts/registry.js';
6
11
  export { allResources } from './resources/registry.js';
12
+ // PIT-10113: the proposal signer (mcp-server) throws CliErrors so a commit maps them to the right code
13
+ export { createCliError, isCliError } from '../core/errors.js';
@@ -4,12 +4,13 @@ import { dsrPrompts } from './dsr.js';
4
4
  import { announcementsPrompts } from './announcements.js';
5
5
  import { ldCanvasCoursePrompts } from './ldCanvasCourse.js';
6
6
  import { analyticsPrompts } from './analytics.js';
7
- // Every recipe prompt the server can expose. Mirrors mcp/registry.ts (allTools).
7
+ const withGroup = (group, prompts) => prompts.map((p) => ({ ...p, group }));
8
+ // Every recipe prompt the server can expose, each in the group of the tools it drives (PIT-10114). Mirrors mcp/registry.ts.
8
9
  export const allPrompts = [
9
- ...emailTemplatePrompts,
10
- ...dsrLandingPrompts,
11
- ...dsrPrompts,
12
- ...announcementsPrompts,
13
- ...ldCanvasCoursePrompts,
14
- ...analyticsPrompts,
10
+ ...withGroup('sharing', emailTemplatePrompts),
11
+ ...withGroup('sharing', dsrLandingPrompts),
12
+ ...withGroup('sharing', dsrPrompts),
13
+ ...withGroup('content', announcementsPrompts),
14
+ ...withGroup('ld', ldCanvasCoursePrompts),
15
+ ...withGroup('analytics', analyticsPrompts),
15
16
  ];
@@ -1,5 +1,6 @@
1
1
  import type { ZodOptional, ZodString } from 'zod';
2
2
  import type { PromptMessage } from '@modelcontextprotocol/sdk/types.js';
3
+ import type { ToolGroup } from '../groups.js';
3
4
  export interface PromptDescriptor {
4
5
  name: string;
5
6
  title: string;
@@ -7,4 +8,6 @@ export interface PromptDescriptor {
7
8
  argsSchema: Record<string, ZodOptional<ZodString>>;
8
9
  build: (args: Record<string, string | undefined>) => PromptMessage[];
9
10
  readOnly?: true;
11
+ /** PIT-10114: served when its group is read_write, or at read when `readOnly`. Set in prompts/registry.ts. */
12
+ group?: ToolGroup;
10
13
  }
@@ -1,2 +1,6 @@
1
1
  import type { ToolDescriptor } from './tools/types.js';
2
+ /**
3
+ * Every CLI-backed tool, each module assigned whole to one tool group (PIT-10114, spec "Group Taxonomy"). `inGroup`
4
+ * throws when a descriptor names another group or a write tool is classed `read` (checkDescriptor).
5
+ */
2
6
  export declare const allTools: ToolDescriptor[];
@@ -1,3 +1,4 @@
1
+ import { inGroup } from './groups.js';
1
2
  import { analyticsTools } from './tools/analytics.js';
2
3
  import { announcementsTools } from './tools/announcements.js';
3
4
  import { canvasesTools } from './tools/canvases.js';
@@ -22,29 +23,33 @@ import { sharedLinksTools } from './tools/sharedLinks.js';
22
23
  import { tagsTools } from './tools/tags.js';
23
24
  import { themesTools } from './tools/themes.js';
24
25
  import { usersTools } from './tools/users.js';
26
+ /**
27
+ * Every CLI-backed tool, each module assigned whole to one tool group (PIT-10114, spec "Group Taxonomy"). `inGroup`
28
+ * throws when a descriptor names another group or a write tool is classed `read` (checkDescriptor).
29
+ */
25
30
  export const allTools = [
26
- ...canvasesTools,
27
- ...filesTools,
28
- ...foldersTools,
29
- ...searchTools,
30
- ...instancesTools,
31
- ...sharedLinksTools,
32
- ...tagsTools,
33
- ...announcementsTools,
34
- ...dsrLandingTools,
35
- ...dsrTools,
36
- ...emailTemplateTools,
37
- ...themesTools,
38
- ...usersTools,
39
- ...invitationsTools,
40
- ...rolesTools,
41
- ...orgTools,
42
- ...analyticsTools,
43
- ...roleplayTools,
44
- ...membershipsTools,
45
- ...groupsTools,
46
- ...metadataTemplateTools,
47
- ...ldTools,
48
- ...devSyncTools,
49
- ...publishAppTools,
31
+ ...inGroup('content', canvasesTools),
32
+ ...inGroup('content', filesTools),
33
+ ...inGroup('content', foldersTools),
34
+ ...inGroup('search', searchTools),
35
+ ...inGroup('search', instancesTools),
36
+ ...inGroup('sharing', sharedLinksTools),
37
+ ...inGroup('content', tagsTools),
38
+ ...inGroup('content', announcementsTools),
39
+ ...inGroup('sharing', dsrLandingTools),
40
+ ...inGroup('sharing', dsrTools),
41
+ ...inGroup('sharing', emailTemplateTools),
42
+ ...inGroup('content', themesTools),
43
+ ...inGroup('admin', usersTools),
44
+ ...inGroup('admin', invitationsTools),
45
+ ...inGroup('admin', rolesTools),
46
+ ...inGroup('search', orgTools),
47
+ ...inGroup('analytics', analyticsTools),
48
+ ...inGroup('roleplay', roleplayTools),
49
+ ...inGroup('admin', membershipsTools),
50
+ ...inGroup('admin', groupsTools),
51
+ ...inGroup('content', metadataTemplateTools),
52
+ ...inGroup('ld', ldTools),
53
+ ...inGroup('developer', devSyncTools),
54
+ ...inGroup('developer', publishAppTools),
50
55
  ];
@@ -1 +1,2 @@
1
1
  export declare const ANALYTICS_RENDER_POLICY = "When answering Pitcher content-analytics or AI Roleplay questions (the pitcher_analytics_* and pitcher_roleplay_* 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\").\n7. AI Roleplay: a session with no score (null) is unscored, never a 0; never fold it into an average, a pass rate or a ranking.\n\nDo not jump a tier without its trigger. The dashboard is opt-in by intent, never a default.";
2
+ export declare const APPS_RENDER_POLICY = "When your host actually renders a Pitcher tool result in its own interactive view (MCP Apps: a Pitcher view), answer in one sentence and do not redraw the same data as a table, chart or artifact. If your host does not render the view, the text result is all the user gets: list the items for the user.\n\nConfirmed writes (tools ending in _propose): a propose call saves nothing. If your host shows the Pitcher confirm view, the user confirms or cancels there: say one sentence and do nothing else. Never call a _commit tool unless the proposal text tells you how and the user explicitly confirmed in this conversation; never change its arguments; never retry a commit the user cancelled.";
@@ -1,8 +1,9 @@
1
1
  // Connector-level MCP instructions surfaced to the host model (Claude Desktop, Cursor) via
2
2
  // InitializeResult.instructions. Governs how analytics answers are RENDERED so a natural-language
3
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.
4
+ // instead of a rigid full dashboard on every question. This constant is the only copy of the policy:
5
+ // edit it here. The Docs Portal spec (Product/Specs/2026-07-24-analytics-answer-rendering-policy.md)
6
+ // points here and keeps only the background and decisions.
6
7
  export const ANALYTICS_RENDER_POLICY = `When answering Pitcher content-analytics or AI Roleplay questions (the pitcher_analytics_* and pitcher_roleplay_* 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
 
8
9
  Rendering tiers (evaluate top-down, stop at the first match):
@@ -22,3 +23,8 @@ Guardrails (every tier):
22
23
  7. AI Roleplay: a session with no score (null) is unscored, never a 0; never fold it into an average, a pass rate or a ranking.
23
24
 
24
25
  Do not jump a tier without its trigger. The dashboard is opt-in by intent, never a default.`;
26
+ // MCP Apps (PIT-10066): appended to the server instructions ONLY when at least one view is enabled, so a
27
+ // server without apps keeps exactly ANALYTICS_RENDER_POLICY. The confirmed-writes paragraph is PIT-10113's (spec 8.2).
28
+ export const APPS_RENDER_POLICY = `When your host actually renders a Pitcher tool result in its own interactive view (MCP Apps: a Pitcher view), answer in one sentence and do not redraw the same data as a table, chart or artifact. If your host does not render the view, the text result is all the user gets: list the items for the user.
29
+
30
+ Confirmed writes (tools ending in _propose): a propose call saves nothing. If your host shows the Pitcher confirm view, the user confirms or cancels there: say one sentence and do nothing else. Never call a _commit tool unless the proposal text tells you how and the user explicitly confirmed in this conversation; never change its arguments; never retry a commit the user cancelled.`;
@@ -4,6 +4,6 @@ import { analyticsCatalogResource } from './analyticsCatalog.js';
4
4
  // Every resource the server can expose. Mirrors mcp/registry.ts (allTools).
5
5
  export const allResources = [
6
6
  contentDesignResource,
7
- ldCanvasDesignResource,
8
- analyticsCatalogResource,
7
+ { ...ldCanvasDesignResource, group: 'ld' },
8
+ { ...analyticsCatalogResource, group: 'analytics' },
9
9
  ];
@@ -1,3 +1,4 @@
1
+ import type { ToolGroup } from '../groups.js';
1
2
  export interface ResourceDescriptor {
2
3
  name: string;
3
4
  uri: string;
@@ -5,4 +6,6 @@ export interface ResourceDescriptor {
5
6
  description: string;
6
7
  mimeType: string;
7
8
  read: () => string;
9
+ /** PIT-10114: hidden when this group is off. Unset = always served (static text no group owns). */
10
+ group?: ToolGroup;
8
11
  }
@@ -10,5 +10,6 @@ export interface CallToolResultShape {
10
10
  [key: string]: unknown;
11
11
  }
12
12
  export declare const MAX_RESULT_BYTES = 900000;
13
+ export declare const MAX_TEXT_BYTES = 8000;
13
14
  export declare function toErrorResult(code: string, message: string): CallToolResultShape;
14
- export declare function envelopeToToolResult(env: Envelope<unknown>, maxBytes?: number): CallToolResultShape;
15
+ export declare function envelopeToToolResult(env: Envelope<unknown>, maxBytes?: number, toText?: (data: unknown) => string, textMaxBytes?: number): CallToolResultShape;
@@ -3,9 +3,9 @@
3
3
  export const MAX_RESULT_BYTES = 900000;
4
4
  // Cap any single variable-length string (a server error message, a warning) so one huge value
5
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)`;
6
+ export const MAX_TEXT_BYTES = 8000;
7
+ function clampText(s, max = MAX_TEXT_BYTES) {
8
+ return Buffer.byteLength(s, 'utf8') <= max ? s : `${s.slice(0, max)}… (truncated)`;
9
9
  }
10
10
  function isPlainObject(v) {
11
11
  return typeof v === 'object' && v !== null && !Array.isArray(v);
@@ -21,7 +21,7 @@ function countItems(data) {
21
21
  export function toErrorResult(code, message) {
22
22
  return { content: [{ type: 'text', text: clampText(`${code}: ${message}`) }], isError: true };
23
23
  }
24
- export function envelopeToToolResult(env, maxBytes = MAX_RESULT_BYTES) {
24
+ export function envelopeToToolResult(env, maxBytes = MAX_RESULT_BYTES, toText, textMaxBytes = MAX_TEXT_BYTES) {
25
25
  if (!env.ok) {
26
26
  return toErrorResult(env.error?.code ?? 'UNKNOWN', env.error?.message ?? 'unknown error');
27
27
  }
@@ -39,8 +39,10 @@ export function envelopeToToolResult(env, maxBytes = MAX_RESULT_BYTES) {
39
39
  // counted — rather than projecting from the raw data size. maxBytes keeps headroom under the
40
40
  // host cap for the outer JSON-RPC framing around this result.
41
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 };
42
+ // Tier 1: full payload with the default dual representation (text + structuredContent). An MCP Apps tool
43
+ // (`toText`) sends the model a short text instead of the full JSON, so the payload is not sent twice.
44
+ const text = toText ? clampText(toText(env.data), textMaxBytes) : full;
45
+ const dual = { content: withWarnings([{ type: 'text', text }]), isError: false };
44
46
  if (obj)
45
47
  dual.structuredContent = obj;
46
48
  if (fits(dual))
@@ -3,6 +3,11 @@ import type { Credential } from '../core/credential.js';
3
3
  import type { ToolDescriptor } from './tools/types.js';
4
4
  import type { PromptDescriptor } from './prompts/types.js';
5
5
  import type { ResourceDescriptor } from './resources/types.js';
6
+ import { type AppViewDescriptor } from './apps/types.js';
7
+ import type { HostClient, ProposalSigner } from './apps/confirm/types.js';
8
+ import { type ToolAccess, type ToolGroup, type ToolPolicy } from './groups.js';
9
+ /** The package version (the hosted audit records it as `server_version`). */
10
+ export declare const CLI_SCRIPTS_VERSION: string;
6
11
  export declare const PITCHER_ICON = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiI+PHJlY3Qgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHJ4PSIxMTYiIGZpbGw9IiMyOTMzNUMiLz48Y2lyY2xlIGN4PSIzNzIiIGN5PSIxNTAiIHI9IjMwIiBmaWxsPSIjQ0MwMDAwIi8+PHBhdGggZmlsbD0iI2ZmZmZmZiIgZD0iTTE1MCAxMTZoOTZhOTIgOTIgMCAwIDEgMCAxODRoLTUwdjk2aC00NnoiLz48cGF0aCBmaWxsPSIjMjkzMzVDIiBkPSJNMTk2IDE2MHY5Nmg1MGE0OCA0OCAwIDAgMCAwLTk2eiIvPjwvc3ZnPg==";
7
12
  /**
8
13
  * The per-server request context: who every tool call runs as, and against which origin.
@@ -22,5 +27,62 @@ export interface BuildServerOptions {
22
27
  * is a build option, never a per-call field. Stdio passes `env.prod`; hosted always passes true.
23
28
  */
24
29
  destructiveWrites?: boolean;
30
+ /**
31
+ * MCP Apps (PIT-10066): the views for this request (all of them when the org's flag is on). Unset (local p mcp,
32
+ * hosted with the flag off) = every `ui` tool is skipped and instructions are unchanged, so the server is exactly today's.
33
+ */
34
+ apps?: {
35
+ views: AppViewDescriptor[];
36
+ loadHtml: (viewName: string) => string;
37
+ /** PIT-10113: the proposal signer for this request. Absent = no confirm tool is registered. */
38
+ proposals?: ProposalSigner;
39
+ /** PIT-10113: this request's host client class (chooses the proposal text tail only). */
40
+ hostClient?: HostClient;
41
+ };
42
+ /**
43
+ * PIT-10114: the org's tool policy. Hosted builds register only allowed tools already; this is defence in depth: a
44
+ * tool the policy forbids is refused (FORBIDDEN) without running. Unset (local p mcp) = no check.
45
+ */
46
+ policy?: ToolPolicy;
47
+ /** PIT-10114: one event per tool call. Unset = no-op (local p mcp). */
48
+ onToolCall?: ToolCallSink;
49
+ }
50
+ /** PIT-10114: one event per tool call. Ids only: never arguments, results, names or emails. */
51
+ export interface ToolCallEvent {
52
+ tool: string;
53
+ group: ToolGroup;
54
+ /** accessOf(descriptor): the explicit class, never readOnlyHint */
55
+ access: ToolAccess;
56
+ outcome: 'ok' | 'error' | 'denied';
57
+ /** '' on ok; the CliError code (envelope or thrown), UNKNOWN for a thrown non-CliError, or
58
+ * INVALID_ARGS | FORBIDDEN | GROUP_INACTIVE | GROUP_READ_ONLY | APP_DISABLED */
59
+ errorCode: string;
60
+ /** epoch ms when the call started (before run(); receipt time for denied and INVALID_ARGS) */
61
+ startedAt: number;
62
+ /** run() only; 0 for denied and INVALID_ARGS */
63
+ latencyMs: number;
64
+ /** HTTP status of the backend's error answer when the failure came from a backend response; 0 otherwise */
65
+ backendStatus: number;
66
+ /** the call's instanceId argument when it is a plain id token, else '' */
67
+ instanceId: string;
68
+ /** descriptor ui.visibility is exactly ['app'] */
69
+ viaApp: boolean;
70
+ /** PIT-10113 confirm tools only */
71
+ confirm?: {
72
+ role: 'propose' | 'commit';
73
+ action: string;
74
+ proposalId?: string;
75
+ status?: 'proposed' | 'committed' | 'already_committed' | 'refused';
76
+ };
25
77
  }
78
+ export type ToolCallSink = (e: ToolCallEvent) => void | Promise<void>;
79
+ /** The event, built field by field (never by spreading args or the envelope). */
80
+ export declare function toolCallEvent(tool: ToolDescriptor, args: Record<string, unknown>, r: {
81
+ outcome: ToolCallEvent['outcome'];
82
+ errorCode: string;
83
+ startedAt: number;
84
+ latencyMs: number;
85
+ backendStatus: number;
86
+ data?: unknown;
87
+ }): ToolCallEvent | undefined;
26
88
  export declare function buildServer(ctx: McpRunContext, tools?: ToolDescriptor[], prompts?: PromptDescriptor[], resources?: ResourceDescriptor[], opts?: BuildServerOptions): McpServer;