@fourier-labs/harbour-contracts 0.99.0 → 0.101.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 (29) hide show
  1. package/contract-release.json +24 -24
  2. package/dist/gen-ts/app-integrations-declaration.d.ts +59 -2
  3. package/dist/gen-ts/contract-release.d.ts +6 -6
  4. package/dist/gen-ts/contract-release.js +6 -6
  5. package/dist/gen-ts/data-grant-v2.d.ts +64 -14
  6. package/dist/gen-ts/integration-error-details.d.ts +1 -1
  7. package/dist/gen-ts/integration-execute-input-rules.d.ts +20 -0
  8. package/dist/gen-ts/integration-execute-input-rules.js +8 -0
  9. package/dist/gen-ts/integration-execute-request.d.ts +17 -3
  10. package/dist/gen-ts/integration-execute-result.d.ts +27 -0
  11. package/dist/gen-ts/integration-operations.d.ts +8 -0
  12. package/dist/gen-ts/integration-operations.js +10 -0
  13. package/dist/gen-ts/saas-connection.d.ts +3 -3
  14. package/gen-ts/app-integrations-declaration.ts +59 -2
  15. package/gen-ts/contract-release.ts +6 -6
  16. package/gen-ts/data-grant-v2.ts +65 -13
  17. package/gen-ts/integration-error-details.ts +11 -1
  18. package/gen-ts/integration-execute-input-rules.ts +8 -0
  19. package/gen-ts/integration-execute-request.ts +18 -3
  20. package/gen-ts/integration-execute-result.ts +29 -0
  21. package/gen-ts/integration-operations.ts +10 -0
  22. package/gen-ts/saas-connection.ts +3 -0
  23. package/package.json +1 -1
  24. package/schema/app-integrations-declaration.schema.json +219 -3
  25. package/schema/data-grant-v2.schema.json +75 -0
  26. package/schema/integration-error-details.schema.json +11 -1
  27. package/schema/integration-execute-request.schema.json +79 -8
  28. package/schema/integration-execute-result.schema.json +97 -0
  29. package/schema/saas-connection.schema.json +15 -0
@@ -2,7 +2,7 @@
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "https://schemas.harbour.dev/app-integrations-declaration.schema.json",
4
4
  "title": "AppIntegrationsDeclaration",
5
- "description": "Committed `.isomorph/integrations.json`: the app's declared use of tenant-wide integration connections. Keys are logical connection aliases (the governance sourceId); resources are logical names (Slack channel alias, warehouse view name), never IDs, tokens or URLs. The operation set is closed (eighty-one operations since 0.86.0, ADR 0018, ADR 0022, ADR 0024, ADR 0028, ADR 0029 and ADR 0032). Each operation carries a fixed identity mode — slack.dm.send=app, slack.user.lookup=app, every gmail.* operation=user, every basecamp.* operation=user, every sharepoint.* operation=user, every calendar.*, drive.*, docs.*, sheets.*, outlook.*, onedrive.*, excel.* and teams.* operation=user (0.86.0), warehouse.view.read=app, service.request=app — except the declared-mode Slack operations (slack.message.post, and since 0.71.0 slack.channel.history, slack.message.update and slack.message.delete), whose mode the app DECLARES: `app` acts as the company's Slack app (the one audited bot identity; a post goes out under the IT-approved per-app display name when the connection declares `presentation`), `user` acts as the signed-in person after their own consent (reads what they can read, edits what they wrote). The declared mode is the one the app requests access for; an app may hold approved grants for both modes after two requests, and each declared-mode call names which approved mode it runs under (`mode` on the execute request, 0.55.0) — a mode IT did not approve is refused, never swapped for the other. A `saas` connection may declare only the Slack, Gmail, Basecamp, SharePoint, phone, Google Workspace (calendar, drive, docs, sheets) and Microsoft 365 (outlook, onedrive, excel, teams) operations and a `database` connection only warehouse.view.read; `presentation` is a `saas` connection's optional per-app display name and icon for app-mode Slack posts, shown to IT at approval time and applied only from the approved grant. Gmail is user-mode only (reads, one send); its single resource is the fixed `inbox` alias. Basecamp (0.76.0, ADR 0022) is user-mode only too: basecamp.project.list runs on the fixed `account` alias (the company's pinned Basecamp account) and every other basecamp.* operation on a project alias IT mapped in the connection's `channels` map, so Basecamp itself refuses an id outside that project. SharePoint (0.78.0, ADR 0024) is user-mode only as well: every sharepoint.* operation runs on a site alias IT mapped in the connection's `channels` map (a Microsoft Graph site id), so Graph itself refuses a list, item or file outside that site. Google Workspace and Microsoft 365 (0.86.0, ADR 0032) are user-mode only too: every calendar.*, drive.*, docs.*, sheets.*, outlook.*, onedrive.* and excel.* operation runs on the provider's one fixed alias for the signed-in person's own data (governance resolves it, the way Gmail's is `inbox`; the contract does not pin the literal), and every teams.* operation on a channel alias IT mapped in the connection's `channels` map (a Teams team and channel id), so Graph itself refuses a message outside that channel. slack.dm.send and slack.user.lookup address people by email rather than a channel, so their single resource is the fixed `workspace` alias (the connection's installed workspace), the way Gmail's is `inbox`. This file also owns the shared IntegrationOperation / IntegrationIdentityMode / IntegrationEnvironment / IntegrationConnectionKind / IntegrationPresentation definitions referenced by the other integration schemas.",
5
+ "description": "Committed `.isomorph/integrations.json`: the app's declared use of tenant-wide integration connections. Keys are logical connection aliases (the governance sourceId); resources are logical names (Slack channel alias, warehouse view name), never IDs, tokens or URLs. The operation set is closed (eighty-one operations since 0.86.0, ADR 0018, ADR 0022, ADR 0024, ADR 0028, ADR 0029 and ADR 0032). Each operation carries a fixed identity mode — slack.dm.send=app, slack.user.lookup=app, every gmail.* operation=user, every basecamp.* operation=user, every sharepoint.* operation=user, every calendar.*, drive.*, docs.*, sheets.*, outlook.*, onedrive.*, excel.* and teams.* operation=user (0.86.0), warehouse.view.read=app, service.request=app — except the declared-mode Slack operations (slack.message.post, and since 0.71.0 slack.channel.history, slack.message.update and slack.message.delete), whose mode the app DECLARES: `app` acts as the company's Slack app (the one audited bot identity; a post goes out under the IT-approved per-app display name when the connection declares `presentation`), `user` acts as the signed-in person after their own consent (reads what they can read, edits what they wrote). The declared mode is the one the app requests access for; an app may hold approved grants for both modes after two requests, and each declared-mode call names which approved mode it runs under (`mode` on the execute request, 0.55.0) — a mode IT did not approve is refused, never swapped for the other. A `saas` connection may declare only the Slack, Gmail, Basecamp, SharePoint, phone, Google Workspace (calendar, drive, docs, sheets) and Microsoft 365 (outlook, onedrive, excel, teams) operations and a `database` connection only warehouse.view.read; `presentation` is a `saas` connection's optional per-app display name and icon for app-mode Slack posts, shown to IT at approval time and applied only from the approved grant. Gmail is user-mode only (reads, one send); its single resource is the fixed `inbox` alias. Basecamp (0.76.0, ADR 0022) is user-mode only too: basecamp.project.list runs on the fixed `account` alias (the company's pinned Basecamp account) and every other basecamp.* operation on a project alias IT mapped in the connection's `channels` map, so Basecamp itself refuses an id outside that project. SharePoint (0.78.0, ADR 0024) is user-mode only as well: every sharepoint.* operation runs on a site alias IT mapped in the connection's `channels` map (a Microsoft Graph site id), so Graph itself refuses a list, item or file outside that site. Google Workspace and Microsoft 365 (0.86.0, ADR 0032) are user-mode only too: every calendar.*, drive.*, docs.*, sheets.*, outlook.*, onedrive.* and excel.* operation runs on the provider's one fixed alias for the signed-in person's own data (governance resolves it, the way Gmail's is `inbox`; the contract does not pin the literal), and every teams.* operation on a channel alias IT mapped in the connection's `channels` map (a Teams team and channel id), so Graph itself refuses a message outside that channel. slack.dm.send and slack.user.lookup address people by email rather than a channel, so their single resource is the fixed `workspace` alias (the connection's installed workspace), the way Gmail's is `inbox`. This file also owns the shared IntegrationOperation / IntegrationIdentityMode / IntegrationEnvironment / IntegrationConnectionKind / IntegrationPresentation definitions referenced by the other integration schemas. Remote MCP connections add one generic user-only mcp.tool.call on server with exact accepted descriptors; an operation alone never authorizes every provider tool.",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": [
@@ -47,7 +47,8 @@
47
47
  "enum": [
48
48
  "saas",
49
49
  "database",
50
- "service"
50
+ "service",
51
+ "mcp"
51
52
  ]
