@opencapstack/mcp-server 0.1.3 → 0.1.5

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 (64) hide show
  1. package/dist/auth.d.ts.map +1 -1
  2. package/dist/auth.js +5 -1
  3. package/dist/auth.js.map +1 -1
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +17 -5
  6. package/dist/client.js.map +1 -1
  7. package/dist/schema.d.ts +13 -0
  8. package/dist/schema.d.ts.map +1 -0
  9. package/dist/schema.js +22 -0
  10. package/dist/schema.js.map +1 -0
  11. package/dist/server.d.ts.map +1 -1
  12. package/dist/server.js +5 -1
  13. package/dist/server.js.map +1 -1
  14. package/dist/tools/dilution.d.ts.map +1 -1
  15. package/dist/tools/dilution.js +6 -13
  16. package/dist/tools/dilution.js.map +1 -1
  17. package/dist/tools/documents.d.ts.map +1 -1
  18. package/dist/tools/documents.js +3 -2
  19. package/dist/tools/documents.js.map +1 -1
  20. package/dist/tools/equityGrants.d.ts +3 -0
  21. package/dist/tools/equityGrants.d.ts.map +1 -0
  22. package/dist/tools/equityGrants.js +145 -0
  23. package/dist/tools/equityGrants.js.map +1 -0
  24. package/dist/tools/equityPlans.d.ts.map +1 -1
  25. package/dist/tools/equityPlans.js +30 -21
  26. package/dist/tools/equityPlans.js.map +1 -1
  27. package/dist/tools/financialReports.d.ts.map +1 -1
  28. package/dist/tools/financialReports.js +33 -17
  29. package/dist/tools/financialReports.js.map +1 -1
  30. package/dist/tools/meta.d.ts +3 -0
  31. package/dist/tools/meta.d.ts.map +1 -0
  32. package/dist/tools/meta.js +161 -0
  33. package/dist/tools/meta.js.map +1 -0
  34. package/dist/tools/safes.d.ts.map +1 -1
  35. package/dist/tools/safes.js +59 -42
  36. package/dist/tools/safes.js.map +1 -1
  37. package/dist/tools/shareClasses.d.ts.map +1 -1
  38. package/dist/tools/shareClasses.js +31 -23
  39. package/dist/tools/shareClasses.js.map +1 -1
  40. package/dist/tools/stakeholders.d.ts.map +1 -1
  41. package/dist/tools/stakeholders.js +51 -17
  42. package/dist/tools/stakeholders.js.map +1 -1
  43. package/dist/tools/valuations.d.ts.map +1 -1
  44. package/dist/tools/valuations.js +30 -17
  45. package/dist/tools/valuations.js.map +1 -1
  46. package/dist/tools/waterfall.d.ts.map +1 -1
  47. package/dist/tools/waterfall.js +7 -16
  48. package/dist/tools/waterfall.js.map +1 -1
  49. package/package.json +1 -1
  50. package/src/auth.ts +7 -1
  51. package/src/client.ts +23 -6
  52. package/src/schema.ts +28 -0
  53. package/src/server.ts +5 -1
  54. package/src/tools/dilution.ts +12 -13
  55. package/src/tools/documents.ts +3 -2
  56. package/src/tools/equityGrants.ts +154 -0
  57. package/src/tools/equityPlans.ts +30 -21
  58. package/src/tools/financialReports.ts +37 -17
  59. package/src/tools/meta.ts +169 -0
  60. package/src/tools/safes.ts +59 -42
  61. package/src/tools/shareClasses.ts +34 -23
  62. package/src/tools/stakeholders.ts +51 -17
  63. package/src/tools/valuations.ts +29 -20
  64. package/src/tools/waterfall.ts +13 -16
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { coerceInt, coerceBool } from '../schema.js';
2
3
  import { type ToolDefinition } from '../types.js';
3
4
 
4
5
  export const financialReportTools: ToolDefinition[] = [
@@ -18,7 +19,7 @@ export const financialReportTools: ToolDefinition[] = [
18
19
  ])
19
20
  .optional()
20
21
  .describe('Filter by report type'),
21
- limit: z.number().optional().default(20).describe('Max results to return'),
22
+ limit: coerceInt('Max results to return').optional().default(20),
22
23
  }),
