grr-gaggiuino-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # grr-gaggiuino-mcp
2
+
3
+ An MCP (Model Context Protocol) server for [Gaggiuino](https://gaggiuino.github.io/)-modified espresso machines.
4
+
5
+ Monitor your machine, analyze shots, and manage brewing profiles from any MCP-compatible client.
6
+
7
+ ## Tools
8
+
9
+ | Tool | Description |
10
+ |------|-------------|
11
+ | `get_status` | Real-time machine state: temperature, pressure, weight, water level, active profile, brewing/steaming status |
12
+ | `get_shot` | Shot data with time-series curves (pressure, flow, temp, weight) and profile used. Defaults to latest shot. |
13
+ | `get_profiles` | List all brewing profiles with IDs and selection status |
14
+ | `select_profile` | Activate a brewing profile by ID |
15
+
16
+ ## Installation
17
+
18
+ ### Prerequisites
19
+
20
+ - Node.js 18+
21
+ - A Gaggiuino-modified espresso machine on your local network
22
+
23
+ ### Setup
24
+
25
+ ```bash
26
+ git clone https://github.com/sgerlach/grr-gaggiuino-mcp.git
27
+ cd grr-gaggiuino-mcp
28
+ npm install
29
+ npm run build
30
+ ```
31
+
32
+ ### Claude Desktop Configuration
33
+
34
+ Add to your Claude Desktop config:
35
+
36
+ **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
37
+ **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
38
+
39
+ ```json
40
+ {
41
+ "mcpServers": {
42
+ "gaggiuino": {
43
+ "command": "node",
44
+ "args": ["/path/to/grr-gaggiuino-mcp/dist/index.js"],
45
+ "env": {
46
+ "GAGGIUINO_BASE_URL": "http://YOUR_GAGGIUINO_IP"
47
+ }
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ > **Note**: If using nvm, specify the full path to Node 18+:
54
+ > ```json
55
+ > "command": "/Users/you/.nvm/versions/node/v20.x.x/bin/node"
56
+ > ```
57
+
58
+ ## Configuration
59
+
60
+ | Variable | Default | Description |
61
+ |----------|---------|-------------|
62
+ | `GAGGIUINO_BASE_URL` | `http://192.168.3.248` | Your Gaggiuino's IP or hostname |
63
+ | `REQUEST_TIMEOUT` | `5000` | API timeout in milliseconds |
64
+
65
+ ## Testing
66
+
67
+ ```bash
68
+ # With MCP Inspector
69
+ npm run inspect
70
+
71
+ # Direct run
72
+ GAGGIUINO_BASE_URL=http://YOUR_IP npm start
73
+ ```
74
+
75
+ ## Example Usage
76
+
77
+ ```
78
+ You: "What's my machine doing?"
79
+ → get_status returns temp, pressure, active profile, etc.
80
+
81
+ You: "Show me my last shot"
82
+ → get_shot returns full shot data with time-series curves
83
+
84
+ You: "That shot was sour and thin, what should I change?"
85
+ → Analyzes the shot data and recommends grind/profile adjustments
86
+ ```
87
+
88
+ ## Unit Conversions
89
+
90
+ The Gaggiuino API returns values in deci-units. This server converts them to standard units:
91
+
92
+ | Raw API | Converted |
93
+ |---------|-----------|
94
+ | deciseconds | seconds |
95
+ | decibar | bar |
96
+ | decidegrees | °C |
97
+ | decigrams | grams |
98
+ | deci-ml/s | ml/s |
99
+
100
+ ## API Reference
101
+
102
+ Based on the [Gaggiuino REST API](https://gaggiuino.github.io/#/rest-api/rest-api).
103
+
104
+ ## License
105
+
106
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ const GAGGIUINO_BASE_URL = process.env.GAGGIUINO_BASE_URL || "http://192.168.3.248";
6
+ const REQUEST_TIMEOUT = parseInt(process.env.REQUEST_TIMEOUT || "5000", 10);
7
+ // ============================================================================
8
+ // API Client - matches official Gaggiuino REST API
9
+ // https://gaggiuino.github.io/#/rest-api/rest-api
10
+ // ============================================================================
11
+ class GaggiuinoClient {
12
+ baseUrl;
13
+ timeout;
14
+ constructor(baseUrl, timeout) {
15
+ this.baseUrl = baseUrl;
16
+ this.timeout = timeout;
17
+ this.baseUrl = baseUrl.replace(/\/$/, "");
18
+ }
19
+ async fetch(endpoint, options) {
20
+ const controller = new AbortController();
21
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
22
+ try {
23
+ const res = await fetch(`${this.baseUrl}${endpoint}`, {
24
+ ...options,
25
+ signal: controller.signal,
26
+ });
27
+ if (!res.ok)
28
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
29
+ return res;
30
+ }
31
+ catch (error) {
32
+ if (error instanceof Error && error.name === "AbortError") {
33
+ throw new Error(`Request timeout after ${this.timeout}ms`);
34
+ }
35
+ throw error;
36
+ }
37
+ finally {
38
+ clearTimeout(timeoutId);
39
+ }
40
+ }
41
+ // GET /api/system/status - system sensors latest data
42
+ async getSystemStatus() {
43
+ const res = await this.fetch("/api/system/status");
44
+ const raw = await res.json();
45
+ if (!Array.isArray(raw) || raw.length === 0) {
46
+ throw new Error("Invalid API response: expected non-empty array from /api/system/status");
47
+ }
48
+ const s = raw[0];
49
+ if (!s || typeof s !== "object") {
50
+ throw new Error("Invalid API response: missing status data");
51
+ }
52
+ return {
53
+ brewing: s.brewSwitchState === "true",
54
+ steaming: s.steamSwitchState === "true",
55
+ profile: { id: parseInt(s.profileId), name: s.profileName },
56
+ temperature: { current: parseFloat(s.temperature), target: parseFloat(s.targetTemperature) },
57
+ pressure: parseFloat(s.pressure),
58
+ weight: parseFloat(s.weight),
59
+ waterLevel: parseInt(s.waterLevel),
60
+ };
61
+ }
62
+ // GET /api/shots/latest - latest shot identifier
63
+ async getLatestShotId() {
64
+ const res = await this.fetch("/api/shots/latest");
65
+ const raw = await res.json();
66
+ if (!Array.isArray(raw) || raw.length === 0 || !raw[0]?.lastShotId) {
67
+ throw new Error("Invalid API response: expected array with lastShotId from /api/shots/latest");
68
+ }
69
+ return parseInt(raw[0].lastShotId);
70
+ }
71
+ // GET /api/shots/{id} - shot data
72
+ async getShot(id) {
73
+ const res = await this.fetch(`/api/shots/${id}`);
74
+ const raw = await res.json();
75
+ if (!raw || typeof raw !== "object") {
76
+ throw new Error(`Invalid API response: expected object from /api/shots/${id}`);
77
+ }
78
+ const dp = raw.datapoints;
79
+ if (!dp || typeof dp !== "object") {
80
+ throw new Error(`Invalid shot data: missing datapoints for shot ${id}`);
81
+ }
82
+ // Convert from deci-units to standard units
83
+ return {
84
+ id: raw.id,
85
+ timestamp: new Date(raw.timestamp * 1000).toISOString(),
86
+ durationSeconds: raw.duration / 10,
87
+ profile: raw.profile ? {
88
+ id: raw.profile.id,
89
+ name: raw.profile.name,
90
+ temperatureC: raw.profile.waterTemperature,
91
+ stopConditions: raw.profile.globalStopConditions ? {
92
+ weightG: raw.profile.globalStopConditions.weight,
93
+ timeSeconds: raw.profile.globalStopConditions.time ? raw.profile.globalStopConditions.time / 1000 : null,
94
+ } : null,
95
+ phases: raw.profile.phases?.map((p) => ({
96
+ name: p.name,
97
+ type: p.type,
98
+ target: { start: p.target.start, end: p.target.end, curve: p.target.curve },
99
+ durationSeconds: p.stopConditions?.time ? p.stopConditions.time / 1000 : null,
100
+ flowRestriction: p.restriction,
101
+ })),
102
+ } : null,
103
+ datapoints: {
104
+ timeSeconds: dp.timeInShot.map((t) => t / 10),
105
+ pressureBar: dp.pressure.map((p) => p / 10),
106
+ flowMlPerSec: dp.pumpFlow.map((f) => f / 10),
107
+ weightFlowGPerSec: dp.weightFlow.map((f) => f / 10),
108
+ temperatureC: dp.temperature.map((t) => t / 10),
109
+ weightG: dp.shotWeight.map((w) => w / 10),
110
+ waterPumpedMl: dp.waterPumped.map((w) => w / 10),
111
+ targets: {
112
+ temperatureC: dp.targetTemperature.map((t) => t / 10),
113
+ flowMlPerSec: dp.targetPumpFlow.map((f) => f / 10),
114
+ pressureBar: dp.targetPressure.map((p) => p / 10),
115
+ },
116
+ },
117
+ };
118
+ }
119
+ // GET /api/profiles/all - all profiles
120
+ async getProfiles() {
121
+ const res = await this.fetch("/api/profiles/all");
122
+ const raw = await res.json();
123
+ if (!Array.isArray(raw)) {
124
+ throw new Error("Invalid API response: expected array from /api/profiles/all");
125
+ }
126
+ return raw.map(p => ({
127
+ id: p.id,
128
+ name: p.name,
129
+ selected: p.selected === "true",
130
+ }));
131
+ }
132
+ // POST /api/profile-select/{id} - select profile
133
+ async selectProfile(id) {
134
+ await this.fetch(`/api/profile-select/${id}`, { method: "POST" });
135
+ }
136
+ }
137
+ const client = new GaggiuinoClient(GAGGIUINO_BASE_URL, REQUEST_TIMEOUT);
138
+ // ============================================================================
139
+ // MCP Tools
140
+ // ============================================================================
141
+ const tools = [
142
+ {
143
+ name: "get_status",
144
+ description: "Get real-time Gaggiuino espresso machine status including: current/target temperature (Celsius), pressure (bar), scale weight (grams), water tank level (%), whether brewing or steaming is active, and the currently selected profile name and ID.",
145
+ inputSchema: { type: "object", properties: {} },
146
+ },
147
+ {
148
+ name: "get_shot",
149
+ description: "Retrieve detailed shot data with time-series curves for analysis. Returns pressure (bar), flow rate (ml/s), temperature (C), weight (g), and target values over time. Includes the profile used and shot duration. Omit ID to get the most recent shot.",
150
+ inputSchema: {
151
+ type: "object",
152
+ properties: {
153
+ id: { type: "number", description: "Shot ID for historical data. Omit to get the latest shot." },
154
+ },
155
+ },
156
+ },
157
+ {
158
+ name: "get_profiles",
159
+ description: "List all available brewing profiles stored on the Gaggiuino. Returns profile IDs, names, and which one is currently selected. Use this to see available profiles before selecting one.",
160
+ inputSchema: { type: "object", properties: {} },
161
+ },
162
+ {
163
+ name: "select_profile",
164
+ description: "Activate a brewing profile by its ID. The machine will use this profile for the next shot. Get available profile IDs using get_profiles first.",
165
+ inputSchema: {
166
+ type: "object",
167
+ properties: {
168
+ id: { type: "number", description: "Profile ID to activate" },
169
+ },
170
+ required: ["id"],
171
+ },
172
+ },
173
+ ];
174
+ // ============================================================================
175
+ // Server
176
+ // ============================================================================
177
+ const server = new Server({ name: "grr-gaggiuino-mcp", version: "1.0.0" }, { capabilities: { tools: {} } });
178
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
179
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
180
+ const { name, arguments: args } = request.params;
181
+ try {
182
+ switch (name) {
183
+ case "get_status": {
184
+ const status = await client.getSystemStatus();
185
+ return { content: [{ type: "text", text: JSON.stringify(status, null, 2) }] };
186
+ }
187
+ case "get_shot": {
188
+ let id;
189
+ if (args?.id !== undefined) {
190
+ if (typeof args.id !== "number" || !Number.isInteger(args.id) || args.id <= 0) {
191
+ throw new Error("Shot ID must be a positive integer");
192
+ }
193
+ id = args.id;
194
+ }
195
+ else {
196
+ id = await client.getLatestShotId();
197
+ }
198
+ const shot = await client.getShot(id);
199
+ return { content: [{ type: "text", text: JSON.stringify(shot, null, 2) }] };
200
+ }
201
+ case "get_profiles": {
202
+ const profiles = await client.getProfiles();
203
+ return { content: [{ type: "text", text: JSON.stringify(profiles, null, 2) }] };
204
+ }
205
+ case "select_profile": {
206
+ if (typeof args?.id !== "number" || !Number.isInteger(args.id)) {
207
+ throw new Error("Profile ID must be an integer");
208
+ }
209
+ await client.selectProfile(args.id);
210
+ return { content: [{ type: "text", text: `Profile ${args.id} selected` }] };
211
+ }
212
+ default:
213
+ throw new Error(`Unknown tool: ${name}`);
214
+ }
215
+ }
216
+ catch (error) {
217
+ return {
218
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
219
+ isError: true,
220
+ };
221
+ }
222
+ });
223
+ async function main() {
224
+ const transport = new StdioServerTransport();
225
+ await server.connect(transport);
226
+ console.error(`grr-gaggiuino-mcp running (${GAGGIUINO_BASE_URL})`);
227
+ }
228
+ main().catch(e => { console.error(e); process.exit(1); });
229
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/manifest.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "dxt_version": "0.1",
3
+ "name": "grr-gaggiuino-mcp",
4
+ "version": "1.0.0",
5
+ "display_name": "Gaggiuino Espresso Tools",
6
+ "description": "MCP server for Gaggiuino-modified espresso machines. Monitor machine status, analyze shot data with time-series curves, and manage brewing profiles.",
7
+ "long_description": "Connect any MCP client to your Gaggiuino-modified espresso machine. Get real-time machine status (temperature, pressure, weight), retrieve detailed shot data with time-series curves for analysis, list and select brewing profiles. Perfect for shot analysis, dial-in recommendations, and understanding your espresso extraction.",
8
+ "author": {
9
+ "name": "Scott Gerlach",
10
+ "url": "https://github.com/sgerlach"
11
+ },
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/sgerlach/grr-gaggiuino-mcp"
16
+ },
17
+ "homepage": "https://github.com/sgerlach/grr-gaggiuino-mcp",
18
+ "keywords": [
19
+ "espresso",
20
+ "coffee",
21
+ "gaggiuino",
22
+ "iot",
23
+ "smart home"
24
+ ],
25
+ "server": {
26
+ "type": "node",
27
+ "entry_point": "dist/index.js",
28
+ "mcp_config": {
29
+ "command": "node",
30
+ "args": ["${__dirname}/dist/index.js"],
31
+ "env": {
32
+ "GAGGIUINO_BASE_URL": "${user_config.gaggiuino_url}",
33
+ "REQUEST_TIMEOUT": "${user_config.request_timeout}"
34
+ }
35
+ }
36
+ },
37
+ "user_config": {
38
+ "gaggiuino_url": {
39
+ "type": "string",
40
+ "title": "Gaggiuino URL",
41
+ "description": "The URL of your Gaggiuino machine (e.g., http://192.168.1.100 or http://gaggiuino.local)",
42
+ "default": "http://192.168.3.248",
43
+ "required": true
44
+ },
45
+ "request_timeout": {
46
+ "type": "string",
47
+ "title": "Request Timeout",
48
+ "description": "API request timeout in milliseconds",
49
+ "default": "5000",
50
+ "required": false
51
+ }
52
+ },
53
+ "tools": [
54
+ {
55
+ "name": "get_status",
56
+ "description": "Get real-time Gaggiuino espresso machine status including: current/target temperature (Celsius), pressure (bar), scale weight (grams), water tank level (%), whether brewing or steaming is active, and the currently selected profile name and ID."
57
+ },
58
+ {
59
+ "name": "get_shot",
60
+ "description": "Retrieve detailed shot data with time-series curves for analysis. Returns pressure (bar), flow rate (ml/s), temperature (C), weight (g), and target values over time. Includes the profile used and shot duration. Omit ID to get the most recent shot, or specify a shot ID for historical data."
61
+ },
62
+ {
63
+ "name": "get_profiles",
64
+ "description": "List all available brewing profiles stored on the Gaggiuino. Returns profile IDs, names, and which one is currently selected. Use this to see available profiles before selecting one."
65
+ },
66
+ {
67
+ "name": "select_profile",
68
+ "description": "Activate a brewing profile by its ID. The machine will use this profile for the next shot. Get available profile IDs using get_profiles first."
69
+ }
70
+ ],
71
+ "compatibility": {
72
+ "claude_desktop": ">=0.10.0",
73
+ "platforms": ["darwin", "win32"],
74
+ "runtimes": {
75
+ "node": ">=18"
76
+ }
77
+ }
78
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "grr-gaggiuino-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for Gaggiuino espresso machine monitoring and control - get shot data, machine status, and manage brewing profiles",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "grr-gaggiuino-mcp": "dist/index.js"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/sgerlach/grr-gaggiuino-mcp.git"
13
+ },
14
+ "homepage": "https://github.com/sgerlach/grr-gaggiuino-mcp#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/sgerlach/grr-gaggiuino-mcp/issues"
17
+ },
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "tsc --watch",
21
+ "start": "node dist/index.js",
22
+ "inspect": "npx @modelcontextprotocol/inspector node dist/index.js",
23
+ "lint": "eslint src/",
24
+ "clean": "rm -rf dist",
25
+ "pack": "npm run build && mcpb pack"
26
+ },
27
+ "keywords": [
28
+ "mcp",
29
+ "model-context-protocol",
30
+ "gaggiuino",
31
+ "espresso",
32
+ "coffee",
33
+ "iot",
34
+ "smart-home",
35
+ "brewing",
36
+ "shot-analysis"
37
+ ],
38
+ "author": "Scott Gerlach <sgerlach@github.com>",
39
+ "license": "MIT",
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@anthropic-ai/mcpb": "^2.1.2",
45
+ "@types/node": "^20.11.0",
46
+ "typescript": "^5.3.0"
47
+ },
48
+ "engines": {
49
+ "node": ">=18"
50
+ },
51
+ "files": [
52
+ "dist",
53
+ "manifest.json"
54
+ ]
55
+ }