52
53
  },
53
54
  "IntegrationOperation": {
@@ -147,7 +148,8 @@
147
148
  "zohobooks.invoice.reviseDraft",
148
149
  "zohobooks.invoice.markSent",
149
150
  "zohobooks.creditnote.create",
150
- "zohobooks.creditnote.read"
151
+ "zohobooks.creditnote.read",
152
+ "mcp.tool.call"
151
153
  ],
152
154
  "description": "Closed operation set. Extending it is a contracts change. 0.86.0 (ADR 0032) adds the forty-five Google Workspace and Microsoft 365 operations, every one user mode only: on the provider's fixed alias for the signed-in person's own data, calendar.calendar.list, calendar.event.list, calendar.event.read, calendar.event.create (a receipted write: explicit action, idempotencyKey), calendar.event.update and calendar.event.cancel (idempotent at Google, no key); drive.file.list, drive.file.search, drive.file.read, drive.file.upload and drive.folder.create (receipted writes); docs.document.read, docs.document.create (receipted), docs.document.append and docs.text.replace (no key); sheets.spreadsheet.read, sheets.values.read, sheets.values.append (receipted), sheets.values.update and sheets.values.clear (no key), sheets.spreadsheet.create (receipted); outlook.message.list, outlook.message.read, outlook.attachment.read, outlook.message.send and outlook.message.reply (receipted sends, as gmail.message.send), outlook.event.list, outlook.event.read, outlook.event.create (receipted), outlook.event.update and outlook.event.cancel (no key); onedrive.file.list, onedrive.file.search, onedrive.file.read, onedrive.file.upload and onedrive.folder.create (receipted); excel.worksheet.list, excel.range.read, excel.range.update (no key), excel.table.list, excel.row.add and excel.worksheet.add (receipted); and on a Teams channel alias (a key of the connection's `channels` map) teams.message.list, teams.message.post and teams.reply.post (receipted). 0.78.0 (ADR 0024) adds the nine SharePoint operations, every one user mode only on a site alias (a key of the connection's `channels` map, holding a Microsoft Graph site id): sharepoint.list.list, sharepoint.item.list, sharepoint.item.create (a receipted write: explicit action, idempotencyKey), sharepoint.item.update (a PATCH, idempotent at Graph, no key), sharepoint.drive.list, sharepoint.file.list, sharepoint.file.search, sharepoint.file.read and sharepoint.file.upload (a receipted write). 0.76.0 (ADR 0022) adds the eleven Basecamp operations, every one user mode only like gmail.*: basecamp.project.list (the projects of the company's pinned Basecamp account; its resource is the fixed `account` alias), and on one mapped project (the resource is a project alias, a key of the connection's `channels` map) basecamp.todolist.list, basecamp.todo.list, basecamp.todo.create (a receipted write: explicit action, idempotencyKey), basecamp.todo.complete (idempotent at Basecamp, no key), basecamp.message.list, basecamp.message.post, basecamp.comment.list, basecamp.comment.post, basecamp.campfire.post (the three posts are receipted writes) and basecamp.people.list. gmail.message.send (0.50.0) sends one email as the signed-in person: a send like slack.message.post (explicit Send, idempotencyKey, write receipt) under user identity; since 0.71.0 it may carry an HTML body and attachments. slack.message.post (0.53.0) runs in the mode the app declares: app (the company's Slack app, under the IT-approved per-app name) or user (the signed-in person, after their consent); since 0.55.0 each call names which of the approved modes it runs under. 0.71.0 (ADR 0018) adds slack.message.update and slack.message.delete (declared mode, like post), slack.dm.send (app mode: a direct message to 1..8 workspace members named by email), slack.user.lookup (app mode: email to Slack user id), gmail.thread.read (every message of one thread) and gmail.attachment.read (one attachment's bytes), and lets slack.channel.history run in app mode as well as user mode. 0.82.0 (ADR 0028) adds phone.sms.list (app mode only, a number alias from a phone connection's `channels` map: the SMS received on that number since a time) and warehouse.table.append (app mode only on a database connection, a receipted write with idempotencyKey: rows appended to a view alias whose mapping says `access: append`). 0.83.0 (ADR 0029) adds phone.number.get (app mode only, read-only, no input, on a number alias from a phone connection's `channels` map: the E.164 number that alias maps to, so the app — or a computer-use agent it drives — can type it into a portal that signs in by mobile number). 0.85.0 (ADR 0031) raises warehouse.table.append's `rows` from 1..500 to 1..5000 records (the byte cap stays governance's, now 8 MiB), so a downloaded report of a few thousand rows lands in one call."
153
155
  },
@@ -623,8 +625,14 @@
623
625
  },
624
626
  "zohobooks.creditnote.read": {
625
627
  "$ref": "#/definitions/UserModeChannelOperation"
628
+ },
629
+ "mcp.tool.call": {
630
+ "$ref": "#/definitions/McpUserOperation"
626
631
  }