23
24
  handler: async (input, client) => {
24
25
  const { data } = await client.get('/api/v1/financial-reports', { params: input });
@@ -30,9 +31,12 @@ export const financialReportTools: ToolDefinition[] = [
30
31
  },
31
32
  {
32
33
  name: 'get_financial_report',
33
- description: 'Get the full contents of a specific financial report by ID.',
34
+ description:
35
+ 'Get the full contents of a specific financial report by ID. Use the `row_id` field from `list_financial_reports`.',
34
36
  inputSchema: z.object({
35
- id: z.string().describe('Financial report ID'),
37
+ id: z
38
+ .string()
39
+ .describe('Financial report ID — use the `row_id` field from list_financial_reports'),
36
40
  }),
37
41
  handler: async (input, client) => {
38
42
  const { data } = await client.get(`/api/v1/financial-reports/${input.id}`);
@@ -58,25 +62,41 @@ export const financialReportTools: ToolDefinition[] = [
58
62
  .string()
59
63
  .optional()
60
64
  .describe('Generate the report as of this date (ISO 8601 YYYY-MM-DD). Defaults to today.'),
61
- includeConvertibles: z
62
- .boolean()
65
+ includeConvertibles: coerceBool(
66
+ 'Include convertible instruments (SAFEs, notes) in the report'
67
+ )
63
68
  .optional()
64
- .default(true)
65
- .describe('Include convertible instruments (SAFEs, notes) in the report'),
66
- includeOptionPool: z
67
- .boolean()
69
+ .default(true),
70
+ includeOptionPool: coerceBool(
71
+ 'Include option pool (granted and unissued) in the report'
72
+ )
68
73
  .optional()
69
- .default(true)
70
- .describe('Include option pool (granted and unissued) in the report'),
74
+ .default(true),
71
75
  title: z.string().optional().describe('Custom report title'),
72
76
  }),
73
77
  handler: async (input, client) => {
74
- const { data } = await client.post('/api/v1/financial-reports', input);
75
- return {
76
- content: [
77
- { type: 'text', text: `Financial report created: ${JSON.stringify(data, null, 2)}` },
78
- ],
79
- };
78
+ const { data: created } = await client.post('/api/v1/financial-reports', input);
79
+ const id = created.row_id ?? created._id;
80
+ try {
81
+ const { data: confirmed } = await client.get(`/api/v1/financial-reports/${id}`);
82
+ return {
83
+ content: [
84
+ {
85
+ type: 'text',
86
+ text: `Financial report created:\n${JSON.stringify(confirmed, null, 2)}\n\nID for follow-up operations: ${id}`,
87
+ },
88
+ ],
89
+ };
90
+ } catch {
91
+ return {
92
+ content: [
93
+ {
94
+ type: 'text',
95
+ text: `Financial report created (could not confirm persisted state — verify with get_financial_report):\n${JSON.stringify(created, null, 2)}`,
96
+ },
97
+ ],
98
+ };
99
+ }
80
100
  },
81
101
  },
82
102
  ];
