Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "carta-plugins",
"description": "Official Carta plugins for Claude Code",
"owner": {
"name": "Carta Engineering",
"email": "engineering@carta.com"
},
"plugins": [
{
"name": "carta-cap-table",
"source": "./plugins/carta-cap-table",
"description": "Carta Cap Table plugin — skills and hooks for querying cap tables, grants, SAFEs, 409A valuations, waterfall scenarios, and more",
"category": "productivity"
}
]
}
25 changes: 25 additions & 0 deletions plugins/carta-cap-table/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "carta-cap-table",
"displayName": "Carta Cap Table",
"version": "0.6.1",
"description": "Carta Cap Table plugin — skills and hooks for querying cap tables, grants, SAFEs, 409A valuations, waterfall scenarios, and more",
"author": {
"name": "Carta Engineering",
"email": "engineering@carta.com"
},
"homepage": "https://github.com/carta/plugins",
"repository": "https://github.com/carta/plugins",
"keywords": [
"cap_table",
"mcp",
"carta",
"equity",
"grants"
],
"mcpServers": {
"carta": {
"type": "http",
"url": "https://mcp.app.carta.com/mcp"
}
}
}
53 changes: 53 additions & 0 deletions plugins/carta-cap-table/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Carta Cap Table

Claude Code plugin that gives Claude access to Carta cap table data.

## How it works

This plugin provides **skills** that teach Claude how to use the Carta MCP server effectively — querying cap tables, modeling rounds, detecting portfolio alerts, and more.

The MCP server handles authentication via OAuth:
1. On first use, Claude opens a browser to the Carta login page
2. You log in with your Carta credentials
3. Carta issues an access token that Claude uses for all subsequent requests

## Installation

Install from the `carta/plugins` marketplace:

```
/plugin install carta-cap-table@carta-plugins
```

This registers the Carta MCP server (`https://mcp.app.carta.com/mcp`) and loads all skills automatically.

After installing, restart Claude Code and run `/mcp` to complete OAuth authentication.

## Try it out

- "What Carta cap table data do I have access to?"
- "Show me the ownership breakdown for [company]"
- "Who are the stakeholders in [company]?"
- "What SAFEs are outstanding for [company]?"
- "When does the 409A expire for [company]?"
- "Model a Series B at $80M pre-money with a $20M raise"

## Skills