627
632
  }
633
+ },
634
+ "mcp": {
635
+ "$ref": "#/definitions/McpToolSelection"
628
636
  }
629
637
  },
630
638
  "allOf": [
@@ -655,6 +663,11 @@
655
663
  "required": [
656
664
  "service.request"
657
665
  ]
666
+ },
667
+ {
668
+ "required": [
669
+ "mcp.tool.call"
670
+ ]
658
671
  }
659
672
  ]
660
673
  }
@@ -1139,6 +1152,11 @@
1139
1152
  "required": [
1140
1153
  "zohobooks.creditnote.read"
1141
1154
  ]
1155
+ },
1156
+ {
1157
+ "required": [
1158
+ "mcp.tool.call"
1159
+ ]
1142
1160
  }
1143
1161
  ]
1144
1162
  }
@@ -1633,6 +1651,11 @@
1633
1651
  "required": [
1634
1652
  "zohobooks.creditnote.read"
1635
1653
  ]
1654
+ },
1655
+ {
1656
+ "required": [
1657
+ "mcp.tool.call"
1658
+ ]
1636
1659
  }
1637
1660
  ]
1638
1661
  }
@@ -1644,6 +1667,35 @@
1644
1667
  ]
1645
1668
  }
1646
1669
  }
1670
+ },
1671
+ {
1672
+ "if": {
1673
+ "properties": {
1674
+ "kind": {
1675
+ "const": "mcp"
1676
+ }
1677
+ }
1678
+ },
1679
+ "then": {
1680
+ "required": [
1681
+ "mcp"
1682
+ ],
1683
+ "properties": {
1684
+ "presentation": false,
1685
+ "operations": {
1686
+ "propertyNames": {
1687
+ "enum": [
1688
+ "mcp.tool.call"
1689
+ ]
1690
+ }
1691
+ }
1692
+ }
1693
+ },
1694
+ "else": {
1695
+ "properties": {
1696
+ "mcp": false
1697
+ }
1698
+ }
1647
1699
  }
1648
1700
  ]
1649
1701
  },
@@ -1669,6 +1721,131 @@
1669
1721
  "maxItems": 1
1670
1722
  }
1671
1723
  }
1724
+ },
1725
+ "McpToolName": {
1726
+ "type": "string",
1727
+ "minLength": 1,
1728
+ "maxLength": 128,
1729
+ "description": "Opaque, case-sensitive provider tool name. Never interpreted as a source alias or endpoint."
1730
+ },
1731
+ "McpToolAnnotations": {
1732
+ "type": "object",
1733
+ "additionalProperties": false,
1734
+ "properties": {
1735
+ "title": {
1736
+ "type": "string",
1737
+ "maxLength": 256
1738
+ },
1739
+ "readOnlyHint": {
1740
+ "type": "boolean"
1741
+ },
1742
+ "destructiveHint": {
1743
+ "type": "boolean"
1744
+ },
1745
+ "idempotentHint": {
1746
+ "type": "boolean"
1747
+ },
1748
+ "openWorldHint": {
1749
+ "type": "boolean"
1750
+ }
1751
+ },
1752
+ "description": "Provider hints pinned with the accepted descriptor; never proof of authorization or safety."
1753
+ },
1754
+ "McpToolDescriptor": {
1755
+ "type": "object",
1756
+ "additionalProperties": false,
1757
+ "required": [
1758
+ "name",
1759
+ "inputSchema"
1760
+ ],
1761
+ "properties": {
1762
+ "name": {
1763
+ "$ref": "#/definitions/McpToolName"
1764
+ },
1765
+ "title": {
1766
+ "type": "string",
1767
+ "maxLength": 256
1768
+ },
1769
+ "description": {
1770
+ "type": "string",
1771
+ "maxLength": 16384
1772
+ },
1773
+ "inputSchema": {
1774
+ "type": "object",
1775
+ "additionalProperties": true
1776
+ },
1777
+ "outputSchema": {
1778
+ "type": "object",
1779
+ "additionalProperties": true
1780
+ },
1781
+ "annotations": {
1782
+ "$ref": "#/definitions/McpToolAnnotations"
1783
+ }
1784
+ },
1785
+ "description": "Accepted provider descriptor. MCP schema dialect validation, in-document references and 64 KiB serialized descriptor bound are runtime checks; no remote references are fetched."
1786
+ },
1787
+ "McpSelectedTool": {
1788
+ "type": "object",
1789
+ "additionalProperties": false,
1790
+ "required": [
1791
+ "name",
1792
+ "contractHash",
1793
+ "descriptor"
1794
+ ],
1795
+ "properties": {
1796
+ "name": {
1797
+ "$ref": "#/definitions/McpToolName"
1798
+ },
1799
+ "contractHash": {
1800
+ "type": "string",
1801
+ "pattern": "^sha256:[0-9a-f]{64}$"
1802
+ },
1803
+ "descriptor": {
1804
+ "$ref": "#/definitions/McpToolDescriptor"
1805
+ }
1806
+ },
1807
+ "description": "Runtime verifies name equals descriptor.name and hash is SHA256 of canonical {name,inputSchema,outputSchema?}; annotations and explanatory text are excluded."
1808
+ },
1809
+ "McpToolSelection": {
1810
+ "type": "object",
1811
+ "additionalProperties": false,
1812
+ "required": [
1813
+ "tools"
1814
+ ],
1815
+ "properties": {
1816
+ "tools": {
1817
+ "type": "array",
1818
+ "minItems": 1,
1819
+ "maxItems": 64,
1820
+ "items": {
1821
+ "$ref": "#/definitions/McpSelectedTool"
1822
+ }
1823
+ }
1824
+ },
1825
+ "description": "Exact accepted tool selection. Runtime rejects duplicate names and bounds all selected descriptors across the app to 128 KiB serialized."
1826
+ },
1827
+ "McpUserOperation": {
1828
+ "type": "object",
1829
+ "additionalProperties": false,
1830
+ "required": [
1831
+ "identity",
1832
+ "resources"
1833
+ ],
1834
+ "properties": {
1835
+ "identity": {
1836
+ "const": "user",
1837
+ "type": "string"
1838
+ },
1839
+ "resources": {
1840
+ "type": "array",
1841
+ "minItems": 1,
1842
+ "maxItems": 1,
1843
+ "items": {
1844
+ "type": "string",
1845
+ "const": "server"
1846
+ }
1847
+ }
1848
+ }
1672
1849
  }
1673
1850
  },
1674
1851
  "examples": [
@@ -2258,6 +2435,45 @@
2258
2435
  }
2259
2436
  }
2260
2437
  }
