@crbonfree/mcp 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Crbon Labs Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,166 @@
1
+ <p align="center">
2
+ <strong>@crbonfree/mcp</strong>
3
+ </p>
4
+
5
+ <p align="center">
6
+ Give your AI agent eyes on your carbon footprint.<br />
7
+ Ask questions in natural language. Get real emissions data back.
8
+ </p>
9
+
10
+ <p align="center">
11
+ <a href="https://www.npmjs.com/package/@crbonfree/mcp"><img src="https://img.shields.io/npm/v/@crbonfree/mcp.svg?style=flat-square" alt="npm" /></a>
12
+ <a href="https://github.com/Crbon-Labs-Inc/crbonfree-mcp/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg?style=flat-square" alt="MIT" /></a>
13
+ <a href="https://modelcontextprotocol.io"><img src="https://img.shields.io/badge/MCP-compatible-047857.svg?style=flat-square" alt="MCP" /></a>
14
+ <a href="https://beta.crbonfree.com"><img src="https://img.shields.io/badge/CrbonFree-beta-047857.svg?style=flat-square" alt="CrbonFree" /></a>
15
+ </p>
16
+
17
+ <br />
18
+
19
+ > *"How much CO₂ did my AI usage produce this month?"*
20
+ >
21
+ > *"Which models are my biggest emitters?"*
22
+ >
23
+ > *"Show me my carbon retirement receipts."*
24
+
25
+ Works with **Claude Desktop** &nbsp;·&nbsp; **Cursor** &nbsp;·&nbsp; **Claude Code** &nbsp;·&nbsp; **Windsurf** &nbsp;·&nbsp; any MCP client
26
+
27
+ ---
28
+
29
+ ## Get started in 2 minutes
30
+
31
+ ### 1. Grab an API key
32
+
33
+ [CrbonFree dashboard](https://beta.crbonfree.com) → **API Keys** → **Create API Key**
34
+
35
+ The key starts with `ck_live_` and is shown once — copy it immediately. New to CrbonFree? [Sign up free.](https://crbonfree.com)
36
+
37
+ ### 2. Pick your client
38
+
39
+ <details open>
40
+ <summary><strong>Claude Desktop</strong></summary>
41
+
42
+ Add to `claude_desktop_config.json`:
43
+
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "crbonfree": {
48
+ "command": "npx",
49
+ "args": ["-y", "@crbonfree/mcp"],
50
+ "env": {
51
+ "CRBONFREE_API_KEY": "ck_live_..."
52
+ }
53
+ }
54
+ }
55
+ }
56
+ ```
57
+ </details>
58
+
59
+ <details>
60
+ <summary><strong>Cursor</strong></summary>
61
+
62
+ Add to `.cursor/mcp.json`:
63
+
64
+ ```json
65
+ {
66
+ "mcpServers": {
67
+ "crbonfree": {
68
+ "command": "npx",
69
+ "args": ["-y", "@crbonfree/mcp"],
70
+ "env": {
71
+ "CRBONFREE_API_KEY": "ck_live_..."
72
+ }
73
+ }
74
+ }
75
+ }
76
+ ```
77
+ </details>
78
+
79
+ <details>
80
+ <summary><strong>Claude Code</strong></summary>
81
+
82
+ ```bash
83
+ claude mcp add crbonfree -- npx -y @crbonfree/mcp
84
+ ```
85
+
86
+ Then set the env var:
87
+ ```bash
88
+ export CRBONFREE_API_KEY="ck_live_..."
89
+ ```
90
+ </details>
91
+
92
+ <details>
93
+ <summary><strong>Windsurf / Other</strong></summary>
94
+
95
+ Any MCP-compatible client works:
96
+
97
+ ```
98
+ command: npx
99
+ args: ["-y", "@crbonfree/mcp"]
100
+ env: { CRBONFREE_API_KEY: "ck_live_..." }
101
+ ```
102
+ </details>
103
+
104
+ ### 3. Ask away
105
+
106
+ That's it. Your agent now has 6 tools:
107
+
108
+ ---
109
+
110
+ ## Tools
111
+
112
+ | | Tool | What you can ask |
113
+ |---|------|-----------------|
114
+ | **Measure** | `get_emissions_summary` | *"How much CO₂ did my AI usage produce in May?"* |
115
+ | **Analyze** | `get_usage_breakdown` | *"Which models are my biggest carbon emitters?"* |
116
+ | **Organize** | `list_projects` | *"What projects am I tracking?"* |
117
+ | **Prove** | `list_carbon_receipts` | *"Show me my carbon retirement receipts"* |
118
+ | **Inspect** | `get_carbon_receipt` | *"Get the details for receipt abc-123"* |
119
+ | **Status** | `get_retirement_status` | *"What's my current retirement status?"* |
120
+
121
+ All tools are **read-only**. No mutations, no deletions, no billing changes.
122
+
123
+ <details>
124
+ <summary><strong>Filtering options</strong></summary>
125
+
126
+ `get_emissions_summary` and `get_usage_breakdown` accept optional filters:
127
+
128
+ | Parameter | Type | Description |
129
+ |-----------|------|-------------|
130
+ | `start_date` | `string` | Start date (`YYYY-MM-DD`). Defaults to 30 days ago. |
131
+ | `end_date` | `string` | End date (`YYYY-MM-DD`). Defaults to today. |
132
+ | `project_id` | `string` | Scope to a specific project. |
133
+
134
+ </details>
135
+
136
+ ---
137
+
138
+ ## How it works
139
+
140
+ ```
141
+ Your AI agent ←── stdio (JSON-RPC) ──→ @crbonfree/mcp ──→ CrbonFree API
142
+
143
+ reads CRBONFREE_API_KEY
144
+ from environment
145
+ ```
146
+
147
+ The server reads your API key from the environment and sends it as an `X-API-Key` header on every request. If the key is missing, the server exits immediately with a clear error — no silent 401s.
148
+
149
+ ---
150
+
151
+ ## TypeScript SDK
152
+
153
+ Building something programmatic? Use [`@crbonfree/sdk`](https://www.npmjs.com/package/@crbonfree/sdk) — the same typed client that powers this MCP server, available directly in your code.
154
+
155
+ ---
156
+
157
+ <p align="center">
158
+ <a href="https://beta.crbonfree.com">Dashboard</a> &nbsp;·&nbsp;
159
+ <a href="https://www.npmjs.com/package/@crbonfree/sdk">SDK</a> &nbsp;·&nbsp;
160
+ <a href="https://github.com/Crbon-Labs-Inc/crbonfree-mcp/issues">Issues</a> &nbsp;·&nbsp;
161
+ <a href="https://crbonfree.com">Website</a>
162
+ </p>
163
+
164
+ <p align="center">
165
+ <sub>MIT — Crbon Labs Inc.</sub>
166
+ </p>
@@ -0,0 +1,2 @@
1
+ import { CarbonLabsClient } from '@crbonfree/sdk';
2
+ export declare const client: CarbonLabsClient;
package/dist/client.js ADDED
@@ -0,0 +1,10 @@
1
+ import { CarbonLabsClient } from '@crbonfree/sdk';
2
+ const apiKey = process.env.CRBONFREE_API_KEY;
3
+ if (!apiKey) {
4
+ console.error('CRBONFREE_API_KEY environment variable is required.');
5
+ console.error('Get one from your CrbonFree dashboard: POST /api/v1/api-keys');
6
+ process.exit(1);
7
+ }
8
+ export const client = new CarbonLabsClient({
9
+ headers: { 'X-API-Key': apiKey },
10
+ });
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { registerGetEmissionsSummary } from './tools/get-emissions-summary.js';
5
+ import { registerGetUsageBreakdown } from './tools/get-usage-breakdown.js';
6
+ import { registerListProjects } from './tools/list-projects.js';
7
+ import { registerListCarbonReceipts } from './tools/list-carbon-receipts.js';
8
+ import { registerGetCarbonReceipt } from './tools/get-carbon-receipt.js';
9
+ import { registerGetRetirementStatus } from './tools/get-retirement-status.js';
10
+ const server = new McpServer({
11
+ name: 'crbonfree',
12
+ version: '0.1.0',
13
+ });
14
+ registerGetEmissionsSummary(server);
15
+ registerGetUsageBreakdown(server);
16
+ registerListProjects(server);
17
+ registerListCarbonReceipts(server);
18
+ registerGetCarbonReceipt(server);
19
+ registerGetRetirementStatus(server);
20
+ async function main() {
21
+ const transport = new StdioServerTransport();
22
+ await server.connect(transport);
23
+ }
24
+ main().catch((error) => {
25
+ console.error('MCP server error:', error);
26
+ process.exit(1);
27
+ });
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerGetCarbonReceipt(server: McpServer): void;
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ import { client } from '../client.js';
3
+ export function registerGetCarbonReceipt(server) {
4
+ server.registerTool('get_carbon_receipt', {
5
+ title: 'Get Carbon Receipt',
6
+ description: 'Fetch full details of a single carbon retirement receipt by ID. This retrieves the receipt — it does NOT verify it cryptographically.',
7
+ inputSchema: {
8
+ id: z.string().describe('The receipt ID (UUID).'),
9
+ },
10
+ }, async ({ id }) => {
11
+ try {
12
+ const result = await client.billing.getReceipt({ id });
13
+ return {
14
+ content: [{
15
+ type: 'text',
16
+ text: JSON.stringify({ ...result, _summary: `Receipt ${id} fetched.` }, null, 2),
17
+ }],
18
+ };
19
+ }
20
+ catch (err) {
21
+ const message = err instanceof Error ? err.message : String(err);
22
+ return {
23
+ content: [{ type: 'text', text: JSON.stringify({ error: message, _summary: `Error: ${message}` }) }],
24
+ isError: true,
25
+ };
26
+ }
27
+ });
28
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerGetEmissionsSummary(server: McpServer): void;
@@ -0,0 +1,30 @@
1
+ import { z } from 'zod';
2
+ import { client } from '../client.js';
3
+ export function registerGetEmissionsSummary(server) {
4
+ server.registerTool('get_emissions_summary', {
5
+ title: 'Get Emissions Summary',
6
+ description: 'Get total CO2 emissions, token usage, per-model rollups, and daily chart for a date range. Optionally filter by project.',
7
+ inputSchema: {
8
+ start_date: z.string().optional().describe('Start date (YYYY-MM-DD). Defaults to 30 days ago.'),
9
+ end_date: z.string().optional().describe('End date (YYYY-MM-DD). Defaults to today.'),
10
+ project_id: z.string().optional().describe('Filter to a specific project ID.'),
11
+ },
12
+ }, async ({ start_date, end_date, project_id }) => {
13
+ try {
14
+ const result = await client.telemetry.getSummary({ start_date, end_date, project_id });
15
+ return {
16
+ content: [{
17
+ type: 'text',
18
+ text: JSON.stringify({ ...result, _summary: `Emissions summary: ${result?.data?.total_co2_kg ?? 'N/A'} kg CO2 (${result?.data?.total_tokens ?? 'N/A'} tokens)` }, null, 2),
19
+ }],
20
+ };
21
+ }
22
+ catch (err) {
23
+ const message = err instanceof Error ? err.message : String(err);
24
+ return {
25
+ content: [{ type: 'text', text: JSON.stringify({ error: message, _summary: `Error: ${message}` }) }],
26
+ isError: true,
27
+ };
28
+ }
29
+ });
30
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerGetRetirementStatus(server: McpServer): void;
@@ -0,0 +1,25 @@
1
+ import { client } from '../client.js';
2
+ export function registerGetRetirementStatus(server) {
3
+ server.registerTool('get_retirement_status', {
4
+ title: 'Get Retirement Status',
5
+ description: 'Get the current billing period including total CO2, credits retired, and period status.',
6
+ inputSchema: {},
7
+ }, async () => {
8
+ try {
9
+ const result = await client.billing.getCurrentPeriod();
10
+ return {
11
+ content: [{
12
+ type: 'text',
13
+ text: JSON.stringify({ ...result, _summary: 'Current billing period returned.' }, null, 2),
14
+ }],
15
+ };
16
+ }
17
+ catch (err) {
18
+ const message = err instanceof Error ? err.message : String(err);
19
+ return {
20
+ content: [{ type: 'text', text: JSON.stringify({ error: message, _summary: `Error: ${message}` }) }],
21
+ isError: true,
22
+ };
23
+ }
24
+ });
25
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerGetUsageBreakdown(server: McpServer): void;
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod';
2
+ import { client } from '../client.js';
3
+ export function registerGetUsageBreakdown(server) {
4
+ server.registerTool('get_usage_breakdown', {
5
+ title: 'Get Usage Breakdown',
6
+ description: 'Get per-model token/cost/carbon/energy breakdown for a date range. Optionally filter by project.',
7
+ inputSchema: {
8
+ range: z.enum(['7d', '30d', '90d', '365d']).optional().describe('Preset range. Ignored when start_date/end_date are provided.'),
9
+ start_date: z.string().optional().describe('Start date (YYYY-MM-DD).'),
10
+ end_date: z.string().optional().describe('End date (YYYY-MM-DD).'),
11
+ project_id: z.string().optional().describe('Filter to a specific project ID.'),
12
+ },
13
+ }, async ({ range, start_date, end_date, project_id }) => {
14
+ try {
15
+ const result = await client.usage.getBreakdown({
16
+ range,
17
+ startDate: start_date,
18
+ endDate: end_date,
19
+ projectId: project_id,
20
+ });
21
+ return {
22
+ content: [{
23
+ type: 'text',
24
+ text: JSON.stringify({ ...result, _summary: 'Per-model usage breakdown returned.' }, null, 2),
25
+ }],
26
+ };
27
+ }
28
+ catch (err) {
29
+ const message = err instanceof Error ? err.message : String(err);
30
+ return {
31
+ content: [{ type: 'text', text: JSON.stringify({ error: message, _summary: `Error: ${message}` }) }],
32
+ isError: true,
33
+ };
34
+ }
35
+ });
36
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerListCarbonReceipts(server: McpServer): void;
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod';
2
+ import { client } from '../client.js';
3
+ export function registerListCarbonReceipts(server) {
4
+ server.registerTool('list_carbon_receipts', {
5
+ title: 'List Carbon Receipts',
6
+ description: 'List carbon retirement receipts (paginated). Returns serial numbers, dates, and amounts.',
7
+ inputSchema: {
8
+ page: z.number().optional().describe('Page number (1-based).'),
9
+ limit: z.number().optional().describe('Number of receipts per page.'),
10
+ },
11
+ }, async ({ page, limit }) => {
12
+ try {
13
+ const result = await client.billing.listReceipts({ page, limit });
14
+ return {
15
+ content: [{
16
+ type: 'text',
17
+ text: JSON.stringify({ ...result, _summary: 'Carbon receipts list returned.' }, null, 2),
18
+ }],
19
+ };
20
+ }
21
+ catch (err) {
22
+ const message = err instanceof Error ? err.message : String(err);
23
+ return {
24
+ content: [{ type: 'text', text: JSON.stringify({ error: message, _summary: `Error: ${message}` }) }],
25
+ isError: true,
26
+ };
27
+ }
28
+ });
29
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerListProjects(server: McpServer): void;
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod';
2
+ import { client } from '../client.js';
3
+ export function registerListProjects(server) {
4
+ server.registerTool('list_projects', {
5
+ title: 'List Projects',
6
+ description: 'List projects (workspaces) in the active organization. Optionally include per-project usage stats.',
7
+ inputSchema: {
8
+ include: z.enum(['usage']).optional().describe('Pass "usage" to include per-project usage stats.'),
9
+ limit: z.number().optional().describe('Maximum number of projects to return.'),
10
+ },
11
+ }, async ({ include, limit }) => {
12
+ try {
13
+ const result = await client.projects.list({ include, limit });
14
+ return {
15
+ content: [{
16
+ type: 'text',
17
+ text: JSON.stringify({ ...result, _summary: 'Project list returned.' }, null, 2),
18
+ }],
19
+ };
20
+ }
21
+ catch (err) {
22
+ const message = err instanceof Error ? err.message : String(err);
23
+ return {
24
+ content: [{ type: 'text', text: JSON.stringify({ error: message, _summary: `Error: ${message}` }) }],
25
+ isError: true,
26
+ };
27
+ }
28
+ });
29
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@crbonfree/mcp",
3
+ "version": "0.1.1",
4
+ "description": "MCP server for the CrbonFree carbon-offset API",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "bin": {
9
+ "crbonfree-mcp": "./dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "test": "vitest run",
19
+ "lint": "tsc --noEmit",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "engines": {
23
+ "node": ">=18"
24
+ },
25
+ "dependencies": {
26
+ "@crbonfree/sdk": "^0.1.0",
27
+ "@modelcontextprotocol/sdk": "^1.12.0",
28
+ "zod": "^3.25.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^22.0.0",
32
+ "typescript": "^5.4.0",
33
+ "vitest": "^1.6.0"
34
+ }
35
+ }