@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.
@@ -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
 
@@ -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+kIFigECEAESLQoOZmlsdGVyX29wdGlvbnMYAyABKAsyFS5hcGkudjEuRmlsdGVyT3B0aW9ucyIgChJEZWxldGVDeWNsZVJlcXVlc3QSCgoCaWQYASABKA0iMAoQQWRkQ3ljbGVSZXNwb25zZRIcCgVjeWNsZRgBIAEoCzINLmFwaS52MS5DeWNsZSIxChFFZGl0Q3ljbGVSZXNwb25zZRIcCgVjeWNsZRgBIAEoCzINLmFwaS52MS5DeWNsZSIwChBHZXRDeWNsZVJlc3BvbnNlEhwKBWN5Y2xlGAEgASgLMg0uYXBpLnYxLkN5Y2xlIloKEUdldEN5Y2xlc1Jlc3BvbnNlEh0KBmN5Y2xlcxgBIAMoCzINLmFwaS52MS5DeWNsZRImCgpwYWdpbmF0aW9uGAIgASgLMhIuYXBpLnYxLlBhZ2luYXRpb24y4AMKDEN5Y2xlU2VydmljZRJYCghBZGRDeWNsZRIXLmFwaS52MS5BZGRDeWNsZVJlcXVlc3QaGC5hcGkudjEuQWRkQ3ljbGVSZXNwb25zZSIZgtPkkwITOgEqIg4vYXBpL3YxL2N5Y2xlcxJgCglFZGl0Q3ljbGUSGC5hcGkudjEuRWRpdEN5Y2xlUmVxdWVzdBoZLmFwaS52MS5FZGl0Q3ljbGVSZXNwb25zZSIegtPkkwIYOgEqGhMvYXBpL3YxL2N5Y2xlcy97aWR9EloKCEdldEN5Y2xlEhcuYXBpLnYxLkdldEN5Y2xlUmVxdWVzdBoYLmFwaS52MS5HZXRDeWNsZVJlc3BvbnNlIhuC0+STAhUSEy9hcGkvdjEvY3ljbGVzL3tpZH0SWAoJR2V0Q3ljbGVzEhguYXBpLnYxLkdldEN5Y2xlc1JlcXVlc3QaGS5hcGkudjEuR2V0Q3ljbGVzUmVzcG9uc2UiFoLT5JMCEBIOL2FwaS92MS9jeWNsZXMSXgoLRGVsZXRlQ3ljbGUSGi5hcGkudjEuRGVsZXRlQ3ljbGVSZXF1ZXN0GhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5IhuC0+STAhUqEy9hcGkvdjEvY3ljbGVzL3tpZH1CbwoKY29tLmFwaS52MUIKQ3ljbGVQcm90b1ABWhxvcHNlZS9iYWNrZW5kL2dlbi9hcGkvdjE7Z2VuogIDQVhYqgIGQXBpLlYxygIGQXBpXFYx4gISQXBpXFYxXEdQQk1ldGFkYXRh6gIHQXBpOjpWMWIGcHJvdG8z", [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]);
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, 5);
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, 6);
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, 7);
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, 8);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opsee/mcp-server",
3
- "version": "0.9.54",
3
+ "version": "0.9.56",
4
4
  "description": "Opsee MCP server — manage projects, tasks, docs, and cycles from AI coding environments",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 optionally carry every remaining task forward to a target cycle. Composes a single bulk_move_to_cycle + EditCycle, so /tpm cycle close becomes one call instead of a loop. Pass createNextCycle:true with name/startDate/endDate to also create the next cycle and carry forward into it in the same call.",
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 remaining tasks into. Mutually exclusive with createNextCycle."),
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
- const cycleRes = await clients.cycles.getCycle({ id: cycleId });
150
- const existing = cycleRes.cycle;
151
- if (!existing) {
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
- // Carry forward all tasks currently in the cycle, if a target was provided.
183
- let carriedForward = 0;
184
- if (targetCycleId) {
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 "${existing.name}" (ID ${existing.id}) closed.`];
221
- if (carriedForward > 0 && targetCycleId) {
222
- lines.push(`Carried ${carriedForward} task(s) forward to cycle ${targetCycleId}${createdNextCycle ? ` ("${createdNextCycle.name}")` : ""}.`);
223
- } else if (targetCycleId) {
224
- lines.push("No tasks to carry forward.");
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) {