@studiometa/productive-mcp 0.10.0 → 0.10.2
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/handlers/batch.d.ts +58 -0
- package/dist/handlers/batch.d.ts.map +1 -0
- package/dist/handlers/deals.d.ts.map +1 -1
- package/dist/handlers/help.d.ts.map +1 -1
- package/dist/handlers/index.d.ts.map +1 -1
- package/dist/handlers/projects.d.ts +1 -1
- package/dist/handlers/projects.d.ts.map +1 -1
- package/dist/handlers/schema.d.ts +33 -0
- package/dist/handlers/schema.d.ts.map +1 -0
- package/dist/handlers/search.d.ts +28 -0
- package/dist/handlers/search.d.ts.map +1 -0
- package/dist/handlers/summaries.d.ts +18 -0
- package/dist/handlers/summaries.d.ts.map +1 -0
- package/dist/handlers/tasks.d.ts.map +1 -1
- package/dist/{handlers-BE3CYyVX.js → handlers-B8GRTaDu.js} +774 -19
- package/dist/handlers-B8GRTaDu.js.map +1 -0
- package/dist/handlers.js +1 -1
- package/dist/http.js +2 -2
- package/dist/index.js +2 -2
- package/dist/schema.d.ts +27 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/server.js +2 -2
- package/dist/stdio.js +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +64 -15
- package/dist/tools.js.map +1 -1
- package/dist/{version-BKUpCCHx.js → version-Bl0krPVf.js} +2 -2
- package/dist/{version-BKUpCCHx.js.map → version-Bl0krPVf.js.map} +1 -1
- package/package.json +3 -3
- package/skills/SKILL.md +39 -16
- package/dist/handlers-BE3CYyVX.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ProductiveApi, formatAttachment, formatBooking, formatComment, formatCompany, formatDeal, formatDiscussion, formatListResponse, formatPage, formatPerson, formatProject, formatService, formatTask, formatTimeEntry, formatTimer } from "@studiometa/productive-api";
|
|
2
|
-
import { RESOURCES, ResolveError, VALID_REPORT_TYPES, createBooking, createComment, createCompany, createDeal, createDiscussion, createPage, createTask, createTimeEntry, deleteAttachment, deleteDiscussion, deletePage, deleteTimeEntry, fromHandlerContext, getAttachment, getBooking, getComment, getCompany, getDeal, getDiscussion, getPage, getPerson, getProject, getReport, getTask, getTimeEntry, getTimer, listAttachments, listBookings, listComments, listCompanies, listDeals, listDiscussions, listPages, listPeople, listProjects, listServices, listTasks, listTimeEntries, listTimers, reopenDiscussion, resolveDiscussion, resolveResource, startTimer, stopTimer, updateBooking, updateComment, updateCompany, updateDeal, updateDiscussion, updatePage, updateTask, updateTimeEntry } from "@studiometa/productive-core";
|
|
2
|
+
import { RESOURCES, ResolveError, VALID_REPORT_TYPES, createBooking, createComment, createCompany, createDeal, createDiscussion, createPage, createTask, createTimeEntry, deleteAttachment, deleteDiscussion, deletePage, deleteTimeEntry, fromHandlerContext, getAttachment, getBooking, getComment, getCompany, getDeal, getDealContext, getDiscussion, getMyDaySummary, getPage, getPerson, getProject, getProjectContext, getProjectHealthSummary, getReport, getTask, getTaskContext, getTeamPulseSummary, getTimeEntry, getTimer, listAttachments, listBookings, listComments, listCompanies, listDeals, listDiscussions, listPages, listPeople, listProjects, listServices, listTasks, listTimeEntries, listTimers, reopenDiscussion, resolveDiscussion, resolveResource, startTimer, stopTimer, updateBooking, updateComment, updateCompany, updateDeal, updateDiscussion, updatePage, updateTask, updateTimeEntry } from "@studiometa/productive-core";
|
|
3
3
|
/**
|
|
4
4
|
* Custom error classes for MCP server
|
|
5
5
|
*
|
|
@@ -988,6 +988,112 @@ const handleAttachments = createResourceHandler({
|
|
|
988
988
|
}
|
|
989
989
|
});
|
|
990
990
|
/**
|
|
991
|
+
* Validate batch operations array
|
|
992
|
+
*/
|
|
993
|
+
function validateOperations(operations) {
|
|
994
|
+
if (!Array.isArray(operations)) throw new UserInputError("operations must be an array", ["Provide an array of operation objects", "Each operation needs: { resource: \"...\", action: \"...\", ...params }"]);
|
|
995
|
+
if (operations.length === 0) throw new UserInputError("operations array cannot be empty", ["Provide at least one operation", "Example: operations: [{ resource: \"projects\", action: \"list\" }]"]);
|
|
996
|
+
if (operations.length > 10) throw new UserInputError(`operations array exceeds maximum size of 10`, [`Split your batch into chunks of 10 or fewer operations`, `You provided ${operations.length} operations`]);
|
|
997
|
+
const validatedOps = [];
|
|
998
|
+
const errors = [];
|
|
999
|
+
for (let i = 0; i < operations.length; i++) {
|
|
1000
|
+
const op = operations[i];
|
|
1001
|
+
if (typeof op !== "object" || op === null) {
|
|
1002
|
+
errors.push(`Operation at index ${i}: must be an object`);
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
const { resource, action } = op;
|
|
1006
|
+
if (typeof resource !== "string" || resource.trim() === "") errors.push(`Operation at index ${i}: missing or invalid "resource" field`);
|
|
1007
|
+
if (typeof action !== "string" || action.trim() === "") errors.push(`Operation at index ${i}: missing or invalid "action" field`);
|
|
1008
|
+
if (errors.length === 0) validatedOps.push(op);
|
|
1009
|
+
}
|
|
1010
|
+
if (errors.length > 0) throw new UserInputError("Invalid operations in batch", errors);
|
|
1011
|
+
return validatedOps;
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Execute a single operation and capture result
|
|
1015
|
+
*/
|
|
1016
|
+
async function executeOperation(operation, index, credentials, execute) {
|
|
1017
|
+
const { resource, action, ...params } = operation;
|
|
1018
|
+
try {
|
|
1019
|
+
const result = await execute("productive", {
|
|
1020
|
+
resource,
|
|
1021
|
+
action,
|
|
1022
|
+
...params
|
|
1023
|
+
}, credentials);
|
|
1024
|
+
const content = result.content[0];
|
|
1025
|
+
if (content?.type === "text") try {
|
|
1026
|
+
const data = JSON.parse(content.text);
|
|
1027
|
+
if (result.isError) return {
|
|
1028
|
+
resource,
|
|
1029
|
+
action,
|
|
1030
|
+
index,
|
|
1031
|
+
error: content.text
|
|
1032
|
+
};
|
|
1033
|
+
return {
|
|
1034
|
+
resource,
|
|
1035
|
+
action,
|
|
1036
|
+
index,
|
|
1037
|
+
data
|
|
1038
|
+
};
|
|
1039
|
+
} catch {
|
|
1040
|
+
if (result.isError) return {
|
|
1041
|
+
resource,
|
|
1042
|
+
action,
|
|
1043
|
+
index,
|
|
1044
|
+
error: content.text
|
|
1045
|
+
};
|
|
1046
|
+
return {
|
|
1047
|
+
resource,
|
|
1048
|
+
action,
|
|
1049
|
+
index,
|
|
1050
|
+
data: content.text
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
return {
|
|
1054
|
+
resource,
|
|
1055
|
+
action,
|
|
1056
|
+
index,
|
|
1057
|
+
data: null
|
|
1058
|
+
};
|
|
1059
|
+
} catch (err) {
|
|
1060
|
+
return {
|
|
1061
|
+
resource,
|
|
1062
|
+
action,
|
|
1063
|
+
index,
|
|
1064
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Handle batch operation - execute multiple operations in parallel
|
|
1070
|
+
*
|
|
1071
|
+
* @param operations - Array of operations to execute
|
|
1072
|
+
* @param credentials - API credentials
|
|
1073
|
+
* @param execute - Function to execute individual operations (injected for testability)
|
|
1074
|
+
* @returns Batch response with summary and individual results
|
|
1075
|
+
*/
|
|
1076
|
+
async function handleBatch(operations, credentials, execute) {
|
|
1077
|
+
let validatedOps;
|
|
1078
|
+
try {
|
|
1079
|
+
validatedOps = validateOperations(operations);
|
|
1080
|
+
} catch (err) {
|
|
1081
|
+
if (err instanceof UserInputError) return inputErrorResult(err);
|
|
1082
|
+
throw err;
|
|
1083
|
+
}
|
|
1084
|
+
const results = await Promise.all(validatedOps.map((op, index) => executeOperation(op, index, credentials, execute)));
|
|
1085
|
+
const succeeded = results.filter((r) => r.data !== void 0 && r.error === void 0).length;
|
|
1086
|
+
const failed = results.filter((r) => r.error !== void 0).length;
|
|
1087
|
+
return jsonResult({
|
|
1088
|
+
_batch: {
|
|
1089
|
+
total: results.length,
|
|
1090
|
+
succeeded,
|
|
1091
|
+
failed
|
|
1092
|
+
},
|
|
1093
|
+
results
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
991
1097
|
* Bookings MCP handler.
|
|
992
1098
|
*/
|
|
993
1099
|
const handleBookings = createResourceHandler({
|
|
@@ -1132,7 +1238,8 @@ const handleDeals = createResourceHandler({
|
|
|
1132
1238
|
"get",
|
|
1133
1239
|
"create",
|
|
1134
1240
|
"update",
|
|
1135
|
-
"resolve"
|
|
1241
|
+
"resolve",
|
|
1242
|
+
"context"
|
|
1136
1243
|
],
|
|
1137
1244
|
formatter: formatDeal$1,
|
|
1138
1245
|
hints: (_data, id) => getDealHints(id),
|
|
@@ -1153,6 +1260,20 @@ const handleDeals = createResourceHandler({
|
|
|
1153
1260
|
})
|
|
1154
1261
|
},
|
|
1155
1262
|
update: { mapOptions: (args) => ({ name: args.name }) },
|
|
1263
|
+
customActions: { context: async (args, ctx, execCtx) => {
|
|
1264
|
+
if (!args.id) return inputErrorResult(ErrorMessages.missingId("context"));
|
|
1265
|
+
const result = await getDealContext({ id: args.id }, execCtx);
|
|
1266
|
+
const formatOptions = {
|
|
1267
|
+
...ctx.formatOptions,
|
|
1268
|
+
included: result.included
|
|
1269
|
+
};
|
|
1270
|
+
return jsonResult({
|
|
1271
|
+
...formatDeal$1(result.data.deal, formatOptions),
|
|
1272
|
+
services: result.data.services.map((s) => formatService$1(s, { compact: true })),
|
|
1273
|
+
comments: result.data.comments.map((c) => formatComment$1(c, { compact: true })),
|
|
1274
|
+
time_entries: result.data.time_entries.map((t) => formatTimeEntry$1(t, { compact: true }))
|
|
1275
|
+
});
|
|
1276
|
+
} },
|
|
1156
1277
|
executors: {
|
|
1157
1278
|
list: listDeals,
|
|
1158
1279
|
get: getDeal,
|
|
@@ -1228,12 +1349,63 @@ const handleDiscussions = createResourceHandler({
|
|
|
1228
1349
|
}
|
|
1229
1350
|
});
|
|
1230
1351
|
var RESOURCE_HELP = {
|
|
1352
|
+
batch: {
|
|
1353
|
+
description: "Execute multiple operations in a single call. Operations run in parallel via Promise.all, reducing round-trips for AI agents.",
|
|
1354
|
+
actions: { run: "Execute a batch of operations (max 10)" },
|
|
1355
|
+
fields: { operations: "Array of operation objects. Each must have \"resource\" and \"action\", plus any additional params for that resource." },
|
|
1356
|
+
examples: [{
|
|
1357
|
+
description: "Batch multiple queries",
|
|
1358
|
+
params: {
|
|
1359
|
+
resource: "batch",
|
|
1360
|
+
action: "run",
|
|
1361
|
+
operations: [
|
|
1362
|
+
{
|
|
1363
|
+
resource: "projects",
|
|
1364
|
+
action: "get",
|
|
1365
|
+
id: "123"
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
resource: "time",
|
|
1369
|
+
action: "list",
|
|
1370
|
+
filter: { project_id: "123" }
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
resource: "services",
|
|
1374
|
+
action: "list",
|
|
1375
|
+
filter: { project_id: "123" }
|
|
1376
|
+
}
|
|
1377
|
+
]
|
|
1378
|
+
}
|
|
1379
|
+
}, {
|
|
1380
|
+
description: "Batch create time entries",
|
|
1381
|
+
params: {
|
|
1382
|
+
resource: "batch",
|
|
1383
|
+
action: "run",
|
|
1384
|
+
operations: [{
|
|
1385
|
+
resource: "time",
|
|
1386
|
+
action: "create",
|
|
1387
|
+
service_id: "111",
|
|
1388
|
+
date: "2024-01-15",
|
|
1389
|
+
time: 60,
|
|
1390
|
+
note: "Morning work"
|
|
1391
|
+
}, {
|
|
1392
|
+
resource: "time",
|
|
1393
|
+
action: "create",
|
|
1394
|
+
service_id: "111",
|
|
1395
|
+
date: "2024-01-15",
|
|
1396
|
+
time: 120,
|
|
1397
|
+
note: "Afternoon work"
|
|
1398
|
+
}]
|
|
1399
|
+
}
|
|
1400
|
+
}]
|
|
1401
|
+
},
|
|
1231
1402
|
projects: {
|
|
1232
1403
|
description: "Manage projects in Productive.io",
|
|
1233
1404
|
actions: {
|
|
1234
1405
|
list: "List all projects with optional filters",
|
|
1235
1406
|
get: "Get a single project by ID (supports PRJ-123, P-123 format)",
|
|
1236
|
-
resolve: "Resolve by project number (PRJ-123, P-123)"
|
|
1407
|
+
resolve: "Resolve by project number (PRJ-123, P-123)",
|
|
1408
|
+
context: "Get full project context in one call: project details + open tasks + services + recent time entries"
|
|
1237
1409
|
},
|
|
1238
1410
|
filters: {
|
|
1239
1411
|
query: "Text search on project name",
|
|
@@ -1274,6 +1446,14 @@ var RESOURCE_HELP = {
|
|
|
1274
1446
|
action: "get",
|
|
1275
1447
|
id: "12345"
|
|
1276
1448
|
}
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
description: "Get full project context",
|
|
1452
|
+
params: {
|
|
1453
|
+
resource: "projects",
|
|
1454
|
+
action: "context",
|
|
1455
|
+
id: "12345"
|
|
1456
|
+
}
|
|
1277
1457
|
}
|
|
1278
1458
|
]
|
|
1279
1459
|
},
|
|
@@ -1284,7 +1464,8 @@ var RESOURCE_HELP = {
|
|
|
1284
1464
|
get: "Get a single task by ID with full details (description, comments, etc.)",
|
|
1285
1465
|
create: "Create a new task (requires title, project_id, task_list_id)",
|
|
1286
1466
|
update: "Update an existing task",
|
|
1287
|
-
resolve: "Resolve by text search"
|
|
1467
|
+
resolve: "Resolve by text search",
|
|
1468
|
+
context: "Get full task context in one call: task details + comments + time entries + subtasks"
|
|
1288
1469
|
},
|
|
1289
1470
|
filters: {
|
|
1290
1471
|
query: "Text search on task title",
|
|
@@ -1360,6 +1541,14 @@ var RESOURCE_HELP = {
|
|
|
1360
1541
|
project_id: "12345",
|
|
1361
1542
|
task_list_id: "111"
|
|
1362
1543
|
}
|
|
1544
|
+
},
|
|
1545
|
+
{
|
|
1546
|
+
description: "Get full task context",
|
|
1547
|
+
params: {
|
|
1548
|
+
resource: "tasks",
|
|
1549
|
+
action: "context",
|
|
1550
|
+
id: "67890"
|
|
1551
|
+
}
|
|
1363
1552
|
}
|
|
1364
1553
|
]
|
|
1365
1554
|
},
|
|
@@ -1677,7 +1866,8 @@ var RESOURCE_HELP = {
|
|
|
1677
1866
|
get: "Get a single deal by ID (supports D-123, DEAL-123 format)",
|
|
1678
1867
|
create: "Create a new deal (requires name, company_id)",
|
|
1679
1868
|
update: "Update an existing deal",
|
|
1680
|
-
resolve: "Resolve by deal number (D-123, DEAL-123)"
|
|
1869
|
+
resolve: "Resolve by deal number (D-123, DEAL-123)",
|
|
1870
|
+
context: "Get full deal context in one call: deal details + services + comments + time entries"
|
|
1681
1871
|
},
|
|
1682
1872
|
filters: {
|
|
1683
1873
|
query: "Text search on deal name",
|
|
@@ -1727,6 +1917,14 @@ var RESOURCE_HELP = {
|
|
|
1727
1917
|
action: "list",
|
|
1728
1918
|
filter: { type: "2" }
|
|
1729
1919
|
}
|
|
1920
|
+
},
|
|
1921
|
+
{
|
|
1922
|
+
description: "Get full deal context",
|
|
1923
|
+
params: {
|
|
1924
|
+
resource: "deals",
|
|
1925
|
+
action: "context",
|
|
1926
|
+
id: "12345"
|
|
1927
|
+
}
|
|
1730
1928
|
}
|
|
1731
1929
|
]
|
|
1732
1930
|
},
|
|
@@ -1949,7 +2147,8 @@ function handleHelp(resource) {
|
|
|
1949
2147
|
const help = RESOURCE_HELP[resource];
|
|
1950
2148
|
if (!help) return jsonResult({
|
|
1951
2149
|
error: `Unknown resource: ${resource}`,
|
|
1952
|
-
available_resources: Object.keys(RESOURCE_HELP)
|
|
2150
|
+
available_resources: Object.keys(RESOURCE_HELP),
|
|
2151
|
+
_tip: "Call { action: 'help' } without a resource to see all available resources."
|
|
1953
2152
|
});
|
|
1954
2153
|
return jsonResult({
|
|
1955
2154
|
resource,
|
|
@@ -1966,7 +2165,8 @@ function handleHelpOverview() {
|
|
|
1966
2165
|
resource,
|
|
1967
2166
|
description: help.description,
|
|
1968
2167
|
actions: Object.keys(help.actions)
|
|
1969
|
-
}))
|
|
2168
|
+
})),
|
|
2169
|
+
_tip: "Always call { action: 'help', resource: '<name>' } before your first interaction with any resource to learn valid filters, required fields, and examples."
|
|
1970
2170
|
});
|
|
1971
2171
|
}
|
|
1972
2172
|
/**
|
|
@@ -2015,7 +2215,7 @@ const handlePages = createResourceHandler({
|
|
|
2015
2215
|
/**
|
|
2016
2216
|
* People MCP handler.
|
|
2017
2217
|
*/
|
|
2018
|
-
var VALID_ACTIONS$
|
|
2218
|
+
var VALID_ACTIONS$2 = [
|
|
2019
2219
|
"list",
|
|
2020
2220
|
"get",
|
|
2021
2221
|
"me",
|
|
@@ -2066,7 +2266,7 @@ async function handlePeople(action, args, ctx, credentials) {
|
|
|
2066
2266
|
});
|
|
2067
2267
|
return jsonResult(response);
|
|
2068
2268
|
}
|
|
2069
|
-
return inputErrorResult(ErrorMessages.invalidAction(action, "people", VALID_ACTIONS$
|
|
2269
|
+
return inputErrorResult(ErrorMessages.invalidAction(action, "people", VALID_ACTIONS$2));
|
|
2070
2270
|
}
|
|
2071
2271
|
/**
|
|
2072
2272
|
* Projects MCP handler.
|
|
@@ -2076,18 +2276,36 @@ async function handlePeople(action, args, ctx, credentials) {
|
|
|
2076
2276
|
/**
|
|
2077
2277
|
* Handle projects resource.
|
|
2078
2278
|
*
|
|
2079
|
-
* Supports: list, get, resolve
|
|
2279
|
+
* Supports: list, get, resolve, context
|
|
2080
2280
|
*/
|
|
2081
2281
|
const handleProjects = createResourceHandler({
|
|
2082
2282
|
resource: "projects",
|
|
2083
2283
|
actions: [
|
|
2084
2284
|
"list",
|
|
2085
2285
|
"get",
|
|
2086
|
-
"resolve"
|
|
2286
|
+
"resolve",
|
|
2287
|
+
"context"
|
|
2087
2288
|
],
|
|
2088
2289
|
formatter: formatProject$1,
|
|
2089
2290
|
hints: (_data, id) => getProjectHints(id),
|
|
2090
2291
|
supportsResolve: true,
|
|
2292
|
+
customActions: { context: async (args, ctx, execCtx) => {
|
|
2293
|
+
if (!args.id) return inputErrorResult(ErrorMessages.missingId("context"));
|
|
2294
|
+
const result = await getProjectContext({ id: args.id }, execCtx);
|
|
2295
|
+
const formatOptions = {
|
|
2296
|
+
...ctx.formatOptions,
|
|
2297
|
+
included: result.included
|
|
2298
|
+
};
|
|
2299
|
+
return jsonResult({
|
|
2300
|
+
...formatProject$1(result.data.project, ctx.formatOptions),
|
|
2301
|
+
tasks: result.data.tasks.map((t) => formatTask$1(t, {
|
|
2302
|
+
...formatOptions,
|
|
2303
|
+
compact: true
|
|
2304
|
+
})),
|
|
2305
|
+
services: result.data.services.map((s) => formatService$1(s, { compact: true })),
|
|
2306
|
+
time_entries: result.data.time_entries.map((t) => formatTimeEntry$1(t, { compact: true }))
|
|
2307
|
+
});
|
|
2308
|
+
} },
|
|
2091
2309
|
executors: {
|
|
2092
2310
|
list: listProjects,
|
|
2093
2311
|
get: getProject
|
|
@@ -2106,11 +2324,11 @@ function formatReportData(data) {
|
|
|
2106
2324
|
};
|
|
2107
2325
|
});
|
|
2108
2326
|
}
|
|
2109
|
-
var VALID_ACTIONS = ["get"];
|
|
2327
|
+
var VALID_ACTIONS$1 = ["get"];
|
|
2110
2328
|
async function handleReports(action, args, ctx) {
|
|
2111
2329
|
const { filter, page, perPage } = ctx;
|
|
2112
2330
|
const { report_type, group, from, to, person_id, project_id, company_id, deal_id, status } = args;
|
|
2113
|
-
if (action !== "get") return inputErrorResult(ErrorMessages.invalidAction(action, "reports", VALID_ACTIONS));
|
|
2331
|
+
if (action !== "get") return inputErrorResult(ErrorMessages.invalidAction(action, "reports", VALID_ACTIONS$1));
|
|
2114
2332
|
if (!report_type) return inputErrorResult(ErrorMessages.missingReportType());
|
|
2115
2333
|
if (!VALID_REPORT_TYPES.includes(report_type)) return inputErrorResult(ErrorMessages.invalidReportType(report_type, [...VALID_REPORT_TYPES]));
|
|
2116
2334
|
const execCtx = ctx.executor();
|
|
@@ -2134,6 +2352,454 @@ async function handleReports(action, args, ctx) {
|
|
|
2134
2352
|
});
|
|
2135
2353
|
}
|
|
2136
2354
|
/**
|
|
2355
|
+
* Schema definitions for all resources.
|
|
2356
|
+
*
|
|
2357
|
+
* This provides a compact, machine-readable specification of each resource's
|
|
2358
|
+
* capabilities. For detailed documentation with examples, use action=help.
|
|
2359
|
+
*/
|
|
2360
|
+
var RESOURCE_SCHEMAS = {
|
|
2361
|
+
projects: {
|
|
2362
|
+
actions: [
|
|
2363
|
+
"list",
|
|
2364
|
+
"get",
|
|
2365
|
+
"resolve"
|
|
2366
|
+
],
|
|
2367
|
+
filters: {
|
|
2368
|
+
query: "string — text search on project name",
|
|
2369
|
+
project_type: "1=internal|2=client",
|
|
2370
|
+
company_id: "string",
|
|
2371
|
+
responsible_id: "string",
|
|
2372
|
+
person_id: "string",
|
|
2373
|
+
status: "1=active|2=archived"
|
|
2374
|
+
}
|
|
2375
|
+
},
|
|
2376
|
+
time: {
|
|
2377
|
+
actions: [
|
|
2378
|
+
"list",
|
|
2379
|
+
"get",
|
|
2380
|
+
"create",
|
|
2381
|
+
"update",
|
|
2382
|
+
"delete"
|
|
2383
|
+
],
|
|
2384
|
+
filters: {
|
|
2385
|
+
person_id: "string — use 'me' for current user",
|
|
2386
|
+
after: "date YYYY-MM-DD",
|
|
2387
|
+
before: "date YYYY-MM-DD",
|
|
2388
|
+
project_id: "string",
|
|
2389
|
+
service_id: "string",
|
|
2390
|
+
task_id: "string",
|
|
2391
|
+
status: "1=approved|2=unapproved|3=rejected"
|
|
2392
|
+
},
|
|
2393
|
+
create: {
|
|
2394
|
+
person_id: {
|
|
2395
|
+
required: true,
|
|
2396
|
+
type: "string"
|
|
2397
|
+
},
|
|
2398
|
+
service_id: {
|
|
2399
|
+
required: true,
|
|
2400
|
+
type: "string"
|
|
2401
|
+
},
|
|
2402
|
+
date: {
|
|
2403
|
+
required: true,
|
|
2404
|
+
type: "date YYYY-MM-DD"
|
|
2405
|
+
},
|
|
2406
|
+
time: {
|
|
2407
|
+
required: true,
|
|
2408
|
+
type: "minutes integer"
|
|
2409
|
+
},
|
|
2410
|
+
note: {
|
|
2411
|
+
required: false,
|
|
2412
|
+
type: "string"
|
|
2413
|
+
},
|
|
2414
|
+
task_id: {
|
|
2415
|
+
required: false,
|
|
2416
|
+
type: "string"
|
|
2417
|
+
}
|
|
2418
|
+
},
|
|
2419
|
+
includes: [
|
|
2420
|
+
"person",
|
|
2421
|
+
"service",
|
|
2422
|
+
"task"
|
|
2423
|
+
]
|
|
2424
|
+
},
|
|
2425
|
+
tasks: {
|
|
2426
|
+
actions: [
|
|
2427
|
+
"list",
|
|
2428
|
+
"get",
|
|
2429
|
+
"create",
|
|
2430
|
+
"update",
|
|
2431
|
+
"resolve"
|
|
2432
|
+
],
|
|
2433
|
+
filters: {
|
|
2434
|
+
query: "string — text search on task title",
|
|
2435
|
+
project_id: "string",
|
|
2436
|
+
assignee_id: "string",
|
|
2437
|
+
status: "1=open|2=closed (or \"open\", \"closed\", \"all\")",
|
|
2438
|
+
task_list_id: "string",
|
|
2439
|
+
workflow_status_id: "string — kanban column"
|
|
2440
|
+
},
|
|
2441
|
+
create: {
|
|
2442
|
+
title: {
|
|
2443
|
+
required: true,
|
|
2444
|
+
type: "string"
|
|
2445
|
+
},
|
|
2446
|
+
project_id: {
|
|
2447
|
+
required: true,
|
|
2448
|
+
type: "string"
|
|
2449
|
+
},
|
|
2450
|
+
task_list_id: {
|
|
2451
|
+
required: true,
|
|
2452
|
+
type: "string"
|
|
2453
|
+
},
|
|
2454
|
+
description: {
|
|
2455
|
+
required: false,
|
|
2456
|
+
type: "string"
|
|
2457
|
+
},
|
|
2458
|
+
assignee_id: {
|
|
2459
|
+
required: false,
|
|
2460
|
+
type: "string"
|
|
2461
|
+
}
|
|
2462
|
+
},
|
|
2463
|
+
includes: [
|
|
2464
|
+
"project",
|
|
2465
|
+
"assignee",
|
|
2466
|
+
"comments",
|
|
2467
|
+
"subtasks",
|
|
2468
|
+
"workflow_status"
|
|
2469
|
+
]
|
|
2470
|
+
},
|
|
2471
|
+
services: {
|
|
2472
|
+
actions: ["list", "get"],
|
|
2473
|
+
filters: {
|
|
2474
|
+
project_id: "string",
|
|
2475
|
+
deal_id: "string",
|
|
2476
|
+
task_id: "string",
|
|
2477
|
+
budget_status: "1=open|2=delivered"
|
|
2478
|
+
}
|
|
2479
|
+
},
|
|
2480
|
+
people: {
|
|
2481
|
+
actions: [
|
|
2482
|
+
"list",
|
|
2483
|
+
"get",
|
|
2484
|
+
"me",
|
|
2485
|
+
"resolve"
|
|
2486
|
+
],
|
|
2487
|
+
filters: {
|
|
2488
|
+
query: "string — text search on name or email",
|
|
2489
|
+
status: "1=active|2=deactivated",
|
|
2490
|
+
company_id: "string"
|
|
2491
|
+
}
|
|
2492
|
+
},
|
|
2493
|
+
companies: {
|
|
2494
|
+
actions: [
|
|
2495
|
+
"list",
|
|
2496
|
+
"get",
|
|
2497
|
+
"create",
|
|
2498
|
+
"update",
|
|
2499
|
+
"resolve"
|
|
2500
|
+
],
|
|
2501
|
+
filters: {
|
|
2502
|
+
query: "string — text search on company name",
|
|
2503
|
+
archived: "boolean"
|
|
2504
|
+
},
|
|
2505
|
+
create: { name: {
|
|
2506
|
+
required: true,
|
|
2507
|
+
type: "string"
|
|
2508
|
+
} }
|
|
2509
|
+
},
|
|
2510
|
+
comments: {
|
|
2511
|
+
actions: [
|
|
2512
|
+
"list",
|
|
2513
|
+
"get",
|
|
2514
|
+
"create",
|
|
2515
|
+
"update"
|
|
2516
|
+
],
|
|
2517
|
+
filters: {
|
|
2518
|
+
task_id: "string",
|
|
2519
|
+
deal_id: "string",
|
|
2520
|
+
page_id: "string",
|
|
2521
|
+
discussion_id: "string"
|
|
2522
|
+
},
|
|
2523
|
+
create: {
|
|
2524
|
+
body: {
|
|
2525
|
+
required: true,
|
|
2526
|
+
type: "string"
|
|
2527
|
+
},
|
|
2528
|
+
task_id: {
|
|
2529
|
+
required: false,
|
|
2530
|
+
type: "string — one of task_id, deal_id required"
|
|
2531
|
+
},
|
|
2532
|
+
deal_id: {
|
|
2533
|
+
required: false,
|
|
2534
|
+
type: "string — one of task_id, deal_id required"
|
|
2535
|
+
}
|
|
2536
|
+
},
|
|
2537
|
+
includes: ["creator"]
|
|
2538
|
+
},
|
|
2539
|
+
attachments: {
|
|
2540
|
+
actions: [
|
|
2541
|
+
"list",
|
|
2542
|
+
"get",
|
|
2543
|
+
"delete"
|
|
2544
|
+
],
|
|
2545
|
+
filters: {
|
|
2546
|
+
task_id: "string",
|
|
2547
|
+
comment_id: "string",
|
|
2548
|
+
deal_id: "string",
|
|
2549
|
+
page_id: "string"
|
|
2550
|
+
}
|
|
2551
|
+
},
|
|
2552
|
+
timers: {
|
|
2553
|
+
actions: [
|
|
2554
|
+
"list",
|
|
2555
|
+
"get",
|
|
2556
|
+
"start",
|
|
2557
|
+
"stop"
|
|
2558
|
+
],
|
|
2559
|
+
filters: {
|
|
2560
|
+
person_id: "string",
|
|
2561
|
+
time_entry_id: "string"
|
|
2562
|
+
}
|
|
2563
|
+
},
|
|
2564
|
+
deals: {
|
|
2565
|
+
actions: [
|
|
2566
|
+
"list",
|
|
2567
|
+
"get",
|
|
2568
|
+
"create",
|
|
2569
|
+
"update",
|
|
2570
|
+
"resolve"
|
|
2571
|
+
],
|
|
2572
|
+
filters: {
|
|
2573
|
+
query: "string — text search on deal name",
|
|
2574
|
+
company_id: "string",
|
|
2575
|
+
type: "1=deal|2=budget",
|
|
2576
|
+
stage_status_id: "1=open|2=won|3=lost"
|
|
2577
|
+
},
|
|
2578
|
+
create: {
|
|
2579
|
+
name: {
|
|
2580
|
+
required: true,
|
|
2581
|
+
type: "string"
|
|
2582
|
+
},
|
|
2583
|
+
company_id: {
|
|
2584
|
+
required: true,
|
|
2585
|
+
type: "string"
|
|
2586
|
+
}
|
|
2587
|
+
},
|
|
2588
|
+
includes: ["company", "deal_status"]
|
|
2589
|
+
},
|
|
2590
|
+
bookings: {
|
|
2591
|
+
actions: [
|
|
2592
|
+
"list",
|
|
2593
|
+
"get",
|
|
2594
|
+
"create",
|
|
2595
|
+
"update"
|
|
2596
|
+
],
|
|
2597
|
+
filters: {
|
|
2598
|
+
person_id: "string",
|
|
2599
|
+
after: "date YYYY-MM-DD",
|
|
2600
|
+
before: "date YYYY-MM-DD",
|
|
2601
|
+
service_id: "string"
|
|
2602
|
+
},
|
|
2603
|
+
create: {
|
|
2604
|
+
person_id: {
|
|
2605
|
+
required: true,
|
|
2606
|
+
type: "string"
|
|
2607
|
+
},
|
|
2608
|
+
started_on: {
|
|
2609
|
+
required: true,
|
|
2610
|
+
type: "date YYYY-MM-DD"
|
|
2611
|
+
},
|
|
2612
|
+
ended_on: {
|
|
2613
|
+
required: true,
|
|
2614
|
+
type: "date YYYY-MM-DD"
|
|
2615
|
+
},
|
|
2616
|
+
service_id: {
|
|
2617
|
+
required: false,
|
|
2618
|
+
type: "string — one of service_id, event_id required"
|
|
2619
|
+
},
|
|
2620
|
+
event_id: {
|
|
2621
|
+
required: false,
|
|
2622
|
+
type: "string — one of service_id, event_id required"
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
},
|
|
2626
|
+
pages: {
|
|
2627
|
+
actions: [
|
|
2628
|
+
"list",
|
|
2629
|
+
"get",
|
|
2630
|
+
"create",
|
|
2631
|
+
"update",
|
|
2632
|
+
"delete"
|
|
2633
|
+
],
|
|
2634
|
+
filters: { project_id: "string" },
|
|
2635
|
+
create: {
|
|
2636
|
+
title: {
|
|
2637
|
+
required: true,
|
|
2638
|
+
type: "string"
|
|
2639
|
+
},
|
|
2640
|
+
project_id: {
|
|
2641
|
+
required: true,
|
|
2642
|
+
type: "string"
|
|
2643
|
+
},
|
|
2644
|
+
body: {
|
|
2645
|
+
required: false,
|
|
2646
|
+
type: "string"
|
|
2647
|
+
},
|
|
2648
|
+
parent_page_id: {
|
|
2649
|
+
required: false,
|
|
2650
|
+
type: "string"
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
},
|
|
2654
|
+
discussions: {
|
|
2655
|
+
actions: [
|
|
2656
|
+
"list",
|
|
2657
|
+
"get",
|
|
2658
|
+
"create",
|
|
2659
|
+
"update",
|
|
2660
|
+
"delete",
|
|
2661
|
+
"resolve",
|
|
2662
|
+
"reopen"
|
|
2663
|
+
],
|
|
2664
|
+
filters: {
|
|
2665
|
+
page_id: "string",
|
|
2666
|
+
status: "1=active|2=resolved"
|
|
2667
|
+
},
|
|
2668
|
+
create: {
|
|
2669
|
+
body: {
|
|
2670
|
+
required: true,
|
|
2671
|
+
type: "string"
|
|
2672
|
+
},
|
|
2673
|
+
page_id: {
|
|
2674
|
+
required: true,
|
|
2675
|
+
type: "string"
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
},
|
|
2679
|
+
reports: {
|
|
2680
|
+
actions: ["get"],
|
|
2681
|
+
filters: {
|
|
2682
|
+
person_id: "string",
|
|
2683
|
+
project_id: "string",
|
|
2684
|
+
company_id: "string",
|
|
2685
|
+
after: "date YYYY-MM-DD",
|
|
2686
|
+
before: "date YYYY-MM-DD"
|
|
2687
|
+
},
|
|
2688
|
+
create: {
|
|
2689
|
+
report_type: {
|
|
2690
|
+
required: true,
|
|
2691
|
+
type: "time_reports|project_reports|budget_reports|..."
|
|
2692
|
+
},
|
|
2693
|
+
from: {
|
|
2694
|
+
required: false,
|
|
2695
|
+
type: "date YYYY-MM-DD"
|
|
2696
|
+
},
|
|
2697
|
+
to: {
|
|
2698
|
+
required: false,
|
|
2699
|
+
type: "date YYYY-MM-DD"
|
|
2700
|
+
},
|
|
2701
|
+
group: {
|
|
2702
|
+
required: false,
|
|
2703
|
+
type: "string — grouping dimension"
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
};
|
|
2708
|
+
/**
|
|
2709
|
+
* Handle schema action - returns compact specification for a specific resource
|
|
2710
|
+
*/
|
|
2711
|
+
function handleSchema(resource) {
|
|
2712
|
+
const schema = RESOURCE_SCHEMAS[resource];
|
|
2713
|
+
if (!schema) return errorResult(`Unknown resource: ${resource}. Valid resources: ${Object.keys(RESOURCE_SCHEMAS).join(", ")}`);
|
|
2714
|
+
return jsonResult({
|
|
2715
|
+
resource,
|
|
2716
|
+
...schema
|
|
2717
|
+
});
|
|
2718
|
+
}
|
|
2719
|
+
/**
|
|
2720
|
+
* Get schema overview for all resources
|
|
2721
|
+
*/
|
|
2722
|
+
function handleSchemaOverview() {
|
|
2723
|
+
return jsonResult({
|
|
2724
|
+
_tip: "Use action=\"schema\" with a specific resource for full filter/create/includes spec",
|
|
2725
|
+
resources: Object.entries(RESOURCE_SCHEMAS).map(([resource, schema]) => ({
|
|
2726
|
+
resource,
|
|
2727
|
+
actions: schema.actions
|
|
2728
|
+
}))
|
|
2729
|
+
});
|
|
2730
|
+
}
|
|
2731
|
+
/**
|
|
2732
|
+
* Resources that support the query filter for text search
|
|
2733
|
+
*/
|
|
2734
|
+
const SEARCHABLE_RESOURCES = [
|
|
2735
|
+
"projects",
|
|
2736
|
+
"companies",
|
|
2737
|
+
"people",
|
|
2738
|
+
"tasks",
|
|
2739
|
+
"deals"
|
|
2740
|
+
];
|
|
2741
|
+
/**
|
|
2742
|
+
* Default resources to search when not specified
|
|
2743
|
+
*/
|
|
2744
|
+
var DEFAULT_SEARCH_RESOURCES = [
|
|
2745
|
+
"projects",
|
|
2746
|
+
"companies",
|
|
2747
|
+
"people",
|
|
2748
|
+
"tasks"
|
|
2749
|
+
];
|
|
2750
|
+
/**
|
|
2751
|
+
* Handle cross-resource search.
|
|
2752
|
+
*
|
|
2753
|
+
* @param query - Search query text (required)
|
|
2754
|
+
* @param resources - Resource types to search (optional, defaults to DEFAULT_SEARCH_RESOURCES)
|
|
2755
|
+
* @param credentials - Productive API credentials
|
|
2756
|
+
* @param execute - Function to execute tool calls (injected for delegation and testing)
|
|
2757
|
+
* @returns Grouped search results across all requested resources
|
|
2758
|
+
*/
|
|
2759
|
+
async function handleSearch(query, resources, credentials, execute) {
|
|
2760
|
+
if (!query || query.trim() === "") return errorResult("Missing required parameter: query. Provide a non-empty search string.");
|
|
2761
|
+
const trimmedQuery = query.trim();
|
|
2762
|
+
const resourcesToSearch = resources && resources.length > 0 ? resources : DEFAULT_SEARCH_RESOURCES;
|
|
2763
|
+
const invalidResources = resourcesToSearch.filter((r) => !SEARCHABLE_RESOURCES.includes(r));
|
|
2764
|
+
if (invalidResources.length > 0) return errorResult(`Invalid searchable resources: ${invalidResources.join(", ")}. Valid searchable resources: ${SEARCHABLE_RESOURCES.join(", ")}.`);
|
|
2765
|
+
const searchPromises = resourcesToSearch.map(async (resource) => {
|
|
2766
|
+
try {
|
|
2767
|
+
const textContent = (await execute("productive", {
|
|
2768
|
+
resource,
|
|
2769
|
+
action: "list",
|
|
2770
|
+
query: trimmedQuery,
|
|
2771
|
+
compact: true,
|
|
2772
|
+
per_page: 10
|
|
2773
|
+
}, credentials)).content.find((c) => c.type === "text");
|
|
2774
|
+
if (!textContent || textContent.type !== "text") return [resource, { error: "No content in response" }];
|
|
2775
|
+
try {
|
|
2776
|
+
const parsed = JSON.parse(textContent.text);
|
|
2777
|
+
const items = parsed.items ?? parsed.data ?? [];
|
|
2778
|
+
return [resource, { items: Array.isArray(items) ? items : [] }];
|
|
2779
|
+
} catch {
|
|
2780
|
+
return [resource, { error: "Failed to parse response JSON" }];
|
|
2781
|
+
}
|
|
2782
|
+
} catch (err) {
|
|
2783
|
+
return [resource, { error: err instanceof Error ? err.message : String(err) }];
|
|
2784
|
+
}
|
|
2785
|
+
});
|
|
2786
|
+
const searchResults = await Promise.all(searchPromises);
|
|
2787
|
+
const results = {};
|
|
2788
|
+
let totalResults = 0;
|
|
2789
|
+
for (const [resource, result] of searchResults) if (result.error) results[resource] = { error: result.error };
|
|
2790
|
+
else {
|
|
2791
|
+
const items = result.items ?? [];
|
|
2792
|
+
results[resource] = items;
|
|
2793
|
+
totalResults += items.length;
|
|
2794
|
+
}
|
|
2795
|
+
return jsonResult({
|
|
2796
|
+
query: trimmedQuery,
|
|
2797
|
+
resources_searched: resourcesToSearch,
|
|
2798
|
+
results,
|
|
2799
|
+
total_results: totalResults
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2802
|
+
/**
|
|
2137
2803
|
* Services MCP handler.
|
|
2138
2804
|
*
|
|
2139
2805
|
* Uses the createResourceHandler factory for the common list pattern.
|
|
@@ -2150,6 +2816,72 @@ const handleServices = createResourceHandler({
|
|
|
2150
2816
|
executors: { list: listServices }
|
|
2151
2817
|
});
|
|
2152
2818
|
/**
|
|
2819
|
+
* Summaries MCP handler.
|
|
2820
|
+
*
|
|
2821
|
+
* Custom handler for dashboard-style summaries (not using createResourceHandler).
|
|
2822
|
+
* Routes actions to the appropriate summary executor.
|
|
2823
|
+
*/
|
|
2824
|
+
var VALID_ACTIONS = [
|
|
2825
|
+
"my_day",
|
|
2826
|
+
"project_health",
|
|
2827
|
+
"team_pulse",
|
|
2828
|
+
"help"
|
|
2829
|
+
];
|
|
2830
|
+
/**
|
|
2831
|
+
* Handle summaries resource.
|
|
2832
|
+
*
|
|
2833
|
+
* Supports: my_day, project_health, team_pulse
|
|
2834
|
+
*/
|
|
2835
|
+
async function handleSummaries(action, args, ctx) {
|
|
2836
|
+
if (!VALID_ACTIONS.includes(action)) return inputErrorResult(ErrorMessages.invalidAction(action, "summaries", VALID_ACTIONS));
|
|
2837
|
+
const execCtx = ctx.executor();
|
|
2838
|
+
switch (action) {
|
|
2839
|
+
case "my_day": return jsonResult((await getMyDaySummary({}, execCtx)).data);
|
|
2840
|
+
case "project_health":
|
|
2841
|
+
if (!args.project_id) return inputErrorResult(new UserInputError("project_id is required for project_health summary", [
|
|
2842
|
+
"Provide the project_id parameter",
|
|
2843
|
+
"You can find project IDs using resource=\"projects\" action=\"list\"",
|
|
2844
|
+
"Or use a project number like \"PRJ-123\""
|
|
2845
|
+
]));
|
|
2846
|
+
return jsonResult((await getProjectHealthSummary({ projectId: args.project_id }, execCtx)).data);
|
|
2847
|
+
case "team_pulse": return jsonResult((await getTeamPulseSummary({}, execCtx)).data);
|
|
2848
|
+
case "help": return jsonResult({
|
|
2849
|
+
resource: "summaries",
|
|
2850
|
+
description: "Dashboard-style summaries that aggregate data from multiple resources",
|
|
2851
|
+
actions: {
|
|
2852
|
+
my_day: {
|
|
2853
|
+
description: "Personal dashboard for the current user",
|
|
2854
|
+
parameters: {},
|
|
2855
|
+
returns: {
|
|
2856
|
+
tasks: "Open and overdue tasks assigned to you",
|
|
2857
|
+
time: "Time entries logged today",
|
|
2858
|
+
timers: "Currently running timers"
|
|
2859
|
+
}
|
|
2860
|
+
},
|
|
2861
|
+
project_health: {
|
|
2862
|
+
description: "Project status with budget burn and task stats",
|
|
2863
|
+
parameters: { project_id: "Required. Project ID or project number (e.g., PRJ-123)" },
|
|
2864
|
+
returns: {
|
|
2865
|
+
project: "Project details",
|
|
2866
|
+
tasks: "Open and overdue task counts",
|
|
2867
|
+
budget: "Budget burn rate by service",
|
|
2868
|
+
recent_activity: "Time tracking activity in last 7 days"
|
|
2869
|
+
}
|
|
2870
|
+
},
|
|
2871
|
+
team_pulse: {
|
|
2872
|
+
description: "Team-wide time tracking activity for today",
|
|
2873
|
+
parameters: {},
|
|
2874
|
+
returns: {
|
|
2875
|
+
team: "Counts of active users, those tracking time, and with timers",
|
|
2876
|
+
people: "Per-person breakdown of time logged and active timers"
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
});
|
|
2881
|
+
default: return inputErrorResult(ErrorMessages.invalidAction(action, "summaries", VALID_ACTIONS));
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
/**
|
|
2153
2885
|
* Tasks MCP handler.
|
|
2154
2886
|
*/
|
|
2155
2887
|
const handleTasks = createResourceHandler({
|
|
@@ -2160,7 +2892,8 @@ const handleTasks = createResourceHandler({
|
|
|
2160
2892
|
"get",
|
|
2161
2893
|
"create",
|
|
2162
2894
|
"update",
|
|
2163
|
-
"resolve"
|
|
2895
|
+
"resolve",
|
|
2896
|
+
"context"
|
|
2164
2897
|
],
|
|
2165
2898
|
formatter: formatTask$1,
|
|
2166
2899
|
hints: (data, id) => {
|
|
@@ -2192,6 +2925,23 @@ const handleTasks = createResourceHandler({
|
|
|
2192
2925
|
description: args.description,
|
|
2193
2926
|
assigneeId: args.assignee_id
|
|
2194
2927
|
}) },
|
|
2928
|
+
customActions: { context: async (args, ctx, execCtx) => {
|
|
2929
|
+
if (!args.id) return inputErrorResult(ErrorMessages.missingId("context"));
|
|
2930
|
+
const result = await getTaskContext({ id: args.id }, execCtx);
|
|
2931
|
+
const formatOptions = {
|
|
2932
|
+
...ctx.formatOptions,
|
|
2933
|
+
included: result.included
|
|
2934
|
+
};
|
|
2935
|
+
return jsonResult({
|
|
2936
|
+
...formatTask$1(result.data.task, formatOptions),
|
|
2937
|
+
comments: result.data.comments.map((c) => formatComment$1(c, { compact: true })),
|
|
2938
|
+
time_entries: result.data.time_entries.map((t) => formatTimeEntry$1(t, { compact: true })),
|
|
2939
|
+
subtasks: result.data.subtasks.map((s) => formatTask$1(s, {
|
|
2940
|
+
...formatOptions,
|
|
2941
|
+
compact: true
|
|
2942
|
+
}))
|
|
2943
|
+
});
|
|
2944
|
+
} },
|
|
2195
2945
|
executors: {
|
|
2196
2946
|
list: listTasks,
|
|
2197
2947
|
get: getTask,
|
|
@@ -2315,14 +3065,17 @@ var DEFAULT_PER_PAGE = 20;
|
|
|
2315
3065
|
* Execute a tool with the given credentials and arguments
|
|
2316
3066
|
*/
|
|
2317
3067
|
async function executeToolWithCredentials(name, args, credentials) {
|
|
3068
|
+
if (name !== "productive") return errorResult(`Unknown tool: ${name}`);
|
|
3069
|
+
const typedArgs = args;
|
|
3070
|
+
if (typedArgs.resource === "batch") return handleBatch(typedArgs.operations, credentials, executeToolWithCredentials);
|
|
3071
|
+
const { resource, action, filter, page, per_page, compact, include, query, resources, no_hints, type, ...restArgs } = typedArgs;
|
|
3072
|
+
if (resource === "search") return await handleSearch(query, resources, credentials, executeToolWithCredentials);
|
|
2318
3073
|
const api = new ProductiveApi({ config: {
|
|
2319
3074
|
apiToken: credentials.apiToken,
|
|
2320
3075
|
organizationId: credentials.organizationId,
|
|
2321
3076
|
userId: credentials.userId,
|
|
2322
3077
|
baseUrl: process.env.PRODUCTIVE_BASE_URL
|
|
2323
3078
|
} });
|
|
2324
|
-
if (name !== "productive") return errorResult(`Unknown tool: ${name}`);
|
|
2325
|
-
const { resource, action, filter, page, per_page, compact, include, query, no_hints, type, ...restArgs } = args;
|
|
2326
3079
|
const isCompact = compact ?? action !== "get";
|
|
2327
3080
|
const formatOptions = { compact: isCompact };
|
|
2328
3081
|
let stringFilter = toStringFilter(filter);
|
|
@@ -2332,7 +3085,7 @@ async function executeToolWithCredentials(name, args, credentials) {
|
|
|
2332
3085
|
query
|
|
2333
3086
|
};
|
|
2334
3087
|
const includeHints = no_hints !== true && action === "get" && !isCompact;
|
|
2335
|
-
const execCtx = fromHandlerContext({ api });
|
|
3088
|
+
const execCtx = fromHandlerContext({ api }, { userId: credentials.userId });
|
|
2336
3089
|
const ctx = {
|
|
2337
3090
|
formatOptions,
|
|
2338
3091
|
filter: stringFilter,
|
|
@@ -2343,7 +3096,8 @@ async function executeToolWithCredentials(name, args, credentials) {
|
|
|
2343
3096
|
executor: () => execCtx
|
|
2344
3097
|
};
|
|
2345
3098
|
try {
|
|
2346
|
-
if (action === "help") return resource ? handleHelp(resource) : handleHelpOverview();
|
|
3099
|
+
if (action === "help" && resource !== "summaries") return resource ? handleHelp(resource) : handleHelpOverview();
|
|
3100
|
+
if (action === "schema") return resource ? handleSchema(resource) : handleSchemaOverview();
|
|
2347
3101
|
const resolveArgs = {
|
|
2348
3102
|
query,
|
|
2349
3103
|
type
|
|
@@ -2381,6 +3135,7 @@ async function executeToolWithCredentials(name, args, credentials) {
|
|
|
2381
3135
|
case "pages": return await handlePages(action, restArgs, ctx);
|
|
2382
3136
|
case "discussions": return await handleDiscussions(action, restArgs, ctx);
|
|
2383
3137
|
case "reports": return await handleReports(action, restArgs, ctx);
|
|
3138
|
+
case "summaries": return await handleSummaries(action, restArgs, ctx);
|
|
2384
3139
|
case "budgets": return inputErrorResult(new UserInputError("The \"budgets\" resource has been removed. Budgets are deals with type=2.", [
|
|
2385
3140
|
"Use resource=\"deals\" with filter[type]=\"2\" to list only budgets",
|
|
2386
3141
|
"To create a budget: resource=\"deals\" action=\"create\" with budget=true",
|
|
@@ -2401,4 +3156,4 @@ async function executeToolWithCredentials(name, args, credentials) {
|
|
|
2401
3156
|
}
|
|
2402
3157
|
export { executeToolWithCredentials as t };
|
|
2403
3158
|
|
|
2404
|
-
//# sourceMappingURL=handlers-
|
|
3159
|
+
//# sourceMappingURL=handlers-B8GRTaDu.js.map
|