@motorical/scope-catalog 0.7.0 → 0.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motorical/scope-catalog",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Canonical MCP/OAuth scope metadata for Motorical — descriptions, Public API scope mapping, implied-read rules, and declarative tool/resource membership. One source of truth consumed by motorical-backend and @motorical/mcp.",
5
5
  "main": "./src/index.js",
6
6
  "engines": {
package/src/comms.js ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Communications (marketing) MCP constants. Spec: motorical-docs specs/2026-10-27-comms-mcp-m2-auth-delegation-scopes-design.md.
3
+ *
4
+ * Shared by the backend (grant resolver, OAuth consent), the Communications Block (delegation verification, route to scope table)
5
+ * and @motorical/mcp (the `communications` server), so the audience and the scope names cannot drift between them.
6
+ */
7
+ const { MCP_HOST } = require('./tools');
8
+
9
+ /** Fixed order; also the order the scopes appear in SCOPES (after the nine core scopes, so existing order is untouched). */
10
+ const COMMS_SCOPES = [
11
+ 'read:comms',
12
+ 'write:comms.audience',
13
+ 'write:comms.content',
14
+ 'send:comms.campaign',
15
+ 'manage:comms.workspace',
16
+ 'manage:comms.webhooks',
17
+ ];
18
+
19
+ /** What the FIRST consent asks for. Sending and managing are a step-up, so sending always has its own consent moment. */
20
+ const COMMS_INITIAL_SCOPES = ['read:comms', 'write:comms.audience', 'write:comms.content'];
21
+
22
+ /** The hosted server's canonical URI = its RFC 8707 audience = the `resource` stored on its grants. */
23
+ const COMMS_CANONICAL_URI = `${MCP_HOST}/v1/motorical_communications/mcp`;
24
+
25
+ /** Audience of the delegation assertion minted for this server. Distinct from the backend's `.../internal/mcp`, so neither can be replayed at the other. */
26
+ const COMMS_DELEGATION_AUDIENCE = 'https://api.motorical.com/internal/mcp/comms';
27
+
28
+ /**
29
+ * How a NEW oauth_resource_servers row is created for a resource that needs more than the transactional defaults. The sync inserts
30
+ * these and never changes `enabled` or `block_types` of an existing row. The communications server is marketing-only and starts
31
+ * DISABLED: nobody can consent to it until it is switched on deliberately (Comms MCP M2d).
32
+ */
33
+ const RESOURCE_SERVER_DEFAULTS = {
34
+ [COMMS_CANONICAL_URI]: { blockTypes: ['marketing'], enabled: false },
35
+ };
36
+
37
+ module.exports = { COMMS_SCOPES, COMMS_INITIAL_SCOPES, COMMS_CANONICAL_URI, COMMS_DELEGATION_AUDIENCE, RESOURCE_SERVER_DEFAULTS };
@@ -7,6 +7,13 @@
7
7
  const MCP_IMPLIED_READ = {
8
8
  'manage:webhooks': 'read:webhooks',
9
9
  'manage:domains': 'read:domains',
10
+ // Every Communications write, send and manage scope can read what it works on. Never the other way, and never sideways:
11
+ // writing content does not imply sending.
12
+ 'write:comms.audience': 'read:comms',
13
+ 'write:comms.content': 'read:comms',
14
+ 'send:comms.campaign': 'read:comms',
15
+ 'manage:comms.workspace': 'read:comms',
16
+ 'manage:comms.webhooks': 'read:comms',
10
17
  };
11
18
 
12
19
  function expandMcpImplied(grantedScopes) {
package/src/index.js CHANGED
@@ -3,4 +3,5 @@ module.exports = {
3
3
  ...require('./implication'),
4
4
  ...require('./tools'),
5
5
  ...require('./consent'),
6
+ ...require('./comms'),
6
7
  };
package/src/scopes.js CHANGED
@@ -81,6 +81,51 @@ const SCOPES = {
81
81
  agentDescription: 'List and read sending domains, their DNS records and email-authentication health, and read a Motor Block\'s configuration, without adding or verifying any domain.',
82
82
  customerAction: 'View your sending domains and Motor Block configuration',
83
83
  },
84
+ // Communications (marketing). One area per scope so the consent screen shows sending on its own line. No Public API scopes:
85
+ // the Comms API is not a Public API route, the Communications Block checks these itself. There is deliberately no scope for
86
+ // permanent delete, recycle-bin cleanup or GDPR export/erase: those stay human-only.
87
+ 'read:comms': {
88
+ resource: 'comms',
89
+ level: 'read',
90
+ publicScopes: [],
91
+ agentDescription: 'Read Communications data for the workspaces the person chose: lists and contacts, templates, campaigns, reports and events, the workspace map and readiness, and run read-only checks (template preview, lint, link check, starter render). Never changes anything.',
92
+ customerAction: 'View your lists, templates, campaigns and results',
93
+ },
94
+ 'write:comms.audience': {
95
+ resource: 'comms-audience',
96
+ level: 'manage',
97
+ publicScopes: [],
98
+ agentDescription: 'Create and change lists and contacts, add contacts (with a dry run), derive lists from lists, import, and add or remove suppressions. Includes everything read:comms grants. Cannot delete anything permanently.',
99
+ customerAction: 'Add and change your lists and contacts',
100
+ },
101
+ 'write:comms.content': {
102
+ resource: 'comms-content',
103
+ level: 'manage',
104
+ publicScopes: [],
105
+ agentDescription: 'Create and change templates, the brand kit and campaign drafts, duplicate and compile campaigns, and send a test email to the account owner only. Includes everything read:comms grants. Does not send to your contacts.',
106
+ customerAction: 'Create and edit email templates and campaign drafts, and send test emails to you',
107
+ },
108
+ 'send:comms.campaign': {
109
+ resource: 'comms-sending',
110
+ level: 'manage',
111
+ publicScopes: [],
112
+ agentDescription: 'Schedule a campaign (which sends it to the audience) and cancel a scheduled or sending one. Cancelling is final. Includes everything read:comms grants.',
113
+ customerAction: 'Send campaigns to your contacts (this is what actually emails people)',
114
+ },
115
+ 'manage:comms.workspace': {
116
+ resource: 'comms-workspace',
117
+ level: 'manage',
118
+ publicScopes: [],
119
+ agentDescription: 'Change workspace settings (consent, open tracking, unsubscribe page) and which marketing Motor Blocks a workspace uses. Includes everything read:comms grants.',
120
+ customerAction: 'Change workspace settings and which sending addresses a workspace uses',
121
+ },
122
+ 'manage:comms.webhooks': {
123
+ resource: 'comms-webhooks',
124
+ level: 'manage',
125
+ publicScopes: [],
126
+ agentDescription: 'Create, update, delete and test campaign event webhooks of the workspaces the person chose. Includes everything read:comms grants.',
127
+ customerAction: 'Manage your campaign event webhooks',
128
+ },
84
129
  };
85
130
 
86
131
  function listGrantableScopes() {
package/src/tools.js CHANGED
@@ -86,6 +86,13 @@ const MOTOR_BLOCK_TOOLS = [
86
86
  'motorical_motor_block_delete_status',
87
87
  ];
88
88
 
89
+ /**
90
+ * The hosted `communications` server (Comms MCP M2c). Two read-only proof tools that exercise the whole path (consent, delegation with
91
+ * the comms audience, grant resolution, scope table, workspace binding). Not in ALL_TOOLS and not on the main server: marketing has its
92
+ * own server and its own consent. More tools arrive in M3, each adding the scope it needs to this server's offer.
93
+ */
94
+ const COMMS_TOOLS = ['comms_get_readiness', 'comms_get_map'];
95
+
89
96
  const ALL_TOOLS = [
90
97
  ...new Set([
91
98
  ...TRANSACTIONAL_TOOLS,
@@ -113,6 +120,7 @@ const SERVER_TOOLS = {
113
120
  sandbox: [...SANDBOX_TOOLS, ...ANY_GRANT_TOOLS],
114
121
  webhooks: [...WEBHOOK_TOOLS, ...ANY_GRANT_TOOLS],
115
122
  motorBlocks: [...MOTOR_BLOCK_TOOLS, ...ANY_GRANT_TOOLS],
123
+ communications: [...COMMS_TOOLS, ...ANY_GRANT_TOOLS],
116
124
  signup: ['motorical_signup_handoff'],
117
125
  };
118
126
 
@@ -172,6 +180,8 @@ const TOOL_SCOPES = {
172
180
  motorical_motor_block_reactivate: ['manage:motor-blocks'],
173
181
  motorical_motor_block_delete: ['manage:motor-blocks'],
174
182
  motorical_motor_block_delete_status: ['manage:motor-blocks'],
183
+ comms_get_readiness: ['read:comms'],
184
+ comms_get_map: ['read:comms'],
175
185
  };
176
186
 
177
187
  /** `|| []`: a tool with no entry (signup's own tool) contributes no scopes rather than throwing. */
@@ -187,6 +197,7 @@ const SERVER_SLUGS = {
187
197
  sandbox: 'motorical_sandbox',
188
198
  webhooks: 'motorical_webhooks',
189
199
  motorBlocks: 'motorical_motor_blocks',
200
+ communications: 'motorical_communications',
190
201
  signup: 'signup',
191
202
  };
192
203