@@ -0,0 +1,169 @@
1
+ import { z } from 'zod';
2
+ import { type ToolDefinition } from '../types.js';
3
+
4
+ export const metaTools: ToolDefinition[] = [
5
+ {
6
+ name: 'whoami',
7
+ description:
8
+ 'Verify your API key is working and return your account details (email, role, companyId). ' +
9
+ 'Run this first to confirm your MCP setup is correct.',
10
+ inputSchema: z.object({}),
11
+ handler: async (_input, client) => {
12
+ const { data } = await client.get('/api/v1/auth/me');
13
+ return {
14
+ content: [
15
+ {
16
+ type: 'text',
17
+ text: `Authenticated as:\n${JSON.stringify(data, null, 2)}`,
18
+ },
19
+ ],
20
+ };
21
+ },
22
+ },
23
+ {
24
+ name: 'list_workflows',
25
+ description:
26
+ 'Get step-by-step workflow guides for common cap table operations. ' +
27
+ 'Call this when you are unsure what tools to use or in what order.',
28
+ inputSchema: z.object({}),
29
+ handler: async () => {
30
+ const workflows = {
31
+ add_advisor_with_equity: [
32
+ {
33
+ step: 1,
34
+ tool: 'create_stakeholder',
35
+ required: ['name', 'email', 'role: advisor', 'companyId'],
36
+ },
37
+ {
38
+ step: 2,
39
+ tool: 'create_equity_plan',
40
+ note: 'Skip if plan exists — use list_equity_plans first',
41
+ required: ['name', 'planType: NSO', 'sharesReserved', 'companyId'],
42
+ },
43
+ {
44
+ step: 3,
45
+ tool: 'create_equity_grant',
46
+ required: [
47
+ 'companyId',
48
+ 'employeeId: <stakeholder row_id>',
49
+ 'equityPlanId: <plan row_id>',
50
+ 'grantType: NSO',
51
+ 'numberOfShares',
52
+ 'grantDate',
53
+ ],
54
+ },
55
+ ],
56
+ record_safe_investment: [
57
+ {
58
+ step: 1,
59
+ tool: 'create_stakeholder',
60
+ note: 'Skip if investor exists — use list_stakeholders first',
61
+ required: ['name', 'email', 'role: investor', 'companyId'],
62
+ },
63
+ {
64
+ step: 2,
65
+ tool: 'create_safe',
66
+ required: [
67
+ 'investmentAmount',
68
+ 'safeType',
69
+ 'investorId: <stakeholder row_id>',
70
+ 'companyId',
71
+ 'investmentDate',
72
+ ],
73
+ },
74
+ ],
75
+ set_up_share_classes: [
76
+ {
77
+ step: 1,
78
+ tool: 'create_share_class',
79
+ note: 'Common stock first',
80
+ required: ['name: Common', 'classType: common', 'authorizedShares', 'companyId'],
81
+ },
82
+ {
83
+ step: 2,
84
+ tool: 'create_share_class',
85
+ note: 'Preferred if needed',
86
+ required: [
87
+ 'name: Series A Preferred',
88
+ 'classType: preferred',
89
+ 'authorizedShares',
90
+ 'companyId',
91
+ ],
92
+ },
93
+ ],
94
+ record_409a_valuation: [
95
+ {
96
+ step: 1,
97
+ tool: 'create_valuation_request',
98
+ required: ['companyId', 'valuationType: 409A', 'valuationDate', 'commonStockFMV'],
99
+ },
100
+ {
101
+ step: 2,
102
+ tool: 'create_financial_report',
103
+ note: 'Optional',
104
+ required: ['companyId', 'reportType: 409A_report'],
105
+ },
106
+ ],
107
+ };
108
+ return {
109
+ content: [{ type: 'text', text: JSON.stringify(workflows, null, 2) }],
110
+ };
111
+ },
112
+ },
113
+ {
114
+ name: 'cap_table_summary',
115
+ description:
116
+ 'Get a quick overview of the current cap table state — stakeholder count, share classes, ' +
117
+ 'open SAFEs, active grants. Use this to understand what is already set up before starting workflows.',
118
+ inputSchema: z.object({
119
+ companyId: z.string().describe('Company ID'),
120
+ }),
121
+ handler: async (input, client) => {
122
+ const params = { companyId: input.companyId, limit: 100 };
123
+ const [stakeholdersRes, shareClassesRes, safesRes, equityPlansRes, grantsRes] =
124
+ await Promise.allSettled([
125
+ client.get('/api/v1/stakeholders', { params }),
126
+ client.get('/api/v1/share-classes', { params }),
127
+ client.get('/api/v1/safes', { params }),
128
+ client.get('/api/v1/equity-plans', { params }),
129
+ client.get('/api/v1/equity-grants', { params }),
130
+ ]);
131
+
132
+ const extract = (res: PromiseSettledResult<{ data: unknown }>, keys: string[]): unknown[] => {
133
+ if (res.status === 'rejected') return [];
134
+ const d = res.value.data as Record<string, unknown>;
135
+ for (const k of keys) {
136
+ if (Array.isArray(d[k])) return d[k] as unknown[];
137
+ }
138
+ return Array.isArray(d) ? d : [];
139
+ };
140
+
141
+ const stakeholders = extract(stakeholdersRes, ['stakeholders']);
142
+ const shareClasses = extract(shareClassesRes, ['shareClasses']);
143
+ const safes = extract(safesRes, ['safes']);
144
+ const equityPlans = extract(equityPlansRes, ['equityPlans']);
145
+ const grants = extract(grantsRes, ['grants']);
146
+
147
+ const summary = {
148
+ companyId: input.companyId,
149
+ stakeholders: stakeholders.length,
150
+ shareClasses: shareClasses.length,
151
+ openSafes: (safes as { status?: string }[]).filter(
152
+ (s) => s.status === 'open' || s.status === 'draft'
153
+ ).length,
154
+ equityPlans: equityPlans.length,
155
+ activeGrants: (grants as { status?: string }[]).filter((g) => g.status === 'active').length,
156
+ unavailable: [
157
+ stakeholdersRes.status === 'rejected' ? 'stakeholders' : null,
158
+ shareClassesRes.status === 'rejected' ? 'share-classes' : null,
159
+ safesRes.status === 'rejected' ? 'safes' : null,
160
+ equityPlansRes.status === 'rejected' ? 'equity-plans' : null,
161
+ grantsRes.status === 'rejected' ? 'equity-grants' : null,
162
+ ].filter(Boolean),
163
+ };
164
+ return {
165
+ content: [{ type: 'text', text: JSON.stringify(summary, null, 2) }],
166
+ };
167
+ },
168
+ },
169
+ ];
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { coerceFloat, coerceBool, coerceInt } from '../schema.js';
2
3
  import { type ToolDefinition } from '../types.js';
3
4
 
4
5
  export const safeTools: ToolDefinition[] = [
@@ -9,7 +10,7 @@ export const safeTools: ToolDefinition[] = [
9
10
  inputSchema: z.object({
10
11
  companyId: z.string().optional().describe('Filter by company ID'),
11
12
  investorId: z.string().optional().describe('Filter by investor stakeholder ID'),
12
- limit: z.number().optional().default(50).describe('Max results to return'),
13
+ limit: coerceInt('Max results to return').optional().default(50),
13
14
  }),
14
15
  handler: async (input, client) => {
15
16
  const { data } = await client.get('/api/v1/safes', { params: input });
@@ -21,9 +22,10 @@ export const safeTools: ToolDefinition[] = [
21
22
  },
22
23
  {
23
24
  name: 'get_safe',
24
- description: 'Get details for a specific SAFE instrument by ID.',
25
+ description:
26
+ 'Get details for a specific SAFE instrument by ID. Use the `safeId` field (e.g. `safe_xxx`) from `list_safes`, not the `_id` field.',
25
27
  inputSchema: z.object({
26
- id: z.string().describe('SAFE ID'),
28
+ id: z.string().describe('SAFE ID — use the `safeId` field from list_safes, not `_id`'),
27
29
  }),
28
30
  handler: async (input, client) => {
29
31
  const { data } = await client.get(`/api/v1/safes/${input.id}`);
@@ -34,47 +36,52 @@ export const safeTools: ToolDefinition[] = [
34
36
  name: 'create_safe',
35
37
  description: 'Record a new SAFE instrument (e.g. post-money SAFE from a YC-style round).',
36
38
  inputSchema: z.object({
37
- investmentAmount: z
38
- .union([z.number(), z.string()])
39
- .transform((v) => parseFloat(String(v)))
40
- .describe('Investment amount in USD'),
41
- valuationCap: z
42
- .union([z.number(), z.string()])
43
- .transform((v) => parseFloat(String(v)))
44
- .optional()
45
- .describe('Valuation cap in USD (for valuation cap SAFEs)'),
46
- discountRate: z
47
- .union([z.number(), z.string()])
48
- .transform((v) => parseFloat(String(v)))
49
- .optional()
50
- .describe('Discount rate percentage (e.g. 20 for 20%)'),
39
+ investmentAmount: coerceFloat('Investment amount in USD'),
40
+ valuationCap: coerceFloat('Valuation cap in USD (for valuation cap SAFEs)')
41
+ .optional(),
42
+ discountRate: coerceFloat('Discount rate percentage (e.g. 20 for 20%)')
43
+ .optional(),
51
44
  safeType: z
52
45
  .enum(['valuation_cap', 'discount', 'mfn', 'valuation_cap_and_discount'])
53
46
  .describe('Type of SAFE'),
54
47
  investorId: z.string().describe('Stakeholder ID of the investor'),
55
48
  companyId: z.string().describe('Company ID'),
56
49
  investmentDate: z.string().describe('Investment date in ISO 8601 format (YYYY-MM-DD)'),
57
- proRataRights: z
58
- .union([z.boolean(), z.string()])
59
- .transform((v) => v === true || v === 'true')
50
+ proRataRights: coerceBool('Whether the investor has pro-rata rights')
60
51
  .optional()
61
- .default(false)
62
- .describe('Whether the investor has pro-rata rights'),
52
+ .default(false),
63
53
  }),
64
54
  handler: async (input, client) => {
65
- const { data } = await client.post('/api/v1/safes', input);
66
- return {
67
- content: [
68
- { type: 'text', text: `SAFE created: ${JSON.stringify(data, null, 2)}` },
69
- ],
70
- };
55
+ const { data: created } = await client.post('/api/v1/safes', input);
56
+ const id = created.safeId ?? created.row_id ?? created._id;
57
+ try {
58
+ const { data: confirmed } = await client.get(`/api/v1/safes/${id}`);
59
+ return {
60
+ content: [
61
+ {
62
+ type: 'text',
63
+ text: `SAFE created:\n${JSON.stringify(confirmed, null, 2)}\n\nID for follow-up operations: ${id}`,
64
+ },
65
+ ],
66
+ };
67
+ } catch {
68
+ return {
69
+ content: [
70
+ {
71
+ type: 'text',
72
+ text: `SAFE created (could not confirm persisted state — verify with get_safe):\n${JSON.stringify(created, null, 2)}`,
73
+ },
74
+ ],
75
+ };
76
+ }
71
77
  },
72
78
  },
73
79
  {
74
80
  name: 'update_safe',
75
- description: 'Update an existing SAFE instrument (e.g. record conversion).',
81
+ description:
82
+ 'Update an existing SAFE instrument (e.g. record conversion). Use the `safeId` field (e.g. `safe_xxx`) from `list_safes`, not the `_id` field.',
76
83
  inputSchema: z.object({
77
- id: z.string().describe('SAFE ID'),
84
+ id: z.string().describe('SAFE ID — use the `safeId` field from list_safes, not `_id`'),
78
85
  status: z
79
86
  .enum(['open', 'converted', 'cancelled'])
80
87
  .optional()
@@ -87,21 +94,31 @@ export const safeTools: ToolDefinition[] = [
87
94
  .string()
88
95
  .optional()
89
96
  .describe('Share class ID that this SAFE converted into'),
90
- convertedShares: z
91
- .number()
92
- .int()
93
- .positive()
94
- .optional()
95
- .describe('Number of shares issued upon conversion'),
97
+ convertedShares: coerceInt('Number of shares issued upon conversion').optional(),
96
98
  }),
97
99
  handler: async (input, client) => {
98
100
  const { id, ...body } = input;
99
- const { data } = await client.put(`/api/v1/safes/${id}`, body);
100
- return {
101
- content: [
102
- { type: 'text', text: `SAFE updated: ${JSON.stringify(data, null, 2)}` },
103
- ],
104
- };
101
+ const { data: updated } = await client.put(`/api/v1/safes/${id}`, body);
102
+ try {
103
+ const { data: confirmed } = await client.get(`/api/v1/safes/${id}`);
104
+ return {
105
+ content: [
106
+ {
107
+ type: 'text',
108
+ text: `SAFE updated:\n${JSON.stringify(confirmed, null, 2)}\n\nID for follow-up operations: ${id}`,
109
+ },
110
+ ],
111
+ };
112
+ } catch {
113
+ return {
114
+ content: [
115
+ {
116
+ type: 'text',
117
+ text: `SAFE updated (could not confirm persisted state — verify with get_safe):\n${JSON.stringify(updated, null, 2)}`,
118
+ },
119
+ ],
120
+ };
121
+ }
105
122
  },
106
123
  },
107
124
  ];
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { coerceInt, coerceFloat } from '../schema.js';
2
3
  import { type ToolDefinition } from '../types.js';
3
4
 
4
5
  export const shareClassTools: ToolDefinition[] = [
@@ -8,7 +9,7 @@ export const shareClassTools: ToolDefinition[] = [
8
9
  'List all share classes defined in the cap table (e.g. Common, Series A Preferred).',
9
10
  inputSchema: z.object({
10
11
  companyId: z.string().optional().describe('Filter by company ID'),
11
- limit: z.number().optional().default(50).describe('Max results to return'),
12
+ limit: coerceInt('Max results to return').optional().default(50),
12
13
  }),
13
14
  handler: async (input, client) => {
14
15
  const { data } = await client.get('/api/v1/share-classes', { params: input });
@@ -20,9 +21,10 @@ export const shareClassTools: ToolDefinition[] = [
20
21
  },
21
22
  {
22
23
  name: 'get_share_class',
23
- description: 'Get details for a specific share class by ID.',
24
+ description:
25
+ 'Get details for a specific share class by ID. Use the `row_id` field from `list_share_classes`.',
24
26
  inputSchema: z.object({
25
- id: z.string().describe('Share class ID'),
27
+ id: z.string().describe('Share class ID — use the `row_id` field from list_share_classes'),
26
28
  }),
27
29
  handler: async (input, client) => {
28
30
  const { data } = await client.get(`/api/v1/share-classes/${input.id}`);
@@ -37,33 +39,42 @@ export const shareClassTools: ToolDefinition[] = [
37
39
  classType: z
38
40
  .enum(['common', 'preferred', 'warrant', 'option'])
39
41
  .describe('Type of share class'),
40
- authorizedShares: z
41
- .union([z.number(), z.string()])
42
- .transform((v) => parseInt(String(v), 10))
43
- .describe('Total number of authorized shares'),
44
- parValue: z
45
- .union([z.number(), z.string()])
46
- .transform((v) => parseFloat(String(v)))
47
- .optional()
48
- .describe('Par value per share in USD'),
42
+ authorizedShares: coerceInt('Total number of authorized shares').refine(
43
+ (v) => v > 0,
44
+ { message: 'authorizedShares must be a positive integer' }
45
+ ),
46
+ parValue: coerceFloat('Par value per share in USD').optional(),
49
47
  companyId: z.string().describe('Company ID this share class belongs to'),
50
- liquidationPreference: z
51
- .union([z.number(), z.string()])
52
- .transform((v) => parseFloat(String(v)))
53
- .optional()
54
- .describe('Liquidation preference multiplier (e.g. 1 for 1x)'),
48
+ liquidationPreference: coerceFloat('Liquidation preference multiplier (e.g. 1 for 1x)')
49
+ .optional(),
55
50
  participationRights: z
56
51
  .enum(['none', 'full', 'capped'])
57
52
  .optional()
58
53
  .describe('Participation rights type'),
59
54
  }),
60
55
  handler: async (input, client) => {
61
- const { data } = await client.post('/api/v1/share-classes', input);
62
- return {
63
- content: [
64
- { type: 'text', text: `Share class created: ${JSON.stringify(data, null, 2)}` },
65
- ],
66
- };
56
+ const { data: created } = await client.post('/api/v1/share-classes', input);
57
+ const id = created.row_id ?? created._id;
58
+ try {
59
+ const { data: confirmed } = await client.get(`/api/v1/share-classes/${id}`);
60
+ return {
61
+ content: [
62
+ {
63
+ type: 'text',
64
+ text: `Share class created:\n${JSON.stringify(confirmed, null, 2)}\n\nID for follow-up operations: ${id}`,
65
+ },
66
+ ],
67
+ };
68
+ } catch {
69
+ return {
70
+ content: [
71
+ {
72
+ type: 'text',
73
+ text: `Share class created (could not confirm persisted state — verify with get_share_class):\n${JSON.stringify(created, null, 2)}`,
74
+ },
75
+ ],
76
+ };
77
+ }
67
78
  },
68
79
  },
69
80
  ];
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { coerceInt } from '../schema.js';
2
3
  import { type ToolDefinition } from '../types.js';
3
4
 
4
5
  export const stakeholderTools: ToolDefinition[] = [
@@ -8,7 +9,7 @@ export const stakeholderTools: ToolDefinition[] = [
8
9
  'List all stakeholders in the cap table. Returns name, email, role, and ownership details.',
9
10
  inputSchema: z.object({
10
11
  companyId: z.string().optional().describe('Filter by company ID'),
11
- limit: z.number().optional().default(50).describe('Max results to return'),
12
+ limit: coerceInt('Max results to return').optional().default(50),
12
13
  }),
13
14
  handler: async (input, client) => {
14
15
  const { data } = await client.get('/api/v1/stakeholders', { params: input });
@@ -20,9 +21,10 @@ export const stakeholderTools: ToolDefinition[] = [
20
21
  },
21
22
  {
22
23
  name: 'get_stakeholder',
23
- description: 'Get details for a specific stakeholder by ID.',
24
+ description:
25
+ 'Get details for a specific stakeholder by ID. Use the `row_id` field from `list_stakeholders`.',
24
26
  inputSchema: z.object({
25
- id: z.string().describe('Stakeholder ID'),
27
+ id: z.string().describe('Stakeholder ID — use the `row_id` field from list_stakeholders'),
26
28
  }),
27
29
  handler: async (input, client) => {
28
30
  const { data } = await client.get(`/api/v1/stakeholders/${input.id}`);
@@ -41,19 +43,36 @@ export const stakeholderTools: ToolDefinition[] = [
41
43
  companyId: z.string().describe('Company ID to add the stakeholder to'),
42
44
  }),
43
45
  handler: async (input, client) => {
44
- const { data } = await client.post('/api/v1/stakeholders', input);
45
- return {
46
- content: [
47
- { type: 'text', text: `Stakeholder created: ${JSON.stringify(data, null, 2)}` },
48
- ],
49
- };
46
+ const { data: created } = await client.post('/api/v1/stakeholders', input);
47
+ const id = created.row_id ?? created._id;
48
+ try {
49
+ const { data: confirmed } = await client.get(`/api/v1/stakeholders/${id}`);
50
+ return {
51
+ content: [
52
+ {
53
+ type: 'text',
54
+ text: `Stakeholder created:\n${JSON.stringify(confirmed, null, 2)}\n\nID for follow-up operations: ${id}`,
55
+ },
56
+ ],
57
+ };
58
+ } catch {
59
+ return {
60
+ content: [
61
+ {
62
+ type: 'text',
63
+ text: `Stakeholder created (could not confirm persisted state — verify with get_stakeholder):\n${JSON.stringify(created, null, 2)}`,
64
+ },
65
+ ],
66
+ };
67
+ }
50
68
  },
51
69
  },
52
70
  {
53
71
  name: 'update_stakeholder',
54
- description: 'Update an existing stakeholder by ID.',
72
+ description:
73
+ 'Update an existing stakeholder by ID. Use the `row_id` field from `list_stakeholders`.',
55
74
  inputSchema: z.object({
56
- id: z.string().describe('Stakeholder ID'),
75
+ id: z.string().describe('Stakeholder ID — use the `row_id` field from list_stakeholders'),
57
76
  name: z.string().optional().describe('Full name'),
58
77
  email: z.string().email().optional().describe('Email address'),
59
78
  role: z
@@ -63,12 +82,27 @@ export const stakeholderTools: ToolDefinition[] = [
63
82
  }),
64
83
  handler: async (input, client) => {
65
84
  const { id, ...body } = input;
66
- const { data } = await client.put(`/api/v1/stakeholders/${id}`, body);
67
- return {
68
- content: [
69
- { type: 'text', text: `Stakeholder updated: ${JSON.stringify(data, null, 2)}` },
70
- ],
71
- };
85
+ const { data: updated } = await client.put(`/api/v1/stakeholders/${id}`, body);
86
+ try {
87
+ const { data: confirmed } = await client.get(`/api/v1/stakeholders/${id}`);
88
+ return {
89
+ content: [
90
+ {
91
+ type: 'text',
92
+ text: `Stakeholder updated:\n${JSON.stringify(confirmed, null, 2)}\n\nID for follow-up operations: ${id}`,
93
+ },
94
+ ],
95
+ };
96
+ } catch {
97
+ return {
98
+ content: [
99
+ {
100
+ type: 'text',
101
+ text: `Stakeholder updated (could not confirm persisted state — verify with get_stakeholder):\n${JSON.stringify(updated, null, 2)}`,
102
+ },
103
+ ],
104
+ };
105
+ }
72
106
  },
73
107
  },
74
108
  ];