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 +106 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +229 -0
- package/dist/index.js.map +1 -0
- package/manifest.json +78 -0
- package/package.json +55 -0
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
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|