@adrata/adrata-mcp 1.0.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 (41) hide show
  1. package/README.md +548 -0
  2. package/access/auth.js +289 -0
  3. package/access/oauth.js +1059 -0
  4. package/access/resource-metadata.js +167 -0
  5. package/access/tiers.js +422 -0
  6. package/analytics.js +634 -0
  7. package/api-bridge.js +499 -0
  8. package/governance/money.js +141 -0
  9. package/output-formatter.js +589 -0
  10. package/package.json +68 -0
  11. package/resources.js +246 -0
  12. package/security.js +690 -0
  13. package/server.js +2139 -0
  14. package/server.json +55 -0
  15. package/skills/backlog-triage/SKILL.md +115 -0
  16. package/skills/board-review/SKILL.md +96 -0
  17. package/skills/incident-to-card/SKILL.md +126 -0
  18. package/skills/log-outreach.md +62 -0
  19. package/skills/ship-the-card/SKILL.md +155 -0
  20. package/tool-annotations.js +269 -0
  21. package/tools/billing.js +149 -0
  22. package/tools/email-tools.js +652 -0
  23. package/tools/enterprise-tools.js +651 -0
  24. package/tools/free-search.js +160 -0
  25. package/tools/memory.js +440 -0
  26. package/tools/morning-brief.js +551 -0
  27. package/tools/paper-tools.js +563 -0
  28. package/tools/scheduling.js +322 -0
  29. package/tools/work-board-tools.js +758 -0
  30. package/toolsets/communications.js +276 -0
  31. package/toolsets/crm.js +495 -0
  32. package/toolsets/extensibility.js +1131 -0
  33. package/toolsets/infrastructure.js +757 -0
  34. package/toolsets/intelligence.js +232 -0
  35. package/toolsets/knowledge.js +154 -0
  36. package/toolsets/matrix.js +217 -0
  37. package/toolsets/outreach.js +432 -0
  38. package/toolsets/prospecting.js +314 -0
  39. package/toolsets/revenue/always-loaded.js +341 -0
  40. package/toolsets/revenue/sloan-tools.js +81 -0
  41. package/transport-http.js +505 -0
