@dodo-planet/mcp 0.1.4

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 ADDED
@@ -0,0 +1,75 @@
1
+ # @dodo-planet/mcp
2
+
3
+ Dodo Planet 가족 여행 데이터를 [Model Context Protocol](https://modelcontextprotocol.io)로 노출하는 stdio 서버. Claude Code, Cursor, Codex, Cline 등 MCP 호환 코딩 에이전트에서 자연어로 호출 가능.
4
+
5
+ ## 사전 작업
6
+
7
+ 먼저 [@dodo-planet/cli](https://www.npmjs.com/package/@dodo-planet/cli)로 PAT 등록(또는 `DODO_TOKEN` 환경변수 설정).
8
+
9
+ ## Claude Code
10
+
11
+ ```bash
12
+ claude mcp add dodo -- npx -y @dodo-planet/mcp
13
+ ```
14
+
15
+ 또는 `~/.config/dodo/credentials.json`을 못 읽는 sandboxed 환경에서:
16
+
17
+ ```bash
18
+ claude mcp add dodo --env DODO_TOKEN=dodo_pat_xxx -- npx -y @dodo-planet/mcp
19
+ ```
20
+
21
+ ## Cursor / Cline
22
+
23
+ `mcpServers` 설정에 추가:
24
+
25
+ ```json
26
+ {
27
+ "mcpServers": {
28
+ "dodo": {
29
+ "command": "npx",
30
+ "args": ["-y", "@dodo-planet/mcp"],
31
+ "env": {
32
+ "DODO_TOKEN": "dodo_pat_xxx"
33
+ }
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ ## Codex
40
+
41
+ ```bash
42
+ codex mcp add dodo --command "npx -y @dodo-planet/mcp" --env DODO_TOKEN=dodo_pat_xxx
43
+ ```
44
+
45
+ ## 환경변수
46
+
47
+ - `DODO_TOKEN` — PAT (CLI credentials 파일 무시)
48
+ - `DODO_API_URL` — API 엔드포인트 (기본 `https://www.dodoplanet.space`)
49
+ - `DODO_CONFIG_HOME` — 설정 디렉토리 (CLI와 공유)
50
+
51
+ ## 노출되는 도구
52
+
53
+ 43개 도메인 함수 — 여행 관리, 경비, 예약, 일정, 피드, 아이 정보, 항공·호텔·액티비티·교통편 검색, 장소·날씨·경로, 가족·친구·초대, 웹 검색.
54
+
55
+ 전체 목록과 인자 스키마는 MCP 클라이언트의 도구 검색 UI에서 확인.
56
+
57
+ **보안 표시**:
58
+ - `readOnlyHint: true` — 읽기 전용 (예: list/get/search)
59
+ - `destructiveHint: true` — 데이터 삭제 (예: delete_*, remove_*)
60
+ - `idempotentHint: true` — 동일 호출이 동일 결과 (read-only와 같음)
61
+ - `openWorldHint: true` — 외부 정보 (Amadeus, Google Maps, Perplexity, OpenWeather)
62
+
63
+ ## 동작 방식
64
+
65
+ ```
66
+ [Claude Code/Cursor]
67
+ ↓ MCP stdio
68
+ [@dodo-planet/mcp]
69
+ ↓ HTTPS Bearer
70
+ [https://www.dodoplanet.space/api/cli/{declarations,execute}]
71
+ ↓ Service Role
72
+ [Supabase + Gemini executeFunction]
73
+ ```
74
+
75
+ CLI와 MCP는 동일한 `/api/cli/*` 엔드포인트를 공유. 시크릿(SUPABASE_SERVICE_ROLE_KEY, Amadeus·Perplexity 키)은 절대 사용자 머신으로 새지 않음.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import("../dist/index.js").then((mod) => mod.run()).catch((err) => {
3
+ process.stderr.write((err?.stack || err?.message || String(err)) + "\n");
4
+ process.exit(1);
5
+ });
package/dist/index.js ADDED
@@ -0,0 +1,342 @@
1
+ // src/index.ts
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z as z2 } from "zod";
5
+
6
+ // src/zod-from-jsonschema.ts
7
+ import { z } from "zod";
8
+ function zodFromJsonSchema(schema) {
9
+ if (!schema) return z.unknown();
10
+ const variants = schema.anyOf ?? schema.oneOf;
11
+ if (variants && variants.length > 0) {
12
+ const nullVariant = variants.find((v) => v.type === "null");
13
+ const nonNullVariants = variants.filter((v) => v.type !== "null");
14
+ if (nonNullVariants.length === 1) {
15
+ const inner = zodFromJsonSchema(nonNullVariants[0]);
16
+ return nullVariant ? inner.nullable() : inner;
17
+ }
18
+ if (nonNullVariants.length > 1) {
19
+ const unioned = z.union(
20
+ nonNullVariants.map((v) => zodFromJsonSchema(v))
21
+ );
22
+ return nullVariant ? unioned.nullable() : unioned;
23
+ }
24
+ }
25
+ if (!schema.type) return z.unknown();
26
+ const wrapNullable = (s) => schema.nullable ? s.nullable() : s;
27
+ switch (schema.type) {
28
+ case "null":
29
+ return z.null();
30
+ case "string": {
31
+ let s = schema.enum && schema.enum.length > 0 ? z.enum(schema.enum) : z.string();
32
+ if (schema.description) s = s.describe(schema.description);
33
+ return wrapNullable(s);
34
+ }
35
+ case "integer": {
36
+ let s = z.number().int();
37
+ if (schema.description) s = s.describe(schema.description);
38
+ return wrapNullable(s);
39
+ }
40
+ case "number": {
41
+ let s = z.number();
42
+ if (schema.description) s = s.describe(schema.description);
43
+ return wrapNullable(s);
44
+ }
45
+ case "boolean": {
46
+ let s = z.boolean();
47
+ if (schema.description) s = s.describe(schema.description);
48
+ return wrapNullable(s);
49
+ }
50
+ case "array": {
51
+ const inner = zodFromJsonSchema(schema.items);
52
+ let s = z.array(inner);
53
+ if (schema.description) s = s.describe(schema.description);
54
+ return wrapNullable(s);
55
+ }
56
+ case "object": {
57
+ const props = schema.properties ?? {};
58
+ const required = new Set(schema.required ?? []);
59
+ const shape = {};
60
+ for (const [key, child] of Object.entries(props)) {
61
+ let v = zodFromJsonSchema(child);
62
+ if (!required.has(key)) v = v.optional();
63
+ shape[key] = v;
64
+ }
65
+ let s = z.object(shape);
66
+ if (schema.description) s = s.describe(schema.description);
67
+ return wrapNullable(s);
68
+ }
69
+ default:
70
+ return z.unknown();
71
+ }
72
+ }
73
+ function zodShapeFromJsonSchema(schema) {
74
+ if (!schema || schema.type !== "object" || !schema.properties) return {};
75
+ const required = new Set(schema.required ?? []);
76
+ const shape = {};
77
+ for (const [key, child] of Object.entries(schema.properties)) {
78
+ let v = zodFromJsonSchema(child);
79
+ if (!required.has(key)) v = v.optional();
80
+ shape[key] = v;
81
+ }
82
+ return shape;
83
+ }
84
+
85
+ // src/credentials.ts
86
+ import { readFileSync, existsSync } from "fs";
87
+ import { homedir } from "os";
88
+ import { join } from "path";
89
+ function getConfigDir() {
90
+ const override = process.env.DODO_CONFIG_HOME;
91
+ if (override) return override;
92
+ const xdg = process.env.XDG_CONFIG_HOME;
93
+ if (xdg) return join(xdg, "dodo");
94
+ return join(homedir(), ".config", "dodo");
95
+ }
96
+ function getCredentialsPath() {
97
+ return join(getConfigDir(), "credentials.json");
98
+ }
99
+ function loadToken() {
100
+ const fromEnv = process.env.DODO_TOKEN;
101
+ if (fromEnv && fromEnv.trim()) return fromEnv.trim();
102
+ const path = getCredentialsPath();
103
+ if (!existsSync(path)) return null;
104
+ try {
105
+ const raw = readFileSync(path, "utf8");
106
+ const parsed = JSON.parse(raw);
107
+ return parsed.token || null;
108
+ } catch {
109
+ return null;
110
+ }
111
+ }
112
+ function loadApiUrl() {
113
+ if (process.env.DODO_API_URL) return process.env.DODO_API_URL.trim();
114
+ const cfgPath = join(getConfigDir(), "config.json");
115
+ if (existsSync(cfgPath)) {
116
+ try {
117
+ const cfg = JSON.parse(readFileSync(cfgPath, "utf8"));
118
+ if (cfg.apiUrl) return cfg.apiUrl;
119
+ } catch {
120
+ }
121
+ }
122
+ return "https://www.dodoplanet.space";
123
+ }
124
+ function loadActiveTripId() {
125
+ if (process.env.DODO_TRIP_ID) return process.env.DODO_TRIP_ID.trim() || void 0;
126
+ const statePath = join(getConfigDir(), "state.json");
127
+ if (!existsSync(statePath)) return void 0;
128
+ try {
129
+ const state = JSON.parse(readFileSync(statePath, "utf8"));
130
+ return state.activeTrip?.id;
131
+ } catch {
132
+ return void 0;
133
+ }
134
+ }
135
+
136
+ // src/function-catalog-shared.ts
137
+ var FUNCTION_CATALOG = [
138
+ // ── 외부 검색 / 정보 조회 (read-only, 11개)
139
+ { name: "get_weather", category: "weather", verb: "show", requiresTrip: false, isWrite: false, isDestructive: false },
140
+ { name: "get_flight_status", category: "flight", verb: "status", requiresTrip: false, isWrite: false, isDestructive: false },
141
+ { name: "search_nearby_places", category: "place", verb: "search", requiresTrip: false, isWrite: false, isDestructive: false },
142
+ { name: "search_places_text", category: "place", verb: "search-text", requiresTrip: false, isWrite: false, isDestructive: false },
143
+ { name: "get_place_details", category: "place", verb: "details", requiresTrip: false, isWrite: false, isDestructive: false },
144
+ { name: "get_directions", category: "directions", verb: "show", requiresTrip: false, isWrite: false, isDestructive: false },
145
+ { name: "search_flights", category: "flight", verb: "search", requiresTrip: false, isWrite: false, isDestructive: false },
146
+ { name: "search_hotels", category: "hotel", verb: "search", requiresTrip: true, isWrite: false, isDestructive: false },
147
+ { name: "search_transfers", category: "transfer", verb: "search", requiresTrip: true, isWrite: false, isDestructive: false },
148
+ { name: "search_activities", category: "activity", verb: "search", requiresTrip: true, isWrite: false, isDestructive: false },
149
+ { name: "search_web_perplexity", category: "web", verb: "search", requiresTrip: false, isWrite: false, isDestructive: false },
150
+ // ── Trip 관리 (4개)
151
+ { name: "list_trips", category: "trip", verb: "list", requiresTrip: false, isWrite: false, isDestructive: false },
152
+ { name: "get_current_trip", category: "trip", verb: "current", requiresTrip: false, isWrite: false, isDestructive: false },
153
+ { name: "switch_trip", category: "trip", verb: "switch", requiresTrip: false, isWrite: true, isDestructive: false },
154
+ { name: "create_trip", category: "trip", verb: "create", requiresTrip: false, isWrite: true, isDestructive: false },
155
+ // ── 경비 (4개)
156
+ { name: "get_expenses", category: "expense", verb: "list", requiresTrip: true, isWrite: false, isDestructive: false },
157
+ { name: "add_expense", category: "expense", verb: "add", requiresTrip: true, isWrite: true, isDestructive: false },
158
+ { name: "update_expense", category: "expense", verb: "update", requiresTrip: true, isWrite: true, isDestructive: false },
159
+ { name: "delete_expense", category: "expense", verb: "delete", requiresTrip: true, isWrite: true, isDestructive: true },
160
+ // ── 예약 (4개)
161
+ { name: "get_bookings", category: "booking", verb: "list", requiresTrip: true, isWrite: false, isDestructive: false },
162
+ { name: "add_booking", category: "booking", verb: "add", requiresTrip: true, isWrite: true, isDestructive: false },
163
+ { name: "update_booking", category: "booking", verb: "update", requiresTrip: true, isWrite: true, isDestructive: false },
164
+ { name: "delete_booking", category: "booking", verb: "delete", requiresTrip: true, isWrite: true, isDestructive: true },
165
+ // ── 일정 (4개)
166
+ { name: "get_itinerary", category: "itinerary", verb: "show", requiresTrip: true, isWrite: false, isDestructive: false },
167
+ { name: "add_place_to_itinerary", category: "itinerary", verb: "add", requiresTrip: true, isWrite: true, isDestructive: false },
168
+ { name: "remove_place_from_itinerary", category: "itinerary", verb: "remove", requiresTrip: true, isWrite: true, isDestructive: true },
169
+ { name: "reorder_itinerary", category: "itinerary", verb: "reorder", requiresTrip: true, isWrite: true, isDestructive: false },
170
+ // ── 피드 (3개)
171
+ { name: "get_feed", category: "feed", verb: "list", requiresTrip: true, isWrite: false, isDestructive: false },
172
+ { name: "add_feed_post", category: "feed", verb: "post", requiresTrip: true, isWrite: true, isDestructive: false },
173
+ { name: "delete_feed_post", category: "feed", verb: "delete", requiresTrip: true, isWrite: true, isDestructive: true },
174
+ // ── 아이 정보 (7개)
175
+ { name: "list_babies", category: "baby", verb: "list", requiresTrip: false, isWrite: false, isDestructive: false },
176
+ { name: "get_baby_info", category: "baby", verb: "info", requiresTrip: false, isWrite: false, isDestructive: false },
177
+ { name: "add_baby", category: "baby", verb: "add", requiresTrip: false, isWrite: true, isDestructive: false },
178
+ { name: "update_baby_info", category: "baby", verb: "update", requiresTrip: false, isWrite: true, isDestructive: false },
179
+ { name: "update_baby_status", category: "baby", verb: "status", requiresTrip: true, isWrite: true, isDestructive: false },
180
+ { name: "add_baby_travel_note", category: "baby", verb: "note", requiresTrip: true, isWrite: true, isDestructive: false },
181
+ { name: "delete_baby", category: "baby", verb: "delete", requiresTrip: false, isWrite: true, isDestructive: true },
182
+ // ── 가족 / 친구 / 초대 (6개)
183
+ { name: "get_family_members", category: "family", verb: "list", requiresTrip: false, isWrite: false, isDestructive: false },
184
+ { name: "add_family_to_trip", category: "family", verb: "add-to-trip", requiresTrip: true, isWrite: true, isDestructive: false },
185
+ { name: "get_friends", category: "friend", verb: "list", requiresTrip: false, isWrite: false, isDestructive: false },
186
+ { name: "add_friend", category: "friend", verb: "add", requiresTrip: false, isWrite: true, isDestructive: false },
187
+ { name: "get_invitations", category: "invite", verb: "list", requiresTrip: true, isWrite: false, isDestructive: false },
188
+ { name: "invite_to_trip", category: "invite", verb: "create", requiresTrip: true, isWrite: true, isDestructive: false }
189
+ ];
190
+ var CATALOG_BY_NAME = new Map(
191
+ FUNCTION_CATALOG.map((meta) => [meta.name, meta])
192
+ );
193
+ function getFunctionMeta(name) {
194
+ return CATALOG_BY_NAME.get(name);
195
+ }
196
+
197
+ // src/index.ts
198
+ async function fetchDeclarations(apiUrl) {
199
+ const url = new URL("/api/cli/declarations", apiUrl);
200
+ url.searchParams.set("hasTrip", "true");
201
+ const response = await fetch(url.toString());
202
+ if (!response.ok) {
203
+ throw new Error(`Failed to fetch declarations: HTTP ${response.status}`);
204
+ }
205
+ const json = await response.json();
206
+ return json.declarations ?? [];
207
+ }
208
+ async function executeOnServer(apiUrl, token, name, args, options = {}) {
209
+ const headers = {
210
+ authorization: `Bearer ${token}`,
211
+ "content-type": "application/json"
212
+ };
213
+ if (options.confirmDestructive) headers["x-dodo-confirm"] = "yes";
214
+ const response = await fetch(new URL("/api/cli/execute", apiUrl).toString(), {
215
+ method: "POST",
216
+ headers,
217
+ body: JSON.stringify({ name, args, tripId: options.tripId })
218
+ });
219
+ const text = await response.text();
220
+ let parsed = text;
221
+ try {
222
+ parsed = text ? JSON.parse(text) : null;
223
+ } catch {
224
+ }
225
+ if (!response.ok) {
226
+ const msg = parsed && typeof parsed === "object" && "error" in parsed ? String(parsed.error) : `HTTP ${response.status}`;
227
+ throw new Error(msg);
228
+ }
229
+ return parsed;
230
+ }
231
+ function humanizeResult(result) {
232
+ if (typeof result === "string") return result;
233
+ if (typeof result !== "object" || result === null) return JSON.stringify(result);
234
+ const r = result;
235
+ if (typeof r.message === "string") return r.message;
236
+ if (r.data !== void 0) return JSON.stringify(r.data, null, 2);
237
+ return JSON.stringify(result, null, 2);
238
+ }
239
+ async function run() {
240
+ const token = loadToken();
241
+ const apiUrl = loadApiUrl();
242
+ const ambientTripId = loadActiveTripId();
243
+ if (!token) {
244
+ process.stderr.write(
245
+ "Error: dodo PAT\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n - \uC6F9 \uC124\uC815 \u2192 \uAC1C\uBC1C\uC790 \uD1A0\uD070 \uC5D0\uC11C \uBC1C\uAE09 \uD6C4\n - CLI: `dodo auth login` \uC2E4\uD589 (~/.config/dodo/credentials.json \uC800\uC7A5)\n - \uB610\uB294 \uD658\uACBD\uBCC0\uC218 DODO_TOKEN \uC124\uC815\n"
246
+ );
247
+ process.exit(2);
248
+ }
249
+ let declarations;
250
+ try {
251
+ declarations = await fetchDeclarations(apiUrl);
252
+ } catch (err) {
253
+ process.stderr.write(`Error: declarations fetch \uC2E4\uD328: ${err instanceof Error ? err.message : err}
254
+ `);
255
+ process.exit(3);
256
+ }
257
+ const server = new McpServer({
258
+ name: "dodo-planet",
259
+ version: "0.1.0"
260
+ });
261
+ for (const decl of declarations) {
262
+ const meta = getFunctionMeta(decl.name) ?? decl.meta;
263
+ if (!meta) continue;
264
+ const inputSchema = zodShapeFromJsonSchema(decl.parametersJsonSchema);
265
+ if (meta.isDestructive) {
266
+ inputSchema.confirm = z2.literal(true).describe("\uC774 \uC791\uC5C5\uC740 \uC601\uAD6C \uC0AD\uC81C\uC774\uBBC0\uB85C \uC0AC\uC6A9\uC790\uAC00 \uC758\uB3C4\uD55C \uACBD\uC6B0\uC5D0\uB9CC confirm: true \uB85C \uD638\uCD9C\uD558\uC138\uC694.");
267
+ }
268
+ if (meta.requiresTrip) {
269
+ inputSchema.tripId = z2.string().optional().describe(
270
+ "Trip UUID. \uBBF8\uC9C0\uC815 \uC2DC \uC0AC\uC6A9\uC790\uAC00 \uB9C8\uC9C0\uB9C9\uC73C\uB85C dodo trip switch \uD55C \uD65C\uC131 trip \uB610\uB294 DODO_TRIP_ID \uD658\uACBD\uBCC0\uC218 \uAC12\uC744 \uC0AC\uC6A9."
271
+ );
272
+ }
273
+ const description = meta.isDestructive ? `${decl.description}
274
+
275
+ \u26A0\uFE0F DESTRUCTIVE: \uC774 \uB3C4\uAD6C\uB294 \uC601\uAD6C \uC0AD\uC81C\uB97C \uC218\uD589\uD569\uB2C8\uB2E4. \uD638\uCD9C \uC2DC args.confirm = true \uAC00 \uD544\uC694\uD558\uBA70, \uD638\uC2A4\uD2B8 \uC0AC\uC6A9\uC790\uC758 \uBA85\uC2DC \uB3D9\uC758\uAC00 \uC788\uC744 \uB54C\uB9CC \uC0AC\uC6A9\uD558\uC138\uC694.` : decl.description;
276
+ server.registerTool(
277
+ decl.name,
278
+ {
279
+ title: decl.name,
280
+ description,
281
+ inputSchema,
282
+ annotations: {
283
+ readOnlyHint: !meta.isWrite,
284
+ destructiveHint: meta.isDestructive,
285
+ idempotentHint: !meta.isWrite,
286
+ openWorldHint: meta.category === "place" || meta.category === "weather" || meta.category === "web" || meta.category === "directions" || meta.category === "flight" || meta.category === "hotel" || meta.category === "activity" || meta.category === "transfer"
287
+ }
288
+ },
289
+ async (args) => {
290
+ try {
291
+ const confirmDestructive = meta.isDestructive && args?.confirm === true;
292
+ if (meta.isDestructive && !confirmDestructive) {
293
+ return {
294
+ content: [{ type: "text", text: "Error: destructive \uB3C4\uAD6C\uB294 args.confirm = true \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4." }],
295
+ isError: true
296
+ };
297
+ }
298
+ const passArgs = { ...args ?? {} };
299
+ delete passArgs.confirm;
300
+ const argTripId = args?.tripId;
301
+ delete passArgs.tripId;
302
+ const tripId = meta.requiresTrip ? typeof argTripId === "string" ? argTripId : ambientTripId : void 0;
303
+ if (meta.requiresTrip && !tripId) {
304
+ return {
305
+ content: [{
306
+ type: "text",
307
+ text: "Error: \uD65C\uC131 trip\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \uD638\uC2A4\uD2B8 \uC178\uC5D0\uC11C `dodo trip switch <name>` \uC73C\uB85C \uD65C\uC131 trip\uC744 \uC9C0\uC815\uD558\uAC70\uB098, DODO_TRIP_ID \uD658\uACBD\uBCC0\uC218 \uB610\uB294 tool args\uC758 tripId\uB97C \uC0AC\uC6A9\uD558\uC138\uC694."
308
+ }],
309
+ isError: true
310
+ };
311
+ }
312
+ const result = await executeOnServer(apiUrl, token, decl.name, passArgs, { confirmDestructive, tripId });
313
+ const text = humanizeResult(result);
314
+ return {
315
+ content: [{ type: "text", text }],
316
+ structuredContent: typeof result === "object" && result !== null ? result : { value: result }
317
+ };
318
+ } catch (err) {
319
+ const msg = err instanceof Error ? err.message : String(err);
320
+ return {
321
+ content: [{ type: "text", text: `Error: ${msg}` }],
322
+ isError: true
323
+ };
324
+ }
325
+ }
326
+ );
327
+ }
328
+ const serverNames = new Set(declarations.map((d) => d.name));
329
+ const localNames = new Set(FUNCTION_CATALOG.map((m) => m.name));
330
+ const missingInServer = [...localNames].filter((n) => !serverNames.has(n));
331
+ if (missingInServer.length > 0) {
332
+ process.stderr.write(
333
+ `Warning: \uC11C\uBC84 declaration\uC5D0 \uB204\uB77D\uB41C \uD568\uC218 ${missingInServer.length}\uAC1C. \uC11C\uBC84 \uBC30\uD3EC\uAC00 \uBBF8\uB7EC\uBCF4\uB2E4 \uC624\uB798\uB418\uC5C8\uC744 \uC218 \uC788\uC74C.
334
+ `
335
+ );
336
+ }
337
+ const transport = new StdioServerTransport();
338
+ await server.connect(transport);
339
+ }
340
+ export {
341
+ run
342
+ };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@dodo-planet/mcp",
3
+ "version": "0.1.4",
4
+ "description": "Dodo Planet MCP server — exposes 43 family-trip functions to Claude Code, Cursor, Codex via stdio.",
5
+ "type": "module",
6
+ "bin": {
7
+ "dodo-mcp": "./bin/dodo-mcp.mjs"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "module": "./dist/index.js",
11
+ "files": [
12
+ "dist",
13
+ "bin",
14
+ "README.md"
15
+ ],
16
+ "engines": {
17
+ "node": ">=20.0.0"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/Engccer/dodo-planet.git",
22
+ "directory": "packages/mcp"
23
+ },
24
+ "homepage": "https://github.com/Engccer/dodo-planet/tree/main/packages/mcp#readme",
25
+ "bugs": "https://github.com/Engccer/dodo-planet/issues",
26
+ "license": "MIT",
27
+ "keywords": [
28
+ "dodo-planet",
29
+ "mcp",
30
+ "claude-code",
31
+ "model-context-protocol"
32
+ ],
33
+ "scripts": {
34
+ "build": "tsup",
35
+ "dev": "tsup --watch",
36
+ "test": "vitest run"
37
+ },
38
+ "dependencies": {
39
+ "@modelcontextprotocol/sdk": "^1.29.0",
40
+ "zod": "^4.3.6"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^20",
44
+ "tsup": "^8.5.1",
45
+ "typescript": "^6.0.2",
46
+ "vitest": "^4.0.18"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }