tripinned-mcp 1.0.0
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/dist/index.js +155 -0
- package/package.json +26 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
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 { z } from "zod";
|
|
5
|
+
const API_KEY = process.env.TRIPINNED_API_KEY;
|
|
6
|
+
const BASE_URL = process.env.TRIPINNED_API_URL ?? "https://api.tripinned.com/v1";
|
|
7
|
+
if (!API_KEY) {
|
|
8
|
+
console.error("Error: TRIPINNED_API_KEY environment variable is required.");
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
async function call(method, path, body) {
|
|
12
|
+
const res = await fetch(`${BASE_URL}${path}`, {
|
|
13
|
+
method,
|
|
14
|
+
headers: {
|
|
15
|
+
"X-API-Key": API_KEY,
|
|
16
|
+
...(body !== undefined ? { "Content-Type": "application/json" } : {}),
|
|
17
|
+
},
|
|
18
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
19
|
+
});
|
|
20
|
+
const text = await res.text();
|
|
21
|
+
let data;
|
|
22
|
+
try {
|
|
23
|
+
data = JSON.parse(text);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
data = text;
|
|
27
|
+
}
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
const err = data?.error ?? text;
|
|
30
|
+
throw new Error(`API ${res.status}: ${err}`);
|
|
31
|
+
}
|
|
32
|
+
return data;
|
|
33
|
+
}
|
|
34
|
+
const server = new McpServer({
|
|
35
|
+
name: "tripinned",
|
|
36
|
+
version: "1.0.0",
|
|
37
|
+
});
|
|
38
|
+
// ── Trips ────────────────────────────────────────────────────────────────────
|
|
39
|
+
server.tool("list_trips", "내 여행 플랜 목록을 가져옵니다.", {}, async () => {
|
|
40
|
+
const data = await call("GET", "/trips");
|
|
41
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
42
|
+
});
|
|
43
|
+
server.tool("get_trip", "특정 여행 플랜의 상세 정보(날짜별 일정 포함)를 가져옵니다.", { trip_id: z.string().describe("플랜 ID") }, async ({ trip_id }) => {
|
|
44
|
+
const data = await call("GET", `/trips/${trip_id}`);
|
|
45
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
46
|
+
});
|
|
47
|
+
server.tool("create_trip", "새 여행 플랜을 만듭니다. 날짜 범위에 맞는 Day가 자동 생성됩니다.", {
|
|
48
|
+
title: z.string().max(30).describe("플랜 제목 (최대 30자)"),
|
|
49
|
+
start_date: z.string().describe("시작일 (YYYY-MM-DD)"),
|
|
50
|
+
end_date: z.string().describe("종료일 (YYYY-MM-DD), 시작일 포함 최대 30일"),
|
|
51
|
+
cover_icon: z.string().optional().describe("커버 이모지 (기본값: ✈️)"),
|
|
52
|
+
}, async ({ title, start_date, end_date, cover_icon }) => {
|
|
53
|
+
const data = await call("POST", "/trips", {
|
|
54
|
+
title,
|
|
55
|
+
startDate: start_date,
|
|
56
|
+
endDate: end_date,
|
|
57
|
+
...(cover_icon ? { coverIcon: cover_icon } : {}),
|
|
58
|
+
});
|
|
59
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
60
|
+
});
|
|
61
|
+
server.tool("update_trip", "여행 플랜 정보를 수정합니다. 변경할 필드만 보내면 됩니다.", {
|
|
62
|
+
trip_id: z.string().describe("플랜 ID"),
|
|
63
|
+
title: z.string().max(30).optional().describe("새 제목"),
|
|
64
|
+
start_date: z.string().optional().describe("새 시작일 (YYYY-MM-DD)"),
|
|
65
|
+
end_date: z.string().optional().describe("새 종료일 (YYYY-MM-DD)"),
|
|
66
|
+
cover_icon: z.string().optional().describe("새 커버 이모지"),
|
|
67
|
+
visibility: z.enum(["PUBLIC", "PRIVATE"]).optional().describe("공개 여부"),
|
|
68
|
+
}, async ({ trip_id, title, start_date, end_date, cover_icon, visibility }) => {
|
|
69
|
+
const body = {};
|
|
70
|
+
if (title !== undefined)
|
|
71
|
+
body.title = title;
|
|
72
|
+
if (start_date !== undefined)
|
|
73
|
+
body.startDate = start_date;
|
|
74
|
+
if (end_date !== undefined)
|
|
75
|
+
body.endDate = end_date;
|
|
76
|
+
if (cover_icon !== undefined)
|
|
77
|
+
body.coverIcon = cover_icon;
|
|
78
|
+
if (visibility !== undefined)
|
|
79
|
+
body.visibility = visibility;
|
|
80
|
+
const data = await call("PATCH", `/trips/${trip_id}`, body);
|
|
81
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
82
|
+
});
|
|
83
|
+
server.tool("delete_trip", "여행 플랜을 삭제합니다. 포함된 모든 Day와 일정도 함께 삭제됩니다.", { trip_id: z.string().describe("플랜 ID") }, async ({ trip_id }) => {
|
|
84
|
+
await call("DELETE", `/trips/${trip_id}`);
|
|
85
|
+
return { content: [{ type: "text", text: `플랜 ${trip_id}이(가) 삭제되었습니다.` }] };
|
|
86
|
+
});
|
|
87
|
+
// ── Items ────────────────────────────────────────────────────────────────────
|
|
88
|
+
server.tool("add_item", "특정 Day에 일정을 추가합니다.", {
|
|
89
|
+
trip_id: z.string().describe("플랜 ID"),
|
|
90
|
+
day_id: z.string().describe("Day ID (get_trip으로 확인 가능)"),
|
|
91
|
+
title: z.string().max(50).describe("일정 제목 (최대 50자)"),
|
|
92
|
+
icon: z.enum([
|
|
93
|
+
"FOOD", "CAFE", "HOTEL", "SIGHTSEEING", "SHOPPING", "ACTIVITY", "ETC",
|
|
94
|
+
"TRANSPORT", "PLANE", "TRAIN", "BUS", "CAR", "BOAT", "WALK", "TRANSPORT_ETC",
|
|
95
|
+
]).optional().describe("아이콘 종류 (기본값: ETC)"),
|
|
96
|
+
start_time: z.string().regex(/^\d{2}:\d{2}$/).optional().describe("시작 시간 (HH:MM)"),
|
|
97
|
+
end_time: z.string().regex(/^\d{2}:\d{2}$/).optional().describe("종료 시간 (HH:MM)"),
|
|
98
|
+
place: z.string().optional().describe("장소명"),
|
|
99
|
+
memo: z.string().max(1000).optional().describe("메모 (최대 1000자)"),
|
|
100
|
+
}, async ({ trip_id, day_id, title, icon, start_time, end_time, place, memo }) => {
|
|
101
|
+
const body = { title };
|
|
102
|
+
if (icon)
|
|
103
|
+
body.icon = icon;
|
|
104
|
+
if (start_time)
|
|
105
|
+
body.startTime = start_time;
|
|
106
|
+
if (end_time)
|
|
107
|
+
body.endTime = end_time;
|
|
108
|
+
if (place)
|
|
109
|
+
body.place = place;
|
|
110
|
+
if (memo)
|
|
111
|
+
body.memo = memo;
|
|
112
|
+
const data = await call("POST", `/trips/${trip_id}/days/${day_id}/items`, body);
|
|
113
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
114
|
+
});
|
|
115
|
+
server.tool("update_item", "일정을 수정합니다. 변경할 필드만 보내면 됩니다.", {
|
|
116
|
+
trip_id: z.string().describe("플랜 ID"),
|
|
117
|
+
day_id: z.string().describe("Day ID"),
|
|
118
|
+
item_id: z.string().describe("일정 ID"),
|
|
119
|
+
title: z.string().max(50).optional().describe("새 제목"),
|
|
120
|
+
icon: z.enum([
|
|
121
|
+
"FOOD", "CAFE", "HOTEL", "SIGHTSEEING", "SHOPPING", "ACTIVITY", "ETC",
|
|
122
|
+
"TRANSPORT", "PLANE", "TRAIN", "BUS", "CAR", "BOAT", "WALK", "TRANSPORT_ETC",
|
|
123
|
+
]).optional().describe("새 아이콘"),
|
|
124
|
+
start_time: z.string().regex(/^\d{2}:\d{2}$/).optional().describe("새 시작 시간 (HH:MM)"),
|
|
125
|
+
end_time: z.string().regex(/^\d{2}:\d{2}$/).optional().describe("새 종료 시간 (HH:MM)"),
|
|
126
|
+
place: z.string().optional().describe("새 장소명"),
|
|
127
|
+
memo: z.string().max(1000).optional().describe("새 메모"),
|
|
128
|
+
}, async ({ trip_id, day_id, item_id, title, icon, start_time, end_time, place, memo }) => {
|
|
129
|
+
const body = {};
|
|
130
|
+
if (title !== undefined)
|
|
131
|
+
body.title = title;
|
|
132
|
+
if (icon !== undefined)
|
|
133
|
+
body.icon = icon;
|
|
134
|
+
if (start_time !== undefined)
|
|
135
|
+
body.startTime = start_time;
|
|
136
|
+
if (end_time !== undefined)
|
|
137
|
+
body.endTime = end_time;
|
|
138
|
+
if (place !== undefined)
|
|
139
|
+
body.place = place;
|
|
140
|
+
if (memo !== undefined)
|
|
141
|
+
body.memo = memo;
|
|
142
|
+
const data = await call("PATCH", `/trips/${trip_id}/days/${day_id}/items/${item_id}`, body);
|
|
143
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
144
|
+
});
|
|
145
|
+
server.tool("delete_item", "일정을 삭제합니다.", {
|
|
146
|
+
trip_id: z.string().describe("플랜 ID"),
|
|
147
|
+
day_id: z.string().describe("Day ID"),
|
|
148
|
+
item_id: z.string().describe("일정 ID"),
|
|
149
|
+
}, async ({ trip_id, day_id, item_id }) => {
|
|
150
|
+
await call("DELETE", `/trips/${trip_id}/days/${day_id}/items/${item_id}`);
|
|
151
|
+
return { content: [{ type: "text", text: `일정 ${item_id}이(가) 삭제되었습니다.` }] };
|
|
152
|
+
});
|
|
153
|
+
// ── Start ─────────────────────────────────────────────────────────────────────
|
|
154
|
+
const transport = new StdioServerTransport();
|
|
155
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tripinned-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Tripinned — manage your travel plans with AI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"tripinned-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "node --loader ts-node/esm src/index.ts",
|
|
13
|
+
"start": "node dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
17
|
+
"zod": "^4.3.6"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^25.5.0",
|
|
21
|
+
"typescript": "^5.0.0"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
}
|
|
26
|
+
}
|