@opencapstack/mcp-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +166 -0
- package/dist/auth.d.ts +7 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +16 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +30 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +73 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/dilution.d.ts +3 -0
- package/dist/tools/dilution.d.ts.map +1 -0
- package/dist/tools/dilution.js +51 -0
- package/dist/tools/dilution.js.map +1 -0
- package/dist/tools/documents.d.ts +3 -0
- package/dist/tools/documents.d.ts.map +1 -0
- package/dist/tools/documents.js +72 -0
- package/dist/tools/documents.js.map +1 -0
- package/dist/tools/equityPlans.d.ts +3 -0
- package/dist/tools/equityPlans.d.ts.map +1 -0
- package/dist/tools/equityPlans.js +65 -0
- package/dist/tools/equityPlans.js.map +1 -0
- package/dist/tools/financialReports.d.ts +3 -0
- package/dist/tools/financialReports.d.ts.map +1 -0
- package/dist/tools/financialReports.js +79 -0
- package/dist/tools/financialReports.js.map +1 -0
- package/dist/tools/safes.d.ts +3 -0
- package/dist/tools/safes.d.ts.map +1 -0
- package/dist/tools/safes.js +102 -0
- package/dist/tools/safes.js.map +1 -0
- package/dist/tools/shareClasses.d.ts +3 -0
- package/dist/tools/shareClasses.d.ts.map +1 -0
- package/dist/tools/shareClasses.js +63 -0
- package/dist/tools/shareClasses.js.map +1 -0
- package/dist/tools/stakeholders.d.ts +3 -0
- package/dist/tools/stakeholders.d.ts.map +1 -0
- package/dist/tools/stakeholders.js +72 -0
- package/dist/tools/stakeholders.js.map +1 -0
- package/dist/tools/valuations.d.ts +3 -0
- package/dist/tools/valuations.d.ts.map +1 -0
- package/dist/tools/valuations.js +67 -0
- package/dist/tools/valuations.js.map +1 -0
- package/dist/tools/waterfall.d.ts +3 -0
- package/dist/tools/waterfall.d.ts.map +1 -0
- package/dist/tools/waterfall.js +46 -0
- package/dist/tools/waterfall.js.map +1 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -0
- package/src/auth.ts +19 -0
- package/src/client.ts +46 -0
- package/src/index.ts +70 -0
- package/src/server.ts +93 -0
- package/src/tools/dilution.ts +54 -0
- package/src/tools/documents.ts +74 -0
- package/src/tools/equityPlans.ts +67 -0
- package/src/tools/financialReports.ts +82 -0
- package/src/tools/safes.ts +104 -0
- package/src/tools/shareClasses.ts +65 -0
- package/src/tools/stakeholders.ts +74 -0
- package/src/tools/valuations.ts +73 -0
- package/src/tools/waterfall.ts +48 -0
- package/src/types.ts +16 -0
- package/tests/auth.test.ts +54 -0
- package/tests/server.test.ts +95 -0
- package/tests/tools/shareClasses.test.ts +133 -0
- package/tests/tools/stakeholders.test.ts +147 -0
- package/tsconfig.json +16 -0
package/src/auth.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication utilities for the OpenCap MCP server.
|
|
3
|
+
* Reads OPENCAP_API_KEY and OPENCAP_BASE_URL from the environment.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function getApiKey(): string {
|
|
7
|
+
const key = process.env.OPENCAP_API_KEY;
|
|
8
|
+
if (!key) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
'Set OPENCAP_API_KEY to your OpenCap JWT token. ' +
|
|
11
|
+
'Get one at https://api.opencapstack.com/api/v1/auth/login'
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
return key;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getBaseUrl(): string {
|
|
18
|
+
return process.env.OPENCAP_BASE_URL ?? 'https://api.opencapstack.com';
|
|
19
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTP client with auth and error handling.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import axios, { type AxiosInstance } from 'axios';
|
|
6
|
+
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
+
import { getBaseUrl } from './auth.js';
|
|
8
|
+
|
|
9
|
+
export function createClient(apiKey: string): AxiosInstance {
|
|
10
|
+
const client = axios.create({
|
|
11
|
+
baseURL: getBaseUrl(),
|
|
12
|
+
headers: {
|
|
13
|
+
Authorization: `Bearer ${apiKey}`,
|
|
14
|
+
'Content-Type': 'application/json',
|
|
15
|
+
},
|
|
16
|
+
timeout: 30_000,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
client.interceptors.response.use(
|
|
20
|
+
(response) => response,
|
|
21
|
+
(error) => {
|
|
22
|
+
const status = error.response?.status;
|
|
23
|
+
|
|
24
|
+
if (status === 401) {
|
|
25
|
+
throw new McpError(
|
|
26
|
+
ErrorCode.InvalidRequest,
|
|
27
|
+
'API key rejected or expired — regenerate at https://app.opencapstack.com/settings'
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (status === 403) {
|
|
32
|
+
throw new McpError(
|
|
33
|
+
ErrorCode.InvalidRequest,
|
|
34
|
+
'Access denied — check your account permissions'
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Re-throw other errors with the server message when available
|
|
39
|
+
const message =
|
|
40
|
+
error.response?.data?.message ?? error.message ?? 'Unknown API error';
|
|
41
|
+
throw new McpError(ErrorCode.InternalError, `OpenCap API error: ${message}`);
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return client;
|
|
46
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* OpenCap MCP Server entry point.
|
|
4
|
+
*
|
|
5
|
+
* Supports two transport modes:
|
|
6
|
+
* TRANSPORT=stdio (default) — for Claude Desktop / Claude Code
|
|
7
|
+
* TRANSPORT=sse — HTTP + SSE, useful for web-based clients
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
11
|
+
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
12
|
+
import { createServer } from './server.js';
|
|
13
|
+
import { getApiKey } from './auth.js';
|
|
14
|
+
import { createClient } from './client.js';
|
|
15
|
+
|
|
16
|
+
async function main() {
|
|
17
|
+
// Fail fast if the API key is missing
|
|
18
|
+
const apiKey = getApiKey();
|
|
19
|
+
const client = createClient(apiKey);
|
|
20
|
+
const server = createServer(client);
|
|
21
|
+
|
|
22
|
+
const transport = process.env.TRANSPORT ?? 'stdio';
|
|
23
|
+
|
|
24
|
+
if (transport === 'sse') {
|
|
25
|
+
// Dynamically import express so it doesn't slow down the stdio path
|
|
26
|
+
const { default: express } = await import('express');
|
|
27
|
+
const app = express();
|
|
28
|
+
const port = Number(process.env.PORT ?? 3001);
|
|
29
|
+
|
|
30
|
+
// Map to hold active SSE transports keyed by session
|
|
31
|
+
const transports = new Map<string, SSEServerTransport>();
|
|
32
|
+
|
|
33
|
+
app.get('/sse', async (req, res) => {
|
|
34
|
+
const sseTransport = new SSEServerTransport('/messages', res);
|
|
35
|
+
transports.set(sseTransport.sessionId, sseTransport);
|
|
36
|
+
|
|
37
|
+
res.on('close', () => {
|
|
38
|
+
transports.delete(sseTransport.sessionId);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await server.connect(sseTransport);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
app.post('/messages', express.json(), async (req, res) => {
|
|
45
|
+
const sessionId = req.query['sessionId'] as string;
|
|
46
|
+
const sseTransport = transports.get(sessionId);
|
|
47
|
+
|
|
48
|
+
if (!sseTransport) {
|
|
49
|
+
res.status(404).json({ error: 'Session not found' });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await sseTransport.handlePostMessage(req, res);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
app.listen(port, () => {
|
|
57
|
+
console.error(`OpenCap MCP server listening on http://localhost:${port}`);
|
|
58
|
+
});
|
|
59
|
+
} else {
|
|
60
|
+
// stdio transport (default)
|
|
61
|
+
const stdioTransport = new StdioServerTransport();
|
|
62
|
+
await server.connect(stdioTransport);
|
|
63
|
+
console.error('OpenCap MCP server running on stdio');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
main().catch((err) => {
|
|
68
|
+
console.error('Fatal error:', err instanceof Error ? err.message : err);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server setup and tool registration.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
6
|
+
import {
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
CallToolResultSchema,
|
|
9
|
+
ListToolsRequestSchema,
|
|
10
|
+
type CallToolResult,
|
|
11
|
+
type Tool,
|
|
12
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
+
import { type AxiosInstance } from 'axios';
|
|
14
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
15
|
+
|
|
16
|
+
import { stakeholderTools } from './tools/stakeholders.js';
|
|
17
|
+
import { shareClassTools } from './tools/shareClasses.js';
|
|
18
|
+
import { equityPlanTools } from './tools/equityPlans.js';
|
|
19
|
+
import { safeTools } from './tools/safes.js';
|
|
20
|
+
import { documentTools } from './tools/documents.js';
|
|
21
|
+
import { valuationTools } from './tools/valuations.js';
|
|
22
|
+
import { dilutionTools } from './tools/dilution.js';
|
|
23
|
+
import { waterfallTools } from './tools/waterfall.js';
|
|
24
|
+
import { financialReportTools } from './tools/financialReports.js';
|
|
25
|
+
import { type ToolDefinition } from './types.js';
|
|
26
|
+
|
|
27
|
+
const ALL_TOOLS: ToolDefinition[] = [
|
|
28
|
+
...stakeholderTools,
|
|
29
|
+
...shareClassTools,
|
|
30
|
+
...equityPlanTools,
|
|
31
|
+
...safeTools,
|
|
32
|
+
...documentTools,
|
|
33
|
+
...valuationTools,
|
|
34
|
+
...dilutionTools,
|
|
35
|
+
...waterfallTools,
|
|
36
|
+
...financialReportTools,
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export function createServer(client: AxiosInstance): Server {
|
|
40
|
+
const server = new Server(
|
|
41
|
+
{
|
|
42
|
+
name: 'opencap-mcp',
|
|
43
|
+
version: '0.1.0',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
capabilities: {
|
|
47
|
+
tools: {},
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// List tools handler
|
|
53
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
54
|
+
const tools: Tool[] = ALL_TOOLS.map((tool) => ({
|
|
55
|
+
name: tool.name,
|
|
56
|
+
description: tool.description,
|
|
57
|
+
inputSchema: zodToJsonSchema(tool.inputSchema) as Tool['inputSchema'],
|
|
58
|
+
}));
|
|
59
|
+
return { tools };
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Call tool handler
|
|
63
|
+
server.setRequestHandler(CallToolRequestSchema, async (request): Promise<CallToolResult> => {
|
|
64
|
+
const { name, arguments: args } = request.params;
|
|
65
|
+
|
|
66
|
+
const tool = ALL_TOOLS.find((t) => t.name === name);
|
|
67
|
+
if (!tool) {
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
70
|
+
isError: true,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const parsed = tool.inputSchema.parse(args ?? {});
|
|
76
|
+
const result = await tool.handler(parsed, client);
|
|
77
|
+
return result;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: 'text', text: `Tool error: ${message}` }],
|
|
82
|
+
isError: true,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return server;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Keep the schema reference to avoid unused-import warnings
|
|
91
|
+
void CallToolResultSchema;
|
|
92
|
+
|
|
93
|
+
export { ALL_TOOLS };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type ToolDefinition } from '../types.js';
|
|
3
|
+
|
|
4
|
+
export const dilutionTools: ToolDefinition[] = [
|
|
5
|
+
{
|
|
6
|
+
name: 'calculate_dilution',
|
|
7
|
+
description:
|
|
8
|
+
'Calculate dilution impact for a hypothetical new funding round or equity issuance. ' +
|
|
9
|
+
'Returns pre- and post-dilution ownership percentages for each stakeholder.',
|
|
10
|
+
inputSchema: z.object({
|
|
11
|
+
companyId: z.string().describe('Company ID'),
|
|
12
|
+
newSharesIssued: z
|
|
13
|
+
.number()
|
|
14
|
+
.int()
|
|
15
|
+
.positive()
|
|
16
|
+
.describe('Number of new shares to be issued in the scenario'),
|
|
17
|
+
includeOptionPool: z
|
|
18
|
+
.boolean()
|
|
19
|
+
.optional()
|
|
20
|
+
.default(false)
|
|
21
|
+
.describe('Whether to include unissued option pool shares in the denominator'),
|
|
22
|
+
includeSafes: z
|
|
23
|
+
.boolean()
|
|
24
|
+
.optional()
|
|
25
|
+
.default(true)
|
|
26
|
+
.describe('Whether to include SAFEs in conversion when calculating dilution'),
|
|
27
|
+
}),
|
|
28
|
+
handler: async (input, client) => {
|
|
29
|
+
const { data } = await client.post('/api/v1/dilution/calculate', input);
|
|
30
|
+
return {
|
|
31
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'get_fully_diluted_shares',
|
|
37
|
+
description:
|
|
38
|
+
'Get the current fully diluted share count including outstanding shares, ' +
|
|
39
|
+
'options (granted and reserved), warrants, and convertible instruments.',
|
|
40
|
+
inputSchema: z.object({
|
|
41
|
+
companyId: z.string().describe('Company ID'),
|
|
42
|
+
asOfDate: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe('Calculate as of a specific date (ISO 8601 YYYY-MM-DD). Defaults to today.'),
|
|
46
|
+
}),
|
|
47
|
+
handler: async (input, client) => {
|
|
48
|
+
const { data } = await client.get('/api/v1/dilution/fully-diluted', {
|
|
49
|
+
params: input,
|
|
50
|
+
});
|
|
51
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type ToolDefinition } from '../types.js';
|
|
3
|
+
|
|
4
|
+
export const documentTools: ToolDefinition[] = [
|
|
5
|
+
{
|
|
6
|
+
name: 'list_documents',
|
|
7
|
+
description:
|
|
8
|
+
'List documents stored in OpenCap (shareholder agreements, option grants, board consents, etc.).',
|
|
9
|
+
inputSchema: z.object({
|
|
10
|
+
companyId: z.string().optional().describe('Filter by company ID'),
|
|
11
|
+
documentType: z
|
|
12
|
+
.enum([
|
|
13
|
+
'shareholder_agreement',
|
|
14
|
+
'option_grant',
|
|
15
|
+
'board_consent',
|
|
16
|
+
'safe',
|
|
17
|
+
'certificate',
|
|
18
|
+
'other',
|
|
19
|
+
])
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('Filter by document type'),
|
|
22
|
+
stakeholderId: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Filter documents associated with a stakeholder'),
|
|
26
|
+
limit: z.number().optional().default(50).describe('Max results to return'),
|
|
27
|
+
}),
|
|
28
|
+
handler: async (input, client) => {
|
|
29
|
+
const { data } = await client.get('/api/v1/documents', { params: input });
|
|
30
|
+
const documents = data.documents ?? data;
|
|
31
|
+
return {
|
|
32
|
+
content: [{ type: 'text', text: JSON.stringify(documents, null, 2) }],
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'get_document',
|
|
38
|
+
description: 'Get metadata and details for a specific document by ID.',
|
|
39
|
+
inputSchema: z.object({
|
|
40
|
+
id: z.string().describe('Document ID'),
|
|
41
|
+
}),
|
|
42
|
+
handler: async (input, client) => {
|
|
43
|
+
const { data } = await client.get(`/api/v1/documents/${input.id}`);
|
|
44
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'search_documents',
|
|
49
|
+
description: 'Search documents by keyword or metadata across all company documents.',
|
|
50
|
+
inputSchema: z.object({
|
|
51
|
+
query: z.string().describe('Search query string'),
|
|
52
|
+
companyId: z.string().optional().describe('Limit search to a specific company'),
|
|
53
|
+
documentType: z
|
|
54
|
+
.enum([
|
|
55
|
+
'shareholder_agreement',
|
|
56
|
+
'option_grant',
|
|
57
|
+
'board_consent',
|
|
58
|
+
'safe',
|
|
59
|
+
'certificate',
|
|
60
|
+
'other',
|
|
61
|
+
])
|
|
62
|
+
.optional()
|
|
63
|
+
.describe('Filter by document type'),
|
|
64
|
+
limit: z.number().optional().default(20).describe('Max results to return'),
|
|
65
|
+
}),
|
|
66
|
+
handler: async (input, client) => {
|
|
67
|
+
const { data } = await client.get('/api/v1/documents/search', { params: input });
|
|
68
|
+
const results = data.results ?? data;
|
|
69
|
+
return {
|
|
70
|
+
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
];
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type ToolDefinition } from '../types.js';
|
|
3
|
+
|
|
4
|
+
export const equityPlanTools: ToolDefinition[] = [
|
|
5
|
+
{
|
|
6
|
+
name: 'list_equity_plans',
|
|
7
|
+
description:
|
|
8
|
+
'List all equity plans (stock option plans, RSU plans, etc.) in the company.',
|
|
9
|
+
inputSchema: z.object({
|
|
10
|
+
companyId: z.string().optional().describe('Filter by company ID'),
|
|
11
|
+
limit: z.number().optional().default(50).describe('Max results to return'),
|
|
12
|
+
}),
|
|
13
|
+
handler: async (input, client) => {
|
|
14
|
+
const { data } = await client.get('/api/v1/equity-plans', { params: input });
|
|
15
|
+
const plans = data.equityPlans ?? data;
|
|
16
|
+
return {
|
|
17
|
+
content: [{ type: 'text', text: JSON.stringify(plans, null, 2) }],
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'get_equity_plan',
|
|
23
|
+
description: 'Get details for a specific equity plan by ID.',
|
|
24
|
+
inputSchema: z.object({
|
|
25
|
+
id: z.string().describe('Equity plan ID'),
|
|
26
|
+
}),
|
|
27
|
+
handler: async (input, client) => {
|
|
28
|
+
const { data } = await client.get(`/api/v1/equity-plans/${input.id}`);
|
|
29
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'create_equity_plan',
|
|
34
|
+
description: 'Create a new equity incentive plan for employees or advisors.',
|
|
35
|
+
inputSchema: z.object({
|
|
36
|
+
name: z.string().describe('Plan name, e.g. "2024 Stock Option Plan"'),
|
|
37
|
+
planType: z
|
|
38
|
+
.enum(['ISO', 'NSO', 'RSA', 'RSU', 'SAR', 'other'])
|
|
39
|
+
.describe('Type of equity plan'),
|
|
40
|
+
sharesReserved: z
|
|
41
|
+
.number()
|
|
42
|
+
.int()
|
|
43
|
+
.positive()
|
|
44
|
+
.describe('Number of shares reserved for this plan'),
|
|
45
|
+
companyId: z.string().describe('Company ID this plan belongs to'),
|
|
46
|
+
expirationDate: z
|
|
47
|
+
.string()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe('Plan expiration date in ISO 8601 format (YYYY-MM-DD)'),
|
|
50
|
+
defaultVestingSchedule: z
|
|
51
|
+
.object({
|
|
52
|
+
totalMonths: z.number().int().positive().describe('Total vesting period in months'),
|
|
53
|
+
cliffMonths: z.number().int().min(0).describe('Cliff period in months'),
|
|
54
|
+
})
|
|
55
|
+
.optional()
|
|
56
|
+
.describe('Default vesting schedule for grants under this plan'),
|
|
57
|
+
}),
|
|
58
|
+
handler: async (input, client) => {
|
|
59
|
+
const { data } = await client.post('/api/v1/equity-plans', input);
|
|
60
|
+
return {
|
|
61
|
+
content: [
|
|
62
|
+
{ type: 'text', text: `Equity plan created: ${JSON.stringify(data, null, 2)}` },
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
];
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type ToolDefinition } from '../types.js';
|
|
3
|
+
|
|
4
|
+
export const financialReportTools: ToolDefinition[] = [
|
|
5
|
+
{
|
|
6
|
+
name: 'list_financial_reports',
|
|
7
|
+
description:
|
|
8
|
+
'List all financial reports (equity summaries, cap table snapshots, tax reports, etc.).',
|
|
9
|
+
inputSchema: z.object({
|
|
10
|
+
companyId: z.string().optional().describe('Filter by company ID'),
|
|
11
|
+
reportType: z
|
|
12
|
+
.enum([
|
|
13
|
+
'cap_table_summary',
|
|
14
|
+
'equity_summary',
|
|
15
|
+
'tax_report',
|
|
16
|
+
'409A_report',
|
|
17
|
+
'custom',
|
|
18
|
+
])
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('Filter by report type'),
|
|
21
|
+
limit: z.number().optional().default(20).describe('Max results to return'),
|
|
22
|
+
}),
|
|
23
|
+
handler: async (input, client) => {
|
|
24
|
+
const { data } = await client.get('/api/v1/financial-reports', { params: input });
|
|
25
|
+
const reports = data.reports ?? data;
|
|
26
|
+
return {
|
|
27
|
+
content: [{ type: 'text', text: JSON.stringify(reports, null, 2) }],
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'get_financial_report',
|
|
33
|
+
description: 'Get the full contents of a specific financial report by ID.',
|
|
34
|
+
inputSchema: z.object({
|
|
35
|
+
id: z.string().describe('Financial report ID'),
|
|
36
|
+
}),
|
|
37
|
+
handler: async (input, client) => {
|
|
38
|
+
const { data } = await client.get(`/api/v1/financial-reports/${input.id}`);
|
|
39
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'create_financial_report',
|
|
44
|
+
description:
|
|
45
|
+
'Generate a new financial report or cap table snapshot for the company.',
|
|
46
|
+
inputSchema: z.object({
|
|
47
|
+
companyId: z.string().describe('Company ID'),
|
|
48
|
+
reportType: z
|
|
49
|
+
.enum([
|
|
50
|
+
'cap_table_summary',
|
|
51
|
+
'equity_summary',
|
|
52
|
+
'tax_report',
|
|
53
|
+
'409A_report',
|
|
54
|
+
'custom',
|
|
55
|
+
])
|
|
56
|
+
.describe('Type of report to generate'),
|
|
57
|
+
asOfDate: z
|
|
58
|
+
.string()
|
|
59
|
+
.optional()
|
|
60
|
+
.describe('Generate the report as of this date (ISO 8601 YYYY-MM-DD). Defaults to today.'),
|
|
61
|
+
includeConvertibles: z
|
|
62
|
+
.boolean()
|
|
63
|
+
.optional()
|
|
64
|
+
.default(true)
|
|
65
|
+
.describe('Include convertible instruments (SAFEs, notes) in the report'),
|
|
66
|
+
includeOptionPool: z
|
|
67
|
+
.boolean()
|
|
68
|
+
.optional()
|
|
69
|
+
.default(true)
|
|
70
|
+
.describe('Include option pool (granted and unissued) in the report'),
|
|
71
|
+
title: z.string().optional().describe('Custom report title'),
|
|
72
|
+
}),
|
|
73
|
+
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
|
+
};
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
];
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type ToolDefinition } from '../types.js';
|
|
3
|
+
|
|
4
|
+
export const safeTools: ToolDefinition[] = [
|
|
5
|
+
{
|
|
6
|
+
name: 'list_safes',
|
|
7
|
+
description:
|
|
8
|
+
'List all SAFE (Simple Agreement for Future Equity) instruments in the cap table.',
|
|
9
|
+
inputSchema: z.object({
|
|
10
|
+
companyId: z.string().optional().describe('Filter by company ID'),
|
|
11
|
+
investorId: z.string().optional().describe('Filter by investor stakeholder ID'),
|
|
12
|
+
limit: z.number().optional().default(50).describe('Max results to return'),
|
|
13
|
+
}),
|
|
14
|
+
handler: async (input, client) => {
|
|
15
|
+
const { data } = await client.get('/api/v1/safes', { params: input });
|
|
16
|
+
const safes = data.safes ?? data;
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: 'text', text: JSON.stringify(safes, null, 2) }],
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'get_safe',
|
|
24
|
+
description: 'Get details for a specific SAFE instrument by ID.',
|
|
25
|
+
inputSchema: z.object({
|
|
26
|
+
id: z.string().describe('SAFE ID'),
|
|
27
|
+
}),
|
|
28
|
+
handler: async (input, client) => {
|
|
29
|
+
const { data } = await client.get(`/api/v1/safes/${input.id}`);
|
|
30
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'create_safe',
|
|
35
|
+
description: 'Record a new SAFE instrument (e.g. post-money SAFE from a YC-style round).',
|
|
36
|
+
inputSchema: z.object({
|
|
37
|
+
investmentAmount: z.number().positive().describe('Investment amount in USD'),
|
|
38
|
+
valuationCap: z
|
|
39
|
+
.number()
|
|
40
|
+
.positive()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe('Valuation cap in USD (for valuation cap SAFEs)'),
|
|
43
|
+
discountRate: z
|
|
44
|
+
.number()
|
|
45
|
+
.min(0)
|
|
46
|
+
.max(100)
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('Discount rate percentage (e.g. 20 for 20%)'),
|
|
49
|
+
safeType: z
|
|
50
|
+
.enum(['valuation_cap', 'discount', 'mfn', 'valuation_cap_and_discount'])
|
|
51
|
+
.describe('Type of SAFE'),
|
|
52
|
+
investorId: z.string().describe('Stakeholder ID of the investor'),
|
|
53
|
+
companyId: z.string().describe('Company ID'),
|
|
54
|
+
investmentDate: z.string().describe('Investment date in ISO 8601 format (YYYY-MM-DD)'),
|
|
55
|
+
proRataRights: z
|
|
56
|
+
.boolean()
|
|
57
|
+
.optional()
|
|
58
|
+
.default(false)
|
|
59
|
+
.describe('Whether the investor has pro-rata rights'),
|
|
60
|
+
}),
|
|
61
|
+
handler: async (input, client) => {
|
|
62
|
+
const { data } = await client.post('/api/v1/safes', input);
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{ type: 'text', text: `SAFE created: ${JSON.stringify(data, null, 2)}` },
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'update_safe',
|
|
72
|
+
description: 'Update an existing SAFE instrument (e.g. record conversion).',
|
|
73
|
+
inputSchema: z.object({
|
|
74
|
+
id: z.string().describe('SAFE ID'),
|
|
75
|
+
status: z
|
|
76
|
+
.enum(['open', 'converted', 'cancelled'])
|
|
77
|
+
.optional()
|
|
78
|
+
.describe('Current status of the SAFE'),
|
|
79
|
+
conversionDate: z
|
|
80
|
+
.string()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe('Conversion date in ISO 8601 format (YYYY-MM-DD)'),
|
|
83
|
+
convertedShareClassId: z
|
|
84
|
+
.string()
|
|
85
|
+
.optional()
|
|
86
|
+
.describe('Share class ID that this SAFE converted into'),
|
|
87
|
+
convertedShares: z
|
|
88
|
+
.number()
|
|
89
|
+
.int()
|
|
90
|
+
.positive()
|
|
91
|
+
.optional()
|
|
92
|
+
.describe('Number of shares issued upon conversion'),
|
|
93
|
+
}),
|
|
94
|
+
handler: async (input, client) => {
|
|
95
|
+
const { id, ...body } = input;
|
|
96
|
+
const { data } = await client.put(`/api/v1/safes/${id}`, body);
|
|
97
|
+
return {
|
|
98
|
+
content: [
|
|
99
|
+
{ type: 'text', text: `SAFE updated: ${JSON.stringify(data, null, 2)}` },
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type ToolDefinition } from '../types.js';
|
|
3
|
+
|
|
4
|
+
export const shareClassTools: ToolDefinition[] = [
|
|
5
|
+
{
|
|
6
|
+
name: 'list_share_classes',
|
|
7
|
+
description:
|
|
8
|
+
'List all share classes defined in the cap table (e.g. Common, Series A Preferred).',
|
|
9
|
+
inputSchema: z.object({
|
|
10
|
+
companyId: z.string().optional().describe('Filter by company ID'),
|
|
11
|
+
limit: z.number().optional().default(50).describe('Max results to return'),
|
|
12
|
+
}),
|
|
13
|
+
handler: async (input, client) => {
|
|
14
|
+
const { data } = await client.get('/api/v1/share-classes', { params: input });
|
|
15
|
+
const shareClasses = data.shareClasses ?? data;
|
|
16
|
+
return {
|
|
17
|
+
content: [{ type: 'text', text: JSON.stringify(shareClasses, null, 2) }],
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'get_share_class',
|
|
23
|
+
description: 'Get details for a specific share class by ID.',
|
|
24
|
+
inputSchema: z.object({
|
|
25
|
+
id: z.string().describe('Share class ID'),
|
|
26
|
+
}),
|
|
27
|
+
handler: async (input, client) => {
|
|
28
|
+
const { data } = await client.get(`/api/v1/share-classes/${input.id}`);
|
|
29
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'create_share_class',
|
|
34
|
+
description: 'Create a new share class (e.g. Series B Preferred).',
|
|
35
|
+
inputSchema: z.object({
|
|
36
|
+
name: z.string().describe('Share class name, e.g. "Series A Preferred"'),
|
|
37
|
+
classType: z
|
|
38
|
+
.enum(['common', 'preferred', 'warrant', 'option'])
|
|
39
|
+
.describe('Type of share class'),
|
|
40
|
+
authorizedShares: z
|
|
41
|
+
.number()
|
|
42
|
+
.int()
|
|
43
|
+
.positive()
|
|
44
|
+
.describe('Total number of authorized shares'),
|
|
45
|
+
parValue: z.number().optional().describe('Par value per share in USD'),
|
|
46
|
+
companyId: z.string().describe('Company ID this share class belongs to'),
|
|
47
|
+
liquidationPreference: z
|
|
48
|
+
.number()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe('Liquidation preference multiplier (e.g. 1 for 1x)'),
|
|
51
|
+
participationRights: z
|
|
52
|
+
.enum(['none', 'full', 'capped'])
|
|
53
|
+
.optional()
|
|
54
|
+
.describe('Participation rights type'),
|
|
55
|
+
}),
|
|
56
|
+
handler: async (input, client) => {
|
|
57
|
+
const { data } = await client.post('/api/v1/share-classes', input);
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{ type: 'text', text: `Share class created: ${JSON.stringify(data, null, 2)}` },
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
];
|