@lovelybunch/mcp 1.0.75-alpha.8 → 1.0.75
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/dist/architecture-context-tool.d.ts +1 -1
- package/dist/architecture-context-tool.d.ts.map +1 -1
- package/dist/events-tool.d.ts +1 -1
- package/dist/events-tool.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/knowledge-tool.d.ts +1 -1
- package/dist/knowledge-tool.d.ts.map +1 -1
- package/dist/knowledge-tool.js +2 -2
- package/dist/knowledge-tool.js.map +1 -1
- package/dist/project-context-tool.d.ts +1 -1
- package/dist/project-context-tool.d.ts.map +1 -1
- package/dist/resources-tool.d.ts +3 -0
- package/dist/resources-tool.d.ts.map +1 -0
- package/dist/resources-tool.js +76 -0
- package/dist/resources-tool.js.map +1 -0
- package/dist/role-context-tool.d.ts +1 -1
- package/dist/role-context-tool.d.ts.map +1 -1
- package/dist/tasks-tool.d.ts +31 -0
- package/dist/tasks-tool.d.ts.map +1 -0
- package/dist/tasks-tool.js +153 -0
- package/dist/tasks-tool.js.map +1 -0
- package/package.json +3 -3
- package/src/architecture-context-tool.ts +1 -1
- package/src/events-tool.ts +1 -1
- package/src/index.ts +3 -2
- package/src/knowledge-tool.ts +3 -3
- package/src/project-context-tool.ts +1 -1
- package/src/resources-tool.test.ts +137 -0
- package/src/resources-tool.ts +77 -0
- package/src/role-context-tool.ts +1 -1
- package/src/tasks-tool.test.ts +139 -0
- package/src/tasks-tool.ts +170 -0
- package/src/proposals-tool.test.ts +0 -109
- package/src/proposals-tool.ts +0 -175
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for resources tool definition
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { resourcesTool } from './resources-tool.js';
|
|
7
|
+
|
|
8
|
+
describe('resourcesTool', () => {
|
|
9
|
+
it('should have correct name', () => {
|
|
10
|
+
expect(resourcesTool.name).toBe('resources');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should have a description', () => {
|
|
14
|
+
expect(resourcesTool.description).toBeTruthy();
|
|
15
|
+
expect(resourcesTool.description).toContain('resource');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should have correct parameter structure', () => {
|
|
19
|
+
expect(resourcesTool.parameters.type).toBe('object');
|
|
20
|
+
expect(resourcesTool.parameters.required).toContain('operation');
|
|
21
|
+
expect(resourcesTool.parameters.additionalProperties).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('operations', () => {
|
|
25
|
+
it('should include all six operations', () => {
|
|
26
|
+
const ops = resourcesTool.parameters.properties.operation.enum;
|
|
27
|
+
expect(ops).toContain('list');
|
|
28
|
+
expect(ops).toContain('get');
|
|
29
|
+
expect(ops).toContain('generate_image');
|
|
30
|
+
expect(ops).toContain('generate_audio');
|
|
31
|
+
expect(ops).toContain('generate_video');
|
|
32
|
+
expect(ops).toContain('add_from_url');
|
|
33
|
+
expect(ops).toHaveLength(6);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('READ parameters', () => {
|
|
38
|
+
it('should have query parameter for list filtering', () => {
|
|
39
|
+
expect(resourcesTool.parameters.properties.query).toBeDefined();
|
|
40
|
+
expect(resourcesTool.parameters.properties.query.type).toBe('string');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should have type_filter parameter', () => {
|
|
44
|
+
expect(resourcesTool.parameters.properties.type_filter).toBeDefined();
|
|
45
|
+
expect(resourcesTool.parameters.properties.type_filter.type).toBe('string');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should have resource_id parameter for get', () => {
|
|
49
|
+
expect(resourcesTool.parameters.properties.resource_id).toBeDefined();
|
|
50
|
+
expect(resourcesTool.parameters.properties.resource_id.type).toBe('string');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('generate_image parameters', () => {
|
|
55
|
+
it('should have prompt parameter', () => {
|
|
56
|
+
expect(resourcesTool.parameters.properties.prompt).toBeDefined();
|
|
57
|
+
expect(resourcesTool.parameters.properties.prompt.type).toBe('string');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should have model parameter with valid enum values', () => {
|
|
61
|
+
const modelParam = resourcesTool.parameters.properties.model;
|
|
62
|
+
expect(modelParam).toBeDefined();
|
|
63
|
+
expect(modelParam.type).toBe('string');
|
|
64
|
+
expect(modelParam.enum).toContain('nano-banana-pro');
|
|
65
|
+
expect(modelParam.enum).toContain('flux-2-dev');
|
|
66
|
+
expect(modelParam.enum).toContain('flux-2-pro');
|
|
67
|
+
expect(modelParam.enum).toContain('gpt-image-1.5');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should have aspect_ratio parameter', () => {
|
|
71
|
+
expect(resourcesTool.parameters.properties.aspect_ratio).toBeDefined();
|
|
72
|
+
expect(resourcesTool.parameters.properties.aspect_ratio.type).toBe('string');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('generate_audio parameters', () => {
|
|
77
|
+
it('should have text parameter', () => {
|
|
78
|
+
expect(resourcesTool.parameters.properties.text).toBeDefined();
|
|
79
|
+
expect(resourcesTool.parameters.properties.text.type).toBe('string');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should have voice parameter', () => {
|
|
83
|
+
expect(resourcesTool.parameters.properties.voice).toBeDefined();
|
|
84
|
+
expect(resourcesTool.parameters.properties.voice.type).toBe('string');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('generate_video parameters', () => {
|
|
89
|
+
it('should have duration parameter', () => {
|
|
90
|
+
expect(resourcesTool.parameters.properties.duration).toBeDefined();
|
|
91
|
+
expect(resourcesTool.parameters.properties.duration.type).toBe('number');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should have resolution parameter', () => {
|
|
95
|
+
expect(resourcesTool.parameters.properties.resolution).toBeDefined();
|
|
96
|
+
expect(resourcesTool.parameters.properties.resolution.type).toBe('string');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('add_from_url parameters', () => {
|
|
101
|
+
it('should have url parameter', () => {
|
|
102
|
+
expect(resourcesTool.parameters.properties.url).toBeDefined();
|
|
103
|
+
expect(resourcesTool.parameters.properties.url.type).toBe('string');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('shared metadata parameters', () => {
|
|
108
|
+
it('should have tags parameter', () => {
|
|
109
|
+
expect(resourcesTool.parameters.properties.tags).toBeDefined();
|
|
110
|
+
expect(resourcesTool.parameters.properties.tags.type).toBe('string');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should have description parameter', () => {
|
|
114
|
+
expect(resourcesTool.parameters.properties.description).toBeDefined();
|
|
115
|
+
expect(resourcesTool.parameters.properties.description.type).toBe('string');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('schema validation compatibility', () => {
|
|
120
|
+
it('should only require operation (all other params are optional)', () => {
|
|
121
|
+
expect(resourcesTool.parameters.required).toEqual(['operation']);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should disallow additional properties', () => {
|
|
125
|
+
expect(resourcesTool.parameters.additionalProperties).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should have descriptions for all parameters', () => {
|
|
129
|
+
const props = resourcesTool.parameters.properties;
|
|
130
|
+
for (const [key, value] of Object.entries(props)) {
|
|
131
|
+
if (key !== 'operation') {
|
|
132
|
+
expect((value as any).description, `${key} should have a description`).toBeTruthy();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { MCPTool } from './tasks-tool.js'
|
|
2
|
+
|
|
3
|
+
export const resourcesTool: MCPTool = {
|
|
4
|
+
name: 'resources',
|
|
5
|
+
description: 'Manage media resources: generate images, audio, and video via AI, save files from URLs, and browse existing resources. Generation operations auto-save to the resource library with thumbnails.',
|
|
6
|
+
parameters: {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
operation: {
|
|
10
|
+
type: 'string',
|
|
11
|
+
enum: ['list', 'get', 'generate_image', 'generate_audio', 'generate_video', 'add_from_url'],
|
|
12
|
+
description: 'The resource operation to execute'
|
|
13
|
+
},
|
|
14
|
+
// READ params
|
|
15
|
+
query: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'Search filter for list operation — matches name, description, or tags'
|
|
18
|
+
},
|
|
19
|
+
type_filter: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'Filter by media type prefix for list operation (e.g. "image", "audio", "video")'
|
|
22
|
+
},
|
|
23
|
+
resource_id: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Resource ID for get operation (e.g. "res-1234567890-abc123def")'
|
|
26
|
+
},
|
|
27
|
+
// generate_image params
|
|
28
|
+
prompt: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'Text prompt describing the image or video to generate'
|
|
31
|
+
},
|
|
32
|
+
model: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
enum: ['nano-banana-pro', 'flux-2-dev', 'flux-2-pro', 'gpt-image-1.5'],
|
|
35
|
+
description: 'AI model for image generation. Default: nano-banana-pro. Use gpt-image-1.5 for transparent backgrounds.'
|
|
36
|
+
},
|
|
37
|
+
aspect_ratio: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
description: 'Aspect ratio for image/video (e.g. "1:1", "16:9", "9:16", "4:3", "3:2"). Default: 16:9 for images, 16:9 for video.'
|
|
40
|
+
},
|
|
41
|
+
// generate_audio params
|
|
42
|
+
text: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description: 'Text to convert to speech for audio generation'
|
|
45
|
+
},
|
|
46
|
+
voice: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
description: 'Voice ID for audio generation. Default: English_Trustworth_Man'
|
|
49
|
+
},
|
|
50
|
+
// generate_video params (prompt is reused)
|
|
51
|
+
duration: {
|
|
52
|
+
type: 'number',
|
|
53
|
+
description: 'Video duration in seconds, 5-16. Default: 8'
|
|
54
|
+
},
|
|
55
|
+
resolution: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
description: 'Video resolution (480p, 720p, 1080p). Default: 720p'
|
|
58
|
+
},
|
|
59
|
+
// add_from_url params
|
|
60
|
+
url: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
description: 'URL to download and save as a resource'
|
|
63
|
+
},
|
|
64
|
+
// Shared metadata
|
|
65
|
+
tags: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
description: 'Comma-separated tags for the resource'
|
|
68
|
+
},
|
|
69
|
+
description: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
description: 'Description of the resource'
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
required: ['operation'],
|
|
75
|
+
additionalProperties: false
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/role-context-tool.ts
CHANGED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for tasks tool utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
validateTaskData,
|
|
8
|
+
createToolCall,
|
|
9
|
+
listTasksTool,
|
|
10
|
+
tasksTool,
|
|
11
|
+
tasksReadOnlyTool,
|
|
12
|
+
tasksFullTool,
|
|
13
|
+
} from './tasks-tool.js';
|
|
14
|
+
|
|
15
|
+
describe('validateTaskData', () => {
|
|
16
|
+
it('should extract intent and content', () => {
|
|
17
|
+
const result = validateTaskData({
|
|
18
|
+
intent: 'Add dark mode',
|
|
19
|
+
content: 'Implement dark mode toggle',
|
|
20
|
+
});
|
|
21
|
+
expect(result.intent).toBe('Add dark mode');
|
|
22
|
+
expect(result.content).toBe('Implement dark mode toggle');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should extract all supported fields', () => {
|
|
26
|
+
const result = validateTaskData({
|
|
27
|
+
intent: 'Test',
|
|
28
|
+
content: 'Body',
|
|
29
|
+
author: 'engineer',
|
|
30
|
+
planSteps: ['step1'],
|
|
31
|
+
status: 'draft',
|
|
32
|
+
metadata: { foo: 'bar' },
|
|
33
|
+
});
|
|
34
|
+
expect(result.intent).toBe('Test');
|
|
35
|
+
expect(result.content).toBe('Body');
|
|
36
|
+
expect(result.author).toBe('engineer');
|
|
37
|
+
expect(result.planSteps).toEqual(['step1']);
|
|
38
|
+
expect(result.status).toBe('draft');
|
|
39
|
+
expect(result.metadata).toEqual({ foo: 'bar' });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should return empty object for empty input', () => {
|
|
43
|
+
const result = validateTaskData({});
|
|
44
|
+
expect(result).toEqual({});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should ignore unknown fields', () => {
|
|
48
|
+
const result = validateTaskData({
|
|
49
|
+
intent: 'Test',
|
|
50
|
+
unknownField: 'ignored',
|
|
51
|
+
});
|
|
52
|
+
expect(result.intent).toBe('Test');
|
|
53
|
+
expect((result as any).unknownField).toBeUndefined();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('createToolCall', () => {
|
|
58
|
+
it('should create tool call with operation and args', () => {
|
|
59
|
+
const call = createToolCall('list', { filters: { status: 'draft' } });
|
|
60
|
+
expect(call.name).toBe('tasks');
|
|
61
|
+
expect(call.arguments).toEqual({
|
|
62
|
+
operation: 'list',
|
|
63
|
+
filters: { status: 'draft' },
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should create tool call for get operation', () => {
|
|
68
|
+
const call = createToolCall('get', { id: 'cp-123' });
|
|
69
|
+
expect(call.name).toBe('tasks');
|
|
70
|
+
expect(call.arguments).toEqual({
|
|
71
|
+
operation: 'get',
|
|
72
|
+
id: 'cp-123',
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should merge operation with spread args', () => {
|
|
77
|
+
const call = createToolCall('create', { task: { intent: 'Test' } });
|
|
78
|
+
expect(call.arguments.operation).toBe('create');
|
|
79
|
+
expect(call.arguments.task).toEqual({ intent: 'Test' });
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('tasks tools', () => {
|
|
84
|
+
it('should have listTasksTool with correct structure', () => {
|
|
85
|
+
expect(listTasksTool.name).toBe('list_tasks');
|
|
86
|
+
expect(listTasksTool.parameters.type).toBe('object');
|
|
87
|
+
expect(listTasksTool.parameters.properties.filters).toBeDefined();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should have tasksTool with list, get, create, update, delete operations', () => {
|
|
91
|
+
expect(tasksTool.name).toBe('tasks');
|
|
92
|
+
const ops = tasksTool.parameters.properties.operation.enum;
|
|
93
|
+
expect(ops).toContain('list');
|
|
94
|
+
expect(ops).toContain('get');
|
|
95
|
+
expect(ops).toContain('create');
|
|
96
|
+
expect(ops).toContain('update');
|
|
97
|
+
expect(ops).toContain('delete');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should have tasksReadOnlyTool with only list and get', () => {
|
|
101
|
+
const ops = tasksReadOnlyTool.parameters.properties.operation.enum;
|
|
102
|
+
expect(ops).toEqual(['list', 'get']);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should have tasksFullTool with task and updates', () => {
|
|
106
|
+
expect(tasksFullTool.parameters.properties.task).toBeDefined();
|
|
107
|
+
expect(tasksFullTool.parameters.properties.updates).toBeDefined();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should mention summaries in tool descriptions to clarify list returns lightweight data', () => {
|
|
111
|
+
expect(tasksFullTool.description).toContain('summaries');
|
|
112
|
+
expect(tasksTool.description).toContain('summaries');
|
|
113
|
+
expect(tasksReadOnlyTool.description).toContain('summaries');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should mention using get for full details in tool descriptions', () => {
|
|
117
|
+
expect(tasksFullTool.description).toContain('get');
|
|
118
|
+
expect(tasksTool.description).toContain('get');
|
|
119
|
+
expect(tasksReadOnlyTool.description).toContain('get');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should mention default limit in filters description', () => {
|
|
123
|
+
expect(tasksFullTool.parameters.properties.filters.description).toContain('limit=20');
|
|
124
|
+
expect(tasksTool.parameters.properties.filters.description).toContain('limit=20');
|
|
125
|
+
expect(tasksReadOnlyTool.parameters.properties.filters.description).toContain('limit=20');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should expose limit in the filters properties via JSON schema', () => {
|
|
129
|
+
// Verify that the auto-generated filter properties include a limit field
|
|
130
|
+
const fullFilters = tasksFullTool.parameters.properties.filters.properties;
|
|
131
|
+
expect(fullFilters).toHaveProperty('limit');
|
|
132
|
+
|
|
133
|
+
const readOnlyFilters = tasksReadOnlyTool.parameters.properties.filters.properties;
|
|
134
|
+
expect(readOnlyFilters).toHaveProperty('limit');
|
|
135
|
+
|
|
136
|
+
const standardFilters = tasksTool.parameters.properties.filters.properties;
|
|
137
|
+
expect(standardFilters).toHaveProperty('limit');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { Task } from '@lovelybunch/types'
|
|
2
|
+
import { taskJsonSchemas } from '@lovelybunch/core'
|
|
3
|
+
|
|
4
|
+
export interface MCPTool {
|
|
5
|
+
name: string
|
|
6
|
+
description: string
|
|
7
|
+
parameters: {
|
|
8
|
+
type: 'object'
|
|
9
|
+
properties: Record<string, any>
|
|
10
|
+
required?: string[]
|
|
11
|
+
additionalProperties?: boolean
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MCPToolCall {
|
|
16
|
+
name: string
|
|
17
|
+
arguments: Record<string, any>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Use pre-computed JSON schemas from core
|
|
21
|
+
const filtersProperties = taskJsonSchemas.filters
|
|
22
|
+
const createProperties = taskJsonSchemas.create
|
|
23
|
+
const updateProperties = taskJsonSchemas.update
|
|
24
|
+
const createRequired = taskJsonSchemas.createRequired
|
|
25
|
+
|
|
26
|
+
export const listTasksTool: MCPTool = {
|
|
27
|
+
name: "list_tasks",
|
|
28
|
+
description: "List all tasks with metadata only (title, ID, status, priority, tags)",
|
|
29
|
+
parameters: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
filters: {
|
|
33
|
+
type: "object",
|
|
34
|
+
description: "Optional filters for the task list",
|
|
35
|
+
properties: filtersProperties
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const tasksTool: MCPTool = {
|
|
42
|
+
name: "tasks",
|
|
43
|
+
description: "Manage tasks - create, read, update, delete tasks. IMPORTANT: When searching for tasks by topic or keyword, always use filters.search to filter results server-side. The 'list' operation returns lightweight summaries — use 'get' with a task ID for full details.",
|
|
44
|
+
parameters: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
operation: {
|
|
48
|
+
type: "string",
|
|
49
|
+
enum: ["list", "get", "create", "update", "delete"],
|
|
50
|
+
description: "The operation to perform on tasks"
|
|
51
|
+
},
|
|
52
|
+
id: {
|
|
53
|
+
type: "string",
|
|
54
|
+
description: "Task ID (required for get, update, delete operations)"
|
|
55
|
+
},
|
|
56
|
+
// Filters for list operation - auto-generated from Zod schema
|
|
57
|
+
filters: {
|
|
58
|
+
type: "object",
|
|
59
|
+
description: "Filters for list operation. Defaults to limit=20. Use search to find tasks by keyword — results are filtered server-side for accuracy.",
|
|
60
|
+
properties: filtersProperties
|
|
61
|
+
},
|
|
62
|
+
// Task data for create operation - auto-generated from Zod schema
|
|
63
|
+
task: {
|
|
64
|
+
type: "object",
|
|
65
|
+
description: "Task data for create/update operations. For create: intent and content are required. For update: at least one field is required.",
|
|
66
|
+
properties: createProperties,
|
|
67
|
+
required: [...createRequired]
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
required: ["operation"]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Read-only version of the tasks tool for AI assistant.
|
|
76
|
+
* Only exposes list and get operations - no create/update/delete.
|
|
77
|
+
* Creating tasks should be done by coding agents (Claude Code, Cursor, etc.) or via the UI.
|
|
78
|
+
*/
|
|
79
|
+
export const tasksReadOnlyTool: MCPTool = {
|
|
80
|
+
name: "tasks",
|
|
81
|
+
description: "READ-ONLY: Search and read tasks. You can ONLY use 'list' and 'get' operations. You CANNOT create, update, or delete tasks — that should be done by coding agents (Claude Code, Cursor, etc.) which have broader codebase context, or via the Tasks UI. The 'list' operation returns lightweight summaries — use 'get' with a task ID for full details.",
|
|
82
|
+
parameters: {
|
|
83
|
+
type: "object",
|
|
84
|
+
properties: {
|
|
85
|
+
operation: {
|
|
86
|
+
type: "string",
|
|
87
|
+
enum: ["list", "get"],
|
|
88
|
+
description: "ONLY 'list' or 'get' are allowed. 'list' to search/browse tasks (returns summaries), 'get' to retrieve a specific task by ID (returns full details)"
|
|
89
|
+
},
|
|
90
|
+
id: {
|
|
91
|
+
type: "string",
|
|
92
|
+
description: "Task ID (required for get operation)"
|
|
93
|
+
},
|
|
94
|
+
// Filters auto-generated from Zod schema
|
|
95
|
+
filters: {
|
|
96
|
+
type: "object",
|
|
97
|
+
description: "Filters for list operation. Defaults to limit=20. Use search to find tasks by keyword.",
|
|
98
|
+
properties: filtersProperties
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
required: ["operation"]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Full CRUD tasks tool that includes create and update operations.
|
|
107
|
+
* Uses Zod schemas for validation.
|
|
108
|
+
*/
|
|
109
|
+
export const tasksFullTool: MCPTool = {
|
|
110
|
+
name: "tasks",
|
|
111
|
+
description: "Full CRUD for tasks - create, read, update, delete. Use this when you need to create or modify tasks via MCP. The 'list' operation returns lightweight summaries (id, title, status, priority, tags, author, dates) — use 'get' with a specific task ID to retrieve full task details including content and plan steps.",
|
|
112
|
+
parameters: {
|
|
113
|
+
type: "object",
|
|
114
|
+
properties: {
|
|
115
|
+
operation: {
|
|
116
|
+
type: "string",
|
|
117
|
+
enum: ["list", "get", "create", "update", "delete"],
|
|
118
|
+
description: "The operation to perform"
|
|
119
|
+
},
|
|
120
|
+
id: {
|
|
121
|
+
type: "string",
|
|
122
|
+
description: "Task ID (required for get, update, delete operations)"
|
|
123
|
+
},
|
|
124
|
+
filters: {
|
|
125
|
+
type: "object",
|
|
126
|
+
description: "Filters for list operation. Defaults to limit=20. Use status, priority, tags, search, or author to narrow results.",
|
|
127
|
+
properties: filtersProperties
|
|
128
|
+
},
|
|
129
|
+
// For create - all fields from CreateTaskInputSchema
|
|
130
|
+
task: {
|
|
131
|
+
type: "object",
|
|
132
|
+
description: "Task data for create operation",
|
|
133
|
+
properties: createProperties,
|
|
134
|
+
required: [...createRequired]
|
|
135
|
+
},
|
|
136
|
+
// For update - partial fields from UpdateTaskInputSchema
|
|
137
|
+
updates: {
|
|
138
|
+
type: "object",
|
|
139
|
+
description: "Fields to update (for update operation). At least one field required.",
|
|
140
|
+
properties: updateProperties
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
required: ["operation"]
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function validateTaskData(data: any): Partial<Task> {
|
|
148
|
+
const task: Partial<Task> = {}
|
|
149
|
+
|
|
150
|
+
if (data.title) task.title = data.title
|
|
151
|
+
if (data.intent) task.intent = data.intent
|
|
152
|
+
if (data.content) task.content = data.content
|
|
153
|
+
if (data.author) task.author = data.author
|
|
154
|
+
if (data.planSteps) task.planSteps = data.planSteps
|
|
155
|
+
if (data.status) task.status = data.status
|
|
156
|
+
if (data.metadata) task.metadata = data.metadata
|
|
157
|
+
if (data.productSpecRef) task.productSpecRef = data.productSpecRef
|
|
158
|
+
|
|
159
|
+
return task
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function createToolCall(operation: string, args: any): MCPToolCall {
|
|
163
|
+
return {
|
|
164
|
+
name: "tasks",
|
|
165
|
+
arguments: {
|
|
166
|
+
operation,
|
|
167
|
+
...args
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for proposals tool utilities
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect } from 'vitest';
|
|
6
|
-
import {
|
|
7
|
-
validateProposalData,
|
|
8
|
-
createToolCall,
|
|
9
|
-
listProposalsTool,
|
|
10
|
-
proposalsTool,
|
|
11
|
-
proposalsReadOnlyTool,
|
|
12
|
-
proposalsFullTool,
|
|
13
|
-
} from './proposals-tool.js';
|
|
14
|
-
|
|
15
|
-
describe('validateProposalData', () => {
|
|
16
|
-
it('should extract intent and content', () => {
|
|
17
|
-
const result = validateProposalData({
|
|
18
|
-
intent: 'Add dark mode',
|
|
19
|
-
content: 'Implement dark mode toggle',
|
|
20
|
-
});
|
|
21
|
-
expect(result.intent).toBe('Add dark mode');
|
|
22
|
-
expect(result.content).toBe('Implement dark mode toggle');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('should extract all supported fields', () => {
|
|
26
|
-
const result = validateProposalData({
|
|
27
|
-
intent: 'Test',
|
|
28
|
-
content: 'Body',
|
|
29
|
-
author: 'engineer',
|
|
30
|
-
planSteps: ['step1'],
|
|
31
|
-
status: 'draft',
|
|
32
|
-
metadata: { foo: 'bar' },
|
|
33
|
-
});
|
|
34
|
-
expect(result.intent).toBe('Test');
|
|
35
|
-
expect(result.content).toBe('Body');
|
|
36
|
-
expect(result.author).toBe('engineer');
|
|
37
|
-
expect(result.planSteps).toEqual(['step1']);
|
|
38
|
-
expect(result.status).toBe('draft');
|
|
39
|
-
expect(result.metadata).toEqual({ foo: 'bar' });
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should return empty object for empty input', () => {
|
|
43
|
-
const result = validateProposalData({});
|
|
44
|
-
expect(result).toEqual({});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should ignore unknown fields', () => {
|
|
48
|
-
const result = validateProposalData({
|
|
49
|
-
intent: 'Test',
|
|
50
|
-
unknownField: 'ignored',
|
|
51
|
-
});
|
|
52
|
-
expect(result.intent).toBe('Test');
|
|
53
|
-
expect((result as any).unknownField).toBeUndefined();
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe('createToolCall', () => {
|
|
58
|
-
it('should create tool call with operation and args', () => {
|
|
59
|
-
const call = createToolCall('list', { filters: { status: 'draft' } });
|
|
60
|
-
expect(call.name).toBe('change_proposals');
|
|
61
|
-
expect(call.arguments).toEqual({
|
|
62
|
-
operation: 'list',
|
|
63
|
-
filters: { status: 'draft' },
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should create tool call for get operation', () => {
|
|
68
|
-
const call = createToolCall('get', { id: 'cp-123' });
|
|
69
|
-
expect(call.name).toBe('change_proposals');
|
|
70
|
-
expect(call.arguments).toEqual({
|
|
71
|
-
operation: 'get',
|
|
72
|
-
id: 'cp-123',
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('should merge operation with spread args', () => {
|
|
77
|
-
const call = createToolCall('create', { proposal: { intent: 'Test' } });
|
|
78
|
-
expect(call.arguments.operation).toBe('create');
|
|
79
|
-
expect(call.arguments.proposal).toEqual({ intent: 'Test' });
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe('proposals tools', () => {
|
|
84
|
-
it('should have listProposalsTool with correct structure', () => {
|
|
85
|
-
expect(listProposalsTool.name).toBe('list_proposals');
|
|
86
|
-
expect(listProposalsTool.parameters.type).toBe('object');
|
|
87
|
-
expect(listProposalsTool.parameters.properties.filters).toBeDefined();
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('should have proposalsTool with list, get, create, update, delete operations', () => {
|
|
91
|
-
expect(proposalsTool.name).toBe('change_proposals');
|
|
92
|
-
const ops = proposalsTool.parameters.properties.operation.enum;
|
|
93
|
-
expect(ops).toContain('list');
|
|
94
|
-
expect(ops).toContain('get');
|
|
95
|
-
expect(ops).toContain('create');
|
|
96
|
-
expect(ops).toContain('update');
|
|
97
|
-
expect(ops).toContain('delete');
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('should have proposalsReadOnlyTool with only list and get', () => {
|
|
101
|
-
const ops = proposalsReadOnlyTool.parameters.properties.operation.enum;
|
|
102
|
-
expect(ops).toEqual(['list', 'get']);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('should have proposalsFullTool with create and updates', () => {
|
|
106
|
-
expect(proposalsFullTool.parameters.properties.proposal).toBeDefined();
|
|
107
|
-
expect(proposalsFullTool.parameters.properties.updates).toBeDefined();
|
|
108
|
-
});
|
|
109
|
-
});
|