mcp-grocy 2.7.0 → 2.7.2
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/CHANGELOG.md +14 -0
- package/README.md +15 -1
- package/build/api/client.js +119 -0
- package/build/config/index.js +283 -0
- package/build/main.js +44 -0
- package/build/resources/CHANGELOG.md +15 -3
- package/build/resources/README.md +15 -1
- package/build/resources/api-reference.md +2 -1
- package/build/resources/response-format.md +1 -3
- package/build/server/http-server.js +398 -0
- package/build/server/mcp-server.js +230 -0
- package/build/server/resources.js +73 -0
- package/build/server/tool-input-zod.js +86 -0
- package/build/tools/base.js +157 -0
- package/build/tools/household/definitions.js +161 -0
- package/build/tools/household/handlers.js +109 -0
- package/build/tools/household/index.js +25 -0
- package/build/tools/index.js +2 -0
- package/build/tools/inventory/definitions.js +416 -0
- package/build/tools/inventory/handlers.js +443 -0
- package/build/tools/inventory/index.js +32 -0
- package/build/tools/module-loader.js +154 -0
- package/build/tools/recipes/definitions.js +278 -0
- package/build/tools/recipes/handlers.js +518 -0
- package/build/tools/recipes/index.js +33 -0
- package/build/tools/recipes/validations.js +29 -0
- package/build/tools/shopping/definitions.js +147 -0
- package/build/tools/shopping/handlers.js +198 -0
- package/build/tools/shopping/index.js +17 -0
- package/build/tools/system/definitions.js +89 -0
- package/build/tools/system/handlers.js +156 -0
- package/build/tools/system/index.js +16 -0
- package/build/tools/types.js +1 -0
- package/build/tools/validation-helpers.js +39 -0
- package/build/types/index.js +64 -0
- package/build/utils/errors.js +143 -0
- package/build/utils/logger.js +142 -0
- package/build/version.js +1 -1
- package/package.json +27 -25
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { SERVER_NAME } from '../version.js';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
/** Static docs exposed as MCP resources (used by ResourceHandler and McpServer registration). */
|
|
7
|
+
export const STATIC_MCP_RESOURCE_ENTRIES = [
|
|
8
|
+
{
|
|
9
|
+
slug: 'examples',
|
|
10
|
+
name: 'mcp-grocy usage examples',
|
|
11
|
+
description: 'Examples of calling this MCP server’s tools against Grocy',
|
|
12
|
+
mimeType: 'text/markdown',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
slug: 'response-format',
|
|
16
|
+
name: 'Tool response format',
|
|
17
|
+
description: 'How tool results and system_dev_test_request responses are shaped',
|
|
18
|
+
mimeType: 'text/markdown',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
slug: 'config',
|
|
22
|
+
name: 'Configuration',
|
|
23
|
+
description: 'YAML and environment configuration for mcp-grocy',
|
|
24
|
+
mimeType: 'text/markdown',
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
export class ResourceHandler {
|
|
28
|
+
__dirname;
|
|
29
|
+
allowedResources = new Set(STATIC_MCP_RESOURCE_ENTRIES.map((e) => e.slug));
|
|
30
|
+
constructor() {
|
|
31
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
32
|
+
this.__dirname = path.dirname(__filename);
|
|
33
|
+
}
|
|
34
|
+
async listResources() {
|
|
35
|
+
return {
|
|
36
|
+
resources: STATIC_MCP_RESOURCE_ENTRIES.map((e) => ({
|
|
37
|
+
uri: `${SERVER_NAME}://${e.slug}`,
|
|
38
|
+
name: e.name,
|
|
39
|
+
description: e.description,
|
|
40
|
+
mimeType: e.mimeType,
|
|
41
|
+
})),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async readResource(uri) {
|
|
45
|
+
const uriPattern = new RegExp(`^${SERVER_NAME}://(.+)$`);
|
|
46
|
+
const match = uri.match(uriPattern);
|
|
47
|
+
if (!match) {
|
|
48
|
+
throw new McpError(ErrorCode.InvalidRequest, `Invalid resource URI format: ${uri}`);
|
|
49
|
+
}
|
|
50
|
+
const resource = match[1];
|
|
51
|
+
if (!resource || !this.allowedResources.has(resource)) {
|
|
52
|
+
throw new McpError(ErrorCode.InvalidRequest, `Resource not found: ${resource}`);
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
// In the built app, resources are in build/resources
|
|
56
|
+
// In development, they're in src/resources
|
|
57
|
+
const resourcePath = path.join(this.__dirname, '../resources', `${resource}.md`);
|
|
58
|
+
const content = await fs.promises.readFile(resourcePath, 'utf8');
|
|
59
|
+
return {
|
|
60
|
+
contents: [
|
|
61
|
+
{
|
|
62
|
+
uri,
|
|
63
|
+
mimeType: 'text/markdown',
|
|
64
|
+
text: content,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
throw new McpError(ErrorCode.InvalidRequest, `Resource not found: ${resource}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simplified base tool handler
|
|
3
|
+
*/
|
|
4
|
+
import apiClient from '../api/client.js';
|
|
5
|
+
import { config } from '../config/index.js';
|
|
6
|
+
import { ErrorHandler, ValidationError } from '../utils/errors.js';
|
|
7
|
+
import { logger } from '../utils/logger.js';
|
|
8
|
+
export class BaseToolHandler {
|
|
9
|
+
/**
|
|
10
|
+
* Create a standardized success result
|
|
11
|
+
*/
|
|
12
|
+
createSuccess(data, message) {
|
|
13
|
+
let serializeStructured;
|
|
14
|
+
try {
|
|
15
|
+
serializeStructured = config?.server?.serialize_structured_to_content ?? false;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
serializeStructured = false;
|
|
19
|
+
}
|
|
20
|
+
const textContent = serializeStructured && data !== undefined && data !== null
|
|
21
|
+
? message
|
|
22
|
+
? `${message}\n${this.safeStringify(data)}`
|
|
23
|
+
: this.safeStringify(data)
|
|
24
|
+
: message || 'Operation completed successfully';
|
|
25
|
+
return {
|
|
26
|
+
content: [
|
|
27
|
+
{
|
|
28
|
+
type: 'text',
|
|
29
|
+
text: textContent,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
structuredContent: {
|
|
33
|
+
data: data ?? null,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create a standardized error result
|
|
39
|
+
*/
|
|
40
|
+
createError(message, details) {
|
|
41
|
+
return {
|
|
42
|
+
content: [
|
|
43
|
+
{
|
|
44
|
+
type: 'text',
|
|
45
|
+
text: `Error: ${message}`,
|
|
46
|
+
},
|
|
47
|
+
...(details
|
|
48
|
+
? [
|
|
49
|
+
{
|
|
50
|
+
type: 'text',
|
|
51
|
+
text: JSON.stringify(details, null, 2),
|
|
52
|
+
},
|
|
53
|
+
]
|
|
54
|
+
: []),
|
|
55
|
+
],
|
|
56
|
+
isError: true,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Validate required parameters
|
|
61
|
+
*/
|
|
62
|
+
validateRequired(params, required) {
|
|
63
|
+
const missing = required.filter((field) => {
|
|
64
|
+
const value = params[field];
|
|
65
|
+
return value === undefined || value === null || value === '';
|
|
66
|
+
});
|
|
67
|
+
if (missing.length > 0) {
|
|
68
|
+
throw new ValidationError(`Missing required parameters: ${missing.join(', ')}`, 'parameter validation');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Safely make API calls with error handling
|
|
73
|
+
*/
|
|
74
|
+
async apiCall(endpoint, method = 'GET', data, options) {
|
|
75
|
+
return ErrorHandler.handleAsync(async () => {
|
|
76
|
+
const response = await apiClient.request(endpoint, {
|
|
77
|
+
method,
|
|
78
|
+
body: data,
|
|
79
|
+
queryParams: options?.queryParams || {},
|
|
80
|
+
});
|
|
81
|
+
return response.data;
|
|
82
|
+
}, `API ${method} ${endpoint}`);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Handle tool execution with standardized error handling
|
|
86
|
+
*/
|
|
87
|
+
async executeToolHandler(handler) {
|
|
88
|
+
try {
|
|
89
|
+
return await handler();
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
ErrorHandler.logError(error, 'tool execution');
|
|
93
|
+
if (error instanceof ValidationError) {
|
|
94
|
+
return this.createError(error.message);
|
|
95
|
+
}
|
|
96
|
+
const message = error instanceof Error ? error.message : 'Internal error';
|
|
97
|
+
return this.createError(`Tool execution failed: ${message}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Safe JSON formatting
|
|
102
|
+
*/
|
|
103
|
+
safeStringify(data) {
|
|
104
|
+
try {
|
|
105
|
+
return JSON.stringify(data, null, 2);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
logger.warn('Failed to stringify data', 'TOOLS', { error });
|
|
109
|
+
return '[Unable to format data]';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Filter object fields based on allowlist
|
|
114
|
+
*/
|
|
115
|
+
filterFields(objects, fields) {
|
|
116
|
+
return objects.map((obj) => {
|
|
117
|
+
const filtered = {};
|
|
118
|
+
fields.forEach((field) => {
|
|
119
|
+
if (field in obj) {
|
|
120
|
+
filtered[field] = obj[field];
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
return filtered;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Parse and validate array parameter
|
|
128
|
+
*/
|
|
129
|
+
parseArrayParam(value, paramName) {
|
|
130
|
+
if (!value) {
|
|
131
|
+
throw new ValidationError(`${paramName} is required`);
|
|
132
|
+
}
|
|
133
|
+
if (!Array.isArray(value)) {
|
|
134
|
+
throw new ValidationError(`${paramName} must be an array`);
|
|
135
|
+
}
|
|
136
|
+
if (value.length === 0) {
|
|
137
|
+
throw new ValidationError(`${paramName} cannot be empty`);
|
|
138
|
+
}
|
|
139
|
+
return value.map((v) => String(v));
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Parse and validate numeric parameter
|
|
143
|
+
*/
|
|
144
|
+
parseNumberParam(value, paramName, required = true) {
|
|
145
|
+
if (value === undefined || value === null) {
|
|
146
|
+
if (required) {
|
|
147
|
+
throw new ValidationError(`${paramName} is required`);
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
const num = Number(value);
|
|
152
|
+
if (isNaN(num)) {
|
|
153
|
+
throw new ValidationError(`${paramName} must be a valid number`);
|
|
154
|
+
}
|
|
155
|
+
return num;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
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
|
+
];
|
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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';
|