exponential-mcp 0.6.0 → 0.7.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 (52) hide show
  1. package/README.md +27 -0
  2. package/dist/cli.js +0 -0
  3. package/dist/domains/actions.d.ts +2 -0
  4. package/dist/domains/actions.js +157 -0
  5. package/dist/domains/areas.d.ts +2 -0
  6. package/dist/domains/areas.js +22 -0
  7. package/dist/domains/comments.d.ts +2 -0
  8. package/dist/domains/comments.js +165 -0
  9. package/dist/domains/contacts.d.ts +2 -0
  10. package/dist/domains/contacts.js +105 -0
  11. package/dist/domains/deals.d.ts +2 -0
  12. package/dist/domains/deals.js +77 -0
  13. package/dist/domains/decisions.d.ts +2 -0
  14. package/dist/domains/decisions.js +160 -0
  15. package/dist/domains/epics.d.ts +2 -0
  16. package/dist/domains/epics.js +54 -0
  17. package/dist/domains/features.d.ts +2 -0
  18. package/dist/domains/features.js +89 -0
  19. package/dist/domains/framework.d.ts +34 -0
  20. package/dist/domains/framework.js +94 -0
  21. package/dist/domains/goals.d.ts +2 -0
  22. package/dist/domains/goals.js +121 -0
  23. package/dist/domains/index.d.ts +5 -0
  24. package/dist/domains/index.js +48 -0
  25. package/dist/domains/keyResults.d.ts +2 -0
  26. package/dist/domains/keyResults.js +112 -0
  27. package/dist/domains/labels.d.ts +2 -0
  28. package/dist/domains/labels.js +77 -0
  29. package/dist/domains/meetings.d.ts +2 -0
  30. package/dist/domains/meetings.js +90 -0
  31. package/dist/domains/organizations.d.ts +2 -0
  32. package/dist/domains/organizations.js +38 -0
  33. package/dist/domains/pages.d.ts +2 -0
  34. package/dist/domains/pages.js +44 -0
  35. package/dist/domains/products.d.ts +2 -0
  36. package/dist/domains/products.js +57 -0
  37. package/dist/domains/projects.d.ts +2 -0
  38. package/dist/domains/projects.js +57 -0
  39. package/dist/domains/requirements.d.ts +2 -0
  40. package/dist/domains/requirements.js +39 -0
  41. package/dist/domains/scopes.d.ts +2 -0
  42. package/dist/domains/scopes.js +42 -0
  43. package/dist/domains/stories.d.ts +2 -0
  44. package/dist/domains/stories.js +46 -0
  45. package/dist/domains/tickets.d.ts +2 -0
  46. package/dist/domains/tickets.js +135 -0
  47. package/dist/domains/time.d.ts +2 -0
  48. package/dist/domains/time.js +64 -0
  49. package/dist/domains/workspaces.d.ts +2 -0
  50. package/dist/domains/workspaces.js +18 -0
  51. package/dist/index.js +15 -2
  52. package/package.json +5 -4
