@ycadence/mcp 0.1.0 → 0.1.1
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/bin.js +354 -62
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -20,129 +20,370 @@ function readEnv(name, env = processEnv()) {
|
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
// ../sdk/src/
|
|
23
|
+
// ../sdk/src/errors.ts
|
|
24
24
|
class YCadenceError extends Error {
|
|
25
25
|
status;
|
|
26
26
|
code;
|
|
27
27
|
issues;
|
|
28
|
-
|
|
28
|
+
requestId;
|
|
29
|
+
details;
|
|
30
|
+
retryAfter;
|
|
31
|
+
method;
|
|
32
|
+
path;
|
|
33
|
+
constructor(status, code, message, issues, extras = {}) {
|
|
29
34
|
super(message);
|
|
30
35
|
this.status = status;
|
|
31
36
|
this.code = code;
|
|
32
37
|
this.issues = issues;
|
|
33
38
|
this.name = "YCadenceError";
|
|
39
|
+
this.requestId = extras.requestId;
|
|
40
|
+
this.details = extras.details;
|
|
41
|
+
this.retryAfter = extras.retryAfter;
|
|
42
|
+
this.method = extras.method;
|
|
43
|
+
this.path = extras.path;
|
|
34
44
|
}
|
|
35
45
|
get retryable() {
|
|
36
|
-
return this.status === 429 || this.status >= 500;
|
|
46
|
+
return this.status === 0 || this.status === 408 || this.status === 429 || this.status >= 500;
|
|
37
47
|
}
|
|
38
48
|
}
|
|
39
49
|
|
|
50
|
+
class YCadenceValidationError extends YCadenceError {
|
|
51
|
+
name = "YCadenceValidationError";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class YCadencePermissionError extends YCadenceError {
|
|
55
|
+
name = "YCadencePermissionError";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
class YCadenceNotFoundError extends YCadenceError {
|
|
59
|
+
name = "YCadenceNotFoundError";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
class YCadenceConflictError extends YCadenceError {
|
|
63
|
+
name = "YCadenceConflictError";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
class YCadencePreconditionError extends YCadenceError {
|
|
67
|
+
name = "YCadencePreconditionError";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
class YCadenceRateLimitError extends YCadenceError {
|
|
71
|
+
name = "YCadenceRateLimitError";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
class YCadenceNetworkError extends YCadenceError {
|
|
75
|
+
name = "YCadenceNetworkError";
|
|
76
|
+
}
|
|
77
|
+
function errorClassFor(status) {
|
|
78
|
+
if (status === 0 || status === 408)
|
|
79
|
+
return YCadenceNetworkError;
|
|
80
|
+
if (status === 400 || status === 422)
|
|
81
|
+
return YCadenceValidationError;
|
|
82
|
+
if (status === 401 || status === 403)
|
|
83
|
+
return YCadencePermissionError;
|
|
84
|
+
if (status === 404)
|
|
85
|
+
return YCadenceNotFoundError;
|
|
86
|
+
if (status === 409)
|
|
87
|
+
return YCadenceConflictError;
|
|
88
|
+
if (status === 412)
|
|
89
|
+
return YCadencePreconditionError;
|
|
90
|
+
if (status === 429)
|
|
91
|
+
return YCadenceRateLimitError;
|
|
92
|
+
return YCadenceError;
|
|
93
|
+
}
|
|
94
|
+
function createError(status, code, message, issues, extras = {}) {
|
|
95
|
+
const ErrorClass = errorClassFor(status);
|
|
96
|
+
return new ErrorClass(status, code, message, issues, extras);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
class RequestIntercepted extends Error {
|
|
100
|
+
request;
|
|
101
|
+
name = "RequestIntercepted";
|
|
102
|
+
constructor(request) {
|
|
103
|
+
super(`${request.method} ${request.url} was not sent.`);
|
|
104
|
+
this.request = request;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ../sdk/src/operations.ts
|
|
109
|
+
var OPERATIONS = {
|
|
110
|
+
health: { method: "GET", path: "/api/health" },
|
|
111
|
+
me: { method: "GET", path: "/api/v1/me" },
|
|
112
|
+
listProjects: { method: "GET", path: "/api/v1/projects" },
|
|
113
|
+
listWorkItems: { method: "GET", path: "/api/v1/projects/{projectId}/work-items" },
|
|
114
|
+
createWorkItem: { method: "POST", path: "/api/v1/projects/{projectId}/work-items" },
|
|
115
|
+
getWorkItem: { method: "GET", path: "/api/v1/work-items/{workItemId}" },
|
|
116
|
+
updateWorkItem: { method: "PATCH", path: "/api/v1/work-items/{workItemId}" },
|
|
117
|
+
deleteWorkItem: { method: "DELETE", path: "/api/v1/work-items/{workItemId}" },
|
|
118
|
+
listStates: { method: "GET", path: "/api/v1/projects/{projectId}/states" },
|
|
119
|
+
listLabels: { method: "GET", path: "/api/v1/projects/{projectId}/labels" },
|
|
120
|
+
createLabel: { method: "POST", path: "/api/v1/projects/{projectId}/labels" },
|
|
121
|
+
listTypes: { method: "GET", path: "/api/v1/projects/{projectId}/types" },
|
|
122
|
+
listCycles: { method: "GET", path: "/api/v1/projects/{projectId}/cycles" },
|
|
123
|
+
createCycle: { method: "POST", path: "/api/v1/projects/{projectId}/cycles" },
|
|
124
|
+
getCycleProgress: { method: "GET", path: "/api/v1/cycles/{cycleId}/progress" },
|
|
125
|
+
addWorkItemsToCycle: { method: "POST", path: "/api/v1/cycles/{cycleId}/work-items" },
|
|
126
|
+
listViews: { method: "GET", path: "/api/v1/projects/{projectId}/views" },
|
|
127
|
+
createView: { method: "POST", path: "/api/v1/projects/{projectId}/views" },
|
|
128
|
+
listIntake: { method: "GET", path: "/api/v1/projects/{projectId}/intake" },
|
|
129
|
+
submitIntake: { method: "POST", path: "/api/v1/projects/{projectId}/intake" },
|
|
130
|
+
triageIntake: { method: "POST", path: "/api/v1/intake-items/{intakeItemId}/triage" },
|
|
131
|
+
previewImport: { method: "POST", path: "/api/v1/projects/{projectId}/imports/preview" },
|
|
132
|
+
startImport: { method: "POST", path: "/api/v1/projects/{projectId}/imports" },
|
|
133
|
+
listImports: { method: "GET", path: "/api/v1/projects/{projectId}/imports" },
|
|
134
|
+
getImport: { method: "GET", path: "/api/v1/imports/{runId}" }
|
|
135
|
+
};
|
|
136
|
+
function isIdempotentMethod(method) {
|
|
137
|
+
return method === "GET" || method === "HEAD";
|
|
138
|
+
}
|
|
139
|
+
function buildPath(template, params = {}) {
|
|
140
|
+
return template.replace(/\{(\w+)\}/g, (_match, name) => {
|
|
141
|
+
const value = params[name];
|
|
142
|
+
if (value === undefined || value === "") {
|
|
143
|
+
throw new TypeError(`Missing path parameter "${name}" for ${template}.`);
|
|
144
|
+
}
|
|
145
|
+
return encodeURIComponent(value);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ../sdk/src/pagination.ts
|
|
150
|
+
async function* paginate(fetchPage, options = {}) {
|
|
151
|
+
let cursor = options.cursor;
|
|
152
|
+
let yielded = 0;
|
|
153
|
+
do {
|
|
154
|
+
const page = await fetchPage(cursor);
|
|
155
|
+
for (const item of page.items) {
|
|
156
|
+
if (options.maxItems !== undefined && yielded >= options.maxItems)
|
|
157
|
+
return;
|
|
158
|
+
yielded += 1;
|
|
159
|
+
yield item;
|
|
160
|
+
}
|
|
161
|
+
cursor = page.nextCursor ?? undefined;
|
|
162
|
+
} while (cursor !== undefined);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ../sdk/src/version.ts
|
|
166
|
+
var SDK_VERSION = "0.2.0";
|
|
167
|
+
|
|
168
|
+
// ../sdk/src/client.ts
|
|
169
|
+
var DEFAULT_RETRY = { retries: 2, baseDelayMs: 250, maxDelayMs: 8000 };
|
|
170
|
+
|
|
40
171
|
class YCadenceClient {
|
|
172
|
+
options;
|
|
41
173
|
baseUrl;
|
|
42
174
|
apiKey;
|
|
43
175
|
identityToken;
|
|
44
176
|
timeoutMs;
|
|
45
177
|
doFetch;
|
|
178
|
+
retry;
|
|
179
|
+
userAgent;
|
|
180
|
+
onResponse;
|
|
181
|
+
sleep;
|
|
46
182
|
constructor(options) {
|
|
183
|
+
this.options = options;
|
|
47
184
|
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
48
185
|
this.apiKey = options.apiKey;
|
|
49
186
|
this.identityToken = options.identityToken;
|
|
50
187
|
this.timeoutMs = options.timeoutMs ?? 30000;
|
|
51
|
-
this.doFetch = options.fetch ?? globalThis.fetch;
|
|
188
|
+
this.doFetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
189
|
+
this.retry = { ...DEFAULT_RETRY, ...options.retry };
|
|
190
|
+
this.userAgent = [options.userAgent, `ycadence-sdk/${SDK_VERSION}`].filter((part) => part !== undefined && part !== "").join(" ");
|
|
191
|
+
this.onResponse = options.onResponse;
|
|
192
|
+
this.sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
193
|
+
}
|
|
194
|
+
withOptions(overrides) {
|
|
195
|
+
return new YCadenceClient({ ...this.options, ...overrides });
|
|
196
|
+
}
|
|
197
|
+
get fetchImplementation() {
|
|
198
|
+
return this.doFetch;
|
|
52
199
|
}
|
|
53
200
|
async health() {
|
|
54
|
-
return this.
|
|
55
|
-
}
|
|
56
|
-
async
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
201
|
+
return this.call("health");
|
|
202
|
+
}
|
|
203
|
+
async me(options) {
|
|
204
|
+
return this.call("me", {}, undefined, undefined, options);
|
|
205
|
+
}
|
|
206
|
+
async listProjects(query = {}, options) {
|
|
207
|
+
return this.call("listProjects", {}, query, undefined, options);
|
|
208
|
+
}
|
|
209
|
+
async listWorkItems(projectId, query = {}, options) {
|
|
210
|
+
return this.call("listWorkItems", { projectId }, {
|
|
211
|
+
filter: query.filter === undefined ? undefined : JSON.stringify(query.filter),
|
|
212
|
+
limit: query.limit,
|
|
213
|
+
cursor: query.cursor,
|
|
214
|
+
state: query.state,
|
|
215
|
+
priority: query.priority
|
|
216
|
+
}, undefined, options);
|
|
217
|
+
}
|
|
218
|
+
iterateWorkItems(projectId, options = {}) {
|
|
219
|
+
return paginate((cursor) => this.listWorkItems(projectId, {
|
|
220
|
+
filter: options.filter,
|
|
221
|
+
limit: options.pageSize ?? 100,
|
|
222
|
+
cursor
|
|
223
|
+
}));
|
|
224
|
+
}
|
|
225
|
+
async createWorkItem(projectId, body, options) {
|
|
226
|
+
return this.call("createWorkItem", { projectId }, undefined, body, options);
|
|
227
|
+
}
|
|
228
|
+
async getWorkItem(workItemId, options) {
|
|
229
|
+
return this.call("getWorkItem", { workItemId }, undefined, undefined, options);
|
|
230
|
+
}
|
|
231
|
+
async updateWorkItem(workItemId, body, options) {
|
|
232
|
+
return this.call("updateWorkItem", { workItemId }, undefined, body, options);
|
|
233
|
+
}
|
|
234
|
+
async deleteWorkItem(workItemId, options) {
|
|
235
|
+
await this.call("deleteWorkItem", { workItemId }, undefined, undefined, options);
|
|
78
236
|
}
|
|
79
|
-
async
|
|
80
|
-
return this.
|
|
237
|
+
async listStates(projectId, options) {
|
|
238
|
+
return this.call("listStates", { projectId }, undefined, undefined, options);
|
|
81
239
|
}
|
|
82
|
-
async
|
|
83
|
-
return this.
|
|
240
|
+
async listLabels(projectId, options) {
|
|
241
|
+
return this.call("listLabels", { projectId }, undefined, undefined, options);
|
|
84
242
|
}
|
|
85
|
-
async
|
|
86
|
-
return this.
|
|
243
|
+
async createLabel(projectId, body, options) {
|
|
244
|
+
return this.call("createLabel", { projectId }, undefined, body, options);
|
|
87
245
|
}
|
|
88
|
-
async
|
|
89
|
-
|
|
246
|
+
async listTypes(projectId, options) {
|
|
247
|
+
return this.call("listTypes", { projectId }, undefined, undefined, options);
|
|
90
248
|
}
|
|
91
|
-
async
|
|
92
|
-
return this.
|
|
249
|
+
async listCycles(projectId, options) {
|
|
250
|
+
return this.call("listCycles", { projectId }, undefined, undefined, options);
|
|
93
251
|
}
|
|
94
|
-
async
|
|
95
|
-
return this.
|
|
252
|
+
async createCycle(projectId, body, options) {
|
|
253
|
+
return this.call("createCycle", { projectId }, undefined, body, options);
|
|
96
254
|
}
|
|
97
|
-
async
|
|
98
|
-
return this.
|
|
255
|
+
async getCycleProgress(cycleId, options) {
|
|
256
|
+
return this.call("getCycleProgress", { cycleId }, undefined, undefined, options);
|
|
99
257
|
}
|
|
100
|
-
async
|
|
101
|
-
return this.
|
|
258
|
+
async addWorkItemsToCycle(cycleId, workItemIds, options) {
|
|
259
|
+
return this.call("addWorkItemsToCycle", { cycleId }, undefined, { workItemIds }, options);
|
|
102
260
|
}
|
|
103
|
-
async
|
|
104
|
-
return this.
|
|
261
|
+
async listViews(projectId, options) {
|
|
262
|
+
return this.call("listViews", { projectId }, undefined, undefined, options);
|
|
105
263
|
}
|
|
106
|
-
async
|
|
107
|
-
return this.
|
|
264
|
+
async createView(projectId, body, options) {
|
|
265
|
+
return this.call("createView", { projectId }, undefined, body, options);
|
|
108
266
|
}
|
|
109
|
-
async
|
|
110
|
-
return this.
|
|
267
|
+
async listIntake(projectId, query = {}, options) {
|
|
268
|
+
return this.call("listIntake", { projectId }, query, undefined, options);
|
|
111
269
|
}
|
|
112
|
-
async
|
|
113
|
-
return this.
|
|
270
|
+
async submitIntake(projectId, body, options) {
|
|
271
|
+
return this.call("submitIntake", { projectId }, undefined, body, options);
|
|
114
272
|
}
|
|
115
|
-
async
|
|
273
|
+
async triageIntake(intakeItemId, body, options) {
|
|
274
|
+
return this.call("triageIntake", { intakeItemId }, undefined, body, options);
|
|
275
|
+
}
|
|
276
|
+
async previewImport(projectId, body, options) {
|
|
277
|
+
return this.call("previewImport", { projectId }, undefined, body, options);
|
|
278
|
+
}
|
|
279
|
+
async startImport(projectId, body, options) {
|
|
280
|
+
return this.call("startImport", { projectId }, undefined, body, options);
|
|
281
|
+
}
|
|
282
|
+
async getImport(runId, options) {
|
|
283
|
+
return this.call("getImport", { runId }, undefined, undefined, options);
|
|
284
|
+
}
|
|
285
|
+
async listImports(projectId, options) {
|
|
286
|
+
return this.call("listImports", { projectId }, undefined, undefined, options);
|
|
287
|
+
}
|
|
288
|
+
async call(operation, params = {}, query, body, options = {}) {
|
|
289
|
+
const { method, path } = OPERATIONS[operation];
|
|
290
|
+
let url = buildPath(path, params);
|
|
291
|
+
if (query !== undefined) {
|
|
292
|
+
const search = new URLSearchParams;
|
|
293
|
+
for (const [name, value] of Object.entries(query)) {
|
|
294
|
+
if (value !== undefined)
|
|
295
|
+
search.set(name, String(value));
|
|
296
|
+
}
|
|
297
|
+
const encoded = search.toString();
|
|
298
|
+
if (encoded.length > 0)
|
|
299
|
+
url += `?${encoded}`;
|
|
300
|
+
}
|
|
301
|
+
return this.request(method, url, body, options);
|
|
302
|
+
}
|
|
303
|
+
async request(method, path, body, options) {
|
|
304
|
+
const retryable = isIdempotentMethod(method) || options.idempotencyKey !== undefined;
|
|
305
|
+
const retries = retryable ? options.retries ?? this.retry.retries : 0;
|
|
306
|
+
for (let attempt = 0;; attempt += 1) {
|
|
307
|
+
try {
|
|
308
|
+
return await this.send(method, path, body, options, attempt);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
const failure = error;
|
|
311
|
+
const again = attempt < retries && typeof failure.retryable === "boolean" && (failure.retryable || failure.code === "request_in_progress");
|
|
312
|
+
if (!again)
|
|
313
|
+
throw error;
|
|
314
|
+
await this.sleep(this.backoff(attempt, failure.retryAfter));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
backoff(attempt, retryAfter) {
|
|
319
|
+
if (retryAfter !== undefined)
|
|
320
|
+
return Math.min(retryAfter * 1000, this.retry.maxDelayMs);
|
|
321
|
+
const exponential = this.retry.baseDelayMs * 2 ** attempt;
|
|
322
|
+
const jittered = exponential / 2 + Math.random() * (exponential / 2);
|
|
323
|
+
return Math.min(jittered, this.retry.maxDelayMs);
|
|
324
|
+
}
|
|
325
|
+
async send(method, path, body, options, attempt) {
|
|
116
326
|
const controller = new AbortController;
|
|
117
327
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
328
|
+
const abort = () => controller.abort();
|
|
329
|
+
options.signal?.addEventListener("abort", abort);
|
|
330
|
+
const started = Date.now();
|
|
118
331
|
let response;
|
|
119
332
|
try {
|
|
120
333
|
const identityToken = typeof this.identityToken === "function" ? await this.identityToken() : this.identityToken;
|
|
334
|
+
const headers = {
|
|
335
|
+
"x-api-key": this.apiKey,
|
|
336
|
+
...identityToken === undefined || identityToken.length === 0 ? {} : { Authorization: `Bearer ${identityToken}` },
|
|
337
|
+
...body === undefined ? {} : { "Content-Type": "application/json" },
|
|
338
|
+
Accept: "application/json",
|
|
339
|
+
"User-Agent": this.userAgent
|
|
340
|
+
};
|
|
341
|
+
if (options.idempotencyKey !== undefined && !isIdempotentMethod(method)) {
|
|
342
|
+
headers["Idempotency-Key"] = options.idempotencyKey;
|
|
343
|
+
}
|
|
344
|
+
if (options.expectedVersion !== undefined && !isIdempotentMethod(method)) {
|
|
345
|
+
headers["If-Match"] = `"${String(options.expectedVersion).replace(/"/g, "")}"`;
|
|
346
|
+
}
|
|
121
347
|
response = await this.doFetch(`${this.baseUrl}${path}`, {
|
|
122
348
|
method,
|
|
123
|
-
headers
|
|
124
|
-
"x-api-key": this.apiKey,
|
|
125
|
-
...identityToken === undefined || identityToken.length === 0 ? {} : { Authorization: `Bearer ${identityToken}` },
|
|
126
|
-
...body === undefined ? {} : { "Content-Type": "application/json" }
|
|
127
|
-
},
|
|
349
|
+
headers,
|
|
128
350
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
129
351
|
signal: controller.signal
|
|
130
352
|
});
|
|
131
353
|
} catch (error) {
|
|
354
|
+
if (error instanceof RequestIntercepted)
|
|
355
|
+
throw error;
|
|
132
356
|
if (error instanceof Error && error.name === "AbortError") {
|
|
133
|
-
throw
|
|
357
|
+
throw createError(408, "timeout", `${method} ${path} timed out after ${this.timeoutMs}ms.`, undefined, { method, path });
|
|
134
358
|
}
|
|
135
|
-
throw
|
|
359
|
+
throw createError(0, "network_error", `Could not reach ${this.baseUrl}: ${error instanceof Error ? error.message : "unknown error"}`, undefined, { method, path });
|
|
136
360
|
} finally {
|
|
137
361
|
clearTimeout(timer);
|
|
362
|
+
options.signal?.removeEventListener("abort", abort);
|
|
138
363
|
}
|
|
364
|
+
const headerRequestId = response.headers?.get?.("x-request-id") ?? undefined;
|
|
365
|
+
this.onResponse?.({
|
|
366
|
+
method,
|
|
367
|
+
path,
|
|
368
|
+
status: response.status,
|
|
369
|
+
requestId: headerRequestId,
|
|
370
|
+
durationMs: Date.now() - started,
|
|
371
|
+
attempt,
|
|
372
|
+
replayed: response.headers?.get?.("idempotent-replayed") === "true"
|
|
373
|
+
});
|
|
139
374
|
if (response.status === 204)
|
|
140
375
|
return;
|
|
141
376
|
const text = await response.text();
|
|
142
377
|
const parsed = text.length === 0 ? undefined : safeJson(text);
|
|
143
378
|
if (!response.ok) {
|
|
144
|
-
const failure = parsed
|
|
145
|
-
throw
|
|
379
|
+
const failure = isRecord(parsed) ? parsed : {};
|
|
380
|
+
throw createError(response.status, typeof failure.code === "string" ? failure.code : codeForStatus(response.status), typeof failure.error === "string" ? failure.error : `${method} ${path} failed with ${response.status}.`, failure.issues, {
|
|
381
|
+
requestId: failure.requestId ?? headerRequestId,
|
|
382
|
+
details: failure.details,
|
|
383
|
+
retryAfter: parseRetryAfter(response.headers?.get?.("retry-after") ?? null),
|
|
384
|
+
method,
|
|
385
|
+
path
|
|
386
|
+
});
|
|
146
387
|
}
|
|
147
388
|
return parsed;
|
|
148
389
|
}
|
|
@@ -154,6 +395,57 @@ function safeJson(text) {
|
|
|
154
395
|
return;
|
|
155
396
|
}
|
|
156
397
|
}
|
|
398
|
+
function isRecord(value) {
|
|
399
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
400
|
+
}
|
|
401
|
+
function codeForStatus(status) {
|
|
402
|
+
const codes = {
|
|
403
|
+
400: "bad_request",
|
|
404
|
+
401: "unauthorized",
|
|
405
|
+
403: "forbidden",
|
|
406
|
+
404: "not_found",
|
|
407
|
+
409: "conflict",
|
|
408
|
+
412: "precondition_failed",
|
|
409
|
+
422: "validation_failed",
|
|
410
|
+
429: "rate_limited"
|
|
411
|
+
};
|
|
412
|
+
return codes[status] ?? (status >= 500 ? "server_error" : "unknown");
|
|
413
|
+
}
|
|
414
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
415
|
+
if (value === null || value.trim() === "")
|
|
416
|
+
return;
|
|
417
|
+
const seconds = Number(value);
|
|
418
|
+
if (Number.isFinite(seconds))
|
|
419
|
+
return Math.max(0, seconds);
|
|
420
|
+
const date = Date.parse(value);
|
|
421
|
+
if (Number.isNaN(date))
|
|
422
|
+
return;
|
|
423
|
+
return Math.max(0, Math.ceil((date - now) / 1000));
|
|
424
|
+
}
|
|
425
|
+
// ../sdk/src/types.ts
|
|
426
|
+
var WORK_ITEM_FIELDS = {
|
|
427
|
+
id: true,
|
|
428
|
+
key: true,
|
|
429
|
+
sequenceId: true,
|
|
430
|
+
name: true,
|
|
431
|
+
priority: true,
|
|
432
|
+
stateId: true,
|
|
433
|
+
assigneeIds: true,
|
|
434
|
+
labelIds: true,
|
|
435
|
+
parentId: true,
|
|
436
|
+
sortOrder: true,
|
|
437
|
+
createdAt: true,
|
|
438
|
+
projectId: true,
|
|
439
|
+
version: true
|
|
440
|
+
};
|
|
441
|
+
var WORK_ITEM_DETAIL_FIELDS = {
|
|
442
|
+
...WORK_ITEM_FIELDS,
|
|
443
|
+
description: true,
|
|
444
|
+
startDate: true,
|
|
445
|
+
targetDate: true,
|
|
446
|
+
archivedAt: true,
|
|
447
|
+
updatedAt: true
|
|
448
|
+
};
|
|
157
449
|
// src/tools.ts
|
|
158
450
|
var projectId = {
|
|
159
451
|
type: "string",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ycadence/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Model Context Protocol server for YCadence: lets an AI agent read and file work items.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ycadence",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"provenance": false
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
|
-
"build": "rm -rf dist && bun build src/bin.ts --outdir dist --target node --format esm --conditions ycadence-source",
|
|
43
|
+
"build": "rm -rf dist && bun build src/bin.ts --outdir dist --target node --format esm --conditions ycadence-source --define __YCADENCE_SDK_VERSION__=\\\"$(node -p \"require('../sdk/package.json').version\")\\\"",
|
|
44
44
|
"prepack": "cp ../../LICENSE LICENSE && bun run build",
|
|
45
45
|
"type-check": "tsc --noEmit",
|
|
46
46
|
"test": "vitest run"
|