| Skill | Description |
|-------|-------------|
| `client-triggers` | Surface time-based BD triggers across the portfolio. Use when asked about client outreach, which clients closed a round recently, stale cap tables, pending grants, tombstones, weekly deals, or BD triggers. |
| `conversion-calculator` | Calculate SAFE and convertible note conversion into equity. Use when asked about SAFE conversion, note conversion, conversion shares, or how instruments convert in a round. |
| `discover-commands` | Find the right carta-cap-table command when no other skill matches. Use when unsure which command to call, exploring available data, or when the user's request doesn't match a specific skill. |
| `grant-vesting` | Fetch vesting schedule for a specific option grant. Use when asked about vesting details, cliff dates, vesting progress, or unvested shares for a particular grant. |
| `list-convertible-notes` | Fetch all convertible instruments (SAFEs and convertible debt) for a company. Use when asked about convertible notes, SAFEs, convertible debt, note terms, caps, discounts, or maturity dates. |
| `list-safes` | Fetch all SAFEs for a company. Use when asked about SAFEs, simple agreements for future equity, SAFE terms, valuation caps, or discounts. |
| `market-benchmarks` | Analyze cap structure patterns across the portfolio as market benchmarks. Use when asked about market benchmarks, typical option pool sizes, average SAFE terms, what's normal for a Series A, cap structure patterns, or portfolio-wide statistics. |
| `ownership` | Ownership structure by share class, voting rights, and liquidation seniority. Use when asked about ownership breakdown, preferred vs common holders, voting power, protective provisions, or consent requirements. |
| `portfolio-alerts` | Detect red flags and time-sensitive issues across portfolio companies. Use when asked to flag problems, find expiring items, or audit portfolio health. |
| `portfolio-query` | Query cap table data for one or more companies. Use when asked about cap tables, ownership breakdown, share classes, stakeholder holdings, portfolio-wide analysis, comparing companies, or finding patterns across multiple entities. |
| `pro-forma-model` | Model a pro-forma financing round to show dilution impact. Use when asked to model a Series A/B/C, new round, or show how a round would affect ownership. |
| `round-history` | Fetch financing round history for a company. Use when asked about funding rounds, capital raised, or financing history. |
| `stakeholders` | List stakeholders for a company. Use when asked who the stakeholders are, stakeholder list, shareholders, investors, or holders. |
| `valuation-history` | Fetch 409A valuation history for a company. Use when asked about 409A valuations, FMV, exercise prices, or valuation expiration dates. |
| `waterfall-scenarios` | Fetch saved waterfall / exit scenario models for a company. Use when asked about liquidation preferences, exit payouts, return multiples, or waterfall analysis. |
27 changes: 27 additions & 0 deletions plugins/carta-cap-table/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/hooks/inject-skill-context.js",
"timeout": 5
}
]
}
],
"PostToolUse": [
{
"matcher": "mcp__carta.*__fetch",
"hooks": [
{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/hooks/warn-empty-fetch.js",
"timeout": 5
}
]
}
]
}
}
29 changes: 29 additions & 0 deletions plugins/carta-cap-table/scripts/hooks/inject-skill-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env node
/**
* SessionStart hook: inject skill-first reminder into every session.
*
* Ensures Claude loads the relevant carta-cap-table skill before making
* any tool calls, even in subagents that don't inherit session context.
*/

let inputData = '';
process.stdin.on('data', chunk => (inputData += chunk));

process.stdin.on('end', () => {
let hookEventName = 'SessionStart';
try {
const input = JSON.parse(inputData);
hookEventName = input.hook_event_name || hookEventName;
} catch {}

const output = {
hookSpecificOutput: {
hookEventName,
additionalContext:
'<EXTREMELY_IMPORTANT>You have carta-cap-table tools available. Before ANY tool call, invoke the matching Skill(\'carta-cap-table:...\') first. The skill defines what to fetch, what inputs are required, and how to present results. If no skill matches, invoke Skill(\'carta-cap-table:discover-commands\') to find the right command via discover(). IMPORTANT: Skill is a deferred tool — if its schema is not yet loaded, you MUST call ToolSearch with query "select:Skill" first, then invoke the Skill tool.</EXTREMELY_IMPORTANT>',
},
};

process.stdout.write(JSON.stringify(output));
process.exit(0);
});
51 changes: 51 additions & 0 deletions plugins/carta-cap-table/scripts/hooks/warn-empty-fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env node
/**
* PostToolUse Hook: Warn on empty fetch() responses
*
* When carta-cap-table's fetch() returns a _warning (empty data),
* outputs a reminder to stderr (exit 2) which gets fed to Claude.
*/

let inputData = '';
process.stdin.on('data', chunk => (inputData += chunk));

process.stdin.on('end', () => {
try {
const input = JSON.parse(inputData);
const { tool_input, tool_response } = input;

// Extract the result string from the MCP response
let resultStr = tool_response?.result || tool_response;
if (Array.isArray(resultStr) && resultStr[0]?.type === 'text') {
resultStr = resultStr[0].text;
} else if (resultStr?.content && Array.isArray(resultStr.content)) {
resultStr = resultStr.content[0]?.text || resultStr;
}

// Parse JSON and check for _warning
let parsed;
try {
parsed = typeof resultStr === 'string' ? JSON.parse(resultStr) : resultStr;
} catch {
process.exit(0);
return;
}

if (parsed && parsed._warning) {
const command = tool_input?.command || 'unknown command';
// Exit 2 = stderr fed back to Claude
process.stderr.write(
`⚠️ EMPTY DATA: ${command} returned no results. ` +
`Per the golden rule, any values you derive from this gap are BEST EFFORT. ` +
`Tell the user what's missing and ask before computing substitutes.`
);
process.exit(2);
return;
}

process.exit(0);
} catch (err) {
// Never block on hook errors
process.exit(0);
}
});
106 changes: 106 additions & 0 deletions plugins/carta-cap-table/skills/client-triggers/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
name: client-triggers
description: Surface time-based BD triggers across the portfolio. Use when asked about client outreach, which clients closed a round recently, stale cap tables, pending grants, tombstones, weekly deals, or BD triggers.
---

