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
6 changes: 6 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
"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"
},
{
"name": "carta-fund-admin",
"source": "./plugins/carta-fund-admin",
"description": "Carta Fund Admin plugin — skills for querying fund admin data, performance benchmarks, regulatory reporting, and more",
"category": "productivity"
}
]
}
19 changes: 19 additions & 0 deletions plugins/carta-fund-admin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "carta-fund-admin",
"displayName": "Carta Fund Admin",
"version": "0.1.0",
"description": "Carta Fund Admin plugin — skills for querying fund admin data, performance benchmarks, regulatory reporting, and more",
"author": {
"name": "Carta Engineering",
"email": "engineering@carta.com"
},
"homepage": "https://github.com/carta/plugins",
"repository": "https://github.com/carta/plugins",
"keywords": ["fund_admin", "mcp", "carta"],
"mcpServers": {
"carta": {
"type": "http",
"url": "https://mcp.app.carta.com/mcp"
}
}
}
47 changes: 47 additions & 0 deletions plugins/carta-fund-admin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Carta Fund Admin

Claude Code plugin that gives Claude access to Carta Fund Admin data.

## How it works

This plugin provides **skills** that teach Claude how to query fund metrics, regulatory reporting, performance benchmarks, and more via the Carta MCP server at `https://mcp.app.carta.com/mcp`.

## Installation

Install from the marketplace via `/plugin`, or add the Carta MCP server manually:

```bash
claude mcp add --transport http carta https://mcp.app.carta.com/mcp
claude plugin marketplace add carta/plugins
claude plugin install carta-fund-admin
```

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

### Try it out

- "What datasets are available in Carta?"
- "Show me NAV and TVPI for all my funds"
- "Pull our Form ADV data for 2025"
- "How does Fund I compare to its benchmark?"
- "What journal entries were posted last quarter?"

## Skills

| Skill | Description |
|-------|-------------|
| `explore-data` | Query and explore fund admin data — NAV, partners, investments, accounting |
| `form-adv` | Form ADV Schedule D regulatory data and firm rollup |
| `performance-benchmarks` | Compare fund performance against peer benchmark cohorts |

## MCP Tools

The Carta MCP server exposes these data warehouse tools:

