mcd-mcp-app 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/dist/index.html +270 -0
- package/dist/server.js +478 -0
- package/package.json +50 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
// server.ts
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import {
|
|
5
|
+
registerAppTool,
|
|
6
|
+
registerAppResource
|
|
7
|
+
} from "@modelcontextprotocol/ext-apps/server";
|
|
8
|
+
import { createUIResource } from "@mcp-ui/server";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import fs from "fs/promises";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
|
|
14
|
+
// src/services/mcdonalds.ts
|
|
15
|
+
import axios from "axios";
|
|
16
|
+
|
|
17
|
+
// src/services/parsers.ts
|
|
18
|
+
function parseCampaignText(text) {
|
|
19
|
+
const events = [];
|
|
20
|
+
const dayChunks = text.split(/####\s+/).slice(1);
|
|
21
|
+
for (const chunk of dayChunks) {
|
|
22
|
+
const firstLineEnd = chunk.indexOf("\n");
|
|
23
|
+
const headerLine = chunk.substring(0, firstLineEnd).trim();
|
|
24
|
+
const [datePart, ...statusParts] = headerLine.split(" ");
|
|
25
|
+
const statusTag = statusParts.join(" ");
|
|
26
|
+
let status = "future";
|
|
27
|
+
if (statusTag.includes("\u5F80\u671F")) status = "past";
|
|
28
|
+
else if (statusTag.includes("\u4ECA\u65E5") || statusTag.includes("\u8FDB\u884C\u4E2D"))
|
|
29
|
+
status = "ongoing";
|
|
30
|
+
const itemChunks = chunk.split(/-\s+\*\*活动标题\*\*/).slice(1);
|
|
31
|
+
for (const itemChunk of itemChunks) {
|
|
32
|
+
let title = "";
|
|
33
|
+
const titleMatch = itemChunk.match(/^:(.*?)(?:\\|\n)/);
|
|
34
|
+
if (titleMatch) {
|
|
35
|
+
title = titleMatch[1].trim();
|
|
36
|
+
}
|
|
37
|
+
let description = "";
|
|
38
|
+
const descMatch = itemChunk.match(
|
|
39
|
+
/\*\*活动内容介绍\*\*:([\s\S]*?)(?=\*\*活动图片介绍\*\*)/
|
|
40
|
+
);
|
|
41
|
+
if (descMatch) {
|
|
42
|
+
description = descMatch[1].trim();
|
|
43
|
+
if (description.endsWith("\\")) {
|
|
44
|
+
description = description.slice(0, -1).trim();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
let imageUrl = "";
|
|
48
|
+
const imgMatch = itemChunk.match(/<img src="\s*([^"]+)\s*"/);
|
|
49
|
+
if (imgMatch) {
|
|
50
|
+
imageUrl = imgMatch[1].trim();
|
|
51
|
+
}
|
|
52
|
+
events.push({
|
|
53
|
+
id: `enevt-${Date.now()}`,
|
|
54
|
+
date: datePart,
|
|
55
|
+
title,
|
|
56
|
+
description,
|
|
57
|
+
status,
|
|
58
|
+
imageUrl: imageUrl || void 0
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return events;
|
|
63
|
+
}
|
|
64
|
+
function parseCouponsText(text) {
|
|
65
|
+
const coupons = [];
|
|
66
|
+
const parts = text.split(/-\s+优惠券标题[::]\s*/);
|
|
67
|
+
for (let i = 1; i < parts.length; i++) {
|
|
68
|
+
const chunk = parts[i].trim();
|
|
69
|
+
if (!chunk) continue;
|
|
70
|
+
let title = "Unknown Coupon";
|
|
71
|
+
const statusIdx = chunk.indexOf("\u72B6\u6001\uFF1A");
|
|
72
|
+
if (statusIdx > 0) {
|
|
73
|
+
title = chunk.substring(0, statusIdx).trim();
|
|
74
|
+
} else {
|
|
75
|
+
const statusIdx2 = chunk.indexOf("\u72B6\u6001:");
|
|
76
|
+
if (statusIdx2 > 0) {
|
|
77
|
+
title = chunk.substring(0, statusIdx2).trim();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
let status = "unclaimed";
|
|
81
|
+
const statusMatch = chunk.match(/状态[::]\s*(.*?)(?:\s+优惠券图片[::]|\\|\n|$)/);
|
|
82
|
+
if (statusMatch) {
|
|
83
|
+
const statusText = statusMatch[1].trim();
|
|
84
|
+
const statusMap = {
|
|
85
|
+
"\u5DF2\u9886\u53D6": "claimed",
|
|
86
|
+
"\u672A\u9886\u53D6": "unclaimed",
|
|
87
|
+
"\u4E0D\u53EF\u9886\u53D6": "unavailable"
|
|
88
|
+
};
|
|
89
|
+
status = statusMap[statusText] || "unclaimed";
|
|
90
|
+
}
|
|
91
|
+
let imageUrl = "";
|
|
92
|
+
const imgMatch = chunk.match(/<img[^>]+src\s*=\s*["']([^"']+)["']/i);
|
|
93
|
+
if (imgMatch) {
|
|
94
|
+
imageUrl = imgMatch[1].trim();
|
|
95
|
+
}
|
|
96
|
+
let discountPrice;
|
|
97
|
+
const priceMatch = title.match(/(\d+(?:\.\d+)?)\s*元/);
|
|
98
|
+
if (priceMatch) {
|
|
99
|
+
discountPrice = parseFloat(priceMatch[1]);
|
|
100
|
+
}
|
|
101
|
+
coupons.push({
|
|
102
|
+
id: `coupon-${Date.now()}-${i}`,
|
|
103
|
+
title,
|
|
104
|
+
imageUrl,
|
|
105
|
+
status,
|
|
106
|
+
...discountPrice !== void 0 && { discountPrice }
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return coupons;
|
|
110
|
+
}
|
|
111
|
+
function parseMyCouponsText(text) {
|
|
112
|
+
const couponBlocks = text.split(/(?=##\s)/).filter((block) => block.trim().startsWith("##"));
|
|
113
|
+
const myCoupons = couponBlocks.map((block, index) => {
|
|
114
|
+
const titleMatch = block.match(/##\s*(?:\*\*)?\s*(.+?)(?:\n|$)/);
|
|
115
|
+
const title = titleMatch ? titleMatch[1].replace(/\*\*/g, "").trim() : "";
|
|
116
|
+
const priceMatch = block.match(/[¥¥]\s*(\d+(?:\.\d+)?)/);
|
|
117
|
+
const price = priceMatch ? `\xA5${priceMatch[1]}` : "";
|
|
118
|
+
const validityMatch = block.match(
|
|
119
|
+
/(?:^|\n)\s*(?:-\s+)?(?:\*\*)?\s*(?:有效期|使用期限)\s*(?:\*\*)?\s*[::]\s*(\d{4}-\d{2}-\d{2})\s*(\d{2}:\d{2})[-~]\s*(\d{4}-\d{2}-\d{2})\s*(\d{2}:\d{2})/
|
|
120
|
+
);
|
|
121
|
+
let validStart = "";
|
|
122
|
+
let validEnd = "";
|
|
123
|
+
if (validityMatch) {
|
|
124
|
+
validStart = `${validityMatch[1]} ${validityMatch[2]}`;
|
|
125
|
+
validEnd = `${validityMatch[3]} ${validityMatch[4]}`;
|
|
126
|
+
}
|
|
127
|
+
const receivedMatch = block.match(
|
|
128
|
+
/(?:^|\n)\s*(?:-\s+)?(?:\*\*)?\s*领取时间\s*(?:\*\*)?\s*[::]\s*(.+?)(?:\n|$)/
|
|
129
|
+
);
|
|
130
|
+
const receivedAt = receivedMatch ? receivedMatch[1].trim() : "";
|
|
131
|
+
const tagsMatch = block.match(
|
|
132
|
+
/(?:^|\n)\s*(?:-\s+)?(?:\*\*)?\s*标签\s*(?:\*\*)?\s*[::]\s*(.+?)(?:\n|$)/
|
|
133
|
+
);
|
|
134
|
+
const tags = tagsMatch ? tagsMatch[1].split(/[,,、]\s*/).filter(Boolean) : [];
|
|
135
|
+
const imgMatch = block.match(/<img[^>]+src\s*=\s*["']([^"']+)["']/i);
|
|
136
|
+
const imageUrl = imgMatch ? imgMatch[1].trim() : "";
|
|
137
|
+
return {
|
|
138
|
+
id: `myCoupon-${Date.now()}-${index}`,
|
|
139
|
+
title,
|
|
140
|
+
price,
|
|
141
|
+
validStart,
|
|
142
|
+
validEnd,
|
|
143
|
+
receivedAt,
|
|
144
|
+
tags,
|
|
145
|
+
imageUrl
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
return myCoupons;
|
|
149
|
+
}
|
|
150
|
+
function parseClaimResultText(markdown) {
|
|
151
|
+
const totalMatch = markdown.match(/\*\*总计\*\*[::]\s*(\d+)/);
|
|
152
|
+
const successMatch = markdown.match(/\*\*成功\*\*[::]\s*(\d+)/);
|
|
153
|
+
const failedMatch = markdown.match(/\*\*失败\*\*[::]\s*(\d+)/);
|
|
154
|
+
const total = totalMatch ? parseInt(totalMatch[1], 10) : 0;
|
|
155
|
+
const success = successMatch ? parseInt(successMatch[1], 10) : 0;
|
|
156
|
+
const failed = failedMatch ? parseInt(failedMatch[1], 10) : 0;
|
|
157
|
+
const coupons = [];
|
|
158
|
+
const couponBlocks = markdown.split(/\n\s*- \*\*/).slice(1);
|
|
159
|
+
for (const block of couponBlocks) {
|
|
160
|
+
const titleMatch = block.match(/^(.+?)\*\*/);
|
|
161
|
+
const title = titleMatch ? titleMatch[1].trim() : "";
|
|
162
|
+
const couponIdMatch = block.match(/couponId[::]\s*([A-Z0-9]+)/i);
|
|
163
|
+
const couponId = couponIdMatch ? couponIdMatch[1] : "";
|
|
164
|
+
const couponCodeMatch = block.match(/couponCode[::]\s*([A-Z0-9]+)/i);
|
|
165
|
+
const couponCode = couponCodeMatch ? couponCodeMatch[1] : "";
|
|
166
|
+
const imageMatch = block.match(/<img[^>]+src=["']([^"']+)["']/);
|
|
167
|
+
const imageUrl = imageMatch ? imageMatch[1].trim() : "";
|
|
168
|
+
if (title && couponId) {
|
|
169
|
+
coupons.push({
|
|
170
|
+
title,
|
|
171
|
+
couponId,
|
|
172
|
+
couponCode,
|
|
173
|
+
imageUrl
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
total,
|
|
179
|
+
success,
|
|
180
|
+
failed,
|
|
181
|
+
coupons
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/services/mcdonalds.ts
|
|
186
|
+
var MCP_SERVER_URL = "https://mcp.mcd.cn";
|
|
187
|
+
var MCPClient = class {
|
|
188
|
+
axiosInstance;
|
|
189
|
+
constructor() {
|
|
190
|
+
this.axiosInstance = axios.create({
|
|
191
|
+
baseURL: MCP_SERVER_URL,
|
|
192
|
+
headers: {
|
|
193
|
+
Authorization: process.env.Authorization,
|
|
194
|
+
"Content-Type": "application/json"
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
async callTool(toolName, arguments_ = {}) {
|
|
199
|
+
const request = {
|
|
200
|
+
jsonrpc: "2.0",
|
|
201
|
+
id: Date.now(),
|
|
202
|
+
method: "tools/call",
|
|
203
|
+
params: {
|
|
204
|
+
name: toolName,
|
|
205
|
+
arguments: arguments_
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
try {
|
|
209
|
+
const response = await this.axiosInstance.post("", request);
|
|
210
|
+
if (response.data.error) {
|
|
211
|
+
throw new Error(response.data.error.message || "\u8BF7\u6C42\u5931\u8D25");
|
|
212
|
+
}
|
|
213
|
+
if (response.data.result) {
|
|
214
|
+
if (typeof response.data.result === "object" && response.data.result.content) {
|
|
215
|
+
const content = response.data.result.content;
|
|
216
|
+
if (Array.isArray(content) && content.length > 0 && content[0].type === "text") {
|
|
217
|
+
try {
|
|
218
|
+
const parsed = JSON.parse(content[0].text);
|
|
219
|
+
if (parsed && parsed.data) {
|
|
220
|
+
return parsed.data;
|
|
221
|
+
}
|
|
222
|
+
return parsed;
|
|
223
|
+
} catch (e) {
|
|
224
|
+
console.warn("Failed to parse tool result content JSON:", e);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return response.data.result;
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
content: Array.isArray(response.data.result) ? response.data.result : [{ type: "text", text: JSON.stringify(response.data.result) }]
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return response.data;
|
|
234
|
+
} catch (error) {
|
|
235
|
+
if (error.code === "ERR_NETWORK" || error.message === "Network Error") {
|
|
236
|
+
console.warn(
|
|
237
|
+
"Network error, falling back to mock implementation for demo purposes."
|
|
238
|
+
);
|
|
239
|
+
throw new Error("\u7F51\u7EDC\u8FDE\u63A5\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u8FDE\u63A5");
|
|
240
|
+
}
|
|
241
|
+
if (error.response) {
|
|
242
|
+
if (error.response.status === 429) {
|
|
243
|
+
throw new Error("\u8BF7\u6C42\u9891\u7387\u8FC7\u9AD8\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\uFF08\u6BCF\u5206\u949F\u6700\u591A 600 \u6B21\u8BF7\u6C42\uFF09");
|
|
244
|
+
}
|
|
245
|
+
if (error.response.status === 401) {
|
|
246
|
+
throw new Error("Token \u65E0\u6548\u6216\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8BBE\u7F6E");
|
|
247
|
+
}
|
|
248
|
+
if (error.response.status === 403) {
|
|
249
|
+
throw new Error("Token \u6743\u9650\u4E0D\u8DB3");
|
|
250
|
+
}
|
|
251
|
+
if (error.response.status === 404) {
|
|
252
|
+
throw new Error("API \u7AEF\u70B9\u4E0D\u5B58\u5728\uFF0C\u8BF7\u68C0\u67E5 MCP Server \u5730\u5740\u662F\u5426\u6B63\u786E");
|
|
253
|
+
}
|
|
254
|
+
if (error.response.status === 500) {
|
|
255
|
+
throw new Error("\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
|
|
256
|
+
}
|
|
257
|
+
throw new Error(
|
|
258
|
+
`\u8BF7\u6C42\u5931\u8D25 (${error.response.status}): ${error.response.data?.message || error.response.statusText || "\u672A\u77E5\u9519\u8BEF"}`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
if (error.message) {
|
|
262
|
+
throw new Error(error.message);
|
|
263
|
+
}
|
|
264
|
+
throw new Error("\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u8FDE\u63A5\u548C MCP Server \u5730\u5740");
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
//活动日历查询工具
|
|
268
|
+
async getCampaignCalendar(specifiedDate) {
|
|
269
|
+
const result = await this.callTool("campaign-calender", { specifiedDate });
|
|
270
|
+
if (result && result.content && Array.isArray(result.content) && result.content[0]?.text) {
|
|
271
|
+
const text = result.content[0].text;
|
|
272
|
+
if (typeof text === "string" && text.includes("### \u6D3B\u52A8\u5217\u8868")) {
|
|
273
|
+
return parseCampaignText(text);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
// 麦麦省券列表查询
|
|
279
|
+
async getAvailableCoupons() {
|
|
280
|
+
const result = await this.callTool("available-coupons", {});
|
|
281
|
+
if (result && result.content && Array.isArray(result.content) && result.content[0]?.text) {
|
|
282
|
+
const text = result.content[0].text;
|
|
283
|
+
if (typeof text === "string" && text.includes("\u4F18\u60E0\u5238\u5217\u8868")) {
|
|
284
|
+
return parseCouponsText(text);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
// 麦麦省一键领券
|
|
290
|
+
async autoBindCoupons() {
|
|
291
|
+
const result = await this.callTool("auto-bind-coupons", {});
|
|
292
|
+
if (result && result.content && Array.isArray(result.content) && result.content[0]?.text) {
|
|
293
|
+
const text = result.content[0].text;
|
|
294
|
+
if (typeof text === "string" && text.includes("\u4F18\u60E0\u5238\u5217\u8868")) {
|
|
295
|
+
return parseClaimResultText(text);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
300
|
+
// 我的优惠券查询
|
|
301
|
+
async getMyCoupons() {
|
|
302
|
+
const result = await this.callTool("my-coupons", {});
|
|
303
|
+
if (result && result.content && Array.isArray(result.content) && result.content[0]?.text) {
|
|
304
|
+
const text = result.content[0].text;
|
|
305
|
+
if (typeof text === "string" && text.includes("# \u60A8\u7684\u4F18\u60E0\u5238\u5217\u8868")) {
|
|
306
|
+
return parseMyCouponsText(text);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
var McDonaldsService = new MCPClient();
|
|
313
|
+
|
|
314
|
+
// server.ts
|
|
315
|
+
if (!process.env.Authorization) {
|
|
316
|
+
throw new Error("\u8BF7\u5148\u8BBE\u7F6E MCP Token");
|
|
317
|
+
}
|
|
318
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
319
|
+
var server = new McpServer({
|
|
320
|
+
name: "mcd-mcp-app",
|
|
321
|
+
version: "0.0.1"
|
|
322
|
+
});
|
|
323
|
+
registerAppTool(
|
|
324
|
+
server,
|
|
325
|
+
"campaign-calender",
|
|
326
|
+
{
|
|
327
|
+
title: "\u6D3B\u52A8\u65E5\u5386\u67E5\u8BE2\u5DE5\u5177",
|
|
328
|
+
description: `\u67E5\u8BE2\u9EA6\u5F53\u52B3\u4E2D\u56FD\u5F53\u6708\u7684\u8425\u9500\u6D3B\u52A8\u65E5\u5386\uFF0C\u8FD4\u56DE\u8FDB\u884C\u4E2D\u3001\u5F80\u671F\u548C\u672A\u6765\u65E5\u671F\u7684\u6D3B\u52A8\u3002
|
|
329
|
+
\u9002\u7528\u4E8E\u67E5\u770B\u7528\u6237\u8FDB\u884C\u4E2D\u548C\u5373\u5C06\u5230\u6765\u7684\u53EF\u53C2\u4E0E\u6D3B\u52A8\uFF0C\u8FD8\u53EF\u67E5\u8BE2\u7528\u6237\u5BF9\u6D3B\u52A8\u7684\u8BA2\u9605\u72B6\u6001\u3002`,
|
|
330
|
+
inputSchema: {
|
|
331
|
+
specifiedDate: z.string().optional().describe(
|
|
332
|
+
"\u67E5\u8BE2\u6307\u5B9A\u65E5\u671F\u8303\u56F4\u7684\u6D3B\u52A8(\u683C\u5F0F: yyyy-MM-dd)\uFF0C\u8FD4\u56DE\u8BE5\u65E5\u671F\u9644\u8FD1\u4E00\u5171\u4E09\u5929\u7684\u6D3B\u52A8\uFF0C\u975E\u5FC5\u4F20\uFF0C\u9ED8\u8BA4\u67E5\u8BE2\u5F53\u524D\u6708\u7684\u6D3B\u52A8\uFF0C\u67E5\u8BE2\u4ECA\u5929\u7684\u6D3B\u52A8\u4E0D\u9700\u8981\u5165\u53C2"
|
|
333
|
+
)
|
|
334
|
+
},
|
|
335
|
+
_meta: {
|
|
336
|
+
ui: { resourceUri: "ui://mcdonalds/home" }
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
async ({ specifiedDate }) => {
|
|
340
|
+
const events = await McDonaldsService.getCampaignCalendar(specifiedDate);
|
|
341
|
+
return {
|
|
342
|
+
content: [
|
|
343
|
+
{
|
|
344
|
+
type: "text",
|
|
345
|
+
text: JSON.stringify({ view: "calendar", data: events })
|
|
346
|
+
}
|
|
347
|
+
]
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
);
|
|
351
|
+
registerAppTool(
|
|
352
|
+
server,
|
|
353
|
+
"available-coupons",
|
|
354
|
+
{
|
|
355
|
+
title: "\u9EA6\u9EA6\u7701\u5238\u5217\u8868\u67E5\u8BE2",
|
|
356
|
+
description: `\u67E5\u8BE2\u7528\u6237\u5F53\u524D\u53EF\u9886\u53D6\u7684\u9EA6\u9EA6\u7701\u7684\u4F18\u60E0\u5238\u5217\u8868\u3002
|
|
357
|
+
\u8FD4\u56DE\u5238\u540D\u79F0\u3001\u56FE\u7247\u3001\u72B6\u6001\u548C\u4FC3\u9500\u6807\u7B7E\u3002\u5F53\u7528\u6237\u8BE2\u95EE\u6709\u4EC0\u4E48\u4F18\u60E0\u3001\u53EF\u4EE5\u9886\u4EC0\u4E48\u5238\u65F6\u4F7F\u7528\u6B64\u5DE5\u5177\u3002`,
|
|
358
|
+
inputSchema: {},
|
|
359
|
+
_meta: {
|
|
360
|
+
ui: { resourceUri: "ui://mcdonalds/home" }
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
async () => {
|
|
364
|
+
const coupons = await McDonaldsService.getAvailableCoupons();
|
|
365
|
+
return {
|
|
366
|
+
content: [
|
|
367
|
+
{
|
|
368
|
+
type: "text",
|
|
369
|
+
text: JSON.stringify({ view: "available-coupons", data: coupons })
|
|
370
|
+
}
|
|
371
|
+
]
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
);
|
|
375
|
+
registerAppTool(
|
|
376
|
+
server,
|
|
377
|
+
"auto-bind-coupons",
|
|
378
|
+
{
|
|
379
|
+
title: "\u9EA6\u9EA6\u7701\u4E00\u952E\u9886\u5238",
|
|
380
|
+
description: `\u81EA\u52A8\u9886\u53D6\u9EA6\u9EA6\u7701\u6240\u6709\u5F53\u524D\u53EF\u7528\u7684\u9EA6\u5F53\u52B3\u4F18\u60E0\u5238\u3002\u65E0\u9700\u6307\u5B9A\u5177\u4F53\u7684\u4F18\u60E0\u5238\u548CcouponId\uFF0C\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u9886\u53D6\u7528\u6237\u53EF\u9886\u7684\u6240\u6709\u5238\u3002
|
|
381
|
+
\u5F53\u7528\u6237\u8BF4"\u5E2E\u6211\u9886\u5238"\u3001"\u81EA\u52A8\u9886\u53D6\u4F18\u60E0\u5238"\u3001"\u4E00\u952E\u9886\u5238"\u65F6\u4F7F\u7528\u6B64\u5DE5\u5177\u3002`,
|
|
382
|
+
inputSchema: {},
|
|
383
|
+
_meta: {
|
|
384
|
+
ui: { resourceUri: "ui://mcdonalds/home" }
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
async () => {
|
|
388
|
+
const result = await McDonaldsService.autoBindCoupons();
|
|
389
|
+
return {
|
|
390
|
+
content: [
|
|
391
|
+
{
|
|
392
|
+
type: "text",
|
|
393
|
+
text: JSON.stringify({
|
|
394
|
+
view: "bind-coupons",
|
|
395
|
+
data: result
|
|
396
|
+
})
|
|
397
|
+
}
|
|
398
|
+
]
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
);
|
|
402
|
+
registerAppTool(
|
|
403
|
+
server,
|
|
404
|
+
"my-coupons",
|
|
405
|
+
{
|
|
406
|
+
title: "\u6211\u7684\u4F18\u60E0\u5238\u67E5\u8BE2",
|
|
407
|
+
description: `\u67E5\u8BE2\u6211\u6709\u54EA\u4E9B\u53EF\u7528\u7684\u4F18\u60E0\u5238\u3002\u5C31\u50CF\u6253\u5F00\u9EA6\u5F53\u52B3APP\u7684"\u6211\u7684\u4F18\u60E0\u5238"\u9875\u9762\uFF0C\u80FD\u770B\u5230\u6240\u6709\u53EF\u4EE5\u7528\u6765\u70B9\u9910\u7684\u4F18\u60E0\u5238\u5217\u8868\u3002
|
|
408
|
+
\u5305\u62EC\u4F46\u4E0D\u9650\u4E8E\u4F7F\u7528\u573A\u666F\uFF1A
|
|
409
|
+
- \u7528\u6237\u60F3\u77E5\u9053\u81EA\u5DF1\u6709\u54EA\u4E9B\u4F18\u60E0\u5238\u53EF\u4EE5\u7528
|
|
410
|
+
- \u68C0\u67E5\u4F18\u60E0\u5238\u7684\u6709\u6548\u671F\u548C\u4F7F\u7528\u6761\u4EF6
|
|
411
|
+
- \u67E5\u770B\u4F18\u60E0\u5238\u6570\u91CF\u548C\u72B6\u6001`,
|
|
412
|
+
inputSchema: {},
|
|
413
|
+
_meta: {
|
|
414
|
+
ui: { resourceUri: "ui://mcdonalds/home" }
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
async () => {
|
|
418
|
+
const coupons = await McDonaldsService.getMyCoupons();
|
|
419
|
+
return {
|
|
420
|
+
content: [
|
|
421
|
+
{
|
|
422
|
+
type: "text",
|
|
423
|
+
text: JSON.stringify({ view: "my-coupons", data: coupons })
|
|
424
|
+
}
|
|
425
|
+
]
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
);
|
|
429
|
+
var uiPath = process.env.NODE_ENV === "production" ? path.resolve(__dirname, "index.html") : path.resolve(__dirname, "dist", "index.html");
|
|
430
|
+
var uiContentCache = null;
|
|
431
|
+
registerAppResource(
|
|
432
|
+
server,
|
|
433
|
+
"McDonald's UI",
|
|
434
|
+
"ui://mcdonalds/home",
|
|
435
|
+
{
|
|
436
|
+
description: "McDonald's UI"
|
|
437
|
+
},
|
|
438
|
+
async () => {
|
|
439
|
+
try {
|
|
440
|
+
let content = uiContentCache;
|
|
441
|
+
if (!content) {
|
|
442
|
+
content = await fs.readFile(uiPath, "utf-8");
|
|
443
|
+
uiContentCache = content;
|
|
444
|
+
}
|
|
445
|
+
const uiResource = createUIResource({
|
|
446
|
+
uri: "ui://mcdonalds/home",
|
|
447
|
+
content: {
|
|
448
|
+
type: "rawHtml",
|
|
449
|
+
htmlString: content
|
|
450
|
+
},
|
|
451
|
+
encoding: "text",
|
|
452
|
+
adapters: {
|
|
453
|
+
mcpApps: { enabled: true }
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
return {
|
|
457
|
+
contents: [uiResource.resource]
|
|
458
|
+
};
|
|
459
|
+
} catch (err) {
|
|
460
|
+
console.error("Error reading UI file:", err);
|
|
461
|
+
return {
|
|
462
|
+
contents: [
|
|
463
|
+
{
|
|
464
|
+
uri: "ui://mcdonalds/home",
|
|
465
|
+
mimeType: "text/html",
|
|
466
|
+
text: `<h1>Error loading UI</h1><p>Please build the project first (npm run build). Error: ${err}</p>`
|
|
467
|
+
}
|
|
468
|
+
]
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
);
|
|
473
|
+
async function runServer() {
|
|
474
|
+
const transport = new StdioServerTransport();
|
|
475
|
+
await server.connect(transport);
|
|
476
|
+
console.error("McDonald's MCP Server running on stdio");
|
|
477
|
+
}
|
|
478
|
+
runServer().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcd-mcp-app",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"mcp-app",
|
|
8
|
+
"mcdonalds",
|
|
9
|
+
"ai",
|
|
10
|
+
"model-context-protocol"
|
|
11
|
+
],
|
|
12
|
+
"bin": {
|
|
13
|
+
"mcd-mcp-app": "./dist/server.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"dev": "vite",
|
|
20
|
+
"build:frontend": "vite build",
|
|
21
|
+
"build:server": "esbuild server.ts --bundle --platform=node --target=node20 --format=esm --outfile=dist/server.js --packages=external",
|
|
22
|
+
"build": "npm run build:frontend && npm run build:server",
|
|
23
|
+
"prepublishOnly": "npm run build",
|
|
24
|
+
"start": "NODE_ENV=production node dist/server.js",
|
|
25
|
+
"serve": "tsx server.ts"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@mcp-ui/server": "^6.0.0",
|
|
29
|
+
"@modelcontextprotocol/ext-apps": "^1.0.1",
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.4.1",
|
|
31
|
+
"axios": "^1.13.4",
|
|
32
|
+
"lucide-react": "^0.563.0",
|
|
33
|
+
"react": "^18.3.1",
|
|
34
|
+
"react-dom": "^18.3.1",
|
|
35
|
+
"zod": "^3.24.1"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@tailwindcss/vite": "^4.1.18",
|
|
39
|
+
"@types/react": "^18.3.18",
|
|
40
|
+
"@types/react-dom": "^18.3.5",
|
|
41
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
42
|
+
"autoprefixer": "^10.4.24",
|
|
43
|
+
"postcss": "^8.5.6",
|
|
44
|
+
"tailwindcss": "^4.1.18",
|
|
45
|
+
"tsx": "^4.19.2",
|
|
46
|
+
"typescript": "~5.7.2",
|
|
47
|
+
"vite": "^6.0.11",
|
|
48
|
+
"vite-plugin-singlefile": "^2.1.0"
|
|
49
|
+
}
|
|
50
|
+
}
|