2438
+ },
2439
+ {
2440
+ "schema": "isomorph.app-integrations/2.0",
2441
+ "connections": {
2442
+ "fixture-mcp": {
2443
+ "kind": "mcp",
2444
+ "operations": {
2445
+ "mcp.tool.call": {
2446
+ "identity": "user",
2447
+ "resources": [
2448
+ "server"
2449
+ ]
2450
+ }
2451
+ },
2452
+ "mcp": {
2453
+ "tools": [
2454
+ {
2455
+ "name": "fixture.Read",
2456
+ "contractHash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
2457
+ "descriptor": {
2458
+ "name": "fixture.Read",
2459
+ "inputSchema": {
2460
+ "type": "object",
2461
+ "properties": {
2462
+ "filter": {
2463
+ "type": "object"
2464
+ }
2465
+ },
2466
+ "additionalProperties": false
2467
+ },
2468
+ "annotations": {
2469
+ "readOnlyHint": true
2470
+ }
2471
+ }
2472
+ }
2473
+ ]
2474
+ }
2475
+ }
2476
+ }
2261
2477
  }
2262
2478
  ]
2263
2479
  }
@@ -29,6 +29,7 @@
29
29
  "items": { "$ref": "#/definitions/IntegrationResource" },
30
30
  "description": "Approved logical resources. Never inherited by wildcard. Empty until the first approval."
31
31
  },
32
+ "mcp": {"$ref": "app-integrations-declaration.schema.json#/definitions/McpToolSelection"},
32
33
  "reviewedScope": { "$ref": "#/definitions/ReviewedIntegrationScope" },
33
34
  "expiresAt": { "type": "string", "format": "date-time", "description": "Absent only for explicitly approved ongoing production access." },
34
35
  "expiredAt": { "type": "string", "format": "date-time", "description": "Set by the recovery sweep once expiry has passed; enforcement itself is the UTC check on every call." },
@@ -137,6 +138,80 @@
137
138
  }
138
139
  }
139
140
  },
141
+ "allOf": [
142
+ {
143
+ "if": {
144
+ "properties": {
145
+ "kind": {
146
+ "const": "mcp"
147
+ }
148
+ }
149
+ },
150
+ "then": {
151
+ "properties": {
152
+ "mode": {
153
+ "const": "user"
154
+ },
155
+ "operations": {
156
+ "items": {
157
+ "const": "mcp.tool.call"
158
+ }
159
+ },
160
+ "resources": {
161
+ "maxItems": 1,
162
+ "items": {
163
+ "type": "object",
164
+ "additionalProperties": false,
165
+ "required": [
166
+ "name"
167
+ ],
168
+ "properties": {
169
+ "name": {
170
+ "const": "server"
171
+ }
172
+ }
173
+ }
174
+ }
175
+ },
176
+ "allOf": [
177
+ {
178
+ "if": {
179
+ "properties": {
180
+ "status": {
181
+ "const": "GRANTED"
182
+ }
183
+ }
184
+ },
185
+ "then": {
186
+ "required": [
187
+ "mcp"
188
+ ],
189
+ "properties": {
190
+ "operations": {
191
+ "minItems": 1
192
+ },
193
+ "resources": {
194
+ "minItems": 1
195
+ }
196
+ }
197
+ }
198
+ }
199
+ ]
200
+ },
201
+ "else": {
202
+ "properties": {
203
+ "mcp": false,
204
+ "operations": {
205
+ "not": {
206
+ "contains": {
207
+ "const": "mcp.tool.call"
208
+ }
209
+ }
210
+ }
211
+ }
212
+ }
213
+ }
214
+ ],
140
215
  "x-status-definition": "DataGrantV2Status",
141
216
  "x-terminal": ["DENIED", "REVOKED"],
142
217
  "x-transitions": [
@@ -63,7 +63,17 @@
63
63
  "UNAVAILABLE",
64
64
  "MODE_NOT_GRANTED",
65
65
  "MODE_REQUIRED",
66
- "SAMPLE_ROWS_NOT_ALLOWED"
66
+ "SAMPLE_ROWS_NOT_ALLOWED",
67
+ "CALL_IN_PROGRESS",
68
+ "CALL_OUTCOME_UNKNOWN",
69
+ "KEY_REUSED",
70
+ "MCP_TOOL_NOT_GRANTED",
71
+ "MCP_ARGUMENTS_INVALID",
72
+ "MCP_OUTPUT_INVALID",
73
+ "MCP_CONNECTION_FAILED",
74
+ "MCP_TOOL_ERROR",
75
+ "MCP_BUSY",
76
+ "MCP_OAUTH_SETUP_REQUIRED"
67
77
  ],
68
78
  "description": "Stable reason codes. SEND_OUTCOME_UNKNOWN: dispatch happened but the outcome is unknown; never re-post automatically. SEND_KEY_REUSED: the idempotency key was seen with a different payload or subject. GRANT_DENIED: IT denied the request. TOKEN_REFRESH_PENDING: another call holds the refresh lease; retry shortly. REQUEST_PENDING: a different request for this grant is still open. INPUT_INVALID: the operation input failed its bounds. IDEMPOTENCY_KEY_REQUIRED: a send without a UUID idempotencyKey. SEND_IN_PROGRESS: a retry arrived while the first send of that key is still running. CONNECTION_NOT_FOUND / APP_NOT_FOUND / GRANT_NOT_FOUND: the named record does not exist. AUTH_REQUIRED: no verified caller. UNAVAILABLE: the governance dependency (store, provider seam) is unreachable. MODE_NOT_GRANTED (403 FORBIDDEN, 0.55.0): a slack.message.post named a mode (app or user) IT has not approved for this app; the post is never served under the other mode. MODE_REQUIRED (400 VALIDATION_FAILED, 0.55.0): the app holds approved grants for both modes and the post named neither. SAMPLE_ROWS_NOT_ALLOWED (400 VALIDATION_FAILED, 0.74.0): an execute request carried sampleRows on any route but the development one. A sample value that does not parse as the column's recorded type is INPUT_INVALID, naming the column and the row."
69
79
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "https://schemas.harbour.dev/integration-execute-request.schema.json",
4
4
  "title": "IntegrationExecuteRequest",
