daiso 1.0.4 → 1.0.6
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 +249 -533
- package/dist/api/actionsProxy.js +82 -0
- package/dist/api/cgvHandlers.js +103 -16
- package/dist/api/configStatus.js +19 -0
- package/dist/api/daisoHandlers.js +127 -0
- package/dist/api/emart24Handlers.js +176 -0
- package/dist/api/gs25Handlers.js +267 -0
- package/dist/api/handlers.js +209 -104
- package/dist/api/healthChecks.js +504 -0
- package/dist/api/lottecinemaHandlers.js +155 -0
- package/dist/api/lottemartHandlers.js +144 -0
- package/dist/api/megaboxHandlers.js +81 -50
- package/dist/api/response.js +2 -0
- package/dist/api/routes/cuRoutes.js +17 -0
- package/dist/api/routes/daisoRoutes.js +6 -1
- package/dist/api/routes/emart24Routes.js +22 -0
- package/dist/api/routes/gs25Routes.js +22 -0
- package/dist/api/routes/healthRoutes.js +61 -0
- package/dist/api/routes/lottecinemaRoutes.js +22 -0
- package/dist/api/routes/lottemartRoutes.js +18 -0
- package/dist/api/routes/oliveyoungRoutes.js +6 -1
- package/dist/api/routes/sevenelevenRoutes.js +32 -0
- package/dist/api/sevenelevenHandlers.js +200 -0
- package/dist/bin.js +0 -0
- package/dist/cli/args.js +75 -0
- package/dist/cli/commandOptions.js +38 -0
- package/dist/cli/commands/convenience.js +293 -0
- package/dist/cli/commands/daiso.js +112 -0
- package/dist/cli/constants.js +5 -0
- package/dist/cli/deps.js +48 -0
- package/dist/cli/http.js +156 -0
- package/dist/cli/interactiveFetch.js +31 -0
- package/dist/cli/interactiveItemSearch.js +261 -0
- package/dist/cli/interactivePrompt.js +50 -0
- package/dist/cli/interactiveTypes.js +4 -0
- package/dist/cli/types.js +4 -0
- package/dist/cli.js +87 -196
- package/dist/cliHelp.js +212 -1
- package/dist/cliInteractive.js +243 -0
- package/dist/cliPicker.js +57 -0
- package/dist/cliRenderer.js +90 -3
- package/dist/core/errors.js +66 -0
- package/dist/core/registry.js +227 -3
- package/dist/index.js +149 -9
- package/dist/pages/openapi.js +2 -1
- package/dist/pages/openapiFullSpec.js +58 -0
- package/dist/pages/openapiResponse.js +28 -0
- package/dist/pages/openapiSpec.js +2 -41
- package/dist/pages/openapiSpecActions.js +109 -0
- package/dist/pages/openapiSpecComponents.js +30 -391
- package/dist/pages/openapiSpecComponentsCgv.js +24 -0
- package/dist/pages/openapiSpecComponentsCu.js +112 -0
- package/dist/pages/openapiSpecComponentsDaisoOliveyoung.js +342 -0
- package/dist/pages/openapiSpecComponentsEmart24.js +141 -0
- package/dist/pages/openapiSpecComponentsGs25.js +109 -0
- package/dist/pages/openapiSpecComponentsLotteMart.js +85 -0
- package/dist/pages/openapiSpecComponentsLottecinema.js +163 -0
- package/dist/pages/openapiSpecComponentsMegabox.js +144 -0
- package/dist/pages/openapiSpecComponentsSeveneleven.js +82 -0
- package/dist/pages/openapiSpecPathsCgv.js +66 -3
- package/dist/pages/openapiSpecPathsCu.js +145 -0
- package/dist/pages/openapiSpecPathsDaiso.js +302 -0
- package/dist/pages/openapiSpecPathsDaisoOliveyoung.js +4 -376
- package/dist/pages/openapiSpecPathsEmart24.js +223 -0
- package/dist/pages/openapiSpecPathsGs25.js +166 -0
- package/dist/pages/openapiSpecPathsLotteMart.js +149 -0
- package/dist/pages/openapiSpecPathsLottecinema.js +212 -0
- package/dist/pages/openapiSpecPathsMegabox.js +52 -3
- package/dist/pages/openapiSpecPathsOliveyoung.js +266 -0
- package/dist/pages/openapiSpecPathsSeveneleven.js +234 -0
- package/dist/pages/prompt.js +8 -402
- package/dist/pages/promptCoreText.js +278 -0
- package/dist/pages/promptReferenceText.js +137 -0
- package/dist/pages/promptRetailCinemaText.js +274 -0
- package/dist/services/cgv/client.js +6 -1
- package/dist/services/cgv/index.js +3 -3
- package/dist/services/cgv/location.js +235 -0
- package/dist/services/cgv/tools/findTheaters.js +30 -5
- package/dist/services/cgv/tools/getTimetable.js +42 -14
- package/dist/services/cgv/tools/searchMovies.js +40 -12
- package/dist/services/cgv/transport.js +0 -3
- package/dist/services/cu/api.js +11 -0
- package/dist/services/cu/client.js +297 -0
- package/dist/services/cu/index.js +21 -0
- package/dist/services/cu/tools/checkInventory.js +123 -0
- package/dist/services/cu/tools/findNearbyStores.js +49 -0
- package/dist/services/cu/types.js +4 -0
- package/dist/services/daiso/api.js +22 -2
- package/dist/services/daiso/client.js +67 -0
- package/dist/services/daiso/index.js +4 -0
- package/dist/services/daiso/product.js +17 -0
- package/dist/services/daiso/tools/checkInventory.js +80 -43
- package/dist/services/daiso/tools/findInventoryByName.js +222 -0
- package/dist/services/daiso/tools/findStores.js +17 -7
- package/dist/services/daiso/tools/getDisplayLocation.js +75 -0
- package/dist/services/emart24/api.js +11 -0
- package/dist/services/emart24/client.js +182 -0
- package/dist/services/emart24/index.js +22 -0
- package/dist/services/emart24/inventoryLookup.js +134 -0
- package/dist/services/emart24/tools/checkInventory.js +54 -0
- package/dist/services/emart24/tools/findNearbyStores.js +72 -0
- package/dist/services/emart24/tools/searchProducts.js +73 -0
- package/dist/services/emart24/types.js +4 -0
- package/dist/services/gs25/api.js +9 -0
- package/dist/services/gs25/client.js +208 -0
- package/dist/services/gs25/index.js +22 -0
- package/dist/services/gs25/storeUtils.js +168 -0
- package/dist/services/gs25/tools/checkInventory.js +163 -0
- package/dist/services/gs25/tools/findNearbyStores.js +103 -0
- package/dist/services/gs25/tools/searchProducts.js +57 -0
- package/dist/services/gs25/types.js +4 -0
- package/dist/services/lottecinema/api.js +12 -0
- package/dist/services/lottecinema/client.js +161 -0
- package/dist/services/lottecinema/index.js +30 -0
- package/dist/services/lottecinema/location.js +191 -0
- package/dist/services/lottecinema/tools/findNearbyTheaters.js +47 -0
- package/dist/services/lottecinema/tools/getRemainingSeats.js +85 -0
- package/dist/services/lottecinema/tools/listNowShowing.js +79 -0
- package/dist/services/lottecinema/types.js +4 -0
- package/dist/services/lottemart/api.js +31 -0
- package/dist/services/lottemart/client.js +254 -0
- package/dist/services/lottemart/clientTypes.js +4 -0
- package/dist/services/lottemart/config.js +4 -0
- package/dist/services/lottemart/debug.js +92 -0
- package/dist/services/lottemart/geocode.js +49 -0
- package/dist/services/lottemart/index.js +30 -0
- package/dist/services/lottemart/keyword.js +29 -0
- package/dist/services/lottemart/parser.js +185 -0
- package/dist/services/lottemart/session.js +331 -0
- package/dist/services/lottemart/storeSearch.js +52 -0
- package/dist/services/lottemart/tools/findNearbyStores.js +67 -0
- package/dist/services/lottemart/tools/searchProducts.js +69 -0
- package/dist/services/lottemart/types.js +4 -0
- package/dist/services/lottemart/zetta.js +105 -0
- package/dist/services/megabox/location.js +219 -0
- package/dist/services/megabox/tools/findNearbyTheaters.js +13 -44
- package/dist/services/megabox/tools/getRemainingSeats.js +31 -3
- package/dist/services/megabox/tools/listNowShowing.js +31 -3
- package/dist/services/oliveyoung/api.js +2 -0
- package/dist/services/oliveyoung/client.js +225 -13
- package/dist/services/oliveyoung/index.js +3 -1
- package/dist/services/oliveyoung/tools/checkInventory.js +21 -5
- package/dist/services/oliveyoung/tools/searchProducts.js +57 -0
- package/dist/services/seveneleven/api.js +15 -0
- package/dist/services/seveneleven/client.js +286 -0
- package/dist/services/seveneleven/index.js +30 -0
- package/dist/services/seveneleven/inventory.js +302 -0
- package/dist/services/seveneleven/productKeyword.js +141 -0
- package/dist/services/seveneleven/tools/checkInventory.js +82 -0
- package/dist/services/seveneleven/tools/getCatalogSnapshot.js +55 -0
- package/dist/services/seveneleven/tools/getSearchPopwords.js +41 -0
- package/dist/services/seveneleven/tools/searchProducts.js +67 -0
- package/dist/services/seveneleven/tools/searchStores.js +45 -0
- package/dist/services/seveneleven/types.js +4 -0
- package/dist/utils/cliInteractiveHelpers.js +63 -0
- package/dist/utils/daisoKeyword.js +30 -0
- package/dist/utils/http.js +98 -12
- package/dist/utils/zyte.js +47 -26
- package/openapi.json +1979 -736
- package/openapi.yaml +1428 -541
- package/package.json +49 -18
- package/dist/tools/checkInventory.js +0 -98
- package/dist/tools/findStores.js +0 -125
- package/dist/tools/getPriceInfo.js +0 -77
- package/dist/tools/searchProducts.js +0 -61
- package/dist/types/index.js +0 -4
package/dist/core/registry.js
CHANGED
|
@@ -3,6 +3,206 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 서비스 프로바이더를 등록하고 MCP 서버에 도구를 연결합니다.
|
|
5
5
|
*/
|
|
6
|
+
import * as z from 'zod';
|
|
7
|
+
import { getErrorMessage, toStandardErrorDiagnostics } from './errors.js';
|
|
8
|
+
const DEFAULT_OUTPUT_SCHEMA = z.object({}).loose().describe('도구 실행 결과(JSON 객체)');
|
|
9
|
+
function createFallbackOutputSchema(toolName) {
|
|
10
|
+
if (toolName.includes('search_products') || toolName.includes('searchProducts') || toolName.endsWith('_products')) {
|
|
11
|
+
return {
|
|
12
|
+
keyword: z.string().optional(),
|
|
13
|
+
query: z.string().optional(),
|
|
14
|
+
page: z.number().optional(),
|
|
15
|
+
pageSize: z.number().optional(),
|
|
16
|
+
totalCount: z.number().optional(),
|
|
17
|
+
count: z.number().optional(),
|
|
18
|
+
products: z.array(z.unknown()).optional(),
|
|
19
|
+
note: z.string().optional(),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (toolName.includes('find_stores') || toolName.includes('nearby_stores') || toolName.includes('search_stores')) {
|
|
23
|
+
return {
|
|
24
|
+
keyword: z.string().optional(),
|
|
25
|
+
searchParams: z.unknown().optional(),
|
|
26
|
+
location: z.unknown().optional(),
|
|
27
|
+
totalCount: z.number().optional(),
|
|
28
|
+
filteredCount: z.number().optional(),
|
|
29
|
+
count: z.number().optional(),
|
|
30
|
+
stores: z.array(z.unknown()).optional(),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (toolName.includes('inventory')) {
|
|
34
|
+
return {
|
|
35
|
+
productId: z.string().optional(),
|
|
36
|
+
keyword: z.string().optional(),
|
|
37
|
+
itemCode: z.string().nullable().optional(),
|
|
38
|
+
location: z.unknown().optional(),
|
|
39
|
+
product: z.unknown().optional(),
|
|
40
|
+
onlineStock: z.number().optional(),
|
|
41
|
+
inventory: z.unknown().optional(),
|
|
42
|
+
storeInventory: z.unknown().optional(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (toolName.includes('display_location')) {
|
|
46
|
+
return {
|
|
47
|
+
productId: z.string().optional(),
|
|
48
|
+
storeCode: z.string().optional(),
|
|
49
|
+
displayLocation: z.unknown().optional(),
|
|
50
|
+
locations: z.array(z.unknown()).optional(),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (toolName.includes('price_info')) {
|
|
54
|
+
return {
|
|
55
|
+
productId: z.string().optional(),
|
|
56
|
+
productName: z.string().optional(),
|
|
57
|
+
product: z.unknown().optional(),
|
|
58
|
+
price: z.number().optional(),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (toolName.includes('theater')) {
|
|
62
|
+
return {
|
|
63
|
+
keyword: z.string().optional(),
|
|
64
|
+
location: z.unknown().optional(),
|
|
65
|
+
count: z.number().optional(),
|
|
66
|
+
theaters: z.array(z.unknown()).optional(),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (toolName.includes('movie') || toolName.includes('now_showing')) {
|
|
70
|
+
return {
|
|
71
|
+
keyword: z.string().optional(),
|
|
72
|
+
playDate: z.string().optional(),
|
|
73
|
+
theaterCode: z.string().optional(),
|
|
74
|
+
count: z.number().optional(),
|
|
75
|
+
movies: z.array(z.unknown()).optional(),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (toolName.includes('timetable') || toolName.includes('remaining_seats')) {
|
|
79
|
+
return {
|
|
80
|
+
playDate: z.string().optional(),
|
|
81
|
+
theaterCode: z.string().optional(),
|
|
82
|
+
movieCode: z.string().optional(),
|
|
83
|
+
count: z.number().optional(),
|
|
84
|
+
showtimes: z.array(z.unknown()).optional(),
|
|
85
|
+
seats: z.array(z.unknown()).optional(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (toolName.includes('popwords')) {
|
|
89
|
+
return {
|
|
90
|
+
label: z.string().optional(),
|
|
91
|
+
keywords: z.array(z.string()).optional(),
|
|
92
|
+
count: z.number().optional(),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (toolName.includes('catalog')) {
|
|
96
|
+
return {
|
|
97
|
+
pages: z.array(z.unknown()).optional(),
|
|
98
|
+
issues: z.array(z.unknown()).optional(),
|
|
99
|
+
exhibitions: z.array(z.unknown()).optional(),
|
|
100
|
+
count: z.number().optional(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return {};
|
|
104
|
+
}
|
|
105
|
+
function createToolOutputSchema(toolName, outputSchema) {
|
|
106
|
+
const schema = outputSchema || createFallbackOutputSchema(toolName);
|
|
107
|
+
if (Object.keys(schema).length === 0) {
|
|
108
|
+
return DEFAULT_OUTPUT_SCHEMA;
|
|
109
|
+
}
|
|
110
|
+
return z
|
|
111
|
+
.object({
|
|
112
|
+
...schema,
|
|
113
|
+
standard: z.unknown().optional().describe('서비스 공통 상품/매장/영화관 정규화 결과'),
|
|
114
|
+
})
|
|
115
|
+
.loose();
|
|
116
|
+
}
|
|
117
|
+
function parseJsonText(text) {
|
|
118
|
+
try {
|
|
119
|
+
const parsed = JSON.parse(text);
|
|
120
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
121
|
+
return parsed;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
function firstString(...values) {
|
|
130
|
+
for (const value of values) {
|
|
131
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
132
|
+
return value.trim();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
function firstNumber(...values) {
|
|
138
|
+
for (const value of values) {
|
|
139
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
if (typeof value === 'string') {
|
|
143
|
+
const parsed = Number.parseFloat(value.replace(/km$/i, ''));
|
|
144
|
+
if (Number.isFinite(parsed)) {
|
|
145
|
+
return value.trim().toLowerCase().endsWith('km') ? Math.round(parsed * 1000) : parsed;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
function normalizeProducts(products) {
|
|
152
|
+
return products
|
|
153
|
+
.filter((item) => Boolean(item) && typeof item === 'object' && !Array.isArray(item))
|
|
154
|
+
.map((item) => ({
|
|
155
|
+
code: firstString(item.itemCode, item.productCode, item.pluCd, item.id, item.productNo),
|
|
156
|
+
name: firstString(item.itemName, item.shortItemName, item.goodsName, item.productName, item.movieName, item.name),
|
|
157
|
+
price: firstNumber(item.viewPrice, item.salePrice, item.sellPrice, item.price, item.originPrice, item.searchItemSellPrice),
|
|
158
|
+
imageUrl: firstString(item.imageUrl),
|
|
159
|
+
raw: item,
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
function normalizeStores(stores) {
|
|
163
|
+
return stores
|
|
164
|
+
.filter((item) => Boolean(item) && typeof item === 'object' && !Array.isArray(item))
|
|
165
|
+
.map((item) => ({
|
|
166
|
+
code: firstString(item.storeCode, item.code, item.id),
|
|
167
|
+
name: firstString(item.storeName, item.name, item.theaterName),
|
|
168
|
+
address: firstString(item.address, item.storeAddress),
|
|
169
|
+
distanceMeters: firstNumber(item.distanceM, item.distanceMeters, item.distance),
|
|
170
|
+
raw: item,
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
function withStandardCollections(payload) {
|
|
174
|
+
const standard = {};
|
|
175
|
+
if (Array.isArray(payload.products)) {
|
|
176
|
+
standard.products = normalizeProducts(payload.products);
|
|
177
|
+
}
|
|
178
|
+
if (Array.isArray(payload.stores)) {
|
|
179
|
+
standard.stores = normalizeStores(payload.stores);
|
|
180
|
+
}
|
|
181
|
+
if (Array.isArray(payload.theaters)) {
|
|
182
|
+
standard.theaters = normalizeStores(payload.theaters);
|
|
183
|
+
}
|
|
184
|
+
if (Array.isArray(payload.movies)) {
|
|
185
|
+
standard.movies = normalizeProducts(payload.movies);
|
|
186
|
+
}
|
|
187
|
+
if (Object.keys(standard).length === 0) {
|
|
188
|
+
return payload;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
...payload,
|
|
192
|
+
standard,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function buildStructuredContent(result) {
|
|
196
|
+
if (result.structuredContent) {
|
|
197
|
+
return withStandardCollections(result.structuredContent);
|
|
198
|
+
}
|
|
199
|
+
const text = result.content[0]?.text || '';
|
|
200
|
+
const parsed = parseJsonText(text);
|
|
201
|
+
if (parsed) {
|
|
202
|
+
return withStandardCollections(parsed);
|
|
203
|
+
}
|
|
204
|
+
return { text };
|
|
205
|
+
}
|
|
6
206
|
/**
|
|
7
207
|
* 서비스 레지스트리 클래스
|
|
8
208
|
*
|
|
@@ -73,14 +273,38 @@ export class ServiceRegistry {
|
|
|
73
273
|
* 단일 도구를 MCP 서버에 등록
|
|
74
274
|
*/
|
|
75
275
|
registerTool(server, tool) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
276
|
+
const metadata = {
|
|
277
|
+
...tool.metadata,
|
|
278
|
+
outputSchema: createToolOutputSchema(tool.name, tool.metadata.outputSchema),
|
|
279
|
+
};
|
|
280
|
+
server.registerTool(tool.name, metadata, async (args) => {
|
|
281
|
+
let result;
|
|
282
|
+
try {
|
|
283
|
+
result = await tool.handler(args);
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
const message = getErrorMessage(error);
|
|
287
|
+
return {
|
|
288
|
+
isError: true,
|
|
289
|
+
content: [{ type: 'text', text: message }],
|
|
290
|
+
structuredContent: {
|
|
291
|
+
error: toStandardErrorDiagnostics('TOOL_EXECUTION_FAILED', message, {
|
|
292
|
+
operation: tool.name,
|
|
293
|
+
}),
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
const response = {
|
|
79
298
|
content: result.content.map((item) => ({
|
|
80
299
|
type: item.type,
|
|
81
300
|
text: item.text,
|
|
82
301
|
})),
|
|
302
|
+
structuredContent: buildStructuredContent(result),
|
|
83
303
|
};
|
|
304
|
+
if (result.isError) {
|
|
305
|
+
response.isError = true;
|
|
306
|
+
}
|
|
307
|
+
return response;
|
|
84
308
|
});
|
|
85
309
|
}
|
|
86
310
|
/**
|
package/dist/index.js
CHANGED
|
@@ -13,16 +13,34 @@ import { createDaisoService } from './services/daiso/index.js';
|
|
|
13
13
|
import { createOliveyoungService } from './services/oliveyoung/index.js';
|
|
14
14
|
import { createMegaboxService } from './services/megabox/index.js';
|
|
15
15
|
import { createCgvService } from './services/cgv/index.js';
|
|
16
|
+
import { createLotteCinemaService } from './services/lottecinema/index.js';
|
|
17
|
+
import { createLotteMartService } from './services/lottemart/index.js';
|
|
18
|
+
import { createCuService } from './services/cu/index.js';
|
|
19
|
+
import { createEmart24Service } from './services/emart24/index.js';
|
|
20
|
+
import { createGs25Service } from './services/gs25/index.js';
|
|
21
|
+
import { createSevenElevenService } from './services/seveneleven/index.js';
|
|
16
22
|
import { createPromptResponse } from './pages/prompt.js';
|
|
17
|
-
import { createOpenApiJsonResponse, createOpenApiYamlResponse } from './pages/openapi.js';
|
|
23
|
+
import { createFullOpenApiJsonResponse, createFullOpenApiYamlResponse, createOpenApiJsonResponse, createOpenApiYamlResponse, } from './pages/openapi.js';
|
|
18
24
|
import { createPrivacyResponse } from './pages/privacy.js';
|
|
25
|
+
import { buildActionQueryTargetUrl } from './api/actionsProxy.js';
|
|
19
26
|
import { registerDaisoRoutes } from './api/routes/daisoRoutes.js';
|
|
20
27
|
import { registerOliveyoungRoutes } from './api/routes/oliveyoungRoutes.js';
|
|
21
28
|
import { registerMegaboxRoutes } from './api/routes/megaboxRoutes.js';
|
|
22
29
|
import { registerCgvRoutes } from './api/routes/cgvRoutes.js';
|
|
30
|
+
import { registerLotteCinemaRoutes } from './api/routes/lottecinemaRoutes.js';
|
|
31
|
+
import { registerLotteMartRoutes } from './api/routes/lottemartRoutes.js';
|
|
32
|
+
import { registerCuRoutes } from './api/routes/cuRoutes.js';
|
|
33
|
+
import { registerEmart24Routes } from './api/routes/emart24Routes.js';
|
|
34
|
+
import { registerGs25Routes } from './api/routes/gs25Routes.js';
|
|
35
|
+
import { registerSevenElevenRoutes } from './api/routes/sevenelevenRoutes.js';
|
|
36
|
+
import { registerHealthRoutes } from './api/routes/healthRoutes.js';
|
|
37
|
+
import { buildConfigStatus } from './api/configStatus.js';
|
|
23
38
|
// 서버 메타데이터
|
|
24
39
|
const SERVER_NAME = 'multi-service-mcp';
|
|
25
40
|
const SERVER_VERSION = '1.0.0';
|
|
41
|
+
const SESSION_HEADER = 'mcp-session-id';
|
|
42
|
+
// 세션별 MCP 전송 계층/서버를 유지해 GET/POST/DELETE를 일관 처리합니다.
|
|
43
|
+
const mcpSessions = new Map();
|
|
26
44
|
/**
|
|
27
45
|
* 요청 컨텍스트 기반 서비스 레지스트리 생성
|
|
28
46
|
*/
|
|
@@ -30,9 +48,21 @@ const createRegistry = (bindings) => {
|
|
|
30
48
|
const registry = new ServiceRegistry();
|
|
31
49
|
registry.registerAll([
|
|
32
50
|
createDaisoService,
|
|
51
|
+
createGs25Service,
|
|
52
|
+
createSevenElevenService,
|
|
53
|
+
createCuService,
|
|
54
|
+
createEmart24Service,
|
|
55
|
+
() => createLotteMartService({
|
|
56
|
+
googleMapsApiKey: bindings?.GOOGLE_MAPS_API_KEY,
|
|
57
|
+
zyteApiKey: bindings?.ZYTE_API_KEY,
|
|
58
|
+
}),
|
|
33
59
|
createMegaboxService,
|
|
60
|
+
() => createLotteCinemaService({
|
|
61
|
+
googleMapsApiKey: bindings?.GOOGLE_MAPS_API_KEY,
|
|
62
|
+
}),
|
|
34
63
|
() => createCgvService({
|
|
35
64
|
zyteApiKey: bindings?.ZYTE_API_KEY,
|
|
65
|
+
googleMapsApiKey: bindings?.GOOGLE_MAPS_API_KEY,
|
|
36
66
|
}),
|
|
37
67
|
() => createOliveyoungService({
|
|
38
68
|
zyteApiKey: bindings?.ZYTE_API_KEY,
|
|
@@ -52,13 +82,88 @@ const createMcpServer = (bindings) => {
|
|
|
52
82
|
registry.applyToServer(server);
|
|
53
83
|
return server;
|
|
54
84
|
};
|
|
85
|
+
/**
|
|
86
|
+
* initialize 요청 여부를 확인합니다.
|
|
87
|
+
*/
|
|
88
|
+
const isInitializeRequest = (body) => {
|
|
89
|
+
if (Array.isArray(body)) {
|
|
90
|
+
return body.some((item) => typeof item === 'object' && item !== null && item.method === 'initialize');
|
|
91
|
+
}
|
|
92
|
+
return typeof body === 'object' && body !== null && body.method === 'initialize';
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* 새 세션 transport/server를 생성합니다.
|
|
96
|
+
*/
|
|
97
|
+
const createSessionTransport = (bindings) => {
|
|
98
|
+
const server = createMcpServer(bindings);
|
|
99
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
100
|
+
sessionIdGenerator: () => crypto.randomUUID(),
|
|
101
|
+
onsessioninitialized: (sessionId) => {
|
|
102
|
+
mcpSessions.set(sessionId, { server, transport });
|
|
103
|
+
},
|
|
104
|
+
onsessionclosed: (sessionId) => {
|
|
105
|
+
mcpSessions.delete(sessionId);
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
return { server, transport };
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* /mcp 요청을 세션 기반으로 처리합니다.
|
|
112
|
+
*/
|
|
113
|
+
const handleMcpRequest = async (c) => {
|
|
114
|
+
const sessionId = c.req.header(SESSION_HEADER);
|
|
115
|
+
if (sessionId) {
|
|
116
|
+
const existing = mcpSessions.get(sessionId);
|
|
117
|
+
if (!existing) {
|
|
118
|
+
if (c.req.method === 'POST') {
|
|
119
|
+
const transport = new WebStandardStreamableHTTPServerTransport();
|
|
120
|
+
const server = createMcpServer(c.env);
|
|
121
|
+
await server.connect(transport);
|
|
122
|
+
const response = await transport.handleRequest(c.req.raw);
|
|
123
|
+
response.headers.set('x-mcp-session-fallback', 'stateless');
|
|
124
|
+
return response;
|
|
125
|
+
}
|
|
126
|
+
return c.json({
|
|
127
|
+
error: 'Session not found',
|
|
128
|
+
message: '유효하지 않은 mcp-session-id 입니다. initialize 요청부터 다시 시작해주세요.',
|
|
129
|
+
}, 404);
|
|
130
|
+
}
|
|
131
|
+
return existing.transport.handleRequest(c.req.raw);
|
|
132
|
+
}
|
|
133
|
+
if (c.req.method === 'POST') {
|
|
134
|
+
const parsedBody = await c.req.raw
|
|
135
|
+
.clone()
|
|
136
|
+
.json()
|
|
137
|
+
.catch(() => undefined);
|
|
138
|
+
if (!isInitializeRequest(parsedBody)) {
|
|
139
|
+
return c.json({
|
|
140
|
+
error: 'Bad Request',
|
|
141
|
+
message: '세션이 없습니다. 먼저 initialize 요청으로 세션을 생성해주세요.',
|
|
142
|
+
}, 400);
|
|
143
|
+
}
|
|
144
|
+
const { server, transport } = createSessionTransport(c.env);
|
|
145
|
+
await server.connect(transport);
|
|
146
|
+
return transport.handleRequest(c.req.raw, { parsedBody });
|
|
147
|
+
}
|
|
148
|
+
return c.json({
|
|
149
|
+
error: 'Bad Request',
|
|
150
|
+
message: `세션이 없습니다. 먼저 POST /mcp initialize 요청 후 ${SESSION_HEADER} 헤더를 사용해주세요.`,
|
|
151
|
+
}, 400);
|
|
152
|
+
};
|
|
55
153
|
// Hono 앱 생성
|
|
56
154
|
const app = new Hono();
|
|
57
155
|
// CORS 설정
|
|
58
156
|
app.use('*', cors({
|
|
59
157
|
origin: '*',
|
|
60
158
|
allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
|
|
61
|
-
allowHeaders: [
|
|
159
|
+
allowHeaders: [
|
|
160
|
+
'Content-Type',
|
|
161
|
+
'Authorization',
|
|
162
|
+
'x-health-check-key',
|
|
163
|
+
'mcp-session-id',
|
|
164
|
+
'Last-Event-ID',
|
|
165
|
+
'mcp-protocol-version',
|
|
166
|
+
],
|
|
62
167
|
exposeHeaders: ['mcp-session-id', 'mcp-protocol-version'],
|
|
63
168
|
}));
|
|
64
169
|
// 기본 정보 엔드포인트 (GET 요청만)
|
|
@@ -73,6 +178,10 @@ app.get('/', (c) => {
|
|
|
73
178
|
endpoints: {
|
|
74
179
|
mcp: '/ 또는 /mcp (POST) - MCP 프로토콜 엔드포인트',
|
|
75
180
|
health: '/health (GET) - 헬스 체크',
|
|
181
|
+
healthChecks: '/api/health/checks (GET) - 서비스별 상세 헬스 체크',
|
|
182
|
+
openapi: '/openapi.json (GET) - OpenAI Actions용 축약 OpenAPI',
|
|
183
|
+
openapiFull: '/openapi-full.json (GET) - 전체 OpenAPI',
|
|
184
|
+
actionsQuery: '/api/actions/query (GET) - 기존 GET API 통합 facade',
|
|
76
185
|
},
|
|
77
186
|
services,
|
|
78
187
|
tools: allTools,
|
|
@@ -88,7 +197,8 @@ app.on(['POST', 'DELETE', 'OPTIONS'], '/', async (c) => {
|
|
|
88
197
|
return transport.handleRequest(c.req.raw);
|
|
89
198
|
});
|
|
90
199
|
// 헬스 체크 엔드포인트
|
|
91
|
-
app.get('/health', (c) => c.json({ status: 'ok' }));
|
|
200
|
+
app.get('/health', (c) => c.json({ status: 'ok', config: buildConfigStatus(c.env) }));
|
|
201
|
+
registerHealthRoutes(app);
|
|
92
202
|
// 프롬프트 페이지 (MCP 미지원 에이전트용)
|
|
93
203
|
app.get('/prompt', (c) => {
|
|
94
204
|
const baseUrl = new URL(c.req.url).origin;
|
|
@@ -103,6 +213,14 @@ app.get('/openapi.yaml', (c) => {
|
|
|
103
213
|
const baseUrl = new URL(c.req.url).origin;
|
|
104
214
|
return createOpenApiYamlResponse(baseUrl);
|
|
105
215
|
});
|
|
216
|
+
app.get('/openapi-full.json', (c) => {
|
|
217
|
+
const baseUrl = new URL(c.req.url).origin;
|
|
218
|
+
return createFullOpenApiJsonResponse(baseUrl);
|
|
219
|
+
});
|
|
220
|
+
app.get('/openapi-full.yaml', (c) => {
|
|
221
|
+
const baseUrl = new URL(c.req.url).origin;
|
|
222
|
+
return createFullOpenApiYamlResponse(baseUrl);
|
|
223
|
+
});
|
|
106
224
|
// 개인정보 처리방침 페이지
|
|
107
225
|
app.get('/privacy', (c) => {
|
|
108
226
|
const baseUrl = new URL(c.req.url).origin;
|
|
@@ -110,14 +228,36 @@ app.get('/privacy', (c) => {
|
|
|
110
228
|
});
|
|
111
229
|
// GET API 엔드포인트 (MCP 미지원 에이전트용)
|
|
112
230
|
registerDaisoRoutes(app);
|
|
231
|
+
registerGs25Routes(app);
|
|
232
|
+
registerSevenElevenRoutes(app);
|
|
233
|
+
registerCuRoutes(app);
|
|
234
|
+
registerEmart24Routes(app);
|
|
235
|
+
registerLotteMartRoutes(app);
|
|
113
236
|
registerOliveyoungRoutes(app);
|
|
114
237
|
registerMegaboxRoutes(app);
|
|
238
|
+
registerLotteCinemaRoutes(app);
|
|
115
239
|
registerCgvRoutes(app);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
240
|
+
app.get('/api/actions/query', async (c) => {
|
|
241
|
+
try {
|
|
242
|
+
const targetUrl = buildActionQueryTargetUrl(c.req.url);
|
|
243
|
+
const headers = new Headers(c.req.raw.headers);
|
|
244
|
+
headers.delete('host');
|
|
245
|
+
return app.fetch(new Request(targetUrl.toString(), {
|
|
246
|
+
method: 'GET',
|
|
247
|
+
headers,
|
|
248
|
+
}), c.env);
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
252
|
+
return c.json({
|
|
253
|
+
success: false,
|
|
254
|
+
error: {
|
|
255
|
+
code: 'INVALID_ACTION_QUERY',
|
|
256
|
+
message,
|
|
257
|
+
},
|
|
258
|
+
}, 400);
|
|
259
|
+
}
|
|
122
260
|
});
|
|
261
|
+
// MCP 엔드포인트
|
|
262
|
+
app.all('/mcp', handleMcpRequest);
|
|
123
263
|
export default app;
|
package/dist/pages/openapi.js
CHANGED
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
* OpenAPI 페이지 엔트리
|
|
3
3
|
*/
|
|
4
4
|
export { generateOpenApiSpec } from './openapiSpec.js';
|
|
5
|
-
export {
|
|
5
|
+
export { generateFullOpenApiSpec } from './openapiFullSpec.js';
|
|
6
|
+
export { createOpenApiJsonResponse, createOpenApiYamlResponse, createFullOpenApiJsonResponse, createFullOpenApiYamlResponse, } from './openapiResponse.js';
|
|
6
7
|
export { __testOnlyJsonToYaml } from './openapiYaml.js';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 전체 OpenAPI 스펙 조합 모듈
|
|
3
|
+
*/
|
|
4
|
+
import { OPENAPI_PATHS_DAISO_OLIVEYOUNG } from './openapiSpecPathsDaisoOliveyoung.js';
|
|
5
|
+
import { OPENAPI_PATHS_CU } from './openapiSpecPathsCu.js';
|
|
6
|
+
import { OPENAPI_PATHS_EMART24 } from './openapiSpecPathsEmart24.js';
|
|
7
|
+
import { OPENAPI_PATHS_LOTTEMART } from './openapiSpecPathsLotteMart.js';
|
|
8
|
+
import { OPENAPI_PATHS_MEGABOX } from './openapiSpecPathsMegabox.js';
|
|
9
|
+
import { OPENAPI_PATHS_CGV } from './openapiSpecPathsCgv.js';
|
|
10
|
+
import { OPENAPI_PATHS_LOTTECINEMA } from './openapiSpecPathsLottecinema.js';
|
|
11
|
+
import { OPENAPI_PATHS_GS25 } from './openapiSpecPathsGs25.js';
|
|
12
|
+
import { OPENAPI_PATHS_SEVENELEVEN } from './openapiSpecPathsSeveneleven.js';
|
|
13
|
+
import { OPENAPI_COMPONENTS } from './openapiSpecComponents.js';
|
|
14
|
+
export function generateFullOpenApiSpec(baseUrl) {
|
|
15
|
+
return {
|
|
16
|
+
openapi: '3.1.0',
|
|
17
|
+
info: {
|
|
18
|
+
title: '다이소 MCP API (전체 스펙)',
|
|
19
|
+
description: `다이소 제품 검색, 매장 찾기, 재고 확인을 위한 API입니다.
|
|
20
|
+
|
|
21
|
+
## 주요 기능
|
|
22
|
+
- 🔍 **제품 검색**: 키워드로 다이소 제품 검색
|
|
23
|
+
- 🏪 **매장 찾기**: 지역/키워드로 매장 검색
|
|
24
|
+
- 📦 **재고 확인**: 온라인 및 오프라인 매장 재고 조회
|
|
25
|
+
- 🧭 **진열 위치 조회**: 매장 내 상품 구역/층 정보 조회
|
|
26
|
+
- 🏪 **GS25 매장/상품/재고 조회**: GS25 매장 탐색, 상품 검색, 재고 조회
|
|
27
|
+
- 7️⃣ **세븐일레븐 상품/매장/재고 조회**: 상품 검색, 매장 검색, 실재고 조회
|
|
28
|
+
- 🏪 **CU 매장/재고 조회**: CU 매장 탐색 및 상품 재고 검색
|
|
29
|
+
- 🎬 **메가박스 지점/영화 조회**: 주변 지점, 상영 목록, 잔여 좌석 조회
|
|
30
|
+
- 🎞️ **롯데시네마 지점/영화 조회**: 주변 지점, 상영 목록, 잔여 좌석 조회
|
|
31
|
+
- 🎥 **CGV 지점/영화 조회**: 극장 목록, 영화 목록, 시간표 조회
|
|
32
|
+
|
|
33
|
+
## 사용 팁
|
|
34
|
+
1. 한글 검색어는 URL 인코딩이 자동 처리됩니다
|
|
35
|
+
2. 재고 확인 시 먼저 제품 검색으로 제품 ID를 확인하세요
|
|
36
|
+
3. 위치 기반 재고 조회 시 lat, lng 파라미터를 활용하세요
|
|
37
|
+
4. 세븐일레븐 재고 조회는 keyword와 storeKeyword를 함께 사용하면 정확도가 높아집니다`,
|
|
38
|
+
version: '1.0.0',
|
|
39
|
+
contact: {
|
|
40
|
+
name: 'GitHub Repository',
|
|
41
|
+
url: 'https://github.com/hmmhmmhm/daiso-mcp',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
servers: [{ url: baseUrl, description: 'Production Server' }],
|
|
45
|
+
paths: {
|
|
46
|
+
...OPENAPI_PATHS_DAISO_OLIVEYOUNG,
|
|
47
|
+
...OPENAPI_PATHS_CU,
|
|
48
|
+
...OPENAPI_PATHS_EMART24,
|
|
49
|
+
...OPENAPI_PATHS_LOTTEMART,
|
|
50
|
+
...OPENAPI_PATHS_GS25,
|
|
51
|
+
...OPENAPI_PATHS_SEVENELEVEN,
|
|
52
|
+
...OPENAPI_PATHS_MEGABOX,
|
|
53
|
+
...OPENAPI_PATHS_LOTTECINEMA,
|
|
54
|
+
...OPENAPI_PATHS_CGV,
|
|
55
|
+
},
|
|
56
|
+
components: OPENAPI_COMPONENTS,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* OpenAPI 응답 생성 모듈
|
|
3
3
|
*/
|
|
4
4
|
import { generateOpenApiSpec } from './openapiSpec.js';
|
|
5
|
+
import { generateFullOpenApiSpec } from './openapiFullSpec.js';
|
|
5
6
|
import { jsonToYaml } from './openapiYaml.js';
|
|
6
7
|
/**
|
|
7
8
|
* OpenAPI 스펙 응답 생성 (JSON)
|
|
@@ -30,3 +31,30 @@ export function createOpenApiYamlResponse(baseUrl) {
|
|
|
30
31
|
},
|
|
31
32
|
});
|
|
32
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* 전체 OpenAPI 스펙 응답 생성 (JSON)
|
|
36
|
+
*/
|
|
37
|
+
export function createFullOpenApiJsonResponse(baseUrl) {
|
|
38
|
+
const spec = generateFullOpenApiSpec(baseUrl);
|
|
39
|
+
return new Response(JSON.stringify(spec, null, 2), {
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
42
|
+
'Cache-Control': 'public, max-age=3600',
|
|
43
|
+
'Access-Control-Allow-Origin': '*',
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 전체 OpenAPI 스펙 응답 생성 (YAML)
|
|
49
|
+
*/
|
|
50
|
+
export function createFullOpenApiYamlResponse(baseUrl) {
|
|
51
|
+
const spec = generateFullOpenApiSpec(baseUrl);
|
|
52
|
+
const yaml = jsonToYaml(spec);
|
|
53
|
+
return new Response(yaml, {
|
|
54
|
+
headers: {
|
|
55
|
+
'Content-Type': 'text/yaml; charset=utf-8',
|
|
56
|
+
'Cache-Control': 'public, max-age=3600',
|
|
57
|
+
'Access-Control-Allow-Origin': '*',
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -1,43 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpenAPI 스펙
|
|
2
|
+
* OpenAI Actions용 기본 OpenAPI 스펙 엔트리
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
import { OPENAPI_PATHS_MEGABOX } from './openapiSpecPathsMegabox.js';
|
|
6
|
-
import { OPENAPI_PATHS_CGV } from './openapiSpecPathsCgv.js';
|
|
7
|
-
import { OPENAPI_COMPONENTS } from './openapiSpecComponents.js';
|
|
8
|
-
/**
|
|
9
|
-
* OpenAPI 스펙 생성
|
|
10
|
-
*/
|
|
11
|
-
export function generateOpenApiSpec(baseUrl) {
|
|
12
|
-
return {
|
|
13
|
-
openapi: '3.1.0',
|
|
14
|
-
info: {
|
|
15
|
-
title: '다이소 MCP API',
|
|
16
|
-
description: `다이소 제품 검색, 매장 찾기, 재고 확인을 위한 API입니다.
|
|
17
|
-
|
|
18
|
-
## 주요 기능
|
|
19
|
-
- 🔍 **제품 검색**: 키워드로 다이소 제품 검색
|
|
20
|
-
- 🏪 **매장 찾기**: 지역/키워드로 매장 검색
|
|
21
|
-
- 📦 **재고 확인**: 온라인 및 오프라인 매장 재고 조회
|
|
22
|
-
- 🎬 **메가박스 지점/영화 조회**: 주변 지점, 상영 목록, 잔여 좌석 조회
|
|
23
|
-
- 🎥 **CGV 지점/영화 조회**: 극장 목록, 영화 목록, 시간표 조회
|
|
24
|
-
|
|
25
|
-
## 사용 팁
|
|
26
|
-
1. 한글 검색어는 URL 인코딩이 자동 처리됩니다
|
|
27
|
-
2. 재고 확인 시 먼저 제품 검색으로 제품 ID를 확인하세요
|
|
28
|
-
3. 위치 기반 재고 조회 시 lat, lng 파라미터를 활용하세요`,
|
|
29
|
-
version: '1.0.0',
|
|
30
|
-
contact: {
|
|
31
|
-
name: 'GitHub Repository',
|
|
32
|
-
url: 'https://github.com/hmmhmmhm/daiso-mcp',
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
servers: [{ url: baseUrl, description: 'Production Server' }],
|
|
36
|
-
paths: {
|
|
37
|
-
...OPENAPI_PATHS_DAISO_OLIVEYOUNG,
|
|
38
|
-
...OPENAPI_PATHS_MEGABOX,
|
|
39
|
-
...OPENAPI_PATHS_CGV,
|
|
40
|
-
},
|
|
41
|
-
components: OPENAPI_COMPONENTS,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
4
|
+
export { generateOpenApiSpec } from './openapiSpecActions.js';
|