mcp-grocy 2.5.0 → 2.7.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +28 -4
  2. package/README.md +13 -0
  3. package/build/resources/CHANGELOG.md +30 -4
  4. package/build/resources/README.md +13 -0
  5. package/build/resources/config.md +21 -4
  6. package/build/resources/response-format.md +12 -8
  7. package/package.json +14 -2
  8. package/build/api/client.js +0 -119
  9. package/build/config/index.js +0 -259
  10. package/build/main.js +0 -44
  11. package/build/server/http-server.js +0 -330
  12. package/build/server/mcp-server.js +0 -210
  13. package/build/server/resources.js +0 -73
  14. package/build/server/tool-input-zod.js +0 -86
  15. package/build/tools/base.js +0 -145
  16. package/build/tools/household/definitions.js +0 -161
  17. package/build/tools/household/handlers.js +0 -109
  18. package/build/tools/household/index.js +0 -25
  19. package/build/tools/index.js +0 -2
  20. package/build/tools/inventory/definitions.js +0 -416
  21. package/build/tools/inventory/handlers.js +0 -443
  22. package/build/tools/inventory/index.js +0 -32
  23. package/build/tools/module-loader.js +0 -154
  24. package/build/tools/recipes/definitions.js +0 -278
  25. package/build/tools/recipes/handlers.js +0 -477
  26. package/build/tools/recipes/index.js +0 -33
  27. package/build/tools/recipes/validations.js +0 -29
  28. package/build/tools/shopping/definitions.js +0 -73
  29. package/build/tools/shopping/handlers.js +0 -43
  30. package/build/tools/shopping/index.js +0 -14
  31. package/build/tools/system/definitions.js +0 -89
  32. package/build/tools/system/handlers.js +0 -156
  33. package/build/tools/system/index.js +0 -16
  34. package/build/tools/types.js +0 -1
  35. package/build/tools/validation-helpers.js +0 -39
  36. package/build/types/index.js +0 -64
  37. package/build/utils/errors.js +0 -143
  38. package/build/utils/logger.js +0 -142