5
- "description": "Body of POST /_harbour/integrations/execute (app SDK), forwarded unchanged to the governance development and runtime execute routes. Exactly one declared operation against one declared resource. The identity mode is fixed by the operation for every operation but the declared-mode Slack operations (slack.channel.history, slack.message.post, slack.message.update, slack.message.delete; 0.71.0 widened history), and always enforced from an approved grant; any identity/userId/scope field is rejected by additionalProperties. A declared-mode operation runs as the app (for a post: the IT-approved per-app name from the grant, plus a \"Posted by <app> on Isomorph\" footer) or as the signed-in person (no name, no footer) — the same input either way — and since 0.55.0 the call names which of the app's approved modes it runs under (`mode`): required when IT approved both modes for the app (MODE_REQUIRED otherwise), optional when it approved one (that one is used), and a named mode without an approved grant is refused (MODE_NOT_GRANTED) rather than served under the other, so approval order never decides what a call does. Per-operation input bounds (0.71.0, ADR 0018): slack.channel.history {limit? 1..100, default 15, cursor?, oldest?, latest?}; slack.message.post {text 1..4000 chars, threadId?, format? plain|mrkdwn} and a required idempotencyKey retained across retries of one explicit Send; slack.message.update {messageId, text, format?}; slack.message.delete {messageId}; slack.dm.send {to 1..8 addresses, text, format?} (app mode only) with a required idempotencyKey; slack.user.lookup {email}; gmail.thread.list {limit? 1..50, default 15, q?, labelIds? default [INBOX], pageToken?}; gmail.thread.read {threadId}; gmail.message.read {messageId}; gmail.attachment.read {messageId, attachmentId}; gmail.message.send {to 1..50 addresses (to+cc+bcc <= 50 in total), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, html? <= 64 KiB, attachments? 1..5 of <= 2 MiB each as base64, replyToMessageId?} and a required idempotencyKey like slack.message.post; warehouse.view.read {columns? 1..32, where? 1..16 filters, groupBy? 1..8 columns, aggregates? 1..16, orderBy? 1..8, limit? 1..1000, default 100} (0.70.0: a plain read names columns, an aggregate read names aggregates and optionally groupBy; the cross-field rules — exactly one of columns or aggregates, groupBy needs aggregates, a filter value that fits its op, orderBy naming a returned column, every named column approved — are enforced by governance, not expressible here; ADR 0017). Basecamp (0.76.0, ADR 0022; every id a string of 1..20 digits, every operation user mode): basecamp.project.list {pageToken?, projectStatus? active|archived} on the fixed `account` alias; on a project alias basecamp.todolist.list {pageToken?}; basecamp.todo.list {todolistId, completed?, pageToken?}; basecamp.todo.create {todolistId, content 1..1000 (the to-do title), description? <= 16384, contentFormat? plain|html, dueOn? YYYY-MM-DD, assigneeIds? 1..20, notify?} with a required idempotencyKey; basecamp.todo.complete {todoId} (idempotent at Basecamp, so no key); basecamp.message.list {pageToken?}; basecamp.message.post {subject 1..256, content 1..65536, contentFormat?} with a required idempotencyKey; basecamp.comment.list {recordingId, pageToken?}; basecamp.comment.post {recordingId, content 1..16384, contentFormat?} with a required idempotencyKey; basecamp.campfire.post {content 1..4000, contentFormat?} with a required idempotencyKey; basecamp.people.list {pageToken?}. `pageToken` is the previous page's `nextPageToken` (a Basecamp page number as digits). SharePoint (0.78.0, ADR 0024; every id a Microsoft Graph id — GraphId, 1..256 characters — every operation user mode on a site alias): sharepoint.list.list {pageToken?}; sharepoint.item.list {listId, filter? 1..512 (an OData filter on `fields/...`), pageToken?}; sharepoint.item.create {listId, fields (1..32 columns, each a string <= 4000, a number, a boolean or null)} with a required idempotencyKey; sharepoint.item.update {listId, itemId, fields} (a PATCH, idempotent at Graph, so no key); sharepoint.drive.list {pageToken?}; sharepoint.file.list {driveId?, folderId?, pageToken?} (no driveId: the site's default drive; no folderId: its root); sharepoint.file.search {q 1..256, driveId?, pageToken?}; sharepoint.file.read {fileId, driveId?}; sharepoint.file.upload {filename 1..255, contentType, dataBase64 <= 2 MiB, driveId?, folderId?, conflict? rename|replace|fail} with a required idempotencyKey. A SharePoint `pageToken` is Graph's `$skiptoken` (up to 2048 characters; the shared field is that wide, the Gmail and Basecamp operations narrow it back to 256). Google Workspace and Microsoft 365 (0.86.0, ADR 0032; every operation user mode; the seven personal providers run on their fixed alias, teams.* on a channel alias): calendar.calendar.list {pageToken?}; calendar.event.list {calendarId?, timeMin, timeMax, q?, pageToken?} (RFC 3339 date-times with a zone); calendar.event.read {calendarId?, eventId}; calendar.event.create {calendarId?, title 1..256, start, end, timeZone?, allDay?, description? <= 8000, location? <= 512, attendees? 1..100 emails, notify?} with a required idempotencyKey; calendar.event.update {calendarId?, eventId, title?, start?, end?, timeZone?, description?, location?, attendees?, notify?} and calendar.event.cancel {calendarId?, eventId, notify?} (both idempotent at Google, so no key); drive.file.list {folderId?, pageToken?}; drive.file.search {q, pageToken?}; drive.file.read {fileId, exportAs? text|csv|pdf} (exportAs renders a Google Docs, Sheets or Slides file; a binary file is downloaded as is); drive.file.upload {filename, contentType, dataBase64 <= 2 MiB, folderId?, conflict?} and drive.folder.create {title, folderId?} with a required idempotencyKey; docs.document.read {documentId}; docs.document.create {title, text? <= 64 KiB} with a required idempotencyKey; docs.document.append {documentId, text <= 64 KiB} and docs.text.replace {documentId, replacements 1..64 of {find 1..512, replace <= 4000, matchCase?}} (no key: the app owns the document's state); sheets.spreadsheet.read {spreadsheetId}; sheets.values.read {spreadsheetId, range} (A1 notation, 1..256); sheets.values.append {spreadsheetId, range, values, valueInput? raw|user} with a required idempotencyKey; sheets.values.update {spreadsheetId, range, values, valueInput?} and sheets.values.clear {spreadsheetId, range} (no key); sheets.spreadsheet.create {title, sheets? 1..20 titles} with a required idempotencyKey (`values` is 1..1000 rows of 1..100 cells, each a string <= 4000, a number, a boolean or null); outlook.message.list {folder?, q?, pageToken?}; outlook.message.read {messageId}; outlook.attachment.read {messageId, attachmentId}; outlook.message.send {to, cc?, bcc?, subject, text 1..4000, html?, attachments?} and outlook.message.reply {messageId, text 1..4000, attachments?} (0.95.0: existing EmailAttachment bounds; recipients remain provider-resolved) with a required idempotencyKey like gmail.message.send; outlook.event.list {start, end, pageToken?}; outlook.event.read {eventId}; outlook.event.create (as calendar.event.create without calendarId) with a required idempotencyKey; outlook.event.update (as calendar.event.update without calendarId) and outlook.event.cancel {eventId, comment? <= 4000} (no key); onedrive.file.list, onedrive.file.search, onedrive.file.read {fileId}, onedrive.file.upload and onedrive.folder.create as the drive.* operations (Graph ids, no exportAs); excel.worksheet.list {fileId, driveId?}; excel.range.read {fileId, driveId?, worksheet, address? 1..64} (no address: the used range); excel.range.update {fileId, driveId?, worksheet, address, values} (no key: a PATCH); excel.table.list {fileId, driveId?, worksheet?}; excel.row.add {fileId, driveId?, tableName, values} and excel.worksheet.add {fileId, driveId?, title} with a required idempotencyKey; teams.message.list {pageToken?}; teams.message.post {text 1..4000, html?} and teams.reply.post {messageId, text, html?} with a required idempotencyKey. An Outlook or Teams `messageId` is a Graph id (the shared field widens to ^[A-Za-z0-9._=-]{1,256}$; the Gmail and Slack operations narrow it back to ^[A-Za-z0-9._-]{1,64}$ in their then-blocks, so their rules are unchanged), and `text` widens to 64 KiB for the Docs body (every other operation that takes it narrows it back to 4000). Each operation's then-block is the one statement of its input rules: propertyNames.enum names the fields it accepts, required the ones it needs, and per-field overrides (maximum, default) narrow the shared property. Codegen derives IntegrationExecuteInputRules and validateIntegrationExecuteInput (gen-ts/integration-execute-input-rules.ts, gen-go/integration_execute_input_rules.go) from exactly these blocks, so the governance execute route, the app SDK and the kit's local check refuse the same request with the same message. Transport limits (not expressible here): request <= 16 KiB except a gmail.message.send carrying attachments (governance sizes that route for the attachment bound) and a basecamp.message.post (its 64 KiB body; governance allows 80 KiB there) and a sharepoint.file.upload, drive.file.upload or onedrive.file.upload (its 2 MiB file as base64; governance sizes it like the Gmail attachment route), an outlook.message.send or outlook.message.reply with attachments (the Gmail route), a docs.document.create or docs.document.append (its 64 KiB body; governance allows 80 KiB there, as for basecamp.message.post) and a sheets.values.append, sheets.values.update, excel.range.update or excel.row.add (its 64 KiB `values`), total deadline 10 s.",
5
+ "description": "Body of POST /_harbour/integrations/execute (app SDK), forwarded unchanged to the governance development and runtime execute routes. Exactly one declared operation against one declared resource. The identity mode is fixed by the operation for every operation but the declared-mode Slack operations (slack.channel.history, slack.message.post, slack.message.update, slack.message.delete; 0.71.0 widened history), and always enforced from an approved grant; any identity/userId/scope field is rejected by additionalProperties. A declared-mode operation runs as the app (for a post: the IT-approved per-app name from the grant, plus a \"Posted by <app> on Isomorph\" footer) or as the signed-in person (no name, no footer) \u2014 the same input either way \u2014 and since 0.55.0 the call names which of the app's approved modes it runs under (`mode`): required when IT approved both modes for the app (MODE_REQUIRED otherwise), optional when it approved one (that one is used), and a named mode without an approved grant is refused (MODE_NOT_GRANTED) rather than served under the other, so approval order never decides what a call does. Per-operation input bounds (0.71.0, ADR 0018): slack.channel.history {limit? 1..100, default 15, cursor?, oldest?, latest?}; slack.message.post {text 1..4000 chars, threadId?, format? plain|mrkdwn} and a required idempotencyKey retained across retries of one explicit Send; slack.message.update {messageId, text, format?}; slack.message.delete {messageId}; slack.dm.send {to 1..8 addresses, text, format?} (app mode only) with a required idempotencyKey; slack.user.lookup {email}; gmail.thread.list {limit? 1..50, default 15, q?, labelIds? default [INBOX], pageToken?}; gmail.thread.read {threadId}; gmail.message.read {messageId}; gmail.attachment.read {messageId, attachmentId}; gmail.message.send {to 1..50 addresses (to+cc+bcc <= 50 in total), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, html? <= 64 KiB, attachments? 1..5 of <= 2 MiB each as base64, replyToMessageId?} and a required idempotencyKey like slack.message.post; warehouse.view.read {columns? 1..32, where? 1..16 filters, groupBy? 1..8 columns, aggregates? 1..16, orderBy? 1..8, limit? 1..1000, default 100} (0.70.0: a plain read names columns, an aggregate read names aggregates and optionally groupBy; the cross-field rules \u2014 exactly one of columns or aggregates, groupBy needs aggregates, a filter value that fits its op, orderBy naming a returned column, every named column approved \u2014 are enforced by governance, not expressible here; ADR 0017). Basecamp (0.76.0, ADR 0022; every id a string of 1..20 digits, every operation user mode): basecamp.project.list {pageToken?, projectStatus? active|archived} on the fixed `account` alias; on a project alias basecamp.todolist.list {pageToken?}; basecamp.todo.list {todolistId, completed?, pageToken?}; basecamp.todo.create {todolistId, content 1..1000 (the to-do title), description? <= 16384, contentFormat? plain|html, dueOn? YYYY-MM-DD, assigneeIds? 1..20, notify?} with a required idempotencyKey; basecamp.todo.complete {todoId} (idempotent at Basecamp, so no key); basecamp.message.list {pageToken?}; basecamp.message.post {subject 1..256, content 1..65536, contentFormat?} with a required idempotencyKey; basecamp.comment.list {recordingId, pageToken?}; basecamp.comment.post {recordingId, content 1..16384, contentFormat?} with a required idempotencyKey; basecamp.campfire.post {content 1..4000, contentFormat?} with a required idempotencyKey; basecamp.people.list {pageToken?}. `pageToken` is the previous page's `nextPageToken` (a Basecamp page number as digits). SharePoint (0.78.0, ADR 0024; every id a Microsoft Graph id \u2014 GraphId, 1..256 characters \u2014 every operation user mode on a site alias): sharepoint.list.list {pageToken?}; sharepoint.item.list {listId, filter? 1..512 (an OData filter on `fields/...`), pageToken?}; sharepoint.item.create {listId, fields (1..32 columns, each a string <= 4000, a number, a boolean or null)} with a required idempotencyKey; sharepoint.item.update {listId, itemId, fields} (a PATCH, idempotent at Graph, so no key); sharepoint.drive.list {pageToken?}; sharepoint.file.list {driveId?, folderId?, pageToken?} (no driveId: the site's default drive; no folderId: its root); sharepoint.file.search {q 1..256, driveId?, pageToken?}; sharepoint.file.read {fileId, driveId?}; sharepoint.file.upload {filename 1..255, contentType, dataBase64 <= 2 MiB, driveId?, folderId?, conflict? rename|replace|fail} with a required idempotencyKey. A SharePoint `pageToken` is Graph's `$skiptoken` (up to 2048 characters; the shared field is that wide, the Gmail and Basecamp operations narrow it back to 256). Google Workspace and Microsoft 365 (0.86.0, ADR 0032; every operation user mode; the seven personal providers run on their fixed alias, teams.* on a channel alias): calendar.calendar.list {pageToken?}; calendar.event.list {calendarId?, timeMin, timeMax, q?, pageToken?} (RFC 3339 date-times with a zone); calendar.event.read {calendarId?, eventId}; calendar.event.create {calendarId?, title 1..256, start, end, timeZone?, allDay?, description? <= 8000, location? <= 512, attendees? 1..100 emails, notify?} with a required idempotencyKey; calendar.event.update {calendarId?, eventId, title?, start?, end?, timeZone?, description?, location?, attendees?, notify?} and calendar.event.cancel {calendarId?, eventId, notify?} (both idempotent at Google, so no key); drive.file.list {folderId?, pageToken?}; drive.file.search {q, pageToken?}; drive.file.read {fileId, exportAs? text|csv|pdf} (exportAs renders a Google Docs, Sheets or Slides file; a binary file is downloaded as is); drive.file.upload {filename, contentType, dataBase64 <= 2 MiB, folderId?, conflict?} and drive.folder.create {title, folderId?} with a required idempotencyKey; docs.document.read {documentId}; docs.document.create {title, text? <= 64 KiB} with a required idempotencyKey; docs.document.append {documentId, text <= 64 KiB} and docs.text.replace {documentId, replacements 1..64 of {find 1..512, replace <= 4000, matchCase?}} (no key: the app owns the document's state); sheets.spreadsheet.read {spreadsheetId}; sheets.values.read {spreadsheetId, range} (A1 notation, 1..256); sheets.values.append {spreadsheetId, range, values, valueInput? raw|user} with a required idempotencyKey; sheets.values.update {spreadsheetId, range, values, valueInput?} and sheets.values.clear {spreadsheetId, range} (no key); sheets.spreadsheet.create {title, sheets? 1..20 titles} with a required idempotencyKey (`values` is 1..1000 rows of 1..100 cells, each a string <= 4000, a number, a boolean or null); outlook.message.list {folder?, q?, pageToken?}; outlook.message.read {messageId}; outlook.attachment.read {messageId, attachmentId}; outlook.message.send {to, cc?, bcc?, subject, text 1..4000, html?, attachments?} and outlook.message.reply {messageId, text 1..4000, attachments?} (0.95.0: existing EmailAttachment bounds; recipients remain provider-resolved) with a required idempotencyKey like gmail.message.send; outlook.event.list {start, end, pageToken?}; outlook.event.read {eventId}; outlook.event.create (as calendar.event.create without calendarId) with a required idempotencyKey; outlook.event.update (as calendar.event.update without calendarId) and outlook.event.cancel {eventId, comment? <= 4000} (no key); onedrive.file.list, onedrive.file.search, onedrive.file.read {fileId}, onedrive.file.upload and onedrive.folder.create as the drive.* operations (Graph ids, no exportAs); excel.worksheet.list {fileId, driveId?}; excel.range.read {fileId, driveId?, worksheet, address? 1..64} (no address: the used range); excel.range.update {fileId, driveId?, worksheet, address, values} (no key: a PATCH); excel.table.list {fileId, driveId?, worksheet?}; excel.row.add {fileId, driveId?, tableName, values} and excel.worksheet.add {fileId, driveId?, title} with a required idempotencyKey; teams.message.list {pageToken?}; teams.message.post {text 1..4000, html?} and teams.reply.post {messageId, text, html?} with a required idempotencyKey. An Outlook or Teams `messageId` is a Graph id (the shared field widens to ^[A-Za-z0-9._=-]{1,256}$; the Gmail and Slack operations narrow it back to ^[A-Za-z0-9._-]{1,64}$ in their then-blocks, so their rules are unchanged), and `text` widens to 64 KiB for the Docs body (every other operation that takes it narrows it back to 4000). Each operation's then-block is the one statement of its input rules: propertyNames.enum names the fields it accepts, required the ones it needs, and per-field overrides (maximum, default) narrow the shared property. Codegen derives IntegrationExecuteInputRules and validateIntegrationExecuteInput (gen-ts/integration-execute-input-rules.ts, gen-go/integration_execute_input_rules.go) from exactly these blocks, so the governance execute route, the app SDK and the kit's local check refuse the same request with the same message. Transport limits (not expressible here): request <= 16 KiB except a gmail.message.send carrying attachments (governance sizes that route for the attachment bound) and a basecamp.message.post (its 64 KiB body; governance allows 80 KiB there) and a sharepoint.file.upload, drive.file.upload or onedrive.file.upload (its 2 MiB file as base64; governance sizes it like the Gmail attachment route), an outlook.message.send or outlook.message.reply with attachments (the Gmail route), a docs.document.create or docs.document.append (its 64 KiB body; governance allows 80 KiB there, as for basecamp.message.post) and a sheets.values.append, sheets.values.update, excel.range.update or excel.row.add (its 64 KiB `values`), total deadline 10 s.",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": [
@@ -153,7 +153,7 @@
153
153
  },
154
154
  "recordingId": {
155
155
  "$ref": "app-integrations-declaration.schema.json#/definitions/BasecampId",
156
- "description": "basecamp.comment.list / basecamp.comment.post (0.76.0): the Basecamp recording commented on — a message, a to-do or any other id a basecamp.* result named."
156
+ "description": "basecamp.comment.list / basecamp.comment.post (0.76.0): the Basecamp recording commented on \u2014 a message, a to-do or any other id a basecamp.* result named."
157
157
  },
158
158
  "content": {
159
159
  "type": "string",
@@ -267,7 +267,7 @@
267
267
  "type": "string",
268
268
  "minLength": 1,
269
269
  "maxLength": 2796203,
270
- "description": "sharepoint.file.upload (0.78.0): the file bytes, standard base64, at most 2 MiB of bytes (2,796,203 base64 characters) — the Gmail attachment bound. drive.file.upload / onedrive.file.upload (0.86.0): the same bound."
270
+ "description": "sharepoint.file.upload (0.78.0): the file bytes, standard base64, at most 2 MiB of bytes (2,796,203 base64 characters) \u2014 the Gmail attachment bound. drive.file.upload / onedrive.file.upload (0.86.0): the same bound."
271
271
  },
272
272
  "columns": {
273
273
  "type": "array",
@@ -385,7 +385,7 @@
385
385
  }
