mcp-intervals 1.5.0 → 1.5.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/cli/api.js +1 -1
- package/dist/cli/init.js +0 -0
- package/dist/client.js +5 -2
- package/dist/index.js +5 -1
- package/dist/tools.js +40 -0
- package/package.json +1 -1
package/dist/cli/api.js
CHANGED
|
@@ -11,7 +11,7 @@ export async function validateToken(token) {
|
|
|
11
11
|
const data = (await response.json());
|
|
12
12
|
return {
|
|
13
13
|
valid: true,
|
|
14
|
-
workspace: data.me?.company || "Unknown workspace",
|
|
14
|
+
workspace: data.me?.[0]?.company || "Unknown workspace",
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
if (response.status === 401) {
|
package/dist/cli/init.js
CHANGED
|
File without changes
|
package/dist/client.js
CHANGED
|
@@ -27,7 +27,7 @@ export class IntervalsClient {
|
|
|
27
27
|
const response = await fetch(url, fetchOptions);
|
|
28
28
|
if (!response.ok) {
|
|
29
29
|
const text = await response.text();
|
|
30
|
-
throw new Error(`Intervals API error ${response.status}: ${text}`);
|
|
30
|
+
throw new Error(`Intervals API error ${response.status}: ${text.slice(0, 500)}`);
|
|
31
31
|
}
|
|
32
32
|
return response.json();
|
|
33
33
|
}
|
|
@@ -127,6 +127,9 @@ export class IntervalsClient {
|
|
|
127
127
|
// First get the current user's person ID
|
|
128
128
|
const me = await this.getMe();
|
|
129
129
|
const personid = me.personid;
|
|
130
|
+
if (typeof personid !== "number") {
|
|
131
|
+
throw new Error("Could not determine current user's person ID from Intervals /me/ response.");
|
|
132
|
+
}
|
|
130
133
|
const data = await this.request(`/time/`, {
|
|
131
134
|
method: "POST",
|
|
132
135
|
body: {
|
|
@@ -149,6 +152,6 @@ export class IntervalsClient {
|
|
|
149
152
|
async getMe() {
|
|
150
153
|
const data = await this.request(`/me/`);
|
|
151
154
|
// Return the first user object with personid from the top-level response
|
|
152
|
-
return { ...data.me[0], personid: data.personid };
|
|
155
|
+
return { ...(data.me?.[0] ?? {}), personid: data.personid };
|
|
153
156
|
}
|
|
154
157
|
}
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,10 @@ async function startServer() {
|
|
|
13
13
|
const { IntervalsClient } = await import("./client.js");
|
|
14
14
|
const { registerTools } = await import("./tools.js");
|
|
15
15
|
const { registerResources } = await import("./resources.js");
|
|
16
|
+
const { createRequire } = await import("node:module");
|
|
17
|
+
// Read version from package.json so it never drifts from the published one.
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
const { version } = require("../package.json");
|
|
16
20
|
const API_TOKEN = process.env.INTERVALS_API_TOKEN;
|
|
17
21
|
if (!API_TOKEN) {
|
|
18
22
|
console.error("Error: INTERVALS_API_TOKEN environment variable is required.");
|
|
@@ -21,7 +25,7 @@ async function startServer() {
|
|
|
21
25
|
const client = new IntervalsClient(API_TOKEN);
|
|
22
26
|
const server = new McpServer({
|
|
23
27
|
name: "mcp-intervals",
|
|
24
|
-
version
|
|
28
|
+
version,
|
|
25
29
|
});
|
|
26
30
|
registerTools(server, client);
|
|
27
31
|
registerResources(server, client);
|
package/dist/tools.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { PDFParse } from "pdf-parse";
|
|
3
3
|
import { parseTaskIdFromUrl, getMimeTypeFromFilename, isImageMimeType, } from "./utils.js";
|
|
4
|
+
// Cap on inline-rendered downloads (images/PDFs). Base64 inflates payload
|
|
5
|
+
// ~33% and the result is fed into the model context, so guard against
|
|
6
|
+
// flooding memory/context with oversized attachments.
|
|
7
|
+
const MAX_INLINE_DOWNLOAD_BYTES = 25 * 1024 * 1024; // 25 MB
|
|
4
8
|
export function registerTools(server, client) {
|
|
5
9
|
// --- get_task ---
|
|
6
10
|
server.tool("get_task", "Retrieve full details of an Intervals task. Accepts a task URL (e.g. https://<subdomain>.intervalsonline.com/tasks/view/12345) or a numeric local task ID (the ID shown in the Intervals web UI).", {
|
|
@@ -252,6 +256,17 @@ export function registerTools(server, client) {
|
|
|
252
256
|
const docField = docData.document;
|
|
253
257
|
let document;
|
|
254
258
|
if (Array.isArray(docField)) {
|
|
259
|
+
if (docField.length === 0) {
|
|
260
|
+
return {
|
|
261
|
+
content: [
|
|
262
|
+
{
|
|
263
|
+
type: "text",
|
|
264
|
+
text: `No document found with ID ${documentId}.`,
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
isError: true,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
255
270
|
document = docField[0];
|
|
256
271
|
}
|
|
257
272
|
else if (docField && typeof docField === "object") {
|
|
@@ -262,10 +277,35 @@ export function registerTools(server, client) {
|
|
|
262
277
|
}
|
|
263
278
|
const filename = document.filename || document.title || "unknown";
|
|
264
279
|
const title = document.title || filename;
|
|
280
|
+
// Reject oversized files before downloading them into memory.
|
|
281
|
+
const reportedSize = Number(document.filesize);
|
|
282
|
+
if (Number.isFinite(reportedSize) &&
|
|
283
|
+
reportedSize > MAX_INLINE_DOWNLOAD_BYTES) {
|
|
284
|
+
return {
|
|
285
|
+
content: [
|
|
286
|
+
{
|
|
287
|
+
type: "text",
|
|
288
|
+
text: `Document "${title}" (${filename}) is ${(reportedSize / (1024 * 1024)).toFixed(2)} MB, exceeding the ${MAX_INLINE_DOWNLOAD_BYTES / (1024 * 1024)} MB inline limit. Download it directly from Intervals instead.`,
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
isError: true,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
265
294
|
const imageMimeType = getMimeTypeFromFilename(filename);
|
|
266
295
|
if (imageMimeType && isImageMimeType(imageMimeType)) {
|
|
267
296
|
try {
|
|
268
297
|
const { buffer } = await client.downloadDocument(documentId);
|
|
298
|
+
if (buffer.byteLength > MAX_INLINE_DOWNLOAD_BYTES) {
|
|
299
|
+
return {
|
|
300
|
+
content: [
|
|
301
|
+
{
|
|
302
|
+
type: "text",
|
|
303
|
+
text: `Document "${title}" (${filename}) is ${(buffer.byteLength / (1024 * 1024)).toFixed(2)} MB, exceeding the ${MAX_INLINE_DOWNLOAD_BYTES / (1024 * 1024)} MB inline limit. Download it directly from Intervals instead.`,
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
isError: true,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
269
309
|
const base64 = Buffer.from(buffer).toString("base64");
|
|
270
310
|
const sizeMB = buffer.byteLength / (1024 * 1024);
|
|
271
311
|
const content = [
|