@@ -1,86 +0,0 @@
1
- /**
2
- * Builds Zod input schemas from tool definitions (JSON Schema subset used by this repo)
3
- * so McpServer.registerTool can validate and advertise tools.
4
- */
5
- import { z } from 'zod';
6
- function isStringTupleEnum(values) {
7
- return values.length > 0 && values.every((v) => typeof v === 'string');
8
- }
9
- function propertyToZod(prop, required) {
10
- const t = prop.type;
11
- let inner;
12
- switch (t) {
13
- case 'string': {
14
- if (Array.isArray(prop.enum) && isStringTupleEnum(prop.enum)) {
15
- inner = z.enum(prop.enum);
16
- }
17
- else {
18
- inner = z.string();
19
- }
20
- break;
21
- }
22
- case 'number':
23
- case 'integer':
24
- inner = z.number();
25
- break;
26
- case 'boolean':
27
- inner = z.boolean();
28
- break;
29
- case 'array': {
30
- const items = prop.items;
31
- if (items?.type === 'string' && Array.isArray(items.enum) && isStringTupleEnum(items.enum)) {
32
- inner = z.array(z.enum(items.enum));
33
- }
34
- else if (items?.type === 'string') {
35
- inner = z.array(z.string());
36
- }
37
- else if (items?.type === 'number' || items?.type === 'integer') {
38
- inner = z.array(z.number());
39
- }
40
- else if (items?.type === 'boolean') {
41
- inner = z.array(z.boolean());
42
- }
43
- else {
44
- inner = z.array(z.unknown());
45
- }
46
- break;
47
- }
48
- case 'object': {
49
- if (prop.properties && typeof prop.properties === 'object') {
50
- inner = objectPropsToZod(prop.properties, prop.required);
51
- }
52
- else if (prop.additionalProperties &&
53
- typeof prop.additionalProperties === 'object' &&
54
- prop.additionalProperties.type === 'string') {
55
- inner = z.record(z.string(), z.string());
56
- }
57
- else {
58
- inner = z.record(z.string(), z.unknown());
59
- }
60
- break;
61
- }
62
- default:
63
- inner = z.unknown();
64
- }
65
- return required ? inner : inner.optional();
66
- }
67
- function objectPropsToZod(properties, requiredList) {
68
- const required = new Set(requiredList ?? []);
69
- const shape = {};
70
- for (const [key, raw] of Object.entries(properties)) {
71
- shape[key] = propertyToZod(raw, required.has(key));
72
- }
73
- return z.object(shape);
74
- }
75
- /** Converts a tool definition's JSON Schema object input into a Zod schema for MCP. */
76
- export function toolDefinitionInputZod(def) {
77
- const schema = def.inputSchema;
78
- if (schema?.type !== 'object') {
79
- return z.record(z.string(), z.unknown());
80
- }
81
- const props = schema.properties;
82
- if (!props || typeof props !== 'object' || Object.keys(props).length === 0) {
83
- return z.object({});
84
- }
85
- return objectPropsToZod(props, schema.required);
86
- }
@@ -1,145 +0,0 @@
1
- /**
2
- * Simplified base tool handler
3
- */
4
- import apiClient from '../api/client.js';
5
- import { ErrorHandler, ValidationError } from '../utils/errors.js';
6
- import { logger } from '../utils/logger.js';
7
- export class BaseToolHandler {
8
- /**
9
- * Create a standardized success result
10
- */
11
- createSuccess(data, message) {
12
- return {
13
- content: [
14
- {
15
- type: 'text',
16
- text: message || 'Operation completed successfully',
17
- },
18
- {
19
- type: 'text',
20
- text: JSON.stringify(data, null, 2),
21
- },
22
- ],
23
- };
24
- }
25
- /**
26
- * Create a standardized error result
27
- */
28
- createError(message, details) {
29
- return {
30
- content: [
31
- {
32
- type: 'text',
33
- text: `Error: ${message}`,
34
- },
35
- ...(details
36
- ? [
37
- {
38
- type: 'text',
39
- text: JSON.stringify(details, null, 2),
40
- },
41
- ]
42
- : []),
43
- ],
44
- isError: true,
45
- };
46
- }
47
- /**
48
- * Validate required parameters
49
- */
50
- validateRequired(params, required) {
51
- const missing = required.filter((field) => {
52
- const value = params[field];
53
- return value === undefined || value === null || value === '';
54
- });
55
- if (missing.length > 0) {
56
- throw new ValidationError(`Missing required parameters: ${missing.join(', ')}`, 'parameter validation');
57
- }
58
- }
59
- /**
60
- * Safely make API calls with error handling
61
- */
62
- async apiCall(endpoint, method = 'GET', data, options) {
63
- return ErrorHandler.handleAsync(async () => {
64
- const response = await apiClient.request(endpoint, {
65
- method,
66
- body: data,
67
- queryParams: options?.queryParams || {},
68
- });
69
- return response.data;
70
- }, `API ${method} ${endpoint}`);
71
- }
72
- /**
73
- * Handle tool execution with standardized error handling
74
- */
75
- async executeToolHandler(handler) {
76
- try {
77
- return await handler();
78
- }
79
- catch (error) {
80
- ErrorHandler.logError(error, 'tool execution');
81
- if (error instanceof ValidationError) {
82
- return this.createError(error.message);
83
- }
84
- const message = error instanceof Error ? error.message : 'Internal error';
85
- return this.createError(`Tool execution failed: ${message}`);
86
- }
87
- }
88
- /**
89
- * Safe JSON formatting
90
- */
91
- safeStringify(data) {
92
- try {
93
- return JSON.stringify(data, null, 2);
94
- }
95
- catch (error) {
96
- logger.warn('Failed to stringify data', 'TOOLS', { error });
97
- return '[Unable to format data]';
98
- }
99
- }
100
- /**
101
- * Filter object fields based on allowlist
102
- */
103
- filterFields(objects, fields) {
104
- return objects.map((obj) => {
105
- const filtered = {};
106
- fields.forEach((field) => {
107
- if (field in obj) {
108
- filtered[field] = obj[field];
109
- }
110
- });
111
- return filtered;
112
- });
113
- }
114
- /**
115
- * Parse and validate array parameter
116
- */
117
- parseArrayParam(value, paramName) {
118
- if (!value) {
119
- throw new ValidationError(`${paramName} is required`);
120
- }
121
- if (!Array.isArray(value)) {
122
- throw new ValidationError(`${paramName} must be an array`);
123
- }
124
- if (value.length === 0) {
125
- throw new ValidationError(`${paramName} cannot be empty`);
126
- }
127
- return value.map((v) => String(v));
128
- }
129
- /**
130
- * Parse and validate numeric parameter
131
- */
132
- parseNumberParam(value, paramName, required = true) {
133
- if (value === undefined || value === null) {
134
- if (required) {
135
- throw new ValidationError(`${paramName} is required`);
136
- }
137
- return undefined;
138
- }
139
- const num = Number(value);
140
- if (isNaN(num)) {
141
- throw new ValidationError(`${paramName} must be a valid number`);
142
- }
143
- return num;
144
- }
145
- }
@@ -1,161 +0,0 @@
1
- export const householdToolDefinitions = [
2
- // ==================== CHORE MANAGEMENT ====================
3
- {
4
- name: 'household_chores_get',
5
- description: '[HOUSEHOLD/CHORES] List **Grocy Chores** (recurring household routines: cleaning schedule, maintenance cadence). Not the same as Tasks—use household_tasks_get for one-off to-dos.',
6
- annotations: { readOnlyHint: true },
7
- inputSchema: {
8
- type: 'object',
9
- properties: {},
10
- required: [],
11
- },
12
- },
13
- {
14
- name: 'household_chores_execute',
15
- description: '[HOUSEHOLD/CHORES] Track the execution of a chore in your Grocy instance.',
16
- inputSchema: {
17
- type: 'object',
18
- properties: {
19
- choreId: {
20
- type: 'number',
21
- description: 'ID of the chore to track execution for. Use household_chores_get tool to find the correct chore ID.',
22
- },
23
- executedBy: {
24
- type: 'number',
25
- description: 'ID of the user who executed the chore (optional). Use system_users_get tool to find user IDs.',
26
- },
27
- trackedTime: {
28
- type: 'string',
29
- description: 'Time when the chore was executed in YYYY-MM-DD HH:mm:ss format (optional, defaults to current time)',
30
- },
31
- note: {
32
- type: 'string',
33
- description: 'Optional note for the chore execution',
34
- },
35
- },
36
- required: ['choreId'],
37
- },
38
- },
39
- // ==================== TASK MANAGEMENT ====================
40
- {
41
- name: 'household_tasks_get',
42
- description: '[HOUSEHOLD/TASKS] List **Grocy Tasks** (discrete to-dos / task tracker). Not recurring Chores—use household_chores_get for scheduled recurring chores.',
43
- annotations: { readOnlyHint: true },
44
- inputSchema: {
45
- type: 'object',
46
- properties: {},
47
- required: [],
48
- },
49
- },
50
- {
51
- name: 'household_tasks_complete',
52
- description: '[HOUSEHOLD/TASKS] Mark a task as completed in your Grocy instance.',
53
- inputSchema: {
54
- type: 'object',
55
- properties: {
56
- taskId: {
57
- type: 'number',
58
- description: 'ID of the task to complete. Use household_tasks_get tool to find the correct task ID.',
59
- },
60
- note: {
61
- type: 'string',
62
- description: 'Optional note for the task completion',
63
- },
64
- },
65
- required: ['taskId'],
66
- },
67
- },
68
- // ==================== BATTERY MANAGEMENT ====================
69
- {
70
- name: 'household_batteries_get',
71
- description: '[HOUSEHOLD/BATTERIES] Get all batteries from your Grocy instance.',
72
- annotations: { readOnlyHint: true },
73
- inputSchema: {
74
- type: 'object',
75
- properties: {},
76
- required: [],
77
- },
78
- },
79
- {
80
- name: 'household_batteries_charge',
81
- description: '[HOUSEHOLD/BATTERIES] Track battery charging in your Grocy instance.',
82
- inputSchema: {
83
- type: 'object',
84
- properties: {
85
- batteryId: {
86
- type: 'number',
87
- description: 'ID of the battery to charge. Use household_batteries_get tool to find the correct battery ID.',
88
- },
89
- trackedTime: {
90
- type: 'string',
91
- description: 'Time when the battery was charged in YYYY-MM-DD HH:mm:ss format (optional, defaults to current time)',
92
- },
93
- note: {
94
- type: 'string',
95
- description: 'Optional note for the battery charge',
96
- },
97
- },
98
- required: ['batteryId'],
99
- },
100
- },
101
- {
102
- name: 'household_batteries_print_label',
103
- description: '[HOUSEHOLD/BATTERIES] Print a Grocycode label for a battery. Use household_batteries_get to find valid batteryId values.',
104
- inputSchema: {
105
- type: 'object',
106
- properties: {
107
- batteryId: {
108
- type: 'number',
109
- description: 'ID of the battery to print label for. Use household_batteries_get tool to find the correct battery ID.',
110
- },
111
- },
112
- required: ['batteryId'],
113
- },
114
- },
115
- // ==================== CHORE LABEL PRINTING ====================
116
- {
117
- name: 'household_chores_print_label',
118
- description: '[HOUSEHOLD/CHORES] Print a Grocycode label for a chore. Use household_chores_get to find valid choreId values.',
119
- inputSchema: {
120
- type: 'object',
121
- properties: {
122
- choreId: {
123
- type: 'number',
124
- description: 'ID of the chore to print label for. Use household_chores_get tool to find the correct chore ID.',
125
- },
126
- },
127
- required: ['choreId'],
128
- },
129
- },
130
- // ==================== EQUIPMENT MANAGEMENT ====================
131
- {
132
- name: 'household_equipment_get',
133
- description: '[HOUSEHOLD/EQUIPMENT] List **equipment** assets tracked in Grocy (appliances, tools—not chore definitions). For recurring chore definitions use household_chores_get.',
134
- annotations: { readOnlyHint: true },
135
- inputSchema: {
136
- type: 'object',
137
- properties: {},
138
- required: [],
139
- },
140
- },
141
- // ==================== ACTION UTILITIES ====================
142
- {
143
- name: 'household_actions_undo',
144
- description: '[HOUSEHOLD/ACTIONS] Undo a previously executed action (chore execution, task completion, or battery charge).',
145
- inputSchema: {
146
- type: 'object',
147
- properties: {
148
- entityType: {
149
- type: 'string',
150
- enum: ['chore', 'chores', 'task', 'tasks', 'battery', 'batteries'],
151
- description: 'Type of entity to undo action for',
152
- },
153
- id: {
154
- type: 'number',
155
- description: 'ID of the specific execution/completion/charge to undo',
156
- },
157
- },
158
- required: ['entityType', 'id'],
159
- },
160
- },
161
- ];
@@ -1,109 +0,0 @@
1
- import { BaseToolHandler } from '../base.js';
2
- export class HouseholdToolHandlers extends BaseToolHandler {
3
- // ==================== CHORE MANAGEMENT ====================
4
- getChores = async () => {
5
- return this.executeToolHandler(async () => {
6
- const data = await this.apiCall('/objects/chores');
7
- return this.createSuccess(data);
8
- });
9
- };
10
- trackChoreExecution = async (args) => {
11
- return this.executeToolHandler(async () => {
12
- const { choreId, executedBy, trackedTime, note } = args || {};
13
- this.validateRequired({ choreId }, ['choreId']);
14
- const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19);
15
- const body = {
16
- tracked_time: trackedTime || timestamp,
17
- ...(executedBy ? { done_by: executedBy } : {}),
18
- ...(note ? { note } : {}),
19
- };
20
- const result = await this.apiCall(`/chores/${choreId}/execute`, 'POST', body);
21
- return this.createSuccess(result, 'Chore execution tracked successfully');
22
- });
23
- };
24
- // ==================== TASK MANAGEMENT ====================
25
- getTasks = async () => {
26
- return this.executeToolHandler(async () => {
27
- const data = await this.apiCall('/objects/tasks');
28
- return this.createSuccess(data);
29
- });
30
- };
31
- completeTask = async (args) => {
32
- return this.executeToolHandler(async () => {
33
- const { taskId, note } = args || {};
34
- this.validateRequired({ taskId }, ['taskId']);
35
- const result = await this.apiCall(`/tasks/${taskId}/complete`, 'POST', note ? { note } : {});
36
- return this.createSuccess(result, 'Task completed successfully');
37
- });
38
- };
39
- // ==================== BATTERY MANAGEMENT ====================
40
- getBatteries = async () => {
41
- return this.executeToolHandler(async () => {
42
- const data = await this.apiCall('/objects/batteries');
43
- return this.createSuccess(data);
44
- });
45
- };
46
- chargeBattery = async (args) => {
47
- return this.executeToolHandler(async () => {
48
- const { batteryId, trackedTime, note } = args || {};
49
- this.validateRequired({ batteryId }, ['batteryId']);
50
- const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19);
51
- const body = {
52
- tracked_time: trackedTime || timestamp,
53
- ...(note ? { note } : {}),
54
- };
55
- const result = await this.apiCall(`/batteries/${batteryId}/charge`, 'POST', body);
56
- return this.createSuccess(result, 'Battery charged successfully');
57
- });
58
- };
59
- // ==================== EQUIPMENT MANAGEMENT ====================
60
- getEquipment = async () => {
61
- return this.executeToolHandler(async () => {
62
- const data = await this.apiCall('/objects/equipment');
63
- return this.createSuccess(data);
64
- });
65
- };
66
- // ==================== LABEL PRINTING ====================
67
- printBatteryLabel = async (args) => {
68
- return this.executeToolHandler(async () => {
69
- const { batteryId } = args || {};
70
- this.validateRequired({ batteryId }, ['batteryId']);
71
- const result = await this.apiCall(`/batteries/${batteryId}/printlabel`);
72
- return this.createSuccess(result, 'Battery label printed successfully');
73
- });
74
- };
75
- printChoreLabel = async (args) => {
76
- return this.executeToolHandler(async () => {
77
- const { choreId } = args || {};
78
- this.validateRequired({ choreId }, ['choreId']);
79
- const result = await this.apiCall(`/chores/${choreId}/printlabel`);
80
- return this.createSuccess(result, 'Chore label printed successfully');
81
- });
82
- };
83
- // ==================== ACTION UTILITIES ====================
84
- undoAction = async (args) => {
85
- return this.executeToolHandler(async () => {
86
- const { entityType, id } = args;
87
- this.validateRequired({ entityType, id }, ['entityType', 'id']);
88
- let endpoint;
89
- switch (entityType.toLowerCase()) {
90
- case 'chore':
91
- case 'chores':
92
- endpoint = `/chores/executions/${id}/undo`;
93
- break;
94
- case 'battery':
95
- case 'batteries':
96
- endpoint = `/batteries/charge-cycles/${id}/undo`;
97
- break;
98
- case 'task':
99
- case 'tasks':
100
- endpoint = `/tasks/${id}/undo`;
101
- break;
102
- default:
103
- return this.createError(`Unsupported entity type: ${entityType}`);
104
- }
105
- const result = await this.apiCall(endpoint, 'POST');
106
- return this.createSuccess(result, `${entityType} action undone successfully`);
107
- });
108
- };
109
- }
@@ -1,25 +0,0 @@
1
- import { householdToolDefinitions } from './definitions.js';
2
- import { HouseholdToolHandlers } from './handlers.js';
3
- const handlers = new HouseholdToolHandlers();
4
- export const householdModule = {
5
- definitions: householdToolDefinitions,
6
- handlers: {
7
- // Chore Management
8
- household_chores_get: handlers.getChores,
9
- household_chores_execute: handlers.trackChoreExecution,
10
- // Task Management
11
- household_tasks_get: handlers.getTasks,
12
- household_tasks_complete: handlers.completeTask,
13
- // Battery Management
14
- household_batteries_get: handlers.getBatteries,
15
- household_batteries_charge: handlers.chargeBattery,
16
- household_batteries_print_label: handlers.printBatteryLabel,
17
- // Chore Label Printing
18
- household_chores_print_label: handlers.printChoreLabel,
19
- // Equipment Management
20
- household_equipment_get: handlers.getEquipment,
21
- // Action Utilities
22
- household_actions_undo: handlers.undoAction,
23
- },
24
- };
25
- export * from './definitions.js';
@@ -1,2 +0,0 @@
1
- // Re-export simplified module loader functionality
2
- export { createToolRegistry } from './module-loader.js';