@opsee/mcp-server 0.9.54 → 0.9.56
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/gen/api/v1/cycle_pb.d.ts +57 -0
- package/gen/api/v1/cycle_pb.js +19 -5
- package/package.json +1 -1
- package/src/tools/cycles.ts +23 -58
package/gen/api/v1/cycle_pb.d.ts
CHANGED
|
@@ -164,6 +164,30 @@ export declare type DeleteCycleRequest = Message<"api.v1.DeleteCycleRequest"> &
|
|
|
164
164
|
*/
|
|
165
165
|
export declare const DeleteCycleRequestSchema: GenMessage<DeleteCycleRequest>;
|
|
166
166
|
|
|
167
|
+
/**
|
|
168
|
+
* @generated from message api.v1.CloseCycleRequest
|
|
169
|
+
*/
|
|
170
|
+
export declare type CloseCycleRequest = Message<"api.v1.CloseCycleRequest"> & {
|
|
171
|
+
/**
|
|
172
|
+
* @generated from field: uint32 id = 1;
|
|
173
|
+
*/
|
|
174
|
+
id: number;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Cycle the incomplete tasks are carried forward into. Must belong to the
|
|
178
|
+
* same project. Omit (or 0) to close without moving anything.
|
|
179
|
+
*
|
|
180
|
+
* @generated from field: optional uint32 target_cycle_id = 2;
|
|
181
|
+
*/
|
|
182
|
+
targetCycleId?: number;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Describes the message api.v1.CloseCycleRequest.
|
|
187
|
+
* Use `create(CloseCycleRequestSchema)` to create a new message.
|
|
188
|
+
*/
|
|
189
|
+
export declare const CloseCycleRequestSchema: GenMessage<CloseCycleRequest>;
|
|
190
|
+
|
|
167
191
|
/**
|
|
168
192
|
* @generated from message api.v1.AddCycleResponse
|
|
169
193
|
*/
|
|
@@ -233,6 +257,31 @@ export declare type GetCyclesResponse = Message<"api.v1.GetCyclesResponse"> & {
|
|
|
233
257
|
*/
|
|
234
258
|
export declare const GetCyclesResponseSchema: GenMessage<GetCyclesResponse>;
|
|
235
259
|
|
|
260
|
+
/**
|
|
261
|
+
* @generated from message api.v1.CloseCycleResponse
|
|
262
|
+
*/
|
|
263
|
+
export declare type CloseCycleResponse = Message<"api.v1.CloseCycleResponse"> & {
|
|
264
|
+
/**
|
|
265
|
+
* The closed cycle, with is_active already flipped to false.
|
|
266
|
+
*
|
|
267
|
+
* @generated from field: api.v1.Cycle cycle = 1;
|
|
268
|
+
*/
|
|
269
|
+
cycle?: Cycle;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* How many incomplete tasks were carried into target_cycle_id.
|
|
273
|
+
*
|
|
274
|
+
* @generated from field: int32 moved_task_count = 2;
|
|
275
|
+
*/
|
|
276
|
+
movedTaskCount: number;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Describes the message api.v1.CloseCycleResponse.
|
|
281
|
+
* Use `create(CloseCycleResponseSchema)` to create a new message.
|
|
282
|
+
*/
|
|
283
|
+
export declare const CloseCycleResponseSchema: GenMessage<CloseCycleResponse>;
|
|
284
|
+
|
|
236
285
|
/**
|
|
237
286
|
* @generated from service api.v1.CycleService
|
|
238
287
|
*/
|
|
@@ -277,5 +326,13 @@ export declare const CycleService: GenService<{
|
|
|
277
326
|
input: typeof DeleteCycleRequestSchema;
|
|
278
327
|
output: typeof EmptySchema;
|
|
279
328
|
},
|
|
329
|
+
/**
|
|
330
|
+
* @generated from rpc api.v1.CycleService.CloseCycle
|
|
331
|
+
*/
|
|
332
|
+
closeCycle: {
|
|
333
|
+
methodKind: "unary";
|
|
334
|
+
input: typeof CloseCycleRequestSchema;
|
|
335
|
+
output: typeof CloseCycleResponseSchema;
|
|
336
|
+
},
|
|
280
337
|
}>;
|
|
281
338
|
|
package/gen/api/v1/cycle_pb.js
CHANGED
|
@@ -14,7 +14,7 @@ import { file_api_v1_models } from "./models_pb";
|
|
|
14
14
|
* Describes the file api/v1/cycle.proto.
|
|
15
15
|
*/
|
|
16
16
|
export const file_api_v1_cycle = /*@__PURE__*/
|
|
17
|
-
fileDesc("ChJhcGkvdjEvY3ljbGUucHJvdG8SBmFwaS52MSKIAgoPQWRkQ3ljbGVSZXF1ZXN0EhcKBG5hbWUYASABKAlCCfpCBnIEEAEYZBIbCgRnb2FsGAIgASgJQgj6QgVyAxj0A0gAiAEBEi4KCnN0YXJ0X2RhdGUYAyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEiwKCGVuZF9kYXRlGAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIbCgpwcm9qZWN0X2lkGAUgASgNQgf6QgQqAigBEhEKCWlzX2FjdGl2ZRgGIAEoCBIYCgtkZXNjcmlwdGlvbhgHIAEoCUgBiAEBQgcKBV9nb2FsQg4KDF9kZXNjcmlwdGlvbiKBAgoQRWRpdEN5Y2xlUmVxdWVzdBITCgJpZBgBIAEoDUIH+kIEKgIoARIXCgRuYW1lGAIgASgJQgn6QgZyBBABGGQSGwoEZ29hbBgDIAEoCUII+kIFcgMY9ANIAIgBARIuCgpzdGFydF9kYXRlGAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIsCghlbmRfZGF0ZRgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASEQoJaXNfYWN0aXZlGAYgASgIEhgKC2Rlc2NyaXB0aW9uGAcgASgJSAGIAQFCBwoFX2dvYWxCDgoMX2Rlc2NyaXB0aW9uIh0KD0dldEN5Y2xlUmVxdWVzdBIKCgJpZBgBIAEoDSKQAQoQR2V0Q3ljbGVzUmVxdWVzdBIbCgpwcm9qZWN0X2lkGAEgASgNQgf6QgQqAigBEjAKCnBhZ2luYXRpb24YAiABKAsyEi5hcGkudjEuUGFnaW5hdGlvbkII+
|
|
17
|
+
fileDesc("ChJhcGkvdjEvY3ljbGUucHJvdG8SBmFwaS52MSKIAgoPQWRkQ3ljbGVSZXF1ZXN0EhcKBG5hbWUYASABKAlCCfpCBnIEEAEYZBIbCgRnb2FsGAIgASgJQgj6QgVyAxj0A0gAiAEBEi4KCnN0YXJ0X2RhdGUYAyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEiwKCGVuZF9kYXRlGAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIbCgpwcm9qZWN0X2lkGAUgASgNQgf6QgQqAigBEhEKCWlzX2FjdGl2ZRgGIAEoCBIYCgtkZXNjcmlwdGlvbhgHIAEoCUgBiAEBQgcKBV9nb2FsQg4KDF9kZXNjcmlwdGlvbiKBAgoQRWRpdEN5Y2xlUmVxdWVzdBITCgJpZBgBIAEoDUIH+kIEKgIoARIXCgRuYW1lGAIgASgJQgn6QgZyBBABGGQSGwoEZ29hbBgDIAEoCUII+kIFcgMY9ANIAIgBARIuCgpzdGFydF9kYXRlGAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIsCghlbmRfZGF0ZRgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASEQoJaXNfYWN0aXZlGAYgASgIEhgKC2Rlc2NyaXB0aW9uGAcgASgJSAGIAQFCBwoFX2dvYWxCDgoMX2Rlc2NyaXB0aW9uIh0KD0dldEN5Y2xlUmVxdWVzdBIKCgJpZBgBIAEoDSKQAQoQR2V0Q3ljbGVzUmVxdWVzdBIbCgpwcm9qZWN0X2lkGAEgASgNQgf6QgQqAigBEjAKCnBhZ2luYXRpb24YAiABKAsyEi5hcGkudjEuUGFnaW5hdGlvbkII+kIFigECEAESLQoOZmlsdGVyX29wdGlvbnMYAyABKAsyFS5hcGkudjEuRmlsdGVyT3B0aW9ucyIgChJEZWxldGVDeWNsZVJlcXVlc3QSCgoCaWQYASABKA0iWgoRQ2xvc2VDeWNsZVJlcXVlc3QSEwoCaWQYASABKA1CB/pCBCoCKAESHAoPdGFyZ2V0X2N5Y2xlX2lkGAIgASgNSACIAQFCEgoQX3RhcmdldF9jeWNsZV9pZCIwChBBZGRDeWNsZVJlc3BvbnNlEhwKBWN5Y2xlGAEgASgLMg0uYXBpLnYxLkN5Y2xlIjEKEUVkaXRDeWNsZVJlc3BvbnNlEhwKBWN5Y2xlGAEgASgLMg0uYXBpLnYxLkN5Y2xlIjAKEEdldEN5Y2xlUmVzcG9uc2USHAoFY3ljbGUYASABKAsyDS5hcGkudjEuQ3ljbGUiWgoRR2V0Q3ljbGVzUmVzcG9uc2USHQoGY3ljbGVzGAEgAygLMg0uYXBpLnYxLkN5Y2xlEiYKCnBhZ2luYXRpb24YAiABKAsyEi5hcGkudjEuUGFnaW5hdGlvbiJMChJDbG9zZUN5Y2xlUmVzcG9uc2USHAoFY3ljbGUYASABKAsyDS5hcGkudjEuQ3ljbGUSGAoQbW92ZWRfdGFza19jb3VudBgCIAEoBTLLBAoMQ3ljbGVTZXJ2aWNlElgKCEFkZEN5Y2xlEhcuYXBpLnYxLkFkZEN5Y2xlUmVxdWVzdBoYLmFwaS52MS5BZGRDeWNsZVJlc3BvbnNlIhmC0+STAhM6ASoiDi9hcGkvdjEvY3ljbGVzEmAKCUVkaXRDeWNsZRIYLmFwaS52MS5FZGl0Q3ljbGVSZXF1ZXN0GhkuYXBpLnYxLkVkaXRDeWNsZVJlc3BvbnNlIh6C0+STAhg6ASoaEy9hcGkvdjEvY3ljbGVzL3tpZH0SWgoIR2V0Q3ljbGUSFy5hcGkudjEuR2V0Q3ljbGVSZXF1ZXN0GhguYXBpLnYxLkdldEN5Y2xlUmVzcG9uc2UiG4LT5JMCFRITL2FwaS92MS9jeWNsZXMve2lkfRJYCglHZXRDeWNsZXMSGC5hcGkudjEuR2V0Q3ljbGVzUmVxdWVzdBoZLmFwaS52MS5HZXRDeWNsZXNSZXNwb25zZSIWgtPkkwIQEg4vYXBpL3YxL2N5Y2xlcxJeCgtEZWxldGVDeWNsZRIaLmFwaS52MS5EZWxldGVDeWNsZVJlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiG4LT5JMCFSoTL2FwaS92MS9jeWNsZXMve2lkfRJpCgpDbG9zZUN5Y2xlEhkuYXBpLnYxLkNsb3NlQ3ljbGVSZXF1ZXN0GhouYXBpLnYxLkNsb3NlQ3ljbGVSZXNwb25zZSIkgtPkkwIeOgEqIhkvYXBpL3YxL2N5Y2xlcy97aWR9L2Nsb3NlQm8KCmNvbS5hcGkudjFCCkN5Y2xlUHJvdG9QAVocb3BzZWUvYmFja2VuZC9nZW4vYXBpL3YxO2dlbqICA0FYWKoCBkFwaS5WMcoCBkFwaVxWMeICEkFwaVxWMVxHUEJNZXRhZGF0YeoCB0FwaTo6VjFiBnByb3RvMw", [file_google_protobuf_timestamp, file_google_protobuf_empty, file_google_api_annotations, file_validate_validate, file_api_v1_pagination, file_api_v1_filter, file_api_v1_models]);
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Describes the message api.v1.AddCycleRequest.
|
|
@@ -51,33 +51,47 @@ export const GetCyclesRequestSchema = /*@__PURE__*/
|
|
|
51
51
|
export const DeleteCycleRequestSchema = /*@__PURE__*/
|
|
52
52
|
messageDesc(file_api_v1_cycle, 4);
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Describes the message api.v1.CloseCycleRequest.
|
|
56
|
+
* Use `create(CloseCycleRequestSchema)` to create a new message.
|
|
57
|
+
*/
|
|
58
|
+
export const CloseCycleRequestSchema = /*@__PURE__*/
|
|
59
|
+
messageDesc(file_api_v1_cycle, 5);
|
|
60
|
+
|
|
54
61
|
/**
|
|
55
62
|
* Describes the message api.v1.AddCycleResponse.
|
|
56
63
|
* Use `create(AddCycleResponseSchema)` to create a new message.
|
|
57
64
|
*/
|
|
58
65
|
export const AddCycleResponseSchema = /*@__PURE__*/
|
|
59
|
-
messageDesc(file_api_v1_cycle,
|
|
66
|
+
messageDesc(file_api_v1_cycle, 6);
|
|
60
67
|
|
|
61
68
|
/**
|
|
62
69
|
* Describes the message api.v1.EditCycleResponse.
|
|
63
70
|
* Use `create(EditCycleResponseSchema)` to create a new message.
|
|
64
71
|
*/
|
|
65
72
|
export const EditCycleResponseSchema = /*@__PURE__*/
|
|
66
|
-
messageDesc(file_api_v1_cycle,
|
|
73
|
+
messageDesc(file_api_v1_cycle, 7);
|
|
67
74
|
|
|
68
75
|
/**
|
|
69
76
|
* Describes the message api.v1.GetCycleResponse.
|
|
70
77
|
* Use `create(GetCycleResponseSchema)` to create a new message.
|
|
71
78
|
*/
|
|
72
79
|
export const GetCycleResponseSchema = /*@__PURE__*/
|
|
73
|
-
messageDesc(file_api_v1_cycle,
|
|
80
|
+
messageDesc(file_api_v1_cycle, 8);
|
|
74
81
|
|
|
75
82
|
/**
|
|
76
83
|
* Describes the message api.v1.GetCyclesResponse.
|
|
77
84
|
* Use `create(GetCyclesResponseSchema)` to create a new message.
|
|
78
85
|
*/
|
|
79
86
|
export const GetCyclesResponseSchema = /*@__PURE__*/
|
|
80
|
-
messageDesc(file_api_v1_cycle,
|
|
87
|
+
messageDesc(file_api_v1_cycle, 9);
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Describes the message api.v1.CloseCycleResponse.
|
|
91
|
+
* Use `create(CloseCycleResponseSchema)` to create a new message.
|
|
92
|
+
*/
|
|
93
|
+
export const CloseCycleResponseSchema = /*@__PURE__*/
|
|
94
|
+
messageDesc(file_api_v1_cycle, 10);
|
|
81
95
|
|
|
82
96
|
/**
|
|
83
97
|
* @generated from service api.v1.CycleService
|
package/package.json
CHANGED
package/src/tools/cycles.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { create } from "@bufbuild/protobuf";
|
|
3
2
|
import { timestampFromDate } from "@bufbuild/protobuf/wkt";
|
|
4
3
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
4
|
import type { ApiClients } from "../client/api.js";
|
|
6
|
-
import { defaultPagination } from "../client/api.js";
|
|
7
5
|
import { formatCycleList, formatCycle, formatError } from "../utils/format.js";
|
|
8
|
-
import {
|
|
9
|
-
FilterOptionsSchema,
|
|
10
|
-
FilterSchema,
|
|
11
|
-
} from "../../gen/api/v1/filter_pb.js";
|
|
12
6
|
|
|
13
7
|
export function registerCycleTools(
|
|
14
8
|
server: McpServer,
|
|
@@ -132,10 +126,10 @@ export function registerCycleTools(
|
|
|
132
126
|
|
|
133
127
|
server.tool(
|
|
134
128
|
"opsee_close_cycle",
|
|
135
|
-
"Close a cycle (mark inactive) and
|
|
129
|
+
"Close a cycle (mark inactive) and carry its UNFINISHED tasks forward to a target cycle. Tasks in a done/archived column stay behind so the closed cycle keeps an accurate completion record. Pass createNextCycle:true with name/startDate/endDate to also create the next cycle and carry forward into it in the same call. Omit both target options to close without moving anything.",
|
|
136
130
|
{
|
|
137
131
|
cycleId: z.number().int().min(1).describe("ID of the cycle to close"),
|
|
138
|
-
nextCycleId: z.number().int().min(1).optional().describe("Existing cycle to carry
|
|
132
|
+
nextCycleId: z.number().int().min(1).optional().describe("Existing cycle to carry unfinished tasks into. Must be in the same project. Mutually exclusive with createNextCycle."),
|
|
139
133
|
createNextCycle: z.boolean().optional().describe("If true, create the next cycle in this project (requires name/startDate/endDate) and carry tasks into it"),
|
|
140
134
|
nextCycleName: z.string().optional().describe("Name of the cycle to create when createNextCycle:true"),
|
|
141
135
|
nextCycleStartDate: z.string().optional().describe("ISO 8601 start date when createNextCycle:true"),
|
|
@@ -146,14 +140,9 @@ export function registerCycleTools(
|
|
|
146
140
|
try {
|
|
147
141
|
const clients = getClients();
|
|
148
142
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return { content: [{ type: "text", text: "Cycle not found." }] };
|
|
153
|
-
}
|
|
154
|
-
const projectId = existing.projectId;
|
|
155
|
-
|
|
156
|
-
// Resolve the target cycle for carry-forward.
|
|
143
|
+
// Resolve the target cycle for carry-forward. Creating one still needs a
|
|
144
|
+
// round-trip; the close itself is a single backend call that filters the
|
|
145
|
+
// unfinished tasks, moves them, and deactivates the cycle in one place.
|
|
157
146
|
let targetCycleId: number | undefined = nextCycleId;
|
|
158
147
|
let createdNextCycle: { id: number; name: string } | undefined;
|
|
159
148
|
if (createNextCycle) {
|
|
@@ -163,8 +152,15 @@ export function registerCycleTools(
|
|
|
163
152
|
if (!nextCycleName || !nextCycleStartDate || !nextCycleEndDate) {
|
|
164
153
|
return { content: [{ type: "text", text: "createNextCycle requires nextCycleName, nextCycleStartDate, nextCycleEndDate." }], isError: true };
|
|
165
154
|
}
|
|
155
|
+
|
|
156
|
+
const cycleRes = await clients.cycles.getCycle({ id: cycleId });
|
|
157
|
+
const existing = cycleRes.cycle;
|
|
158
|
+
if (!existing) {
|
|
159
|
+
return { content: [{ type: "text", text: "Cycle not found." }] };
|
|
160
|
+
}
|
|
161
|
+
|
|
166
162
|
const addRes = await clients.cycles.addCycle({
|
|
167
|
-
projectId,
|
|
163
|
+
projectId: existing.projectId,
|
|
168
164
|
name: nextCycleName,
|
|
169
165
|
goal: "",
|
|
170
166
|
startDate: timestampFromDate(new Date(nextCycleStartDate)),
|
|
@@ -179,49 +175,18 @@ export function registerCycleTools(
|
|
|
179
175
|
targetCycleId = addRes.cycle.id;
|
|
180
176
|
}
|
|
181
177
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const tasksRes = await clients.tasks.getTasks({
|
|
186
|
-
projectId,
|
|
187
|
-
pagination: defaultPagination(1, 500),
|
|
188
|
-
filterOptions: create(FilterOptionsSchema, {
|
|
189
|
-
filters: [create(FilterSchema, { key: "CycleId", value: String(cycleId) })],
|
|
190
|
-
}),
|
|
191
|
-
labelIds: [],
|
|
192
|
-
});
|
|
193
|
-
const taskIds = (tasksRes.tasks ?? []).map((t: { id: number }) => t.id);
|
|
194
|
-
if (taskIds.length > 0) {
|
|
195
|
-
const bulkRes = await clients.tasks.bulkEditTasks({
|
|
196
|
-
projectId,
|
|
197
|
-
taskIds,
|
|
198
|
-
cycleId: targetCycleId,
|
|
199
|
-
repositoryIds: [],
|
|
200
|
-
labelIds: [],
|
|
201
|
-
});
|
|
202
|
-
carriedForward = bulkRes.updatedCount;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Close the cycle by marking it inactive.
|
|
207
|
-
const editRes = await clients.cycles.editCycle({
|
|
208
|
-
id: existing.id,
|
|
209
|
-
name: existing.name,
|
|
210
|
-
goal: existing.goal,
|
|
211
|
-
startDate: existing.startDate,
|
|
212
|
-
endDate: existing.endDate,
|
|
213
|
-
isActive: false,
|
|
214
|
-
description: existing.description,
|
|
215
|
-
});
|
|
216
|
-
if (!editRes.cycle) {
|
|
217
|
-
return { content: [{ type: "text", text: "Failed to close cycle. Carry-forward may have partially applied; check opsee_list_tasks." }], isError: true };
|
|
178
|
+
const res = await clients.cycles.closeCycle({ id: cycleId, targetCycleId });
|
|
179
|
+
if (!res.cycle) {
|
|
180
|
+
return { content: [{ type: "text", text: "Failed to close cycle. The backend returned an empty response (no error). Retry; if it persists, surface the call to the Opsee team." }], isError: true };
|
|
218
181
|
}
|
|
219
182
|
|
|
220
|
-
const lines = [`Cycle "${
|
|
221
|
-
if (
|
|
222
|
-
lines.push(
|
|
223
|
-
|
|
224
|
-
|
|
183
|
+
const lines = [`Cycle "${res.cycle.name}" (ID ${res.cycle.id}) closed.`];
|
|
184
|
+
if (targetCycleId) {
|
|
185
|
+
lines.push(
|
|
186
|
+
res.movedTaskCount > 0
|
|
187
|
+
? `Carried ${res.movedTaskCount} unfinished task(s) forward to cycle ${targetCycleId}${createdNextCycle ? ` ("${createdNextCycle.name}")` : ""}.`
|
|
188
|
+
: "No unfinished tasks to carry forward.",
|
|
189
|
+
);
|
|
225
190
|
}
|
|
226
191
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
227
192
|
} catch (error) {
|