@@ -0,0 +1,112 @@
1
+ import { z } from 'zod';
2
+ import { id, op, workspaceId } from './framework.js';
3
+ const KR_STATUSES = ['not-started', 'on-track', 'at-risk', 'off-track', 'achieved'];
4
+ const KR_UNITS = ['percent', 'count', 'currency', 'hours', 'custom'];
5
+ /** Key results are cuid-keyed; the objective they hang off is an integer. */
6
+ const keyResultId = id('Key result');
7
+ const goalId = z.number().int().describe('Objective (goal) ID — an integer, unlike the key result’s own cuid');
8
+ const period = z.string().describe('Free-form OKR period, e.g. "Q3-2026" (see `goals` periods)');
9
+ export const keyResults = {
10
+ name: 'key_results',
11
+ description: 'Key results — the measurable half of an OKR, each hanging off one objective. Objectives themselves are in the `goals` tool; key results can be linked to projects and features as the work that moves them.',
12
+ operations: {
13
+ list: op({
14
+ summary: 'List key results. With workspaceId: every member’s (older servers may return only yours — prefer by_objective); without: only your own.',
15
+ params: z.object({
16
+ workspaceId: workspaceId.optional(),
17
+ goalId: goalId.optional(),
18
+ period: period.optional(),
19
+ status: z.enum(KR_STATUSES).optional(),
20
+ onlyMine: z.boolean().optional().describe('Narrow a workspace-scoped list back to key results you own'),
21
+ }),
22
+ run: (client, params) => client.keyResults.list(params),
23
+ }),
24
+ by_objective: op({
25
+ summary: 'Objectives with their key results nested and progress/status counts — the richest OKR read; workspace-correct on every server.',
26
+ params: z.object({
27
+ workspaceId: workspaceId.optional(),
28
+ period: period.optional(),
29
+ includePairedPeriod: z
30
+ .boolean()
31
+ .optional()
32
+ .describe('Also include the period’s parent annual period (Q3-2026 → Annual-2026)'),
33
+ onlyMine: z.boolean().optional(),
34
+ }),
35
+ run: (client, params) => client.keyResults.byObjective(params),
36
+ }),
37
+ get: op({
38
+ summary: 'Get one key result with its check-ins and linked projects/features.',
39
+ params: z.object({ id: keyResultId }),
40
+ run: (client, { id }) => client.keyResults.get(id),
41
+ }),
42
+ create: op({
43
+ summary: 'Create a key result on an objective. Omit workspaceId to inherit the objective’s workspace.',
44
+ params: z.object({
45
+ goalId,
46
+ title: z.string(),
47
+ targetValue: z.number(),
48
+ period,
49
+ description: z.string().optional(),
50
+ startValue: z.number().optional(),
51
+ currentValue: z.number().optional(),
52
+ unit: z.enum(KR_UNITS).optional(),
53
+ unitLabel: z.string().optional().describe('Display label for the unit, e.g. "users" or "€"'),
54
+ driUserId: id('Directly responsible user').optional(),
55
+ workspaceId: workspaceId.optional(),
56
+ }),
57
+ run: (client, params) => client.keyResults.create(params),
58
+ }),
59
+ update: op({
60
+ summary: 'Update a key result. Only the fields you pass change. Use check_in to record progress with history.',
61
+ params: z.object({
62
+ id: keyResultId,
63
+ title: z.string().optional(),
64
+ description: z.string().optional(),
65
+ targetValue: z.number().optional(),
66
+ currentValue: z.number().optional(),
67
+ startValue: z.number().optional(),
68
+ unit: z.enum(KR_UNITS).optional(),
69
+ unitLabel: z.string().optional(),
70
+ status: z.enum(KR_STATUSES).optional(),
71
+ confidence: z.number().min(0).max(100).optional().describe('0–100'),
72
+ driUserId: id('Directly responsible user').optional(),
73
+ goalId: goalId.optional().describe('Move the key result to a different objective (integer ID)'),
74
+ }),
75
+ run: (client, params) => client.keyResults.update(params),
76
+ }),
77
+ check_in: op({
78
+ summary: 'Record a progress check-in. The server re-derives status from where value lands between start and target.',
79
+ params: z.object({
80
+ id: keyResultId,
81
+ value: z.number().describe('The new current value'),
82
+ note: z.string().optional(),
83
+ }),
84
+ run: (client, params) => client.keyResults.checkIn(params),
85
+ }),
86
+ delete: op({
87
+ summary: 'Delete a key result permanently.',
88
+ params: z.object({ id: keyResultId }),
89
+ run: (client, { id }) => client.keyResults.delete(id),
90
+ }),
91
+ link_project: op({
92
+ summary: 'Link a project to a key result as executing work. Idempotent.',
93
+ params: z.object({ keyResultId, projectId: id('Project') }),
94
+ run: (client, params) => client.keyResults.linkProject(params),
95
+ }),
96
+ unlink_project: op({
97
+ summary: 'Remove a project link from a key result.',
98
+ params: z.object({ keyResultId, projectId: id('Project') }),
99
+ run: (client, params) => client.keyResults.unlinkProject(params),
100
+ }),
101
+ link_feature: op({
102
+ summary: 'Link a feature as executing work. Idempotent; feature must be in the same workspace. An unaligned feature inherits the objective.',
103
+ params: z.object({ keyResultId, featureId: id('Feature') }),
104
+ run: (client, params) => client.keyResults.linkFeature(params),
105
+ }),
106
+ unlink_feature: op({
107
+ summary: 'Remove a feature link. Removes only the link row — never the feature’s objective alignment.',
108
+ params: z.object({ keyResultId, featureId: id('Feature') }),
109
+ run: (client, params) => client.keyResults.unlinkFeature(params),
110
+ }),
111
+ },
112
+ };
@@ -0,0 +1,2 @@
1
+ import { type Domain } from './framework.js';
2
+ export declare const labels: Domain;
@@ -0,0 +1,77 @@
1
+ import { z } from 'zod';
2
+ import { id, op, workspaceId } from './framework.js';
3
+ const ENTITY_TYPES = ['action', 'ticket', 'feature', 'epic'];
4
+ const TAG_COLORS = [
5
+ 'avatar-red',
6
+ 'avatar-teal',
7
+ 'avatar-blue',
8
+ 'avatar-green',
9
+ 'avatar-yellow',
10
+ 'avatar-plum',
11
+ 'avatar-mint',
12
+ 'avatar-lightYellow',
13
+ 'avatar-lightPurple',
14
+ 'avatar-lightBlue',
15
+ 'avatar-orange',
16
+ 'avatar-lightGreen',
17
+ 'avatar-lightRed',
18
+ 'avatar-skyBlue',
19
+ 'avatar-paleGreen',
20
+ 'avatar-paleYellow',
21
+ 'avatar-lavender',
22
+ 'avatar-powderBlue',
23
+ 'avatar-lightPink',
24
+ 'avatar-lightGray',
25
+ 'brand-primary',
26
+ 'brand-success',
27
+ 'brand-warning',
28
+ 'brand-error',
29
+ 'brand-info',
30
+ ];
31
+ const category = z
32
+ .string()
33
+ .nullable()
34
+ .optional()
35
+ .describe('Tag category; null means a plain label');
36
+ export const labels = {
37
+ name: 'labels',
38
+ description: 'Labels (tags) that can be attached to actions (`actions`), tickets (`tickets`), features (`features`) and epics (`epics`). There are global system tags and per-workspace tags; a "label" is a tag whose category is null.',
39
+ operations: {
40
+ list: op({
41
+ summary: 'List available tags as { globalTags, workspaceTags, allTags }. Without workspaceId, workspaceTags covers every workspace you belong to. Omit category for all categories; null for plain labels only.',
42
+ params: z.object({
43
+ workspaceId: workspaceId.optional(),
44
+ category,
45
+ }),
46
+ run: (client, params) => client.labels.list(params),
47
+ }),
48
+ create: op({
49
+ summary: 'Create a workspace tag. Fails if a tag with the same name (slug) already exists in the workspace.',
50
+ params: z.object({
51
+ workspaceId,
52
+ name: z.string().min(1).max(50),
53
+ color: z.string().describe(`One of: ${TAG_COLORS.join(', ')}`),
54
+ description: z.string().max(200).optional(),
55
+ category,
56
+ }),
57
+ run: (client, params) => client.labels.create(params),
58
+ }),
59
+ set_for_entity: op({
60
+ summary: 'Replace the full tag set on an action, ticket, feature or epic (an empty list clears it). Tags must be global or from the entity’s workspace.',
61
+ params: z.object({
62
+ entityType: z.enum(ENTITY_TYPES),
63
+ entityId: z.string().describe('ID of the action, ticket, feature or epic'),
64
+ tagIds: z.array(id('Tag')),
65
+ }),
66
+ run: (client, params) => client.labels.setEntityTags(params),
67
+ }),
68
+ list_for_entity: op({
69
+ summary: 'List the tags on an action, ticket, feature or epic.',
70
+ params: z.object({
71
+ entityType: z.enum(ENTITY_TYPES),
72
+ entityId: z.string().describe('ID of the action, ticket, feature or epic'),
73
+ }),
74
+ run: (client, params) => client.labels.listForEntity(params),
75
+ }),
76
+ },
77
+ };
@@ -0,0 +1,2 @@
1
+ import { type Domain } from './framework.js';
2
+ export declare const meetings: Domain;
@@ -0,0 +1,90 @@
1
+ import { z } from 'zod';
2
+ import { date, id, op } from './framework.js';
3
+ const meetingId = id('Meeting');
4
+ export const meetings = {
5
+ name: 'meetings',
6
+ description: 'Meetings (transcription sessions): recorded, imported or manually created, each with free-form `notes`, an AI `summary` and the raw `transcription`. Actions extracted from them are in the `actions` tool; people in `contacts`.',
7
+ operations: {
8
+ list: op({
9
+ summary: 'List meetings you can see (owner, participant, project or workspace access), newest first. Rows include full notes/summary/transcription — expect large payloads.',
10
+ params: z.object({
11
+ workspaceId: z
12
+ .string()
13
+ .optional()
14
+ .describe('Workspace ID (see the `workspaces` tool); omit for all workspaces'),
15
+ includeArchived: z.boolean().optional(),
16
+ meetingType: z
17
+ .enum(['all', 'mine', 'one_on_one', 'customer', 'internal'])
18
+ .optional()
19
+ .describe('mine = you own or attend; one_on_one = exactly two participants; customer/internal always empty for now'),
20
+ }),
21
+ run: (client, params) => client.meetings.list(params),
22
+ }),
23
+ get: op({
24
+ summary: 'Get one meeting with participants, linked actions, workspace and project.',
25
+ params: z.object({ id: meetingId }),
26
+ run: (client, { id }) => client.meetings.get(id),
27
+ }),
28
+ create: op({
29
+ summary: 'Create a meeting manually. A project-linked meeting inherits its project’s workspace.',
30
+ params: z.object({
31
+ title: z.string(),
32
+ transcription: z.string().min(1).describe('Raw transcript text (required, non-empty)'),
33
+ description: z.string().optional(),
34
+ notes: z.string().optional().describe('Meeting notes (Markdown)'),
35
+ meetingDate: date('When the meeting happened').optional(),
36
+ projectId: id('Project').optional(),
37
+ workspaceId: z.string().optional().describe('Workspace ID (see the `workspaces` tool)'),
38
+ participants: z
39
+ .array(z.object({
40
+ userId: z.string().optional().describe('Workspace member user ID'),
41
+ contactId: z.string().optional().describe('CRM contact ID'),
42
+ email: z.string().optional(),
43
+ name: z.string().optional(),
44
+ }))
45
+ .optional()
46
+ .describe('Each: a member (userId), a CRM contact (contactId), or a name/email'),
47
+ }),
48
+ run: (client, params) => client.meetings.create(params),
49
+ }),
50
+ update: op({
51
+ summary: 'Update a meeting. Only the fields you pass change; passing none is an error. `notes` replaces the whole body — use append_notes to add.',
52
+ params: z.object({
53
+ id: meetingId,
54
+ title: z.string().optional(),
55
+ description: z.string().optional(),
56
+ notes: z.string().optional(),
57
+ summary: z.string().optional(),
58
+ transcription: z.string().optional(),
59
+ meetingDate: date('When the meeting happened').nullable().optional(),
60
+ workspaceId: z.string().nullable().optional().describe('Workspace ID (null to clear)'),
61
+ }),
62
+ run: (client, params) => client.meetings.update(params),
63
+ }),
64
+ get_notes: op({
65
+ summary: 'Get just the meeting’s notes body (null when none written).',
66
+ params: z.object({ id: meetingId }),
67
+ run: (client, { id }) => client.meetings.getNotes(id),
68
+ }),
69
+ set_notes: op({
70
+ summary: 'Replace the meeting’s notes wholesale.',
71
+ params: z.object({ id: meetingId, notes: z.string() }),
72
+ run: (client, { id, notes }) => client.meetings.setNotes(id, notes),
73
+ }),
74
+ append_notes: op({
75
+ summary: 'Append a block to the meeting’s notes (blank-line separated), creating them if empty. Read-modify-write, not atomic.',
76
+ params: z.object({ id: meetingId, text: z.string() }),
77
+ run: (client, { id, text }) => client.meetings.appendNotes(id, text),
78
+ }),
79
+ delete: op({
80
+ summary: 'Permanently delete one meeting you own (FORBIDDEN otherwise). Linked actions survive, unlinked.',
81
+ params: z.object({ id: meetingId }),
82
+ run: (client, { id }) => client.meetings.delete(id),
83
+ }),
84
+ delete_many: op({
85
+ summary: 'Permanently delete meetings in bulk. Ids that are missing or not yours are silently skipped — compare the returned count to what you sent.',
86
+ params: z.object({ ids: z.array(meetingId) }),
87
+ run: (client, { ids }) => client.meetings.deleteMany(ids),
88
+ }),
89
+ },
90
+ };
@@ -0,0 +1,2 @@
1
+ import { type Domain } from './framework.js';
2
+ export declare const organizations: Domain;
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ import { id, op, workspaceId } from './framework.js';
3
+ const ORGANIZATION_SIZES = ['1-10', '11-50', '51-200', '201-500', '501-1000', '1000+'];
4
+ export const organizations = {
5
+ name: 'organizations',
6
+ description: 'CRM organizations (companies) in a workspace. People are in the `contacts` tool and deals in `deals`.',
7
+ operations: {
8
+ list: op({
9
+ summary: 'List organizations alphabetically, with contact counts. Paginate with the returned nextCursor.',
10
+ params: z.object({
11
+ workspaceId,
12
+ search: z.string().optional().describe('Case-insensitive match on name or description'),
13
+ industry: z.string().optional().describe('Exact match'),
14
+ limit: z.number().int().min(1).max(100).optional().describe('Default 50'),
15
+ cursor: z.string().optional(),
16
+ }),
17
+ run: (client, params) => client.organizations.list(params),
18
+ }),
19
+ get: op({
20
+ summary: 'Get one organization.',
21
+ params: z.object({ id: id('Organization') }),
22
+ run: (client, { id }) => client.organizations.get(id),
23
+ }),
24
+ create: op({
25
+ summary: 'Create an organization.',
26
+ params: z.object({
27
+ workspaceId,
28
+ name: z.string(),
29
+ websiteUrl: z.string().nullable().optional().describe('Full URL including scheme'),
30
+ logoUrl: z.string().nullable().optional().describe('Full URL including scheme'),
31
+ description: z.string().optional(),
32
+ industry: z.string().optional(),
33
+ size: z.enum(ORGANIZATION_SIZES).optional().describe('Headcount band'),
34
+ }),
35
+ run: (client, params) => client.organizations.create(params),
36
+ }),
37
+ },
38
+ };
@@ -0,0 +1,2 @@
1
+ import { type Domain } from './framework.js';
2
+ export declare const pages: Domain;
@@ -0,0 +1,44 @@
1
+ import { z } from 'zod';
2
+ import { id, op, workspaceId } from './framework.js';
3
+ export const pages = {
4
+ name: 'pages',
5
+ description: 'Knowledge pages: workspace-scoped Markdown documents (PRDs, research, technical specs), optionally filed under a project. Link one to a feature with `features` link_page; page comments are in the `comments` tool.',
6
+ operations: {
7
+ list: op({
8
+ summary: 'List pages you can view, most recently edited first. Returns metadata only (no body) — use get for the content.',
9
+ params: z.object({
10
+ workspaceId,
11
+ projectId: id('Project').optional(),
12
+ search: z.string().optional().describe('Case-insensitive match on title only'),
13
+ }),
14
+ run: (client, params) => client.pages.list(params),
15
+ }),
16
+ get: op({
17
+ summary: 'Get one page with its Markdown body and whether you can edit it (canEdit).',
18
+ params: z.object({ id: id('Page') }),
19
+ run: (client, { id }) => client.pages.get(id),
20
+ }),
21
+ create: op({
22
+ summary: 'Create a page. Title defaults to "Untitled"; a non-empty body is indexed for search.',
23
+ params: z.object({
24
+ workspaceId,
25
+ projectId: z.string().nullable().optional().describe('Project ID to file it under (null/omit = workspace-level)'),
26
+ title: z.string().optional(),
27
+ body: z.string().optional().describe('Markdown body'),
28
+ includeInSearch: z.boolean().optional().describe('Default true'),
29
+ }),
30
+ run: (client, params) => client.pages.create(params),
31
+ }),
32
+ update: op({
33
+ summary: 'Update a page. Only the fields you pass change; body replaces the whole document (comment anchors are carried across where their text survives).',
34
+ params: z.object({
35
+ id: id('Page'),
36
+ title: z.string().optional(),
37
+ projectId: z.string().nullable().optional().describe('Project ID (null to move to workspace-level)'),
38
+ includeInSearch: z.boolean().optional(),
39
+ body: z.string().optional().describe('Markdown body; replaces the existing body entirely'),
40
+ }),
41
+ run: (client, params) => client.pages.update(params),
42
+ }),
43
+ },
44
+ };
@@ -0,0 +1,2 @@
1
+ import { type Domain } from './framework.js';
2
+ export declare const products: Domain;
@@ -0,0 +1,57 @@
1
+ import { z } from 'zod';
2
+ import { id, op, workspaceId } from './framework.js';
3
+ const productFields = {
4
+ description: z.string().optional(),
5
+ icon: z.string().optional(),
6
+ color: z.string().optional(),
7
+ };
8
+ export const products = {
9
+ name: 'products',
10
+ description: 'Products: the container under a workspace that owns features (`features`), tickets (`tickets`), product epics (`epics`) and research. Most product-side tools need a product ID — find it here by slug or ID.',
11
+ operations: {
12
+ list: op({
13
+ summary: 'List the products in a workspace.',
14
+ params: z.object({ workspaceId }),
15
+ run: (client, { workspaceId }) => client.products.list(workspaceId),
16
+ }),
17
+ get: op({
18
+ summary: 'Get one product by ID, or by slug when workspaceId is given (a slug-or-ID with workspaceId tries the ID first, then the slug).',
19
+ params: z.object({
20
+ slugOrId: z.string().describe('Product ID, or its URL slug (slug requires workspaceId)'),
21
+ workspaceId: workspaceId.optional().describe('Required to look up by slug'),
22
+ }),
23
+ run: (client, { slugOrId, workspaceId }) => workspaceId ? client.products.resolve(workspaceId, slugOrId) : client.products.get(slugOrId),
24
+ }),
25
+ create: op({
26
+ summary: 'Create a product in a workspace. The slug must be unique within the workspace.',
27
+ params: z.object({
28
+ workspaceId,
29
+ name: z.string(),
30
+ slug: z
31
+ .string()
32
+ .regex(/^[a-z0-9-]+$/)
33
+ .describe('Kebab-case URL slug (a–z, 0–9, hyphens)'),
34
+ ...productFields,
35
+ }),
36
+ run: (client, params) => client.products.create(params),
37
+ }),
38
+ update: op({
39
+ summary: 'Update a product. Only the fields you pass change.',
40
+ params: z.object({
41
+ id: id('Product'),
42
+ name: z.string().optional(),
43
+ ...productFields,
44
+ funTicketIds: z
45
+ .boolean()
46
+ .optional()
47
+ .describe('Give new tickets word-pair shortIds (e.g. "prime.toucan") instead of plain numbers'),
48
+ }),
49
+ run: (client, params) => client.products.update(params),
50
+ }),
51
+ delete: op({
52
+ summary: 'Delete a product permanently. Cascades: all its features, tickets, research, insights and product-bound epics are deleted too. Cycles and retrospectives survive, unlinked.',
53
+ params: z.object({ id: id('Product') }),
54
+ run: (client, { id }) => client.products.delete(id),
55
+ }),
56
+ },
57
+ };
@@ -0,0 +1,2 @@
1
+ import { type Domain } from './framework.js';
2
+ export declare const projects: Domain;
@@ -0,0 +1,57 @@
1
+ import { z } from 'zod';
2
+ import { date, id, op } from './framework.js';
3
+ const clearableDate = (what) => date(what).nullable().optional().describe(`${what} (ISO 8601; null to clear)`);
4
+ export const projects = {
5
+ name: 'projects',
6
+ description: 'Projects: containers of actions, optionally linked to a product, objectives (goals) and key results. Their actions are in the `actions` tool; workspaces in `workspaces`.',
7
+ operations: {
8
+ list: op({
9
+ summary: 'List projects you can see. Carries no OKR links — use get for those.',
10
+ params: z.object({
11
+ workspaceId: z
12
+ .string()
13
+ .optional()
14
+ .describe('Workspace ID (see the `workspaces` tool); omit for all workspaces'),
15
+ includeActions: z.boolean().optional(),
16
+ }),
17
+ run: (client, params) => client.projects.list(params),
18
+ }),
19
+ get: op({
20
+ summary: 'Get one project with its actions, linked objectives and key results, DRI and team — the only way to see what a project is driving.',
21
+ params: z.object({
22
+ id: z.string().describe('Project cuid, slug, or the `slug-cuid` form from app URLs'),
23
+ }),
24
+ run: (client, { id }) => client.projects.get(id),
25
+ }),
26
+ update: op({
27
+ summary: 'Update a project. Only the fields you pass change (omitted name/status/priority cost one extra read).',
28
+ params: z.object({
29
+ id: id('Project'),
30
+ name: z.string().optional(),
31
+ description: z.string().optional().describe("Pass '' to blank it; there is no null-to-clear"),
32
+ status: z.enum(['ACTIVE', 'ON_HOLD', 'COMPLETED', 'CANCELLED']).optional(),
33
+ priority: z.enum(['HIGH', 'MEDIUM', 'LOW', 'NONE']).optional(),
34
+ productId: z.string().nullable().optional().describe('Product ID (null to unlink)'),
35
+ workspaceId: z.string().nullable().optional().describe('Workspace ID (null makes the project personal)'),
36
+ driId: z.string().nullable().optional().describe('User ID of the directly responsible individual (null to clear)'),
37
+ goalIds: z
38
+ .array(z.string())
39
+ .optional()
40
+ .describe('Replace objective links; integer goal ids as strings. [] is a no-op, not a clear'),
41
+ outcomeIds: z.array(z.string()).optional(),
42
+ keyResultIds: z.array(z.string()).optional().describe('Replace key-result links wholesale; [] clears them'),
43
+ lifeDomainIds: z.array(z.number().int()).optional(),
44
+ reviewDate: clearableDate('Review date'),
45
+ nextActionDate: clearableDate('Next action date'),
46
+ startDate: clearableDate('Start date'),
47
+ endDate: clearableDate('End date'),
48
+ }),
49
+ run: (client, params) => client.projects.update(params),
50
+ }),
51
+ delete: op({
52
+ summary: 'Delete a project AND all its actions. No undo.',
53
+ params: z.object({ id: id('Project') }),
54
+ run: (client, { id }) => client.projects.delete(id),
55
+ }),
56
+ },
57
+ };
@@ -0,0 +1,2 @@
1
+ import { type Domain } from './framework.js';
2
+ export declare const requirements: Domain;
@@ -0,0 +1,39 @@
1
+ import { z } from 'zod';
2
+ import { id, op } from './framework.js';
3
+ export const requirements = {
4
+ name: 'requirements',
5
+ description: 'Requirements on a feature: atomic EARS-style "shall" statements, optionally pinned to one of its scopes, each checkable met/unmet. Features are in `features`, scopes in `scopes`.',
6
+ operations: {
7
+ list: op({
8
+ summary: 'List a feature’s requirements in display order, optionally only those pinned to one scope.',
9
+ params: z.object({
10
+ featureId: id('Feature'),
11
+ scopeId: id('Scope').optional(),
12
+ }),
13
+ run: (client, params) => client.requirements.list(params),
14
+ }),
15
+ create: op({
16
+ summary: 'Add a requirement to the end of a feature’s list.',
17
+ params: z.object({
18
+ featureId: id('Feature'),
19
+ scopeId: id('Scope').optional().describe('Scope ID; must belong to the same feature'),
20
+ statement: z.string().describe('One EARS-style "shall" statement'),
21
+ kind: z.enum(['FUNCTIONAL', 'NON_FUNCTIONAL', 'CONSTRAINT']).optional(),
22
+ }),
23
+ run: (client, params) => client.requirements.create(params),
24
+ }),
25
+ set_checked: op({
26
+ summary: 'Mark a requirement met (true, stamped with you and now) or unmet (false, clears the stamp).',
27
+ params: z.object({
28
+ id: id('Requirement'),
29
+ checked: z.boolean(),
30
+ }),
31
+ run: (client, { id, checked }) => client.requirements.setChecked(id, checked),
32
+ }),
33
+ delete: op({
34
+ summary: 'Delete a requirement permanently.',
35
+ params: z.object({ id: id('Requirement') }),
36
+ run: (client, { id }) => client.requirements.delete(id),
37
+ }),
38
+ },
39
+ };
@@ -0,0 +1,2 @@
1
+ import { type Domain } from './framework.js';
2
+ export declare const scopes: Domain;
@@ -0,0 +1,42 @@
1
+ import { z } from 'zod';
2
+ import { date, id, op } from './framework.js';
3
+ const SCOPE_STATUSES = ['PLANNED', 'IN_PROGRESS', 'SHIPPED', 'DEPRECATED'];
4
+ export const scopes = {
5
+ name: 'scopes',
6
+ description: 'Feature scopes: shippable increments of a feature ("V1", "V2: auto-sync"), each with its own lifecycle. Scope status rolls up to the parent feature (see `features`); requirements can be pinned to a scope (see `requirements`).',
7
+ operations: {
8
+ list: op({
9
+ summary: 'List a feature’s scopes in display order.',
10
+ params: z.object({ featureId: id('Feature') }),
11
+ run: (client, params) => client.scopes.list(params),
12
+ }),
13
+ create: op({
14
+ summary: 'Add a scope to the end of a feature’s list. Status defaults to PLANNED; rolls up the feature’s status.',
15
+ params: z.object({
16
+ featureId: id('Feature'),
17
+ version: z.string().describe('Short label for the increment, e.g. "V1" or "V2: auto-sync" (max 60 chars)'),
18
+ description: z.string(),
19
+ status: z.enum(SCOPE_STATUSES).optional(),
20
+ shippedAt: date('When it shipped').optional(),
21
+ }),
22
+ run: (client, params) => client.scopes.create(params),
23
+ }),
24
+ update: op({
25
+ summary: 'Update a scope. Only the fields you pass change; setting SHIPPED stamps shippedAt now unless you pass one, and any status change rolls up to the feature.',
26
+ params: z.object({
27
+ id: id('Scope'),
28
+ version: z.string().optional(),
29
+ description: z.string().optional(),
30
+ status: z.enum(SCOPE_STATUSES).optional(),
31
+ shippedAt: date('When it shipped').nullable().optional().describe('ISO 8601 date or datetime (null to clear)'),
32
+ displayOrder: z.number().int().optional().describe('Position among the feature’s scopes, 0-based'),
33
+ }),
34
+ run: (client, params) => client.scopes.update(params),
35
+ }),
36
+ delete: op({
37
+ summary: 'Delete a scope permanently. Requirements, tickets and pages pinned to it are kept but unpinned; its comments are deleted.',
38
+ params: z.object({ id: id('Scope') }),
39
+ run: (client, { id }) => client.scopes.delete(id),
40
+ }),
41
+ },
42
+ };
@@ -0,0 +1,2 @@
1
+ import { type Domain } from './framework.js';
2
+ export declare const stories: Domain;
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod';
2
+ import { id, op } from './framework.js';
3
+ const storyFields = {
4
+ asA: z.string().max(500).optional().describe('"As a …" — the role'),
5
+ iWant: z.string().max(1000).optional().describe('"I want …" — the capability'),
6
+ soThat: z.string().max(1000).optional().describe('"So that …" — the benefit'),
7
+ acceptanceCriteria: z.string().optional(),
8
+ };
9
+ export const stories = {
10
+ name: 'stories',
11
+ description: 'User stories on a feature (As a / I want / So that, plus acceptance criteria), optionally grouped under one of the feature’s scopes. Features live in `features`, scopes in `scopes`.',
12
+ operations: {
13
+ list: op({
14
+ summary: 'List a feature’s user stories in display order.',
15
+ params: z.object({ featureId: id('Feature') }),
16
+ run: (client, params) => client.userStories.list(params),
17
+ }),
18
+ create: op({
19
+ summary: 'Add a user story to a feature, appended at the end of the display order.',
20
+ params: z.object({
21
+ featureId: id('Feature'),
22
+ ...storyFields,
23
+ scopeId: id('Feature scope').optional().describe('Feature scope ID; must belong to the same feature'),
24
+ }),
25
+ run: (client, params) => client.userStories.create(params),
26
+ }),
27
+ update: op({
28
+ summary: 'Update a user story. Only the fields you pass change.',
29
+ params: z.object({
30
+ id: id('User story'),
31
+ ...storyFields,
32
+ scopeId: z
33
+ .string()
34
+ .nullable()
35
+ .optional()
36
+ .describe('Feature scope ID, which must belong to the story’s feature (null to ungroup)'),
37
+ }),
38
+ run: (client, params) => client.userStories.update(params),
39
+ }),
40
+ delete: op({
41
+ summary: 'Delete a user story permanently.',
42
+ params: z.object({ id: id('User story') }),
43
+ run: (client, { id }) => client.userStories.delete(id),
44
+ }),
45
+ },
46
+ };
@@ -0,0 +1,2 @@
1
+ import { type Domain } from './framework.js';
2
+ export declare const tickets: Domain;