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.
Files changed (166) hide show
  1. package/README.md +249 -533
  2. package/dist/api/actionsProxy.js +82 -0
  3. package/dist/api/cgvHandlers.js +103 -16
  4. package/dist/api/configStatus.js +19 -0
  5. package/dist/api/daisoHandlers.js +127 -0
  6. package/dist/api/emart24Handlers.js +176 -0
  7. package/dist/api/gs25Handlers.js +267 -0
  8. package/dist/api/handlers.js +209 -104
  9. package/dist/api/healthChecks.js +504 -0
  10. package/dist/api/lottecinemaHandlers.js +155 -0
  11. package/dist/api/lottemartHandlers.js +144 -0
  12. package/dist/api/megaboxHandlers.js +81 -50
  13. package/dist/api/response.js +2 -0
  14. package/dist/api/routes/cuRoutes.js +17 -0
  15. package/dist/api/routes/daisoRoutes.js +6 -1
  16. package/dist/api/routes/emart24Routes.js +22 -0
  17. package/dist/api/routes/gs25Routes.js +22 -0
  18. package/dist/api/routes/healthRoutes.js +61 -0
  19. package/dist/api/routes/lottecinemaRoutes.js +22 -0
  20. package/dist/api/routes/lottemartRoutes.js +18 -0
  21. package/dist/api/routes/oliveyoungRoutes.js +6 -1
  22. package/dist/api/routes/sevenelevenRoutes.js +32 -0
  23. package/dist/api/sevenelevenHandlers.js +200 -0
  24. package/dist/bin.js +0 -0
  25. package/dist/cli/args.js +75 -0
  26. package/dist/cli/commandOptions.js +38 -0
  27. package/dist/cli/commands/convenience.js +293 -0
  28. package/dist/cli/commands/daiso.js +112 -0
  29. package/dist/cli/constants.js +5 -0
  30. package/dist/cli/deps.js +48 -0
  31. package/dist/cli/http.js +156 -0
  32. package/dist/cli/interactiveFetch.js +31 -0
  33. package/dist/cli/interactiveItemSearch.js +261 -0
  34. package/dist/cli/interactivePrompt.js +50 -0
  35. package/dist/cli/interactiveTypes.js +4 -0
  36. package/dist/cli/types.js +4 -0
  37. package/dist/cli.js +87 -196
  38. package/dist/cliHelp.js +212 -1
  39. package/dist/cliInteractive.js +243 -0
  40. package/dist/cliPicker.js +57 -0
  41. package/dist/cliRenderer.js +90 -3
  42. package/dist/core/errors.js +66 -0
  43. package/dist/core/registry.js +227 -3
  44. package/dist/index.js +149 -9
  45. package/dist/pages/openapi.js +2 -1
  46. package/dist/pages/openapiFullSpec.js +58 -0
  47. package/dist/pages/openapiResponse.js +28 -0
  48. package/dist/pages/openapiSpec.js +2 -41
  49. package/dist/pages/openapiSpecActions.js +109 -0
  50. package/dist/pages/openapiSpecComponents.js +30 -391
  51. package/dist/pages/openapiSpecComponentsCgv.js +24 -0
  52. package/dist/pages/openapiSpecComponentsCu.js +112 -0
  53. package/dist/pages/openapiSpecComponentsDaisoOliveyoung.js +342 -0
  54. package/dist/pages/openapiSpecComponentsEmart24.js +141 -0
  55. package/dist/pages/openapiSpecComponentsGs25.js +109 -0
  56. package/dist/pages/openapiSpecComponentsLotteMart.js +85 -0
  57. package/dist/pages/openapiSpecComponentsLottecinema.js +163 -0
  58. package/dist/pages/openapiSpecComponentsMegabox.js +144 -0
  59. package/dist/pages/openapiSpecComponentsSeveneleven.js +82 -0
  60. package/dist/pages/openapiSpecPathsCgv.js +66 -3
  61. package/dist/pages/openapiSpecPathsCu.js +145 -0
  62. package/dist/pages/openapiSpecPathsDaiso.js +302 -0
  63. package/dist/pages/openapiSpecPathsDaisoOliveyoung.js +4 -376
  64. package/dist/pages/openapiSpecPathsEmart24.js +223 -0
  65. package/dist/pages/openapiSpecPathsGs25.js +166 -0
  66. package/dist/pages/openapiSpecPathsLotteMart.js +149 -0
  67. package/dist/pages/openapiSpecPathsLottecinema.js +212 -0
  68. package/dist/pages/openapiSpecPathsMegabox.js +52 -3
  69. package/dist/pages/openapiSpecPathsOliveyoung.js +266 -0
  70. package/dist/pages/openapiSpecPathsSeveneleven.js +234 -0
  71. package/dist/pages/prompt.js +8 -402
  72. package/dist/pages/promptCoreText.js +278 -0
  73. package/dist/pages/promptReferenceText.js +137 -0
  74. package/dist/pages/promptRetailCinemaText.js +274 -0
  75. package/dist/services/cgv/client.js +6 -1
  76. package/dist/services/cgv/index.js +3 -3
  77. package/dist/services/cgv/location.js +235 -0
  78. package/dist/services/cgv/tools/findTheaters.js +30 -5
  79. package/dist/services/cgv/tools/getTimetable.js +42 -14
  80. package/dist/services/cgv/tools/searchMovies.js +40 -12
  81. package/dist/services/cgv/transport.js +0 -3
  82. package/dist/services/cu/api.js +11 -0
  83. package/dist/services/cu/client.js +297 -0
  84. package/dist/services/cu/index.js +21 -0
  85. package/dist/services/cu/tools/checkInventory.js +123 -0
  86. package/dist/services/cu/tools/findNearbyStores.js +49 -0
  87. package/dist/services/cu/types.js +4 -0
  88. package/dist/services/daiso/api.js +22 -2
  89. package/dist/services/daiso/client.js +67 -0
  90. package/dist/services/daiso/index.js +4 -0
  91. package/dist/services/daiso/product.js +17 -0
  92. package/dist/services/daiso/tools/checkInventory.js +80 -43
  93. package/dist/services/daiso/tools/findInventoryByName.js +222 -0
  94. package/dist/services/daiso/tools/findStores.js +17 -7
  95. package/dist/services/daiso/tools/getDisplayLocation.js +75 -0
  96. package/dist/services/emart24/api.js +11 -0
  97. package/dist/services/emart24/client.js +182 -0
  98. package/dist/services/emart24/index.js +22 -0
  99. package/dist/services/emart24/inventoryLookup.js +134 -0
  100. package/dist/services/emart24/tools/checkInventory.js +54 -0
  101. package/dist/services/emart24/tools/findNearbyStores.js +72 -0
  102. package/dist/services/emart24/tools/searchProducts.js +73 -0
  103. package/dist/services/emart24/types.js +4 -0
  104. package/dist/services/gs25/api.js +9 -0
  105. package/dist/services/gs25/client.js +208 -0
  106. package/dist/services/gs25/index.js +22 -0
  107. package/dist/services/gs25/storeUtils.js +168 -0
  108. package/dist/services/gs25/tools/checkInventory.js +163 -0
  109. package/dist/services/gs25/tools/findNearbyStores.js +103 -0
  110. package/dist/services/gs25/tools/searchProducts.js +57 -0
  111. package/dist/services/gs25/types.js +4 -0
  112. package/dist/services/lottecinema/api.js +12 -0
  113. package/dist/services/lottecinema/client.js +161 -0
  114. package/dist/services/lottecinema/index.js +30 -0
  115. package/dist/services/lottecinema/location.js +191 -0
  116. package/dist/services/lottecinema/tools/findNearbyTheaters.js +47 -0
  117. package/dist/services/lottecinema/tools/getRemainingSeats.js +85 -0
  118. package/dist/services/lottecinema/tools/listNowShowing.js +79 -0
  119. package/dist/services/lottecinema/types.js +4 -0
  120. package/dist/services/lottemart/api.js +31 -0
  121. package/dist/services/lottemart/client.js +254 -0
  122. package/dist/services/lottemart/clientTypes.js +4 -0
  123. package/dist/services/lottemart/config.js +4 -0
  124. package/dist/services/lottemart/debug.js +92 -0
  125. package/dist/services/lottemart/geocode.js +49 -0
  126. package/dist/services/lottemart/index.js +30 -0
  127. package/dist/services/lottemart/keyword.js +29 -0
  128. package/dist/services/lottemart/parser.js +185 -0
  129. package/dist/services/lottemart/session.js +331 -0
  130. package/dist/services/lottemart/storeSearch.js +52 -0
  131. package/dist/services/lottemart/tools/findNearbyStores.js +67 -0
  132. package/dist/services/lottemart/tools/searchProducts.js +69 -0
  133. package/dist/services/lottemart/types.js +4 -0
  134. package/dist/services/lottemart/zetta.js +105 -0
  135. package/dist/services/megabox/location.js +219 -0
  136. package/dist/services/megabox/tools/findNearbyTheaters.js +13 -44
  137. package/dist/services/megabox/tools/getRemainingSeats.js +31 -3
  138. package/dist/services/megabox/tools/listNowShowing.js +31 -3
  139. package/dist/services/oliveyoung/api.js +2 -0
  140. package/dist/services/oliveyoung/client.js +225 -13
  141. package/dist/services/oliveyoung/index.js +3 -1
  142. package/dist/services/oliveyoung/tools/checkInventory.js +21 -5
  143. package/dist/services/oliveyoung/tools/searchProducts.js +57 -0
  144. package/dist/services/seveneleven/api.js +15 -0
  145. package/dist/services/seveneleven/client.js +286 -0
  146. package/dist/services/seveneleven/index.js +30 -0
  147. package/dist/services/seveneleven/inventory.js +302 -0
  148. package/dist/services/seveneleven/productKeyword.js +141 -0
  149. package/dist/services/seveneleven/tools/checkInventory.js +82 -0
  150. package/dist/services/seveneleven/tools/getCatalogSnapshot.js +55 -0
  151. package/dist/services/seveneleven/tools/getSearchPopwords.js +41 -0
  152. package/dist/services/seveneleven/tools/searchProducts.js +67 -0
  153. package/dist/services/seveneleven/tools/searchStores.js +45 -0
  154. package/dist/services/seveneleven/types.js +4 -0
  155. package/dist/utils/cliInteractiveHelpers.js +63 -0
  156. package/dist/utils/daisoKeyword.js +30 -0
  157. package/dist/utils/http.js +98 -12
  158. package/dist/utils/zyte.js +47 -26
  159. package/openapi.json +1979 -736
  160. package/openapi.yaml +1428 -541
  161. package/package.json +49 -18
  162. package/dist/tools/checkInventory.js +0 -98
  163. package/dist/tools/findStores.js +0 -125
  164. package/dist/tools/getPriceInfo.js +0 -77
  165. package/dist/tools/searchProducts.js +0 -61
  166. package/dist/types/index.js +0 -4
@@ -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
- server.registerTool(tool.name, tool.metadata, async (args) => {
77
- const result = await tool.handler(args);
78
- return {
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: ['Content-Type', 'mcp-session-id', 'Last-Event-ID', 'mcp-protocol-version'],
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
- // MCP 엔드포인트
117
- app.all('/mcp', async (c) => {
118
- const transport = new WebStandardStreamableHTTPServerTransport();
119
- const server = createMcpServer(c.env);
120
- await server.connect(transport);
121
- return transport.handleRequest(c.req.raw);
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;
@@ -2,5 +2,6 @@
2
2
  * OpenAPI 페이지 엔트리
3
3
  */
4
4
  export { generateOpenApiSpec } from './openapiSpec.js';
5
- export { createOpenApiJsonResponse, createOpenApiYamlResponse } from './openapiResponse.js';
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
- import { OPENAPI_PATHS_DAISO_OLIVEYOUNG } from './openapiSpecPathsDaisoOliveyoung.js';
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';