| Tool | Description |
|------|-------------|
| `list_tables` | Browse available datasets with descriptions and record counts |
| `describe_table` | Get column names, types, and descriptions for a specific table |
| `execute_query` | Run a read-only SELECT query against the data warehouse |
| `list_contexts` | See which firms you have access to |
| `set_context` | Switch to a different firm |
27 changes: 27 additions & 0 deletions plugins/carta-fund-admin/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__execute_query",
"hooks": [
{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/hooks/warn-empty-query.js",
"timeout": 5
}
]
}
]
}
}
29 changes: 29 additions & 0 deletions plugins/carta-fund-admin/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-fund-admin 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-fund-admin tools available via the Carta MCP server (list_tables, describe_table, execute_query). Before ANY tool call, invoke the matching Skill(\'carta-fund-admin:...\') first. The skill defines what to query, what inputs are required, and how to present results. If no skill matches the user\'s request, use list_tables to browse available datasets and describe_table to understand schemas. 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);
});
56 changes: 56 additions & 0 deletions plugins/carta-fund-admin/scripts/hooks/warn-empty-query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env node
/**
* PostToolUse Hook: Warn on empty execute_query responses
*
* When execute_query returns no rows, 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 and check for empty results
let parsed;
try {
parsed = typeof resultStr === 'string' ? JSON.parse(resultStr) : resultStr;
} catch {
process.exit(0);
return;
}

const isEmpty =
(parsed && parsed._warning) ||
(Array.isArray(parsed) && parsed.length === 0) ||
(parsed?.rows && parsed.rows.length === 0) ||
(parsed?.count === 0);

if (isEmpty) {
const sql = tool_input?.sql || 'query';
process.stderr.write(
`⚠️ EMPTY DATA: execute_query returned no results. ` +
`Tell the user what data was expected but missing and suggest next steps ` +
`(check firm context with list_contexts, verify table names with list_tables, etc.).`
);
process.exit(2);
return;
}

process.exit(0);
} catch (err) {
// Never block on hook errors
process.exit(0);
}
});
101 changes: 101 additions & 0 deletions plugins/carta-fund-admin/skills/explore-data/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
name: explore-data
description: Query and explore fund admin data in the Carta data warehouse. Use when asked about fund metrics, NAV, LP data, portfolio financials, journal entries, investments, or any general data warehouse query.
---

# Explore Data

Query the Carta data warehouse for fund admin data — NAV, partner data, portfolio financials, journal entries, investments, and more.

## When to Use

- "What's the current NAV for [Fund]?"
- "Show me TVPI for all funds"
- "List all LP investors in [Fund] with their contributions"
- "What journal entries were posted for [Fund] last quarter?"
- "Which portfolio companies have the highest MOIC?"
- "Show me total contributions and distributions for each LP"
- "What are the fund metrics as of Q4 2024?"

## Prerequisites

The user must have the Carta MCP server connected. If this is their first query in the session:

1. Call `list_contexts` to see which firms are accessible
2. Call `set_context` with the target `firm_id` if needed

## How to Query

Use the three MCP tools in sequence:

1. **Find the right table:** `list_tables(schema="FUND_ADMIN")` — browse available datasets
2. **Understand the schema:** `describe_table(table_name="<TABLE>", schema="FUND_ADMIN")` — get column details
3. **Run the query:** `execute_query(sql="SELECT ... FROM FUND_ADMIN.<TABLE> WHERE ... LIMIT 1000")` — fetch results

## Common Datasets

| Table | Use For |
|-------|---------|
| `MONTHLY_NAV_CALCULATIONS` | NAV, commitments, distributions, DPI/TVPI/MOIC per fund per month |
| `AGGREGATE_FUND_METRICS` | LP/GP investor counts, fund-level summary metrics |
| `AGGREGATE_INVESTMENTS` | Portfolio company list, active investments, cost basis, FMV |
| `JOURNAL_ENTRIES` | Balance sheet data — cash, cost of investment, unrealized G/L, liabilities |
| `ALLOCATIONS` | Fund list with entity types (Fund, SPV), fund names, firm info |
| `TEMPORAL_FUND_COHORT_BENCHMARKS` | Performance benchmarks by vintage year, AUM bucket, percentiles |

## Query Guidelines

- **Always include LIMIT** — default to `LIMIT 1000` unless the user asks for more
- **Only SELECT** — no INSERT, UPDATE, DELETE, or DDL. The MCP validates this server-side
- **Date fields** — use `effective_date` for accounting dates, `month_end_date` for NAV periods
- **Deduplication** — some tables have multiple rows per entity; use `ROW_NUMBER() OVER (PARTITION BY ... ORDER BY last_refreshed_at DESC) = 1` when needed

## Presentation

1. **Lead with a summary** — "Your firm has 5 funds with a combined NAV of $X"
2. **Format as tables** — use markdown tables with clear column headers
3. **Format currency** — use `$X,XXX` for amounts, `X.XXx` for multiples
4. **Flag notable items** — low NAV, negative performance, missing data
5. **Use Carta voice** — say "your funds" not "query results"; say "your NAV" not "MONTHLY_NAV_CALCULATIONS data"

## Example: Fund NAV Summary

```sql
SELECT
n.fund_name,
n.month_end_date,
n.ending_total_nav,
n.total_tvpi,
n.total_dpi,
n.total_moic
FROM FUND_ADMIN.MONTHLY_NAV_CALCULATIONS n
WHERE n.is_firm_rollup = FALSE
QUALIFY ROW_NUMBER() OVER (PARTITION BY n.fund_uuid ORDER BY n.month_end_date DESC, n.last_refreshed_at DESC) = 1
ORDER BY n.ending_total_nav DESC
LIMIT 50
```

| Fund | As Of | NAV | TVPI | DPI | MOIC |
|------|-------|-----|------|-----|------|
| Fund I | 2024-12-31 | $150,000,000 | 1.85x | 0.42x | 1.85x |

## Example: LP Contributions by Fund

```sql
SELECT
fund_name,
SUM(cumulative_lp_contributions) AS total_lp_contributions,
SUM(cumulative_total_distributions) AS total_distributions,
COUNT(DISTINCT fund_uuid) AS fund_count
FROM FUND_ADMIN.MONTHLY_NAV_CALCULATIONS
WHERE is_firm_rollup = FALSE
AND month_end_date = (SELECT MAX(month_end_date) FROM FUND_ADMIN.MONTHLY_NAV_CALCULATIONS)
GROUP BY fund_name
ORDER BY total_lp_contributions DESC
LIMIT 50
```

## Best Effort

- **Computed:** summaries, aggregations, and trend analysis derived by Claude from query results
- **Authoritative:** raw data values returned directly from the Carta data warehouse
Loading
Loading