@carven-time/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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,157 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ // Mock the client module before importing tools
3
+ vi.mock('../client.js', () => ({
4
+ api: {
5
+ get: vi.fn(),
6
+ post: vi.fn(),
7
+ put: vi.fn(),
8
+ delete: vi.fn(),
9
+ },
10
+ }));
11
+ import { handleTool, toolDefs } from '../tools.js';
12
+ import { api } from '../client.js';
13
+ const mockApi = vi.mocked(api);
14
+ beforeEach(() => {
15
+ vi.clearAllMocks();
16
+ });
17
+ describe('handleTool', () => {
18
+ // ── check_availability ──────────────────────────────────────────
19
+ it('check_availability calls GET /v1/availability with params', async () => {
20
+ mockApi.get.mockResolvedValue({ slots: [] });
21
+ const result = await handleTool('check_availability', {
22
+ start_date: '2026-04-01',
23
+ end_date: '2026-04-03',
24
+ duration_mins: 60,
25
+ });
26
+ expect(mockApi.get).toHaveBeenCalledWith('/v1/availability?start_date=2026-04-01&end_date=2026-04-03&duration_mins=60');
27
+ expect(JSON.parse(result.content[0].text)).toEqual({ slots: [] });
28
+ });
29
+ it('check_availability with only required param', async () => {
30
+ mockApi.get.mockResolvedValue({ slots: [] });
31
+ await handleTool('check_availability', { start_date: '2026-04-01' });
32
+ expect(mockApi.get).toHaveBeenCalledWith('/v1/availability?start_date=2026-04-01');
33
+ });
34
+ // ── create_booking_link ─────────────────────────────────────────
35
+ it('create_booking_link calls POST /v1/links', async () => {
36
+ mockApi.post.mockResolvedValue({ links: ['https://carven.time/...'] });
37
+ await handleTool('create_booking_link', {
38
+ template_id: 'tmpl-1',
39
+ timeslots: [{ start_time: '2026-04-01T10:00:00Z', end_time: '2026-04-01T10:30:00Z' }],
40
+ recipient_name: 'Alice',
41
+ recipient_email: 'alice@example.com',
42
+ });
43
+ expect(mockApi.post).toHaveBeenCalledWith('/v1/links', {
44
+ template_id: 'tmpl-1',
45
+ timeslots: [{ start_time: '2026-04-01T10:00:00Z', end_time: '2026-04-01T10:30:00Z' }],
46
+ recipient_name: 'Alice',
47
+ recipient_email: 'alice@example.com',
48
+ message: undefined,
49
+ });
50
+ });
51
+ // ── get_booking_status ──────────────────────────────────────────
52
+ it('get_booking_status calls GET /v1/bookings/:id', async () => {
53
+ mockApi.get.mockResolvedValue({ status: 'CONFIRMED' });
54
+ await handleTool('get_booking_status', { booking_id: 'b-123' });
55
+ expect(mockApi.get).toHaveBeenCalledWith('/v1/bookings/b-123');
56
+ });
57
+ // ── get_signup_sheet ────────────────────────────────────────────
58
+ it('get_signup_sheet calls GET /v1/sheets/:id', async () => {
59
+ mockApi.get.mockResolvedValue({ title: 'Potluck' });
60
+ await handleTool('get_signup_sheet', { sheet_id: 's-1' });
61
+ expect(mockApi.get).toHaveBeenCalledWith('/v1/sheets/s-1');
62
+ });
63
+ // ── claim_signup_slot ───────────────────────────────────────────
64
+ it('claim_signup_slot calls POST /v1/sheets/:id/claims', async () => {
65
+ mockApi.post.mockResolvedValue({ claimed: true });
66
+ await handleTool('claim_signup_slot', {
67
+ sheet_id: 's-1',
68
+ slot_id: 'slot-1',
69
+ claimant_name: 'Bob',
70
+ claimant_email: 'bob@example.com',
71
+ });
72
+ expect(mockApi.post).toHaveBeenCalledWith('/v1/sheets/s-1/claims', {
73
+ slot_id: 'slot-1',
74
+ claimant_name: 'Bob',
75
+ claimant_email: 'bob@example.com',
76
+ claimant_phone: undefined,
77
+ custom_field_responses: undefined,
78
+ });
79
+ });
80
+ // ── get_round ───────────────────────────────────────────────────
81
+ it('get_round calls GET /v1/rounds/:id', async () => {
82
+ mockApi.get.mockResolvedValue({ id: 'r-1' });
83
+ await handleTool('get_round', { round_id: 'r-1' });
84
+ expect(mockApi.get).toHaveBeenCalledWith('/v1/rounds/r-1');
85
+ });
86
+ // ── submit_availability ─────────────────────────────────────────
87
+ it('submit_availability calls POST /v1/rounds/:id/availability', async () => {
88
+ mockApi.post.mockResolvedValue({ ok: true });
89
+ await handleTool('submit_availability', {
90
+ round_id: 'r-1',
91
+ availability: { '2026-04-01T10:00:00Z': 'PREFERRED' },
92
+ guest_name: 'Alice',
93
+ guest_email: 'alice@example.com',
94
+ });
95
+ expect(mockApi.post).toHaveBeenCalledWith('/v1/rounds/r-1/availability', {
96
+ availability: { '2026-04-01T10:00:00Z': 'PREFERRED' },
97
+ guest_name: 'Alice',
98
+ guest_email: 'alice@example.com',
99
+ is_flexible: undefined,
100
+ });
101
+ });
102
+ // ── get_working_hours ───────────────────────────────────────────
103
+ it('get_working_hours calls GET /v1/working-hours', async () => {
104
+ mockApi.get.mockResolvedValue({ hours: [] });
105
+ await handleTool('get_working_hours', {});
106
+ expect(mockApi.get).toHaveBeenCalledWith('/v1/working-hours');
107
+ });
108
+ // ── set_working_hours ───────────────────────────────────────────
109
+ it('set_working_hours calls PUT /v1/working-hours', async () => {
110
+ const hours = [{ day_of_week: 1, start_time: '09:00', end_time: '17:00' }];
111
+ mockApi.put.mockResolvedValue({ ok: true });
112
+ await handleTool('set_working_hours', { hours });
113
+ expect(mockApi.put).toHaveBeenCalledWith('/v1/working-hours', { hours });
114
+ });
115
+ // ── delete_working_hours_day ────────────────────────────────────
116
+ it('delete_working_hours_day calls DELETE /v1/working-hours/:day', async () => {
117
+ mockApi.delete.mockResolvedValue(undefined);
118
+ const result = await handleTool('delete_working_hours_day', { day_of_week: 0 });
119
+ expect(mockApi.delete).toHaveBeenCalledWith('/v1/working-hours/0');
120
+ expect(JSON.parse(result.content[0].text)).toEqual({ ok: true, deleted_day: 0 });
121
+ });
122
+ // ── get_date_overrides ──────────────────────────────────────────
123
+ it('get_date_overrides calls GET /v1/date-overrides', async () => {
124
+ mockApi.get.mockResolvedValue({ overrides: [] });
125
+ await handleTool('get_date_overrides', {});
126
+ expect(mockApi.get).toHaveBeenCalledWith('/v1/date-overrides');
127
+ });
128
+ // ── set_date_overrides ──────────────────────────────────────────
129
+ it('set_date_overrides calls PUT /v1/date-overrides', async () => {
130
+ const overrides = [{ date: '2026-04-01', is_blocked: true }];
131
+ mockApi.put.mockResolvedValue({ ok: true });
132
+ await handleTool('set_date_overrides', { overrides });
133
+ expect(mockApi.put).toHaveBeenCalledWith('/v1/date-overrides', { overrides });
134
+ });
135
+ // ── delete_date_override ────────────────────────────────────────
136
+ it('delete_date_override calls DELETE /v1/date-overrides/:date', async () => {
137
+ mockApi.delete.mockResolvedValue(undefined);
138
+ const result = await handleTool('delete_date_override', { date: '2026-04-01' });
139
+ expect(mockApi.delete).toHaveBeenCalledWith('/v1/date-overrides/2026-04-01');
140
+ expect(JSON.parse(result.content[0].text)).toEqual({ ok: true, deleted_date: '2026-04-01' });
141
+ });
142
+ // ── Error handling ──────────────────────────────────────────────
143
+ it('wraps API errors in errorResult', async () => {
144
+ mockApi.get.mockRejectedValue(new Error('Network failure'));
145
+ const result = await handleTool('check_availability', { start_date: '2026-04-01' });
146
+ expect(JSON.parse(result.content[0].text)).toEqual({ error: 'Network failure' });
147
+ });
148
+ it('returns error for unknown tool', async () => {
149
+ const result = await handleTool('nonexistent_tool', {});
150
+ expect(JSON.parse(result.content[0].text)).toEqual({ error: 'Unknown tool: nonexistent_tool' });
151
+ });
152
+ // ── Tool count check ────────────────────────────────────────────
153
+ it('all 13 tool definitions are defined', () => {
154
+ expect(toolDefs).toHaveLength(13);
155
+ });
156
+ });
157
+ //# sourceMappingURL=tools.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.test.js","sourceRoot":"","sources":["../../src/__tests__/tools.test.ts"],"names":[],"mappings":"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"}
package/dist/auth.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export declare function getApiBase(): string;
2
+ /**
3
+ * Get a valid access token, refreshing if needed.
4
+ * Returns null if not authenticated.
5
+ */
6
+ export declare function getAccessToken(): Promise<string | null>;
7
+ export declare function isAuthenticated(): boolean;
8
+ /**
9
+ * Interactive OAuth login flow.
10
+ * Opens browser for consent, runs local callback server.
11
+ */
12
+ export declare function login(apiBase?: string): Promise<void>;
package/dist/auth.js ADDED
@@ -0,0 +1,299 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { createServer } from 'node:http';
5
+ import { randomBytes, createHash } from 'node:crypto';
6
+ const CONFIG_DIR = join(homedir(), '.carven');
7
+ /** Per-environment token file based on API URL */
8
+ function getTokenFile() {
9
+ const apiBase = process.env.CARVEN_API_URL ?? 'https://api.carven.app';
10
+ const envHash = createHash('sha256').update(apiBase).digest('hex').slice(0, 8);
11
+ return join(CONFIG_DIR, `mcp-token-${envHash}.json`);
12
+ }
13
+ let cachedTokens = null;
14
+ export function getApiBase() {
15
+ return cachedTokens?.api_base ?? process.env.CARVEN_API_URL ?? 'https://api.carven.app';
16
+ }
17
+ /** Frontend app base URL — used for the OAuth consent page */
18
+ function getAppBase() {
19
+ return process.env.CARVEN_APP_URL ?? 'https://carven.app';
20
+ }
21
+ function loadTokens() {
22
+ if (cachedTokens)
23
+ return cachedTokens;
24
+ try {
25
+ if (!existsSync(getTokenFile()))
26
+ return null;
27
+ const data = JSON.parse(readFileSync(getTokenFile(), 'utf-8'));
28
+ cachedTokens = data;
29
+ return data;
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ function saveTokens(tokens) {
36
+ mkdirSync(CONFIG_DIR, { recursive: true });
37
+ writeFileSync(getTokenFile(), JSON.stringify(tokens, null, 2), { mode: 0o600 });
38
+ cachedTokens = tokens;
39
+ }
40
+ async function refreshTokens(stored) {
41
+ const res = await fetch(`${stored.api_base}/api/oauth/token`, {
42
+ method: 'POST',
43
+ headers: { 'Content-Type': 'application/json' },
44
+ body: JSON.stringify({
45
+ grant_type: 'refresh_token',
46
+ refresh_token: stored.refresh_token,
47
+ client_id: stored.client_id,
48
+ client_secret: stored.client_secret,
49
+ }),
50
+ });
51
+ if (!res.ok) {
52
+ throw new Error(`Token refresh failed: ${res.status} ${await res.text()}`);
53
+ }
54
+ const data = await res.json();
55
+ const updated = {
56
+ ...stored,
57
+ access_token: data.access_token,
58
+ refresh_token: data.refresh_token,
59
+ expires_at: Date.now() + data.expires_in * 1000 - 60_000, // 1min buffer
60
+ };
61
+ saveTokens(updated);
62
+ return updated;
63
+ }
64
+ /**
65
+ * Get a valid access token, refreshing if needed.
66
+ * Returns null if not authenticated.
67
+ */
68
+ export async function getAccessToken() {
69
+ let tokens = loadTokens();
70
+ if (!tokens)
71
+ return null;
72
+ // Refresh if expired or within 1 minute of expiry
73
+ if (Date.now() >= tokens.expires_at) {
74
+ try {
75
+ tokens = await refreshTokens(tokens);
76
+ }
77
+ catch {
78
+ // Refresh failed — token may be revoked
79
+ cachedTokens = null;
80
+ return null;
81
+ }
82
+ }
83
+ return tokens.access_token;
84
+ }
85
+ export function isAuthenticated() {
86
+ return loadTokens() !== null;
87
+ }
88
+ // ── Callback page HTML ──────────────────────────────────────────
89
+ function callbackPage(status, detail) {
90
+ const configs = {
91
+ success: {
92
+ icon: `<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#16a34a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>`,
93
+ title: 'Connected to Carven',
94
+ message: 'You can close this window and return to your AI assistant.',
95
+ color: '#16a34a',
96
+ },
97
+ denied: {
98
+ icon: `<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#dc2626" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>`,
99
+ title: 'Authorization Denied',
100
+ message: 'You denied access. Close this window and try again if needed.',
101
+ color: '#dc2626',
102
+ },
103
+ error: {
104
+ icon: `<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#dc2626" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>`,
105
+ title: 'Something Went Wrong',
106
+ message: detail ?? 'An error occurred during authentication.',
107
+ color: '#dc2626',
108
+ },
109
+ };
110
+ const c = configs[status];
111
+ return `<!DOCTYPE html>
112
+ <html lang="en">
113
+ <head>
114
+ <meta charset="utf-8">
115
+ <meta name="viewport" content="width=device-width, initial-scale=1">
116
+ <title>Carven — ${c.title}</title>
117
+ <style>
118
+ * { margin: 0; padding: 0; box-sizing: border-box; }
119
+ body {
120
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
121
+ background: #f8fafc;
122
+ color: #0f172a;
123
+ display: flex;
124
+ align-items: center;
125
+ justify-content: center;
126
+ min-height: 100vh;
127
+ }
128
+ .card {
129
+ background: #fff;
130
+ border-radius: 16px;
131
+ box-shadow: 0 1px 3px rgba(0,0,0,0.08), 0 4px 24px rgba(0,0,0,0.04);
132
+ padding: 48px;
133
+ max-width: 420px;
134
+ width: 100%;
135
+ text-align: center;
136
+ }
137
+ .icon { margin-bottom: 20px; }
138
+ h1 {
139
+ font-size: 20px;
140
+ font-weight: 600;
141
+ letter-spacing: -0.3px;
142
+ margin-bottom: 8px;
143
+ color: #0f172a;
144
+ }
145
+ p {
146
+ font-size: 15px;
147
+ color: #64748b;
148
+ line-height: 1.5;
149
+ }
150
+ .badge {
151
+ display: inline-block;
152
+ margin-top: 24px;
153
+ padding: 6px 14px;
154
+ border-radius: 999px;
155
+ font-size: 12px;
156
+ font-weight: 500;
157
+ letter-spacing: 0.3px;
158
+ background: ${c.color}10;
159
+ color: ${c.color};
160
+ border: 1px solid ${c.color}20;
161
+ }
162
+ .logo {
163
+ margin-bottom: 32px;
164
+ font-size: 14px;
165
+ font-weight: 600;
166
+ letter-spacing: 1.5px;
167
+ text-transform: uppercase;
168
+ color: #94a3b8;
169
+ }
170
+ </style>
171
+ </head>
172
+ <body>
173
+ <div class="card">
174
+ <div class="logo">CARVEN</div>
175
+ <div class="icon">${c.icon}</div>
176
+ <h1>${c.title}</h1>
177
+ <p>${c.message}</p>
178
+ <div class="badge">${status === 'success' ? 'MCP Connected' : status === 'denied' ? 'Access Denied' : 'Error'}</div>
179
+ </div>
180
+ </body>
181
+ </html>`;
182
+ }
183
+ /**
184
+ * Interactive OAuth login flow.
185
+ * Opens browser for consent, runs local callback server.
186
+ */
187
+ export async function login(apiBase) {
188
+ const base = apiBase ?? getApiBase();
189
+ // PKCE
190
+ const codeVerifier = randomBytes(32).toString('base64url');
191
+ const codeChallenge = createHash('sha256').update(codeVerifier).digest('base64url');
192
+ const state = randomBytes(16).toString('hex');
193
+ // Official Carven MCP app credentials (first-party, pre-registered).
194
+ // These are safe to embed in source: this is a public OAuth client (CLI tool
195
+ // running on user machines), so the secret is not truly confidential — anyone
196
+ // who installs the package can extract it. Security relies on the per-user
197
+ // OAuth consent flow and PKCE challenge, not on secret confidentiality.
198
+ // This is the standard pattern used by GitHub CLI, Slack CLI, and similar tools.
199
+ const OFFICIAL_CLIENT_ID = '65f58750-cbac-4fb5-8668-572e68a735bd';
200
+ const OFFICIAL_CLIENT_SECRET = 'yA6JLOZ2fqeEWg0ZhBarSsKfjvWzNwo8dCcGADG_PWU';
201
+ // Allow env var overrides for third-party developers building their own agents
202
+ const clientId = process.env.CARVEN_CLIENT_ID || OFFICIAL_CLIENT_ID;
203
+ const clientSecret = process.env.CARVEN_CLIENT_SECRET || OFFICIAL_CLIENT_SECRET;
204
+ // Fetch the app's registered scopes dynamically
205
+ const infoRes = await fetch(`${base}/api/oauth/apps/info?client_id=${clientId}`);
206
+ if (!infoRes.ok) {
207
+ throw new Error(`Failed to fetch app info: ${infoRes.status}`);
208
+ }
209
+ const appInfo = await infoRes.json();
210
+ const scopeString = appInfo.scopes.join(' ');
211
+ const port = 9876;
212
+ const redirectUri = `http://localhost:${port}/callback`;
213
+ const authUrl = new URL(`${getAppBase()}/oauth/authorize`);
214
+ authUrl.searchParams.set('client_id', clientId);
215
+ authUrl.searchParams.set('redirect_uri', redirectUri);
216
+ authUrl.searchParams.set('response_type', 'code');
217
+ authUrl.searchParams.set('scope', scopeString);
218
+ authUrl.searchParams.set('state', state);
219
+ authUrl.searchParams.set('code_challenge', codeChallenge);
220
+ authUrl.searchParams.set('code_challenge_method', 'S256');
221
+ return new Promise((resolve, reject) => {
222
+ const server = createServer(async (req, res) => {
223
+ const url = new URL(req.url ?? '/', `http://localhost:${port}`);
224
+ if (url.pathname !== '/callback') {
225
+ res.writeHead(404);
226
+ res.end();
227
+ return;
228
+ }
229
+ const code = url.searchParams.get('code');
230
+ const returnedState = url.searchParams.get('state');
231
+ const error = url.searchParams.get('error');
232
+ if (error) {
233
+ res.writeHead(200, { 'Content-Type': 'text/html' });
234
+ res.end(callbackPage('denied'));
235
+ server.close();
236
+ reject(new Error(`Authorization denied: ${error}`));
237
+ return;
238
+ }
239
+ if (!code || returnedState !== state) {
240
+ res.writeHead(400, { 'Content-Type': 'text/html' });
241
+ res.end(callbackPage('error', 'Invalid callback parameters'));
242
+ server.close();
243
+ reject(new Error('Invalid callback parameters'));
244
+ return;
245
+ }
246
+ try {
247
+ // Exchange code for tokens
248
+ const tokenRes = await fetch(`${base}/api/oauth/token`, {
249
+ method: 'POST',
250
+ headers: { 'Content-Type': 'application/json' },
251
+ body: JSON.stringify({
252
+ grant_type: 'authorization_code',
253
+ code,
254
+ redirect_uri: redirectUri,
255
+ client_id: clientId,
256
+ client_secret: clientSecret,
257
+ code_verifier: codeVerifier,
258
+ }),
259
+ });
260
+ if (!tokenRes.ok) {
261
+ throw new Error(`Token exchange failed: ${tokenRes.status}`);
262
+ }
263
+ const data = await tokenRes.json();
264
+ saveTokens({
265
+ access_token: data.access_token,
266
+ refresh_token: data.refresh_token,
267
+ expires_at: Date.now() + data.expires_in * 1000 - 60_000,
268
+ client_id: clientId,
269
+ client_secret: clientSecret,
270
+ api_base: base,
271
+ });
272
+ res.writeHead(200, { 'Content-Type': 'text/html' });
273
+ res.end(callbackPage('success'));
274
+ server.close();
275
+ resolve();
276
+ }
277
+ catch (err) {
278
+ res.writeHead(500, { 'Content-Type': 'text/html' });
279
+ res.end(callbackPage('error', 'Token exchange failed'));
280
+ server.close();
281
+ reject(err);
282
+ }
283
+ });
284
+ server.listen(port, () => {
285
+ console.error(`[carven-mcp] Opening browser for authorization...`);
286
+ console.error(`[carven-mcp] If the browser doesn't open, visit: ${authUrl.toString()}`);
287
+ // Dynamic import for ESM 'open' package
288
+ import('open').then(m => m.default(authUrl.toString())).catch(() => {
289
+ // open failed — user will use the URL printed above
290
+ });
291
+ });
292
+ // Timeout after 5 minutes
293
+ setTimeout(() => {
294
+ server.close();
295
+ reject(new Error('Login timed out after 5 minutes'));
296
+ }, 5 * 60 * 1000);
297
+ });
298
+ }
299
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export declare class CarvenApiError extends Error {
2
+ status: number;
3
+ body: unknown;
4
+ constructor(status: number, body: unknown);
5
+ }
6
+ export declare const api: {
7
+ get: (path: string) => Promise<unknown>;
8
+ post: (path: string, body: unknown) => Promise<unknown>;
9
+ put: (path: string, body: unknown) => Promise<unknown>;
10
+ patch: (path: string, body: unknown) => Promise<unknown>;
11
+ delete: (path: string) => Promise<unknown>;
12
+ };
package/dist/client.js ADDED
@@ -0,0 +1,39 @@
1
+ import { getAccessToken, getApiBase } from './auth.js';
2
+ export class CarvenApiError extends Error {
3
+ status;
4
+ body;
5
+ constructor(status, body) {
6
+ super(`Carven API error ${status}: ${JSON.stringify(body)}`);
7
+ this.status = status;
8
+ this.body = body;
9
+ this.name = 'CarvenApiError';
10
+ }
11
+ }
12
+ async function request(method, path, body) {
13
+ const token = await getAccessToken();
14
+ if (!token) {
15
+ throw new Error('Not authenticated. Run the MCP server with CARVEN_CLIENT_ID and CARVEN_CLIENT_SECRET to connect your Carven account.');
16
+ }
17
+ const url = `${getApiBase()}/api${path}`;
18
+ const res = await fetch(url, {
19
+ method,
20
+ headers: {
21
+ 'Authorization': `Bearer ${token}`,
22
+ ...(body ? { 'Content-Type': 'application/json' } : {}),
23
+ },
24
+ ...(body ? { body: JSON.stringify(body) } : {}),
25
+ });
26
+ const data = await res.json().catch(() => null);
27
+ if (!res.ok) {
28
+ throw new CarvenApiError(res.status, data);
29
+ }
30
+ return data;
31
+ }
32
+ export const api = {
33
+ get: (path) => request('GET', path),
34
+ post: (path, body) => request('POST', path, body),
35
+ put: (path, body) => request('PUT', path, body),
36
+ patch: (path, body) => request('PATCH', path, body),
37
+ delete: (path) => request('DELETE', path),
38
+ };
39
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAEtD,MAAM,OAAO,cAAe,SAAQ,KAAK;IAE9B;IACA;IAFT,YACS,MAAc,EACd,IAAa;QAEpB,KAAK,CAAC,oBAAoB,MAAM,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAHrD,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAS;QAGpB,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAA;IAC9B,CAAC;CACF;AAED,KAAK,UAAU,OAAO,CAAC,MAAc,EAAE,IAAY,EAAE,IAAc;IACjE,MAAM,KAAK,GAAG,MAAM,cAAc,EAAE,CAAA;IACpC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,sHAAsH,CAAC,CAAA;IACzI,CAAC;IAED,MAAM,GAAG,GAAG,GAAG,UAAU,EAAE,OAAO,IAAI,EAAE,CAAA;IACxC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC3B,MAAM;QACN,OAAO,EAAE;YACP,eAAe,EAAE,UAAU,KAAK,EAAE;YAClC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxD;QACD,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChD,CAAC,CAAA;IAEF,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;IAE/C,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IAC5C,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,GAAG,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC;IAC3C,IAAI,EAAE,CAAC,IAAY,EAAE,IAAa,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC;IAClE,GAAG,EAAE,CAAC,IAAY,EAAE,IAAa,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC;IAChE,KAAK,EAAE,CAAC,IAAY,EAAE,IAAa,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC;IACpE,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC;CAClD,CAAA"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
+ import { isAuthenticated, login } from './auth.js';
6
+ import { toolDefs, handleTool } from './tools.js';
7
+ const server = new Server({ name: 'carven', version: '0.1.0' }, { capabilities: { tools: {} } });
8
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
9
+ tools: toolDefs.map(t => ({
10
+ name: t.name,
11
+ description: t.description,
12
+ inputSchema: t.inputSchema,
13
+ })),
14
+ }));
15
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
16
+ return handleTool(req.params.name, (req.params.arguments ?? {}));
17
+ });
18
+ async function main() {
19
+ // Connect transport immediately so the client doesn't time out
20
+ const transport = new StdioServerTransport();
21
+ await server.connect(transport);
22
+ console.error('[carven-mcp] Server running on stdio');
23
+ // Trigger OAuth login in the background if not authenticated
24
+ // (works with both env var overrides and embedded official credentials)
25
+ if (!isAuthenticated()) {
26
+ console.error('[carven-mcp] Not authenticated. Starting OAuth flow...');
27
+ login()
28
+ .then(() => console.error('[carven-mcp] Successfully connected to Carven!'))
29
+ .catch((err) => console.error(`[carven-mcp] Auth failed: ${err.message}`));
30
+ }
31
+ }
32
+ main().catch(err => {
33
+ console.error('[carven-mcp] Fatal error:', err);
34
+ process.exit(1);
35
+ });
36
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAA;AAClG,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEjD,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EACpC,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAA;AAED,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;IAC5D,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACxB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,WAAW,EAAE,CAAC,CAAC,WAAW;KAC3B,CAAC,CAAC;CACJ,CAAC,CAAC,CAAA;AAEH,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAC,GAAG,EAAC,EAAE;IAC1D,OAAO,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAA4B,CAAC,CAAA;AAC7F,CAAC,CAAC,CAAA;AAEF,KAAK,UAAU,IAAI;IACjB,+DAA+D;IAC/D,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;IAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IAC/B,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAA;IAErD,6DAA6D;IAC7D,wEAAwE;IACxE,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC;QACvB,OAAO,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAA;QACvE,KAAK,EAAE;aACJ,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;aAC3E,KAAK,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,6BAA6B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;IACnF,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;IACjB,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,GAAG,CAAC,CAAA;IAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}