@@ -0,0 +1,563 @@
1
+ import { z } from 'zod';
2
+
3
+ const PAPER_DOC_TYPE = 'paper';
4
+ const paperVisibility = z.enum(['workspace', 'owner_private']);
5
+
6
+ const writeControls = {
7
+ dryRun: z.boolean().optional().describe('Defaults to true. Set false only after explicit user approval.'),
8
+ approved: z.boolean().optional().describe('Must be true for live writes.'),
9
+ reason: z.string().optional().describe('Audit reason for live writes.'),
10
+ idempotencyKey: z.string().optional().describe('Required for live writes.'),
11
+ };
12
+
13
+ const shareTier = z.enum(['public', 'internal', 'confidential', 'highly_confidential']);
14
+
15
+ function requireLiveWrite(args, preview) {
16
+ if (args.dryRun !== false) {
17
+ return {
18
+ dryRun: true,
19
+ wouldSend: true,
20
+ requiredForLiveWrite: ['dryRun:false', 'approved:true', 'reason', 'idempotencyKey'],
21
+ preview,
22
+ };
23
+ }
24
+ if (args.approved !== true) throw new Error('live writes require approved:true');
25
+ if (!args.reason || !String(args.reason).trim()) throw new Error('live writes require a reason');
26
+ if (!args.idempotencyKey || !String(args.idempotencyKey).trim()) {
27
+ throw new Error('live writes require an idempotencyKey');
28
+ }
29
+ return null;
30
+ }
31
+
32
+ function mutationHeaders(args, buildMutationHeaders) {
33
+ return buildMutationHeaders({
34
+ reason: args.reason,
35
+ idempotencyKey: args.idempotencyKey,
36
+ actor: 'mcp-paper',
37
+ });
38
+ }
39
+
40
+ function cleanBody(fields) {
41
+ const body = {};
42
+ for (const [key, value] of Object.entries(fields)) {
43
+ if (value !== undefined) body[key] = value;
44
+ }
45
+ return body;
46
+ }
47
+
48
+ function paperAudit() {
49
+ return {
50
+ app: {
51
+ name: 'Paper',
52
+ bundleId: 'com.paper.desktop',
53
+ localCache: 'paper.docs.v1',
54
+ deepLinkScheme: 'paper://',
55
+ documentType: PAPER_DOC_TYPE,
56
+ },
57
+ coverage: [
58
+ {
59
+ surface: 'Documents',
60
+ appRoutes: ['GET /api/v1/documents?type=paper&limit=100', 'POST /api/v1/documents', 'PUT /api/v1/documents/{id}', 'DELETE /api/v1/documents/{id}'],
61
+ mcpTools: ['paper_list_documents', 'paper_get_document', 'paper_create_document', 'paper_update_document', 'paper_archive_document', 'paper_delete_document'],
62
+ status: 'covered',
63
+ },
64
+ {
65
+ surface: 'Secure sharing',
66
+ appRoutes: ['POST /api/v1/shares', 'GET /api/v1/shares', 'DELETE /api/v1/shares/{id}', 'GET /api/v1/shares/resolve/{token}'],
67
+ mcpTools: ['paper_create_share', 'paper_list_shares', 'paper_revoke_share', 'paper_resolve_share'],
68
+ status: 'covered',
69
+ },
70
+ {
71
+ surface: 'Tasks',
72
+ appRoutes: ['GET /api/v1/actions?viewMode=seller', 'POST /api/v1/actions', 'PATCH /api/v1/actions/{id}'],
73
+ mcpTools: ['paper_list_tasks', 'paper_create_task', 'paper_update_task'],
74
+ status: 'covered',
75
+ },
76
+ {
77
+ surface: 'Calendar',
78
+ appRoutes: ['GET /api/v2/events/calendar', 'POST /api/v1/events'],
79
+ mcpTools: ['paper_list_calendar_events', 'paper_create_calendar_event'],
80
+ status: 'covered',
81
+ },
82
+ {
83
+ surface: 'Email',
84
+ appRoutes: ['GET /api/v2/emails/threads', 'GET /api/v1/emails/threads/{id}', 'POST /api/v2/emails/direct'],
85
+ mcpTools: ['paper_list_email_threads', 'paper_get_email_thread', 'paper_send_email'],
86
+ status: 'covered',
87
+ },
88
+ {
89
+ surface: 'Company context',
90
+ appRoutes: ['GET /api/v1/companies/{id}'],
91
+ mcpTools: ['paper_get_company'],
92
+ status: 'covered',
93
+ },
94
+ {
95
+ surface: 'Mailbox OAuth connections',
96
+ appRoutes: ['GET /api/v1/oauth/email/providers', 'POST /api/v1/oauth/email/initiate', 'DELETE /api/v1/oauth/email/providers/{id}'],
97
+ mcpTools: [],
98
+ status: 'intentionally UI-led',
99
+ reason: 'OAuth initiation and disconnect affect live email/calendar integrations and should remain user-driven in Paper settings or Adrata auth flows.',
100
+ },
101
+ {
102
+ surface: 'Local-only cache',
103
+ storage: 'Chromium Local Storage LevelDB / paper.docs.v1',
104
+ mcpTools: [],
105
+ status: 'intentionally not covered',
106
+ reason: 'Direct cache writes bypass auth, sync reconciliation, audit, and conflict handling.',
107
+ },
108
+ ],
109
+ };
110
+ }
111
+
112
+ function adrataDesktopAudit() {
113
+ return {
114
+ app: {
115
+ name: 'Adrata',
116
+ bundleId: 'com.adrata.desktop',
117
+ deepLinkScheme: 'adrata://',
118
+ connectionModes: ['connect_workspace OAuth', 'ADRATA_OAUTH_TOKEN', 'Adrata CLI config'],
119
+ },
120
+ coverage: [
121
+ {
122
+ surface: 'Workspace connection',
123
+ mcpTools: ['connect_workspace', 'workspace_status', 'disconnect_workspace'],
124
+ status: 'covered',
125
+ },
126
+ {
127
+ surface: 'Adrata chat tool parity',
128
+ mcpTools: ['adrata_ai_tool_catalog', 'adrata_ai_tool_execute'],
129
+ status: 'covered',
130
+ reason: 'Uses the same governed AI CRM tool dispatcher as Adrata chat when a workspace is connected.',
131
+ },
132
+ {
133
+ surface: 'CRM and revenue records',
134
+ mcpTools: ['search_companies', 'get_company', 'create_company', 'update_company', 'search_people', 'get_person', 'search_opportunities', 'create_opportunity', 'list_actions', 'create_action', 'list_notes', 'create_note'],
135
+ status: 'covered',
136
+ },
137
+ {
138
+ surface: 'Build Pipeline',
139
+ mcpTools: ['list_external_pipelines', 'list_external_pipeline_members', 'import_external_pipeline_members', 'list_external_companies', 'import_external_companies', 'rank_companies_by_icp', 'check_batch_import_status', 'qualify_company', 'research_company', 'research_person', 'discover_prospects', 'get_next_contacts'],
140
+ status: 'covered',
141
+ },
142
+ {
143
+ surface: 'Win Pipeline',
144
+ mcpTools: ['get_account_read', 'rank_paths_to_power', 'recommend_deal_move', 'move_pipeline_card', 'get_access_score', 'get_path_to_power', 'find_intro_path', 'get_deal_coaching', 'get_forecast', 'schedule_meeting'],
145
+ status: 'covered',
146
+ },
147
+ {
148
+ surface: 'Low-level API bridge',
149
+ mcpTools: ['adrata_api_catalog', 'adrata_api_request'],
150
+ status: 'covered',
151
+ reason: 'Governed escape hatch with allowlisted API paths and dry-run write protection.',
152
+ },
153
+ {
154
+ surface: 'Mailbox OAuth connections',
155
+ mcpTools: [],
156
+ status: 'intentionally UI-led',
157
+ reason: 'Provider consent and disconnect flows affect live integrations and should stay user-driven.',
158
+ },
159
+ ],
160
+ };
161
+ }
162
+
163
+ export function registerPaperTools(server, { api, ok, buildMutationHeaders }) {
164
+ server.tool(
165
+ 'paper_app_audit',
166
+ 'Audit Paper app MCP coverage. Returns the Paper app API surfaces, local cache model, and which MCP tools cover them.',
167
+ {},
168
+ async () => ok(paperAudit()),
169
+ );
170
+
171
+ server.tool(
172
+ 'adrata_desktop_app_audit',
173
+ 'Audit Adrata desktop app MCP coverage. Returns which existing MCP tools cover Adrata desktop revenue, pipeline, and API surfaces.',
174
+ {},
175
+ async () => ok(adrataDesktopAudit()),
176
+ );
177
+
178
+ server.tool(
179
+ 'paper_list_documents',
180
+ 'List Paper documents from the active workspace. Paper documents are Adrata documents with type=paper.',
181
+ {
182
+ limit: z.number().int().min(1).max(100).default(100),
183
+ page: z.number().int().min(1).default(1),
184
+ status: z.string().optional(),
185
+ folderId: z.string().optional(),
186
+ },
187
+ async (args) => ok(await api('GET', '/api/v1/documents', {
188
+ params: {
189
+ type: PAPER_DOC_TYPE,
190
+ limit: args.limit,
191
+ page: args.page,
192
+ status: args.status,
193
+ folderId: args.folderId,
194
+ },
195
+ })),
196
+ );
197
+
198
+ server.tool(
199
+ 'paper_get_document',
200
+ 'Get a Paper document by id from the active workspace.',
201
+ { id: z.string() },
202
+ async (args) => ok(await api('GET', `/api/v1/documents/${encodeURIComponent(args.id)}`)),
203
+ );
204
+
205
+ server.tool(
206
+ 'paper_create_document',
207
+ 'Create a Paper document. Live writes require dryRun:false, approved:true, reason, and idempotencyKey.',
208
+ {
209
+ id: z.string().optional().describe('Optional stable ULID/id for idempotent offline-friendly creation.'),
210
+ title: z.string().min(1),
211
+ content: z.string().optional().describe('HTML content. Paper stores this as local body on hydrate.'),
212
+ status: z.string().default('draft'),
213
+ visibility: paperVisibility.default('workspace'),
214
+ metadata: z.record(z.unknown()).optional(),
215
+ ...writeControls,
216
+ },
217
+ async (args) => {
218
+ try {
219
+ const payload = cleanBody({
220
+ id: args.id,
221
+ title: args.title,
222
+ content: args.content ?? '',
223
+ type: PAPER_DOC_TYPE,
224
+ status: args.status,
225
+ visibility: args.visibility,
226
+ metadata: {
227
+ source: 'mcp',
228
+ app: 'paper',
229
+ ...(args.metadata || {}),
230
+ },
231
+ });
232
+ const dry = requireLiveWrite(args, { method: 'POST', path: '/api/v1/documents', body: payload });
233
+ if (dry) return ok(dry);
234
+ return ok(await api('POST', '/api/v1/documents', {
235
+ body: payload,
236
+ headers: mutationHeaders(args, buildMutationHeaders),
237
+ }));
238
+ } catch (err) {
239
+ return ok({ error: true, message: err.message });
240
+ }
241
+ },
242
+ );
243
+
244
+ server.tool(
245
+ 'paper_update_document',
246
+ 'Update a Paper document title/content/status/metadata. Live writes require dryRun:false, approved:true, reason, and idempotencyKey.',
247
+ {
248
+ id: z.string(),
249
+ title: z.string().optional(),
250
+ content: z.string().optional(),
251
+ status: z.string().optional(),
252
+ metadata: z.record(z.unknown()).optional(),
253
+ expectedRevision: z.number().int().min(1),
254
+ ...writeControls,
255
+ },
256
+ async (args) => {
257
+ try {
258
+ const payload = cleanBody({
259
+ title: args.title,
260
+ content: args.content,
261
+ type: PAPER_DOC_TYPE,
262
+ status: args.status,
263
+ metadata: args.metadata,
264
+ expectedRevision: args.expectedRevision,
265
+ });
266
+ const path = `/api/v1/documents/${encodeURIComponent(args.id)}`;
267
+ const dry = requireLiveWrite(args, { method: 'PUT', path, body: payload });
268
+ if (dry) return ok(dry);
269
+ return ok(await api('PUT', path, {
270
+ body: payload,
271
+ headers: mutationHeaders(args, buildMutationHeaders),
272
+ }));
273
+ } catch (err) {
274
+ return ok({ error: true, message: err.message });
275
+ }
276
+ },
277
+ );
278
+
279
+ server.tool(
280
+ 'paper_delete_document',
281
+ 'Delete a Paper document. Live writes require dryRun:false, approved:true, reason, and idempotencyKey.',
282
+ {
283
+ id: z.string(),
284
+ expectedRevision: z.number().int().min(1),
285
+ ...writeControls,
286
+ },
287
+ async (args) => {
288
+ try {
289
+ const path = `/api/v1/documents/${encodeURIComponent(args.id)}`;
290
+ const params = { expectedRevision: args.expectedRevision };
291
+ const dry = requireLiveWrite(args, { method: 'DELETE', path, params });
292
+ if (dry) return ok(dry);
293
+ return ok(await api('DELETE', path, {
294
+ params,
295
+ headers: mutationHeaders(args, buildMutationHeaders),
296
+ }));
297
+ } catch (err) {
298
+ return ok({ error: true, message: err.message });
299
+ }
300
+ },
301
+ );
302
+
303
+ server.tool(
304
+ 'paper_archive_document',
305
+ 'Archive a Paper document without deleting it. Live writes require dryRun:false, approved:true, reason, and idempotencyKey.',
306
+ {
307
+ id: z.string(),
308
+ expectedRevision: z.number().int().min(1),
309
+ ...writeControls,
310
+ },
311
+ async (args) => {
312
+ try {
313
+ const path = `/api/v1/documents/${encodeURIComponent(args.id)}`;
314
+ const payload = {
315
+ type: PAPER_DOC_TYPE,
316
+ status: 'archived',
317
+ expectedRevision: args.expectedRevision,
318
+ };
319
+ const dry = requireLiveWrite(args, { method: 'PUT', path, body: payload });
320
+ if (dry) return ok(dry);
321
+ return ok(await api('PUT', path, {
322
+ body: payload,
323
+ headers: mutationHeaders(args, buildMutationHeaders),
324
+ }));
325
+ } catch (err) {
326
+ return ok({ error: true, message: err.message });
327
+ }
328
+ },
329
+ );
330
+
331
+ server.tool(
332
+ 'paper_list_shares',
333
+ 'List workspace share links, optionally filtered by Paper document/resource id.',
334
+ { resourceId: z.string().optional() },
335
+ async (args) => {
336
+ return ok(await api('GET', '/api/v1/shares', {
337
+ params: {
338
+ resourceType: 'document',
339
+ resourceId: args.resourceId,
340
+ limit: 100,
341
+ page: 1,
342
+ },
343
+ }));
344
+ },
345
+ );
346
+
347
+ server.tool(
348
+ 'paper_create_share',
349
+ 'Create a secure share link for a Paper document. Live writes require dryRun:false, approved:true, reason, and idempotencyKey.',
350
+ {
351
+ documentId: z.string(),
352
+ tier: shareTier.default('internal'),
353
+ expiresAt: z.string().optional(),
354
+ allowedEmails: z.array(z.string()).optional(),
355
+ settings: z.record(z.unknown()).optional(),
356
+ ...writeControls,
357
+ },
358
+ async (args) => {
359
+ try {
360
+ const payload = cleanBody({
361
+ resourceType: 'document',
362
+ resourceId: args.documentId,
363
+ tier: args.tier,
364
+ expiresAt: args.expiresAt,
365
+ allowedEmails: args.allowedEmails,
366
+ settings: args.settings,
367
+ });
368
+ const dry = requireLiveWrite(args, { method: 'POST', path: '/api/v1/shares', body: payload });
369
+ if (dry) return ok(dry);
370
+ return ok(await api('POST', '/api/v1/shares', {
371
+ body: payload,
372
+ headers: mutationHeaders(args, buildMutationHeaders),
373
+ }));
374
+ } catch (err) {
375
+ return ok({ error: true, message: err.message });
376
+ }
377
+ },
378
+ );
379
+
380
+ server.tool(
381
+ 'paper_revoke_share',
382
+ 'Revoke a Paper share link. Live writes require dryRun:false, approved:true, reason, and idempotencyKey.',
383
+ {
384
+ shareId: z.string(),
385
+ ...writeControls,
386
+ },
387
+ async (args) => {
388
+ try {
389
+ const path = `/api/v1/shares/${encodeURIComponent(args.shareId)}`;
390
+ const dry = requireLiveWrite(args, { method: 'DELETE', path });
391
+ if (dry) return ok(dry);
392
+ return ok(await api('DELETE', path, {
393
+ headers: mutationHeaders(args, buildMutationHeaders),
394
+ }));
395
+ } catch (err) {
396
+ return ok({ error: true, message: err.message });
397
+ }
398
+ },
399
+ );
400
+
401
+ server.tool(
402
+ 'paper_resolve_share',
403
+ 'Resolve a public Paper share token.',
404
+ { token: z.string() },
405
+ async (args) => ok(await api('GET', `/api/v1/shares/resolve/${encodeURIComponent(args.token)}`)),
406
+ );
407
+
408
+ server.tool(
409
+ 'paper_list_tasks',
410
+ 'List the seller task board used by Paper.',
411
+ { limit: z.number().int().min(1).max(200).default(200) },
412
+ async (args) => ok(await api('GET', '/api/v1/actions', {
413
+ params: {
414
+ viewMode: 'seller',
415
+ limit: args.limit,
416
+ sort_by: 'createdAt',
417
+ sort_order: 'desc',
418
+ },
419
+ })),
420
+ );
421
+
422
+ server.tool(
423
+ 'paper_create_task',
424
+ 'Create a Paper task/action. Live writes require dryRun:false, approved:true, reason, and idempotencyKey.',
425
+ {
426
+ title: z.string().min(1),
427
+ ...writeControls,
428
+ },
429
+ async (args) => {
430
+ try {
431
+ const payload = { title: args.title, source: 'desktop' };
432
+ const dry = requireLiveWrite(args, { method: 'POST', path: '/api/v1/actions', body: payload });
433
+ if (dry) return ok(dry);
434
+ return ok(await api('POST', '/api/v1/actions', {
435
+ body: payload,
436
+ headers: mutationHeaders(args, buildMutationHeaders),
437
+ }));
438
+ } catch (err) {
439
+ return ok({ error: true, message: err.message });
440
+ }
441
+ },
442
+ );
443
+
444
+ server.tool(
445
+ 'paper_update_task',
446
+ 'Update a Paper task/action title or status. Live writes require dryRun:false, approved:true, reason, and idempotencyKey.',
447
+ {
448
+ id: z.string(),
449
+ title: z.string().optional(),
450
+ status: z.string().optional(),
451
+ ...writeControls,
452
+ },
453
+ async (args) => {
454
+ try {
455
+ const payload = cleanBody({ title: args.title, status: args.status });
456
+ const path = `/api/v1/actions/${encodeURIComponent(args.id)}`;
457
+ const dry = requireLiveWrite(args, { method: 'PATCH', path, body: payload });
458
+ if (dry) return ok(dry);
459
+ return ok(await api('PATCH', path, {
460
+ body: payload,
461
+ headers: mutationHeaders(args, buildMutationHeaders),
462
+ }));
463
+ } catch (err) {
464
+ return ok({ error: true, message: err.message });
465
+ }
466
+ },
467
+ );
468
+
469
+ server.tool(
470
+ 'paper_list_calendar_events',
471
+ 'List Paper calendar events for a UTC ISO-8601 start/end window.',
472
+ {
473
+ start: z.string(),
474
+ end: z.string(),
475
+ },
476
+ async (args) => ok(await api('GET', '/api/v2/events/calendar', {
477
+ params: { start: args.start, end: args.end },
478
+ })),
479
+ );
480
+
481
+ server.tool(
482
+ 'paper_create_calendar_event',
483
+ 'Create a Paper calendar event. Live writes require dryRun:false, approved:true, reason, and idempotencyKey.',
484
+ {
485
+ title: z.string().min(1),
486
+ startTime: z.string(),
487
+ endTime: z.string(),
488
+ location: z.string().optional(),
489
+ attendees: z.array(z.string()).default([]),
490
+ ...writeControls,
491
+ },
492
+ async (args) => {
493
+ try {
494
+ const payload = cleanBody({
495
+ title: args.title,
496
+ startTime: args.startTime,
497
+ endTime: args.endTime,
498
+ location: args.location,
499
+ attendees: args.attendees.map((email) => ({ email })),
500
+ });
501
+ const dry = requireLiveWrite(args, { method: 'POST', path: '/api/v1/events', body: payload });
502
+ if (dry) return ok(dry);
503
+ return ok(await api('POST', '/api/v1/events', {
504
+ body: payload,
505
+ headers: mutationHeaders(args, buildMutationHeaders),
506
+ }));
507
+ } catch (err) {
508
+ return ok({ error: true, message: err.message });
509
+ }
510
+ },
511
+ );
512
+
513
+ server.tool(
514
+ 'paper_list_email_threads',
515
+ 'List Paper inbox thread summaries.',
516
+ {},
517
+ async () => ok(await api('GET', '/api/v2/emails/threads')),
518
+ );
519
+
520
+ server.tool(
521
+ 'paper_get_email_thread',
522
+ 'Get messages for a Paper email thread.',
523
+ { threadId: z.string() },
524
+ async (args) => ok(await api('GET', `/api/v1/emails/threads/${encodeURIComponent(args.threadId)}`)),
525
+ );
526
+
527
+ server.tool(
528
+ 'paper_send_email',
529
+ 'Send an email from the rep connected mailbox. Live sends require dryRun:false, approved:true, reason, and idempotencyKey.',
530
+ {
531
+ to: z.array(z.string()).min(1),
532
+ subject: z.string().min(1),
533
+ body: z.string().min(1),
534
+ threadId: z.string().optional(),
535
+ ...writeControls,
536
+ },
537
+ async (args) => {
538
+ try {
539
+ const payload = cleanBody({
540
+ to: args.to,
541
+ subject: args.subject,
542
+ body: args.body,
543
+ threadId: args.threadId,
544
+ });
545
+ const dry = requireLiveWrite(args, { method: 'POST', path: '/api/v2/emails/direct', body: payload });
546
+ if (dry) return ok(dry);
547
+ return ok(await api('POST', '/api/v2/emails/direct', {
548
+ body: payload,
549
+ headers: mutationHeaders(args, buildMutationHeaders),
550
+ }));
551
+ } catch (err) {
552
+ return ok({ error: true, message: err.message });
553
+ }
554
+ },
555
+ );
556
+
557
+ server.tool(
558
+ 'paper_get_company',
559
+ 'Get the company context record Paper shows in calendar and workspace context rails.',
560
+ { id: z.string() },
561
+ async (args) => ok(await api('GET', `/api/v1/companies/${encodeURIComponent(args.id)}`)),
562
+ );
563
+ }