386
386
  ]
387
387
  },
388
- "description": "One row keyed by column name, 1..64 columns; each value a string of at most 4000 characters, a number, a boolean or null. Which columns the alias approves, and whether a value fits its column type (the catalog's columnTypes, which only governance holds), is governance's check — the way warehouse.view.read's cross-field rules are."
388
+ "description": "One row keyed by column name, 1..64 columns; each value a string of at most 4000 characters, a number, a boolean or null. Which columns the alias approves, and whether a value fits its column type (the catalog's columnTypes, which only governance holds), is governance's check \u2014 the way warehouse.view.read's cross-field rules are."
389
389
  },
390
390
  "description": "warehouse.table.append (0.82.0; 1..5000 since 0.85.0): the rows to append, 1..5000 of them, every one a record of the approved columns. Governance refuses an unapproved column, a value outside its column type and a request above its byte cap (8 MiB) with ROW_VALUE_INVALID; nothing is coerced."
391
391
  },
@@ -524,7 +524,7 @@
524
524
  "items": {
525
525
  "$ref": "#/definitions/DocsReplacement"
526
526
  },
527
- "description": "docs.text.replace (0.86.0): the find-and-replace pairs applied over the whole document, 1..64, in order (a mail merge: `{{customer}}` → the name)."
527
+ "description": "docs.text.replace (0.86.0): the find-and-replace pairs applied over the whole document, 1..64, in order (a mail merge: `{{customer}}` \u2192 the name)."
528
528
  },