# Client Triggers

Scan the portfolio for actionable outreach triggers: recent round closes, expiring 409As, and companies with pending grants but no current valuation.

## Prerequisites

No inputs required — this skill loops the full portfolio automatically. Cap at 20 companies.

## Commands

- `list_accounts` — get all portfolio companies
- `fetch("cap_table:get:financing_history", {"corporation_id": corporation_id})` — financing history per company
- `fetch("cap_table:get:409a_valuations", {"corporation_id": corporation_id})` — 409A status (optional)

## Key Fields

From financing history:
- `issue_date`: date of issuance
- `round_name`: name of the round (e.g. "Series A")
- `is_grant`: true if this is a grant issuance (not a priced round)

From 409A FMVs:
- `expiration_date`: when the valuation expires
- `price`: FMV per share

## How to Present

See Step 4 below. Group by trigger type. Omit sections with no results.

## Step 1 — Get Portfolio

Call `list_accounts`. Filter to accounts where `id` starts with `corporation_pk:`. Extract the numeric corporation IDs (up to 20).

## Step 2 — Collect Data Per Company

For each company, fetch in sequence:

**Financing history:**
- `fetch("cap_table:get:financing_history", {"corporation_id": corporation_id})`
- Key fields: `issue_date`, `round_name`, `is_grant`
- Group by `round_name`, find max `issue_date` per round → "last round date"

**409A status** (optional, only if checking valuation triggers):
- `fetch("cap_table:get:409a_valuations", {"corporation_id": corporation_id})`
- Key fields: `expiration_date`, `price` (FMV per share)
- Find the most recent valuation (sort by `effective_date` desc)

## Step 3 — Classify Triggers

### Recent Closes
Companies whose last round closed within N days (default 90, or user-specified):
- Trigger: `last_round_date ≥ today - N days`
- Action: Congratulations / cap table review outreach

### Stale 409A
Companies with an expired or near-expiry 409A (within 60 days):
- Trigger: `expiration_date ≤ today + 60 days` or no 409A on file
- Action: Renewal reminder outreach

### Pending Grants (no current 409A)
Companies that issued grants recently (within 90 days) but have no valid 409A:
- Trigger: recent `is_grant=true` entry + no active 409A
- Action: Urgency outreach — grants issued without a current FMV create 409A exposure

## Step 4 — Present Results

Group output by trigger type. Example:

---

**Recent Closes (last 90 days)**

| Company | Round | Close Date | Days Ago |
|---------|-------|------------|----------|
| Acme Inc | Series B | 2026-01-15 | 63 days |

**Stale or Expiring 409As**

| Company | Last 409A | Expiration | Status |
|---------|-----------|------------|--------|
| Beta Corp | $2.50 | 2026-04-01 | Expiring soon |
| Gamma LLC | — | — | No 409A on file |

**Grants Issued Without Current 409A**

| Company | Last Grant Date | 409A Status |
|---------|----------------|-------------|
| Delta Co | 2026-02-20 | Expired |

---

If no triggers found in a category, omit that section.

## Parameters

If the user specifies a time window (e.g., "last 60 days", "last 6 months"), use that instead of the default 90 days.

## Best Effort

- **Computed:** trigger classification (recent close / stale 409A / pending grants) and time-window detection are heuristic
- **Authoritative:** round close dates, 409A expiration dates, and grant issuance dates come directly from Carta
Loading