nutri-cal 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/.env +4 -0
- package/index.js +132 -0
- package/package.json +19 -0
package/.env
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
require("dotenv").config();
|
|
2
|
+
const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
3
|
+
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
4
|
+
const { z } = require("zod");
|
|
5
|
+
const Redis = require("ioredis");
|
|
6
|
+
|
|
7
|
+
// Redis configuration
|
|
8
|
+
const SCORED_REDIS_CONFIG = {
|
|
9
|
+
host: process.env.REDIS_HOST || "127.0.0.1",
|
|
10
|
+
port: process.env.REDIS_PORT || 6379,
|
|
11
|
+
db: process.env.REDIS_DB || 0,
|
|
12
|
+
password: process.env.REDIS_PASSWORD,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const redis = new Redis(SCORED_REDIS_CONFIG);
|
|
16
|
+
|
|
17
|
+
// Create an MCP server
|
|
18
|
+
const server = new McpServer({
|
|
19
|
+
name: "nutri-cal-demo",
|
|
20
|
+
version: "1.0.0",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Add the hello_world tool
|
|
24
|
+
// server.tool(
|
|
25
|
+
// "hello_world",
|
|
26
|
+
// {
|
|
27
|
+
// name: z.string().optional().describe("A name to greet"),
|
|
28
|
+
// },
|
|
29
|
+
// async ({ name }) => {
|
|
30
|
+
// const greetingName = name || "World";
|
|
31
|
+
// return {
|
|
32
|
+
// content: [
|
|
33
|
+
// {
|
|
34
|
+
// type: "text",
|
|
35
|
+
// text: `Hello ${greetingName}`,
|
|
36
|
+
// },
|
|
37
|
+
// ],
|
|
38
|
+
// };
|
|
39
|
+
// }
|
|
40
|
+
// );
|
|
41
|
+
|
|
42
|
+
// Add the calculate_nutrition tool
|
|
43
|
+
server.tool(
|
|
44
|
+
"calculate_nutrition",
|
|
45
|
+
{
|
|
46
|
+
chatID: z.string().describe("The chat session ID"),
|
|
47
|
+
ingredientName: z.string().describe("The name of the ingredient"),
|
|
48
|
+
amount: z.number().describe("The amount of ingredient in grams"),
|
|
49
|
+
},
|
|
50
|
+
async ({ chatID, ingredientName, amount }) => {
|
|
51
|
+
try {
|
|
52
|
+
// 1. Fetch food data from Redis
|
|
53
|
+
const foodKey = `food:${ingredientName}`;
|
|
54
|
+
const foodData = await redis.hgetall(foodKey);
|
|
55
|
+
|
|
56
|
+
if (!foodData || Object.keys(foodData).length === 0) {
|
|
57
|
+
return {
|
|
58
|
+
isError: true,
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: "text",
|
|
62
|
+
text: `Food item '${ingredientName}' not found in database.`,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 2. Parse and calculate nutrition values (based on 100g)
|
|
69
|
+
// Original values are per 100g
|
|
70
|
+
const multiplier = amount / 100.0;
|
|
71
|
+
|
|
72
|
+
const calculatedNutrition = {
|
|
73
|
+
Protein: parseFloat(((parseFloat(foodData.Protein) || 0) * multiplier).toFixed(2)),
|
|
74
|
+
Fats: parseFloat(((parseFloat(foodData.Fats) || 0) * multiplier).toFixed(2)),
|
|
75
|
+
Carbs: parseFloat(((parseFloat(foodData.Carbs) || 0) * multiplier).toFixed(2)),
|
|
76
|
+
Fiber: parseFloat(((parseFloat(foodData.Fiber) || 0) * multiplier).toFixed(2)),
|
|
77
|
+
Calories: parseFloat(((parseFloat(foodData.Calories) || 0) * multiplier).toFixed(2)),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// 3. Store/Accumulate in Redis atomically
|
|
81
|
+
const userKey = `cal_res:${chatID}`;
|
|
82
|
+
|
|
83
|
+
const pipeline = redis.multi();
|
|
84
|
+
pipeline.hincrbyfloat(userKey, "Protein", calculatedNutrition.Protein);
|
|
85
|
+
pipeline.hincrbyfloat(userKey, "Fats", calculatedNutrition.Fats);
|
|
86
|
+
pipeline.hincrbyfloat(userKey, "Carbs", calculatedNutrition.Carbs);
|
|
87
|
+
pipeline.hincrbyfloat(userKey, "Fiber", calculatedNutrition.Fiber);
|
|
88
|
+
pipeline.hincrbyfloat(userKey, "Calories", calculatedNutrition.Calories);
|
|
89
|
+
pipeline.expire(userKey, 600); // Set expiration to 10 minutes (600 seconds)
|
|
90
|
+
|
|
91
|
+
await pipeline.exec();
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
content: [
|
|
95
|
+
{
|
|
96
|
+
type: "text",
|
|
97
|
+
text: `Calculated nutrition for ${amount}g of ${ingredientName}:
|
|
98
|
+
Protein: ${calculatedNutrition.Protein.toFixed(2)}g
|
|
99
|
+
Fats: ${calculatedNutrition.Fats.toFixed(2)}g
|
|
100
|
+
Carbs: ${calculatedNutrition.Carbs.toFixed(2)}g
|
|
101
|
+
Fiber: ${calculatedNutrition.Fiber.toFixed(2)}g
|
|
102
|
+
Calories: ${calculatedNutrition.Calories.toFixed(2)}kcal
|
|
103
|
+
|
|
104
|
+
Successfully added to daily total for ChatID: ${chatID} (Expires in 10 minutes)`,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return {
|
|
111
|
+
isError: true,
|
|
112
|
+
content: [
|
|
113
|
+
{
|
|
114
|
+
type: "text",
|
|
115
|
+
text: `Error processing request: ${error.message}`,
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
async function main() {
|
|
124
|
+
const transport = new StdioServerTransport();
|
|
125
|
+
await server.connect(transport);
|
|
126
|
+
console.error("Nutri-Cal MCP Server running on stdio");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
main().catch((error) => {
|
|
130
|
+
console.error("Fatal error in main():", error);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nutri-cal",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [],
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"type": "commonjs",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
15
|
+
"dotenv": "^17.2.3",
|
|
16
|
+
"ioredis": "^5.9.0",
|
|
17
|
+
"zod": "^4.3.5"
|
|
18
|
+
}
|
|
19
|
+
}
|