529
529
  "worksheet": {
530
530
  "type": "string",
@@ -654,17 +654,32 @@
654
654
  }
655
655
  }
656
656
  }
657
+ },
658
+ "tool": {
659
+ "$ref": "app-integrations-declaration.schema.json#/definitions/McpToolName"
660
+ },
661
+ "arguments": {
662
+ "type": "object",
663
+ "additionalProperties": true,
664
+ "description": "Validated against the accepted tool inputSchema at runtime; arbitrary nested provider arguments are permitted only here."
665
+ },
666
+ "orderStatus": {
667
+ "type": "string",
668
+ "enum": [
669
+ "all"
670
+ ],
671
+ "description": "Zoho salesorder.list: include closed and previously invoiced orders for read-only historical reconciliation; omitted keeps open orders only."
657
672
  }
658
673
  }
659
674
  },
660
675
  "idempotencyKey": {
661
676
  "type": "string",
662
677
  "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
663
- "description": "UUID of one explicit Send action; required for the sends (slack.message.post, slack.dm.send, gmail.message.send), the receipted Basecamp writes (basecamp.todo.create, basecamp.message.post, basecamp.comment.post, basecamp.campfire.post; 0.76.0) the receipted SharePoint writes (sharepoint.item.create, sharepoint.file.upload; 0.78.0) the receipted warehouse append (warehouse.table.append; 0.82.0, where it is also the BigQuery load job id) and the receipted Google Workspace and Microsoft 365 writes (0.86.0: calendar.event.create, drive.file.upload, drive.folder.create, docs.document.create, sheets.values.append, sheets.spreadsheet.create, outlook.message.send, outlook.message.reply, outlook.event.create, onedrive.file.upload, onedrive.folder.create, excel.row.add, excel.worksheet.add, teams.message.post, teams.reply.post), refused everywhere else. The same key with the same payload and subject returns the recorded outcome for seven days."
678
+ "description": "UUID of one explicit Send action; required for the sends (slack.message.post, slack.dm.send, gmail.message.send), the receipted Basecamp writes (basecamp.todo.create, basecamp.message.post, basecamp.comment.post, basecamp.campfire.post; 0.76.0) the receipted SharePoint writes (sharepoint.item.create, sharepoint.file.upload; 0.78.0) the receipted warehouse append (warehouse.table.append; 0.82.0, where it is also the BigQuery load job id) and the receipted Google Workspace and Microsoft 365 writes (0.86.0: calendar.event.create, drive.file.upload, drive.folder.create, docs.document.create, sheets.values.append, sheets.spreadsheet.create, outlook.message.send, outlook.message.reply, outlook.event.create, onedrive.file.upload, onedrive.folder.create, excel.row.add, excel.worksheet.add, teams.message.post, teams.reply.post), refused everywhere else. The same key with the same payload and subject returns the recorded outcome for seven days. MCP permits omission only for an accepted readOnlyHint=true descriptor; runtime requires the UUID for unknown/write tools."
664
679
  },
665
680
  "mode": {
666
681
  "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationIdentityMode",
667
- "description": "The declared-mode Slack operations only (slack.message.post since 0.55.0; slack.channel.history, slack.message.update and slack.message.delete since 0.71.0): which of the app's IT-approved modes this call runs under — app (the company's Slack app; a post goes out under the grant's per-app name and the footer) or user (the signed-in person, after their consent; no name, no footer). Required when the app holds approved grants for both modes (else MODE_REQUIRED); may be omitted when it holds one, which is then used. A named mode without an approved grant is refused with MODE_NOT_GRANTED; governance never falls back to the other mode. Every other operation's mode is fixed, so the field is refused there."
682
+ "description": "The declared-mode Slack operations only (slack.message.post since 0.55.0; slack.channel.history, slack.message.update and slack.message.delete since 0.71.0): which of the app's IT-approved modes this call runs under \u2014 app (the company's Slack app; a post goes out under the grant's per-app name and the footer) or user (the signed-in person, after their consent; no name, no footer). Required when the app holds approved grants for both modes (else MODE_REQUIRED); may be omitted when it holds one, which is then used. A named mode without an approved grant is refused with MODE_NOT_GRANTED; governance never falls back to the other mode. Every other operation's mode is fixed, so the field is refused there. MCP accepts explicit user only; shared app mode is deferred."
668
683
  },
669
684
  "sampleRows": {
670
685
  "type": "array",
@@ -3860,7 +3875,8 @@
3860
3875
  "propertyNames": {
3861
3876
  "enum": [
3862
3877
  "customerId",
3863
- "pageToken"
3878
+ "pageToken",
3879
+ "orderStatus"
3864
3880
  ]
3865
3881
  },
3866
3882
  "required": [
@@ -4110,6 +4126,43 @@
4110
4126
  ]
4111
4127
  }
4112
4128
  }
4129
+ },
4130
+ {
4131
+ "if": {
4132
+ "properties": {
4133
+ "operation": {
4134
+ "const": "mcp.tool.call"
4135
+ }
4136
+ }
4137
+ },
4138
+ "then": {
4139
+ "x-read-only": false,
4140
+ "x-runtime-idempotency": true,
4141
+ "x-primary-identity": "user",
4142
+ "required": [
4143
+ "resource"
4144
+ ],
4145
+ "properties": {
4146
+ "resource": {
4147
+ "const": "server"
4148
+ },
4149
+ "mode": {
4150
+ "const": "user"
4151
+ },
4152
+ "input": {
4153
+ "propertyNames": {
4154
+ "enum": [
4155
+ "tool",
4156
+ "arguments"
4157
+ ]
4158
+ },
4159
+ "required": [
4160
+ "tool",
4161
+ "arguments"
4162
+ ]
4163
+ }
4164
+ }
4165
+ }
4113
4166
  }
4114
4167
  ],
4115
4168
  "definitions": {
@@ -5527,6 +5580,24 @@
5527
5580
  "input": {
5528
5581
  "documentId": "123"
5529
5582
  }
5583
+ },
5584
+ {
5585
+ "connection": "fixture-mcp",
5586
+ "operation": "mcp.tool.call",
5587
+ "resource": "server",
5588
+ "mode": "user",
5589
+ "input": {
5590
+ "tool": "fixture.Read",
5591
+ "arguments": {
5592
+ "filter": {
5593
+ "status": [
5594
+ "open",
5595
+ null,
5596
+ true
5597
+ ]
5598
+ }
5599
+ }
5600
+ }
5530
5601
  }
5531
5602
  ]
5532
5603
  }