@mgsoftwarebv/mcp-server-bridge 3.3.3 → 3.3.5
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/index.js +1294 -154
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -93101,6 +93101,9 @@ var tickets = pgTable(
|
|
|
93101
93101
|
precision: 10,
|
|
93102
93102
|
scale: 2
|
|
93103
93103
|
}).default(0),
|
|
93104
|
+
// Deadline / due date - shown as a virtual "deadline" event in the agenda
|
|
93105
|
+
dueDate: timestamp("due_date", { withTimezone: true, mode: "string" }),
|
|
93106
|
+
dueDateAllDay: boolean3("due_date_all_day").notNull().default(false),
|
|
93104
93107
|
// Origin tracking - how the ticket was created
|
|
93105
93108
|
origin: ticketOriginEnum().default("manual").notNull(),
|
|
93106
93109
|
// Structured context (e.g. visual review anchor metadata)
|
|
@@ -93177,6 +93180,7 @@ var tickets = pgTable(
|
|
|
93177
93180
|
index("idx_tickets_status").on(table.status),
|
|
93178
93181
|
index("idx_tickets_priority").on(table.priority),
|
|
93179
93182
|
index("idx_tickets_created_at").using("btree", table.createdAt.desc()),
|
|
93183
|
+
index("idx_tickets_due_date").on(table.teamId, table.dueDate),
|
|
93180
93184
|
index("idx_tickets_fts").using(
|
|
93181
93185
|
"gin",
|
|
93182
93186
|
table.fts.asc().nullsLast().op("tsvector_ops")
|
|
@@ -95053,6 +95057,15 @@ var quotations = pgTable(
|
|
|
95053
95057
|
topBlock: jsonb("top_block"),
|
|
95054
95058
|
bottomBlock: jsonb("bottom_block"),
|
|
95055
95059
|
template: jsonb(),
|
|
95060
|
+
// Typst block-document content (mirrors `documents.blocks`). The pricing
|
|
95061
|
+
// block is the financial source of truth; line_items/amount/subtotal/vat
|
|
95062
|
+
// are derived from it on save for invoicing/KPIs/AI.
|
|
95063
|
+
blocks: jsonb().default([]).notNull(),
|
|
95064
|
+
branding: jsonb().default({}).notNull(),
|
|
95065
|
+
pageSize: text("page_size").default("a4").notNull(),
|
|
95066
|
+
// PDF staleness marker: hash of the compiled block content + file size.
|
|
95067
|
+
compiledHash: text("compiled_hash"),
|
|
95068
|
+
fileSize: integer2("file_size"),
|
|
95056
95069
|
vat: numericCasted({ precision: 10, scale: 2 }),
|
|
95057
95070
|
tax: numericCasted({ precision: 10, scale: 2 }),
|
|
95058
95071
|
discount: numericCasted({ precision: 10, scale: 2 }),
|
|
@@ -105507,7 +105520,7 @@ var TOOLS = [
|
|
|
105507
105520
|
},
|
|
105508
105521
|
{
|
|
105509
105522
|
name: "get-tickets",
|
|
105510
|
-
description: "Get tickets with optional filtering by status, priority, project, customer, or search query",
|
|
105523
|
+
description: "Get tickets with optional filtering by status, priority, project, customer, tags, or search query. Each ticket includes its tags.",
|
|
105511
105524
|
inputSchema: {
|
|
105512
105525
|
type: "object",
|
|
105513
105526
|
properties: {
|
|
@@ -105533,6 +105546,20 @@ var TOOLS = [
|
|
|
105533
105546
|
type: "string",
|
|
105534
105547
|
description: "Search query for ticket number, title, or description"
|
|
105535
105548
|
},
|
|
105549
|
+
tag: {
|
|
105550
|
+
type: "string",
|
|
105551
|
+
description: "Filter by a single tag name (case-insensitive, team-specific)"
|
|
105552
|
+
},
|
|
105553
|
+
tags: {
|
|
105554
|
+
type: "array",
|
|
105555
|
+
items: { type: "string" },
|
|
105556
|
+
description: "Filter by tag names (case-insensitive). Tickets matching any listed tag are returned."
|
|
105557
|
+
},
|
|
105558
|
+
tagIds: {
|
|
105559
|
+
type: "array",
|
|
105560
|
+
items: { type: "string" },
|
|
105561
|
+
description: "Filter by tag IDs"
|
|
105562
|
+
},
|
|
105536
105563
|
pageSize: { type: "number", default: 20, maximum: 100 }
|
|
105537
105564
|
},
|
|
105538
105565
|
required: []
|
|
@@ -105540,7 +105567,7 @@ var TOOLS = [
|
|
|
105540
105567
|
},
|
|
105541
105568
|
{
|
|
105542
105569
|
name: "get-ticket-by-id",
|
|
105543
|
-
description: "Get a specific ticket by its ID, including comment text and a full attachment listing (with ids). Images from ticket and comment attachments are downloaded and returned inline as base64. For non-image attachments, call get-ticket-attachment with the listed id to get a download URL.",
|
|
105570
|
+
description: "Get a specific ticket by its ID, including tags, comment text and a full attachment listing (with ids). Images from ticket and comment attachments are downloaded and returned inline as base64. For non-image attachments, call get-ticket-attachment with the listed id to get a download URL.",
|
|
105544
105571
|
inputSchema: {
|
|
105545
105572
|
type: "object",
|
|
105546
105573
|
properties: {
|
|
@@ -105552,7 +105579,7 @@ var TOOLS = [
|
|
|
105552
105579
|
},
|
|
105553
105580
|
{
|
|
105554
105581
|
name: "create-ticket",
|
|
105555
|
-
description: "Create a new ticket",
|
|
105582
|
+
description: "Create a new ticket. Tags can be passed by name (auto-created if missing) or by existing tag IDs. Tag names are matched case-insensitively within the team.",
|
|
105556
105583
|
inputSchema: {
|
|
105557
105584
|
type: "object",
|
|
105558
105585
|
properties: {
|
|
@@ -105589,14 +105616,28 @@ var TOOLS = [
|
|
|
105589
105616
|
default: "task"
|
|
105590
105617
|
},
|
|
105591
105618
|
projectId: { type: "string" },
|
|
105592
|
-
customerId: { type: "string" }
|
|
105619
|
+
customerId: { type: "string" },
|
|
105620
|
+
dueDate: {
|
|
105621
|
+
type: "string",
|
|
105622
|
+
description: "Optional deadline / due date (ISO 8601, e.g. 2026-06-30). Shows as a deadline event in the agenda."
|
|
105623
|
+
},
|
|
105624
|
+
tags: {
|
|
105625
|
+
type: "array",
|
|
105626
|
+
items: { type: "string" },
|
|
105627
|
+
description: "Tag names to attach. Missing tags are created as general team tags (case-insensitive deduplication)."
|
|
105628
|
+
},
|
|
105629
|
+
tagIds: {
|
|
105630
|
+
type: "array",
|
|
105631
|
+
items: { type: "string" },
|
|
105632
|
+
description: "Existing tag IDs to attach"
|
|
105633
|
+
}
|
|
105593
105634
|
},
|
|
105594
105635
|
required: ["title"]
|
|
105595
105636
|
}
|
|
105596
105637
|
},
|
|
105597
105638
|
{
|
|
105598
105639
|
name: "update-ticket",
|
|
105599
|
-
description: "Update an existing ticket's fields (title, description, status, priority, type, project, customer, assignee, estimated hours). Only provided fields are changed. Changes are written to the ticket activity feed but do NOT send notifications. Set assigneeId to null to unassign; a provided assigneeId must be a member of the ticket's team. Common workflow: set status=in_progress when starting work; after merge/push set status=review and assigneeId to the requester (creator) id from get-ticket-by-id.",
|
|
105640
|
+
description: "Update an existing ticket's fields (title, description, status, priority, type, project, customer, assignee, estimated hours, deadline/due date, tags). Only provided fields are changed. Changes are written to the ticket activity feed but do NOT send notifications. Set assigneeId to null to unassign; a provided assigneeId must be a member of the ticket's team. Set dueDate (ISO 8601, e.g. 2026-06-30 or 2026-06-30T00:00:00.000Z) to give the ticket a deadline that shows up in the agenda; set dueDate to null to clear it. Tags: use tagMode replace (default) to set the full tag list, merge to add tags, or remove to drop listed tags. Common workflow: set status=in_progress when starting work; after merge/push set status=review and assigneeId to the requester (creator) id from get-ticket-by-id.",
|
|
105600
105641
|
inputSchema: {
|
|
105601
105642
|
type: "object",
|
|
105602
105643
|
properties: {
|
|
@@ -105639,7 +105680,159 @@ var TOOLS = [
|
|
|
105639
105680
|
type: ["string", "null"],
|
|
105640
105681
|
description: "User ID to assign, or null to unassign."
|
|
105641
105682
|
},
|
|
105642
|
-
estimatedHours: { type: "number" }
|
|
105683
|
+
estimatedHours: { type: "number" },
|
|
105684
|
+
dueDate: {
|
|
105685
|
+
type: ["string", "null"],
|
|
105686
|
+
description: "Deadline / due date (ISO 8601, e.g. 2026-06-30 or 2026-06-30T00:00:00.000Z). Shows as a deadline event in the agenda. Pass null to clear the deadline."
|
|
105687
|
+
},
|
|
105688
|
+
tags: {
|
|
105689
|
+
type: "array",
|
|
105690
|
+
items: { type: "string" },
|
|
105691
|
+
description: "Tag names. With tagMode replace (default), sets the ticket's tags. With merge, adds tags. With remove, removes these tags. Missing names are auto-created when adding."
|
|
105692
|
+
},
|
|
105693
|
+
tagIds: {
|
|
105694
|
+
type: "array",
|
|
105695
|
+
items: { type: "string" },
|
|
105696
|
+
description: "Tag IDs (same tagMode semantics as tags)"
|
|
105697
|
+
},
|
|
105698
|
+
tagMode: {
|
|
105699
|
+
type: "string",
|
|
105700
|
+
enum: ["replace", "merge", "remove"],
|
|
105701
|
+
default: "replace",
|
|
105702
|
+
description: "How to apply tags/tagIds: replace = set full list, merge = add, remove = remove listed tags"
|
|
105703
|
+
}
|
|
105704
|
+
},
|
|
105705
|
+
required: ["id"]
|
|
105706
|
+
}
|
|
105707
|
+
},
|
|
105708
|
+
{
|
|
105709
|
+
name: "get-tags",
|
|
105710
|
+
description: "List tags for the provider team. Tags are team-specific; optional projectId includes general tags plus project-specific tags.",
|
|
105711
|
+
inputSchema: {
|
|
105712
|
+
type: "object",
|
|
105713
|
+
properties: {
|
|
105714
|
+
teamId: teamIdProp,
|
|
105715
|
+
projectId: {
|
|
105716
|
+
type: "string",
|
|
105717
|
+
description: "Optional project ID to include project-specific tags"
|
|
105718
|
+
},
|
|
105719
|
+
pageSize: { type: "number", default: 100, maximum: 200 }
|
|
105720
|
+
},
|
|
105721
|
+
required: []
|
|
105722
|
+
}
|
|
105723
|
+
},
|
|
105724
|
+
{
|
|
105725
|
+
name: "create-tag",
|
|
105726
|
+
description: "Create a team tag. Tag names are deduplicated case-insensitively within the same team/project scope.",
|
|
105727
|
+
inputSchema: {
|
|
105728
|
+
type: "object",
|
|
105729
|
+
properties: {
|
|
105730
|
+
teamId: teamIdProp,
|
|
105731
|
+
name: { type: "string", description: "Tag name" },
|
|
105732
|
+
projectId: {
|
|
105733
|
+
type: "string",
|
|
105734
|
+
description: "Optional project ID for a project-specific tag; omit for a general team tag"
|
|
105735
|
+
}
|
|
105736
|
+
},
|
|
105737
|
+
required: ["name"]
|
|
105738
|
+
}
|
|
105739
|
+
},
|
|
105740
|
+
{
|
|
105741
|
+
name: "get-calendar-items",
|
|
105742
|
+
description: "List agenda/calendar items (deadlines, meetings, reminders, deliveries) with optional filters by date range, project, ticket, customer, assignee, type, or status. Returns items with id, title, startsAt, endsAt, dueDate, linked ticket/project/customer ids, and status.",
|
|
105743
|
+
inputSchema: {
|
|
105744
|
+
type: "object",
|
|
105745
|
+
properties: {
|
|
105746
|
+
teamId: teamIdProp,
|
|
105747
|
+
projectId: { type: "string" },
|
|
105748
|
+
ticketId: { type: "string" },
|
|
105749
|
+
customerId: { type: "string" },
|
|
105750
|
+
assigneeId: { type: "string" },
|
|
105751
|
+
dateFrom: {
|
|
105752
|
+
type: "string",
|
|
105753
|
+
description: "ISO datetime or YYYY-MM-DD (inclusive start)"
|
|
105754
|
+
},
|
|
105755
|
+
dateTo: {
|
|
105756
|
+
type: "string",
|
|
105757
|
+
description: "ISO datetime or YYYY-MM-DD (inclusive end)"
|
|
105758
|
+
},
|
|
105759
|
+
status: {
|
|
105760
|
+
type: "string",
|
|
105761
|
+
enum: ["draft", "scheduled", "completed", "cancelled"]
|
|
105762
|
+
},
|
|
105763
|
+
type: {
|
|
105764
|
+
type: "string",
|
|
105765
|
+
enum: ["deadline", "meeting", "reminder", "delivery"]
|
|
105766
|
+
},
|
|
105767
|
+
pageSize: { type: "number", default: 50, maximum: 100 }
|
|
105768
|
+
},
|
|
105769
|
+
required: []
|
|
105770
|
+
}
|
|
105771
|
+
},
|
|
105772
|
+
{
|
|
105773
|
+
name: "create-calendar-item",
|
|
105774
|
+
description: "Create an agenda/calendar item linked to tickets, projects, or customers. Use type 'deadline' with dueDate (YYYY-MM-DD) for delivery deadlines. Prevents duplicate deadline items for the same ticket \u2014 updates the existing one instead. Types: deadline, meeting, reminder, delivery.",
|
|
105775
|
+
inputSchema: {
|
|
105776
|
+
type: "object",
|
|
105777
|
+
properties: {
|
|
105778
|
+
teamId: teamIdProp,
|
|
105779
|
+
title: { type: "string" },
|
|
105780
|
+
description: { type: "string" },
|
|
105781
|
+
dueDate: {
|
|
105782
|
+
type: "string",
|
|
105783
|
+
description: "Deadline date YYYY-MM-DD (all-day). Preferred for deadlines."
|
|
105784
|
+
},
|
|
105785
|
+
startsAt: {
|
|
105786
|
+
type: "string",
|
|
105787
|
+
description: "Start datetime (ISO). Use when a specific time is needed."
|
|
105788
|
+
},
|
|
105789
|
+
endsAt: { type: "string", description: "End datetime (ISO)" },
|
|
105790
|
+
projectId: { type: "string" },
|
|
105791
|
+
ticketId: { type: "string" },
|
|
105792
|
+
customerId: { type: "string" },
|
|
105793
|
+
assigneeId: {
|
|
105794
|
+
type: "string",
|
|
105795
|
+
description: "User ID of the assignee (defaults to the API key user)"
|
|
105796
|
+
},
|
|
105797
|
+
type: {
|
|
105798
|
+
type: "string",
|
|
105799
|
+
enum: ["deadline", "meeting", "reminder", "delivery"],
|
|
105800
|
+
default: "deadline"
|
|
105801
|
+
},
|
|
105802
|
+
status: {
|
|
105803
|
+
type: "string",
|
|
105804
|
+
enum: ["draft", "scheduled", "completed"],
|
|
105805
|
+
default: "scheduled"
|
|
105806
|
+
}
|
|
105807
|
+
},
|
|
105808
|
+
required: ["title"]
|
|
105809
|
+
}
|
|
105810
|
+
},
|
|
105811
|
+
{
|
|
105812
|
+
name: "update-calendar-item",
|
|
105813
|
+
description: "Update an existing agenda/calendar item by id. Supports changing title, dates, status, type, and linked ticket/project/customer. Set status to 'completed' or 'cancelled' to finish or remove the item.",
|
|
105814
|
+
inputSchema: {
|
|
105815
|
+
type: "object",
|
|
105816
|
+
properties: {
|
|
105817
|
+
teamId: teamIdProp,
|
|
105818
|
+
id: { type: "string", description: "Calendar item ID" },
|
|
105819
|
+
title: { type: "string" },
|
|
105820
|
+
description: { type: ["string", "null"] },
|
|
105821
|
+
dueDate: { type: "string", description: "YYYY-MM-DD" },
|
|
105822
|
+
startsAt: { type: "string" },
|
|
105823
|
+
endsAt: { type: "string" },
|
|
105824
|
+
projectId: { type: ["string", "null"] },
|
|
105825
|
+
ticketId: { type: ["string", "null"] },
|
|
105826
|
+
customerId: { type: ["string", "null"] },
|
|
105827
|
+
assigneeId: { type: "string" },
|
|
105828
|
+
type: {
|
|
105829
|
+
type: "string",
|
|
105830
|
+
enum: ["deadline", "meeting", "reminder", "delivery"]
|
|
105831
|
+
},
|
|
105832
|
+
status: {
|
|
105833
|
+
type: "string",
|
|
105834
|
+
enum: ["draft", "scheduled", "completed", "cancelled"]
|
|
105835
|
+
}
|
|
105643
105836
|
},
|
|
105644
105837
|
required: ["id"]
|
|
105645
105838
|
}
|
|
@@ -106494,120 +106687,661 @@ async function resolveTeamScope(requestedTeamId) {
|
|
|
106494
106687
|
return { ok: true, teamIds, projectIds, customerIds };
|
|
106495
106688
|
}
|
|
106496
106689
|
|
|
106497
|
-
// src/tools/
|
|
106498
|
-
|
|
106499
|
-
const { q: q3, pageSize = 20 } = input;
|
|
106500
|
-
const resolved = await resolveTeamId(input.teamId);
|
|
106501
|
-
if (!resolved.ok) return resolved.response;
|
|
106502
|
-
const customerIds = await getAccessibleCustomerIds(resolved.teamId);
|
|
106503
|
-
if (customerIds.length === 0) {
|
|
106504
|
-
return {
|
|
106505
|
-
content: [
|
|
106506
|
-
{
|
|
106507
|
-
type: "text",
|
|
106508
|
-
text: "No customers found or no access to any customers."
|
|
106509
|
-
}
|
|
106510
|
-
]
|
|
106511
|
-
};
|
|
106512
|
-
}
|
|
106513
|
-
const filters = [inArray(schema_exports.customers.id, customerIds)];
|
|
106514
|
-
if (q3) {
|
|
106515
|
-
const pattern = `%${q3}%`;
|
|
106516
|
-
filters.push(
|
|
106517
|
-
or(
|
|
106518
|
-
ilike(schema_exports.customers.name, pattern),
|
|
106519
|
-
ilike(schema_exports.customers.email, pattern)
|
|
106520
|
-
)
|
|
106521
|
-
);
|
|
106522
|
-
}
|
|
106523
|
-
const rows = await db.select({
|
|
106524
|
-
id: schema_exports.customers.id,
|
|
106525
|
-
name: schema_exports.customers.name,
|
|
106526
|
-
email: schema_exports.customers.email,
|
|
106527
|
-
website: schema_exports.customers.website,
|
|
106528
|
-
createdAt: schema_exports.customers.createdAt
|
|
106529
|
-
}).from(schema_exports.customers).where(and(...filters)).orderBy(asc(schema_exports.customers.name)).limit(Math.min(pageSize, 100));
|
|
106690
|
+
// src/tools/ticket-access.ts
|
|
106691
|
+
function notFoundResponse(ticketId) {
|
|
106530
106692
|
return {
|
|
106531
106693
|
content: [
|
|
106532
106694
|
{
|
|
106533
106695
|
type: "text",
|
|
106534
|
-
text: `
|
|
106535
|
-
|
|
106536
|
-
${rows.map(
|
|
106537
|
-
(c6) => `**${c6.name}**
|
|
106538
|
-
${c6.email ? `Email: ${c6.email}
|
|
106539
|
-
` : ""}${c6.website ? `Website: ${c6.website}
|
|
106540
|
-
` : ""}Created: ${new Date(c6.createdAt).toLocaleDateString()}
|
|
106541
|
-
`
|
|
106542
|
-
).join("\n") || "No customers found."}`
|
|
106696
|
+
text: `Ticket not found or no access: ${ticketId}. Call get-tickets to find the correct ticket.`
|
|
106543
106697
|
}
|
|
106544
106698
|
]
|
|
106545
106699
|
};
|
|
106546
106700
|
}
|
|
106547
|
-
async function
|
|
106548
|
-
const
|
|
106549
|
-
|
|
106550
|
-
|
|
106551
|
-
|
|
106552
|
-
teamId:
|
|
106553
|
-
|
|
106554
|
-
|
|
106555
|
-
|
|
106556
|
-
|
|
106557
|
-
|
|
106558
|
-
|
|
106559
|
-
|
|
106560
|
-
|
|
106561
|
-
|
|
106701
|
+
async function loadAccessibleTicket(requestedTeamId, ticketId) {
|
|
106702
|
+
const scope = await resolveTeamScope(requestedTeamId);
|
|
106703
|
+
if (!scope.ok) return scope;
|
|
106704
|
+
const [ticket] = await db.select({
|
|
106705
|
+
id: schema_exports.tickets.id,
|
|
106706
|
+
teamId: schema_exports.tickets.teamId,
|
|
106707
|
+
projectId: schema_exports.tickets.projectId,
|
|
106708
|
+
customerId: schema_exports.tickets.customerId,
|
|
106709
|
+
ticketNumber: schema_exports.tickets.ticketNumber,
|
|
106710
|
+
title: schema_exports.tickets.title,
|
|
106711
|
+
status: schema_exports.tickets.status,
|
|
106712
|
+
priority: schema_exports.tickets.priority,
|
|
106713
|
+
type: schema_exports.tickets.type,
|
|
106714
|
+
assigneeId: schema_exports.tickets.assigneeId
|
|
106715
|
+
}).from(schema_exports.tickets).where(eq(schema_exports.tickets.id, ticketId)).limit(1);
|
|
106716
|
+
if (!ticket) return { ok: false, response: notFoundResponse(ticketId) };
|
|
106717
|
+
const hasAccess = scope.teamIds.includes(ticket.teamId) || !!ticket.projectId && scope.projectIds.includes(ticket.projectId) || !!ticket.customerId && scope.customerIds.includes(ticket.customerId);
|
|
106718
|
+
if (!hasAccess) return { ok: false, response: notFoundResponse(ticketId) };
|
|
106719
|
+
return { ok: true, ticket };
|
|
106720
|
+
}
|
|
106562
106721
|
|
|
106563
|
-
|
|
106564
|
-
|
|
106565
|
-
|
|
106566
|
-
|
|
106567
|
-
|
|
106568
|
-
|
|
106722
|
+
// src/tools/calendar-items.ts
|
|
106723
|
+
function mapInputType(type) {
|
|
106724
|
+
switch (type) {
|
|
106725
|
+
case "reminder":
|
|
106726
|
+
return "task";
|
|
106727
|
+
case "delivery":
|
|
106728
|
+
return "work";
|
|
106729
|
+
default:
|
|
106730
|
+
return type;
|
|
106731
|
+
}
|
|
106732
|
+
}
|
|
106733
|
+
function mapOutputType(type, metadata) {
|
|
106734
|
+
const meta5 = metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata : null;
|
|
106735
|
+
const stored = meta5?.calendarType;
|
|
106736
|
+
if (typeof stored === "string") return stored;
|
|
106737
|
+
if (type === "task") return "reminder";
|
|
106738
|
+
if (type === "work") return "delivery";
|
|
106739
|
+
return type;
|
|
106740
|
+
}
|
|
106741
|
+
function mapOutputStatus(status, metadata, isDeleted) {
|
|
106742
|
+
if (isDeleted) return "cancelled";
|
|
106743
|
+
const meta5 = metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata : null;
|
|
106744
|
+
if (meta5?.completed === true) return "completed";
|
|
106745
|
+
return status === "draft" ? "draft" : "scheduled";
|
|
106746
|
+
}
|
|
106747
|
+
function parseDueDate(dueDate) {
|
|
106748
|
+
const dateOnly = dueDate.split("T")[0];
|
|
106749
|
+
return {
|
|
106750
|
+
startTime: `${dateOnly}T00:00:00.000Z`,
|
|
106751
|
+
endTime: `${dateOnly}T23:59:59.999Z`
|
|
106569
106752
|
};
|
|
106570
106753
|
}
|
|
106571
|
-
|
|
106572
|
-
|
|
106573
|
-
|
|
106574
|
-
|
|
106575
|
-
|
|
106576
|
-
|
|
106577
|
-
|
|
106578
|
-
|
|
106579
|
-
|
|
106580
|
-
|
|
106581
|
-
|
|
106582
|
-
|
|
106583
|
-
|
|
106584
|
-
|
|
106585
|
-
|
|
106586
|
-
|
|
106587
|
-
|
|
106588
|
-
|
|
106589
|
-
|
|
106590
|
-
|
|
106591
|
-
|
|
106592
|
-
|
|
106593
|
-
|
|
106594
|
-
|
|
106595
|
-
|
|
106596
|
-
|
|
106597
|
-
|
|
106598
|
-
|
|
106599
|
-
|
|
106600
|
-
|
|
106601
|
-
|
|
106602
|
-
|
|
106603
|
-
|
|
106604
|
-
|
|
106605
|
-
|
|
106606
|
-
|
|
106607
|
-
|
|
106608
|
-
|
|
106609
|
-
}
|
|
106610
|
-
|
|
106754
|
+
function resolveEventTimes(input) {
|
|
106755
|
+
if (input.dueDate) {
|
|
106756
|
+
const { startTime, endTime } = parseDueDate(input.dueDate);
|
|
106757
|
+
return { startTime, endTime, allDay: true };
|
|
106758
|
+
}
|
|
106759
|
+
if (input.startsAt) {
|
|
106760
|
+
const startTime = input.startsAt.includes("T") ? input.startsAt : `${input.startsAt}T09:00:00.000Z`;
|
|
106761
|
+
const endTime = input.endsAt ? input.endsAt.includes("T") ? input.endsAt : `${input.endsAt}T10:00:00.000Z` : null;
|
|
106762
|
+
const allDay = input.type === "deadline" || !input.endsAt && !input.startsAt.includes(":");
|
|
106763
|
+
return { startTime, endTime, allDay };
|
|
106764
|
+
}
|
|
106765
|
+
throw new Error(
|
|
106766
|
+
"Provide dueDate (YYYY-MM-DD) or startsAt (ISO datetime) for the calendar item."
|
|
106767
|
+
);
|
|
106768
|
+
}
|
|
106769
|
+
function formatCalendarItem(row) {
|
|
106770
|
+
const dueDate = row.allDay || row.type === "deadline" ? row.startTime.split("T")[0] : void 0;
|
|
106771
|
+
return {
|
|
106772
|
+
id: row.id,
|
|
106773
|
+
title: row.title,
|
|
106774
|
+
description: row.description,
|
|
106775
|
+
type: mapOutputType(row.type, row.metadata),
|
|
106776
|
+
startsAt: row.startTime,
|
|
106777
|
+
endsAt: row.endTime,
|
|
106778
|
+
dueDate,
|
|
106779
|
+
projectId: row.projectId,
|
|
106780
|
+
customerId: row.customerId,
|
|
106781
|
+
assigneeId: row.userId,
|
|
106782
|
+
ticketIds: row.ticketIds,
|
|
106783
|
+
status: mapOutputStatus(row.status, row.metadata, row.isDeleted)
|
|
106784
|
+
};
|
|
106785
|
+
}
|
|
106786
|
+
async function loadTicketLinks(eventIds) {
|
|
106787
|
+
const map3 = /* @__PURE__ */ new Map();
|
|
106788
|
+
if (eventIds.length === 0) return map3;
|
|
106789
|
+
const links = await db.select({
|
|
106790
|
+
eventId: schema_exports.timesheetEventTickets.timesheetEventId,
|
|
106791
|
+
ticketId: schema_exports.timesheetEventTickets.ticketId
|
|
106792
|
+
}).from(schema_exports.timesheetEventTickets).where(
|
|
106793
|
+
inArray(schema_exports.timesheetEventTickets.timesheetEventId, eventIds)
|
|
106794
|
+
);
|
|
106795
|
+
for (const link of links) {
|
|
106796
|
+
const existing = map3.get(link.eventId) ?? [];
|
|
106797
|
+
existing.push(link.ticketId);
|
|
106798
|
+
map3.set(link.eventId, existing);
|
|
106799
|
+
}
|
|
106800
|
+
return map3;
|
|
106801
|
+
}
|
|
106802
|
+
async function findExistingTicketDeadline(teamId, ticketId) {
|
|
106803
|
+
const links = await db.select({ eventId: schema_exports.timesheetEventTickets.timesheetEventId }).from(schema_exports.timesheetEventTickets).innerJoin(
|
|
106804
|
+
schema_exports.timesheetEvents,
|
|
106805
|
+
eq(
|
|
106806
|
+
schema_exports.timesheetEvents.id,
|
|
106807
|
+
schema_exports.timesheetEventTickets.timesheetEventId
|
|
106808
|
+
)
|
|
106809
|
+
).where(
|
|
106810
|
+
and(
|
|
106811
|
+
eq(schema_exports.timesheetEventTickets.ticketId, ticketId),
|
|
106812
|
+
eq(schema_exports.timesheetEvents.teamId, teamId),
|
|
106813
|
+
eq(schema_exports.timesheetEvents.type, "deadline"),
|
|
106814
|
+
eq(schema_exports.timesheetEvents.isDeleted, false)
|
|
106815
|
+
)
|
|
106816
|
+
).limit(1);
|
|
106817
|
+
return links[0]?.eventId ?? null;
|
|
106818
|
+
}
|
|
106819
|
+
async function validateProjectAccess(teamId, projectId, projectIds) {
|
|
106820
|
+
if (!projectIds.includes(projectId)) {
|
|
106821
|
+
throw new Error(
|
|
106822
|
+
`Project not found or no access: ${projectId}. Call get-projects first.`
|
|
106823
|
+
);
|
|
106824
|
+
}
|
|
106825
|
+
}
|
|
106826
|
+
async function validateAssignee(teamId, assigneeId) {
|
|
106827
|
+
const member2 = await isUserTeamMember(assigneeId, teamId);
|
|
106828
|
+
if (!member2) {
|
|
106829
|
+
throw new Error(
|
|
106830
|
+
`Assignee ${assigneeId} is not a member of team ${teamId}.`
|
|
106831
|
+
);
|
|
106832
|
+
}
|
|
106833
|
+
}
|
|
106834
|
+
async function loadCalendarItemById(teamId, id) {
|
|
106835
|
+
const [event] = await db.select({
|
|
106836
|
+
id: schema_exports.timesheetEvents.id,
|
|
106837
|
+
title: schema_exports.timesheetEvents.title,
|
|
106838
|
+
description: schema_exports.timesheetEvents.description,
|
|
106839
|
+
type: schema_exports.timesheetEvents.type,
|
|
106840
|
+
status: schema_exports.timesheetEvents.status,
|
|
106841
|
+
startTime: schema_exports.timesheetEvents.startTime,
|
|
106842
|
+
endTime: schema_exports.timesheetEvents.endTime,
|
|
106843
|
+
allDay: schema_exports.timesheetEvents.allDay,
|
|
106844
|
+
projectId: schema_exports.timesheetEvents.projectId,
|
|
106845
|
+
customerId: schema_exports.timesheetEvents.customerId,
|
|
106846
|
+
userId: schema_exports.timesheetEvents.userId,
|
|
106847
|
+
metadata: schema_exports.timesheetEvents.metadata,
|
|
106848
|
+
isDeleted: schema_exports.timesheetEvents.isDeleted
|
|
106849
|
+
}).from(schema_exports.timesheetEvents).where(
|
|
106850
|
+
and(
|
|
106851
|
+
eq(schema_exports.timesheetEvents.id, id),
|
|
106852
|
+
eq(schema_exports.timesheetEvents.teamId, teamId),
|
|
106853
|
+
eq(schema_exports.timesheetEvents.isDeleted, false)
|
|
106854
|
+
)
|
|
106855
|
+
).limit(1);
|
|
106856
|
+
if (!event) return null;
|
|
106857
|
+
const ticketLinks = await loadTicketLinks([event.id]);
|
|
106858
|
+
return {
|
|
106859
|
+
...event,
|
|
106860
|
+
ticketIds: ticketLinks.get(event.id) ?? []
|
|
106861
|
+
};
|
|
106862
|
+
}
|
|
106863
|
+
async function handleGetCalendarItems(input) {
|
|
106864
|
+
getAuthContext();
|
|
106865
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
106866
|
+
if (!resolved.ok) return resolved.response;
|
|
106867
|
+
const teamId = resolved.teamId;
|
|
106868
|
+
const teamIds = await getAccessibleTeamIds(teamId);
|
|
106869
|
+
const pageSize = Math.min(input.pageSize ?? 50, 100);
|
|
106870
|
+
const dateFrom = input.dateFrom ?? (/* @__PURE__ */ new Date()).toISOString().split("T")[0] + "T00:00:00.000Z";
|
|
106871
|
+
const dateTo = input.dateTo ?? new Date(Date.now() + 90 * 24 * 60 * 60 * 1e3).toISOString();
|
|
106872
|
+
const filters = [
|
|
106873
|
+
inArray(schema_exports.timesheetEvents.teamId, teamIds),
|
|
106874
|
+
gte(schema_exports.timesheetEvents.startTime, dateFrom),
|
|
106875
|
+
lte(schema_exports.timesheetEvents.startTime, dateTo),
|
|
106876
|
+
eq(schema_exports.timesheetEvents.isDeleted, false)
|
|
106877
|
+
];
|
|
106878
|
+
if (input.projectId) {
|
|
106879
|
+
filters.push(eq(schema_exports.timesheetEvents.projectId, input.projectId));
|
|
106880
|
+
}
|
|
106881
|
+
if (input.customerId) {
|
|
106882
|
+
filters.push(eq(schema_exports.timesheetEvents.customerId, input.customerId));
|
|
106883
|
+
}
|
|
106884
|
+
if (input.assigneeId) {
|
|
106885
|
+
filters.push(eq(schema_exports.timesheetEvents.userId, input.assigneeId));
|
|
106886
|
+
}
|
|
106887
|
+
if (input.status === "draft") {
|
|
106888
|
+
filters.push(eq(schema_exports.timesheetEvents.status, "draft"));
|
|
106889
|
+
} else if (input.status === "scheduled") {
|
|
106890
|
+
filters.push(eq(schema_exports.timesheetEvents.status, "confirmed"));
|
|
106891
|
+
}
|
|
106892
|
+
let eventIdsForTicket;
|
|
106893
|
+
if (input.ticketId) {
|
|
106894
|
+
const links = await db.select({ eventId: schema_exports.timesheetEventTickets.timesheetEventId }).from(schema_exports.timesheetEventTickets).where(eq(schema_exports.timesheetEventTickets.ticketId, input.ticketId));
|
|
106895
|
+
eventIdsForTicket = links.map((l4) => l4.eventId);
|
|
106896
|
+
if (eventIdsForTicket.length === 0) {
|
|
106897
|
+
return {
|
|
106898
|
+
content: [
|
|
106899
|
+
{
|
|
106900
|
+
type: "text",
|
|
106901
|
+
text: JSON.stringify({ count: 0, items: [] }, null, 2)
|
|
106902
|
+
}
|
|
106903
|
+
]
|
|
106904
|
+
};
|
|
106905
|
+
}
|
|
106906
|
+
filters.push(inArray(schema_exports.timesheetEvents.id, eventIdsForTicket));
|
|
106907
|
+
}
|
|
106908
|
+
const rows = await db.select({
|
|
106909
|
+
id: schema_exports.timesheetEvents.id,
|
|
106910
|
+
title: schema_exports.timesheetEvents.title,
|
|
106911
|
+
description: schema_exports.timesheetEvents.description,
|
|
106912
|
+
type: schema_exports.timesheetEvents.type,
|
|
106913
|
+
status: schema_exports.timesheetEvents.status,
|
|
106914
|
+
startTime: schema_exports.timesheetEvents.startTime,
|
|
106915
|
+
endTime: schema_exports.timesheetEvents.endTime,
|
|
106916
|
+
allDay: schema_exports.timesheetEvents.allDay,
|
|
106917
|
+
projectId: schema_exports.timesheetEvents.projectId,
|
|
106918
|
+
customerId: schema_exports.timesheetEvents.customerId,
|
|
106919
|
+
userId: schema_exports.timesheetEvents.userId,
|
|
106920
|
+
metadata: schema_exports.timesheetEvents.metadata,
|
|
106921
|
+
isDeleted: schema_exports.timesheetEvents.isDeleted
|
|
106922
|
+
}).from(schema_exports.timesheetEvents).where(and(...filters)).orderBy(asc(schema_exports.timesheetEvents.startTime)).limit(pageSize);
|
|
106923
|
+
const ticketLinks = await loadTicketLinks(rows.map((r6) => r6.id));
|
|
106924
|
+
let items = rows.map(
|
|
106925
|
+
(row) => formatCalendarItem({
|
|
106926
|
+
...row,
|
|
106927
|
+
ticketIds: ticketLinks.get(row.id) ?? []
|
|
106928
|
+
})
|
|
106929
|
+
);
|
|
106930
|
+
if (input.status === "completed") {
|
|
106931
|
+
items = items.filter((item) => item.status === "completed");
|
|
106932
|
+
} else if (input.status === "scheduled") {
|
|
106933
|
+
items = items.filter((item) => item.status === "scheduled");
|
|
106934
|
+
}
|
|
106935
|
+
if (input.type) {
|
|
106936
|
+
items = items.filter((item) => item.type === input.type);
|
|
106937
|
+
}
|
|
106938
|
+
return {
|
|
106939
|
+
content: [
|
|
106940
|
+
{
|
|
106941
|
+
type: "text",
|
|
106942
|
+
text: JSON.stringify({ count: items.length, items }, null, 2)
|
|
106943
|
+
}
|
|
106944
|
+
]
|
|
106945
|
+
};
|
|
106946
|
+
}
|
|
106947
|
+
async function handleCreateCalendarItem(input) {
|
|
106948
|
+
const ctx = getAuthContext();
|
|
106949
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
106950
|
+
if (!resolved.ok) return resolved.response;
|
|
106951
|
+
const teamId = resolved.teamId;
|
|
106952
|
+
const projectIds = await getAccessibleProjectIds(ctx.userId, teamId);
|
|
106953
|
+
const customerIds = await getAccessibleCustomerIds(teamId);
|
|
106954
|
+
if (input.projectId) {
|
|
106955
|
+
await validateProjectAccess(teamId, input.projectId, projectIds);
|
|
106956
|
+
}
|
|
106957
|
+
if (input.customerId && !customerIds.includes(input.customerId)) {
|
|
106958
|
+
throw new Error(
|
|
106959
|
+
`Customer not found or no access: ${input.customerId}. Call get-customers first.`
|
|
106960
|
+
);
|
|
106961
|
+
}
|
|
106962
|
+
let ticketId = input.ticketId;
|
|
106963
|
+
let projectId = input.projectId ?? null;
|
|
106964
|
+
let customerId = input.customerId ?? null;
|
|
106965
|
+
if (ticketId) {
|
|
106966
|
+
const access = await loadAccessibleTicket(input.teamId, ticketId);
|
|
106967
|
+
if (!access.ok) return access.response;
|
|
106968
|
+
if (!projectId) projectId = access.ticket.projectId;
|
|
106969
|
+
if (!customerId) customerId = access.ticket.customerId;
|
|
106970
|
+
}
|
|
106971
|
+
const itemType = input.type ?? "deadline";
|
|
106972
|
+
const timesheetType = mapInputType(itemType);
|
|
106973
|
+
if (ticketId && timesheetType === "deadline") {
|
|
106974
|
+
const existingId = await findExistingTicketDeadline(teamId, ticketId);
|
|
106975
|
+
if (existingId) {
|
|
106976
|
+
return handleUpdateCalendarItem({
|
|
106977
|
+
teamId: input.teamId,
|
|
106978
|
+
id: existingId,
|
|
106979
|
+
title: input.title,
|
|
106980
|
+
description: input.description,
|
|
106981
|
+
dueDate: input.dueDate,
|
|
106982
|
+
startsAt: input.startsAt,
|
|
106983
|
+
endsAt: input.endsAt,
|
|
106984
|
+
projectId: projectId ?? void 0,
|
|
106985
|
+
customerId: customerId ?? void 0,
|
|
106986
|
+
assigneeId: input.assigneeId,
|
|
106987
|
+
status: input.status,
|
|
106988
|
+
type: itemType
|
|
106989
|
+
});
|
|
106990
|
+
}
|
|
106991
|
+
}
|
|
106992
|
+
const assigneeId = input.assigneeId ?? ctx.userId;
|
|
106993
|
+
await validateAssignee(teamId, assigneeId);
|
|
106994
|
+
const { startTime, endTime, allDay } = resolveEventTimes({
|
|
106995
|
+
dueDate: input.dueDate,
|
|
106996
|
+
startsAt: input.startsAt,
|
|
106997
|
+
endsAt: input.endsAt,
|
|
106998
|
+
type: itemType
|
|
106999
|
+
});
|
|
107000
|
+
const metadata = {
|
|
107001
|
+
calendarType: itemType,
|
|
107002
|
+
source: "mcp_calendar_item"
|
|
107003
|
+
};
|
|
107004
|
+
if (input.status === "completed") metadata.completed = true;
|
|
107005
|
+
const [created] = await db.insert(schema_exports.timesheetEvents).values({
|
|
107006
|
+
teamId,
|
|
107007
|
+
userId: assigneeId,
|
|
107008
|
+
title: input.title,
|
|
107009
|
+
description: input.description ?? null,
|
|
107010
|
+
type: timesheetType,
|
|
107011
|
+
status: input.status === "draft" ? "draft" : "confirmed",
|
|
107012
|
+
startTime,
|
|
107013
|
+
endTime,
|
|
107014
|
+
allDay,
|
|
107015
|
+
projectId,
|
|
107016
|
+
customerId,
|
|
107017
|
+
metadata,
|
|
107018
|
+
billingStatus: "unbillable"
|
|
107019
|
+
}).returning({ id: schema_exports.timesheetEvents.id });
|
|
107020
|
+
if (!created) throw new Error("Failed to create calendar item.");
|
|
107021
|
+
if (ticketId) {
|
|
107022
|
+
await db.insert(schema_exports.timesheetEventTickets).values({
|
|
107023
|
+
timesheetEventId: created.id,
|
|
107024
|
+
ticketId
|
|
107025
|
+
});
|
|
107026
|
+
}
|
|
107027
|
+
const item = await loadCalendarItemById(teamId, created.id);
|
|
107028
|
+
return {
|
|
107029
|
+
content: [
|
|
107030
|
+
{
|
|
107031
|
+
type: "text",
|
|
107032
|
+
text: `\u2705 **Calendar item created**
|
|
107033
|
+
|
|
107034
|
+
` + JSON.stringify(formatCalendarItem(item), null, 2)
|
|
107035
|
+
}
|
|
107036
|
+
]
|
|
107037
|
+
};
|
|
107038
|
+
}
|
|
107039
|
+
async function handleUpdateCalendarItem(input) {
|
|
107040
|
+
const ctx = getAuthContext();
|
|
107041
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
107042
|
+
if (!resolved.ok) return resolved.response;
|
|
107043
|
+
const teamId = resolved.teamId;
|
|
107044
|
+
const existing = await loadCalendarItemById(teamId, input.id);
|
|
107045
|
+
if (!existing) {
|
|
107046
|
+
return {
|
|
107047
|
+
content: [
|
|
107048
|
+
{
|
|
107049
|
+
type: "text",
|
|
107050
|
+
text: `Calendar item not found or no access: ${input.id}. Call get-calendar-items to find the correct id.`
|
|
107051
|
+
}
|
|
107052
|
+
]
|
|
107053
|
+
};
|
|
107054
|
+
}
|
|
107055
|
+
const projectIds = await getAccessibleProjectIds(ctx.userId, teamId);
|
|
107056
|
+
const customerIds = await getAccessibleCustomerIds(teamId);
|
|
107057
|
+
if (input.projectId) {
|
|
107058
|
+
await validateProjectAccess(teamId, input.projectId, projectIds);
|
|
107059
|
+
}
|
|
107060
|
+
if (input.customerId && !customerIds.includes(input.customerId)) {
|
|
107061
|
+
throw new Error(`Customer not found or no access: ${input.customerId}.`);
|
|
107062
|
+
}
|
|
107063
|
+
if (input.assigneeId) {
|
|
107064
|
+
await validateAssignee(teamId, input.assigneeId);
|
|
107065
|
+
}
|
|
107066
|
+
const updateValues = {
|
|
107067
|
+
updatedAt: sql`NOW()`
|
|
107068
|
+
};
|
|
107069
|
+
if (input.title !== void 0) updateValues.title = input.title;
|
|
107070
|
+
if (input.description !== void 0) {
|
|
107071
|
+
updateValues.description = input.description;
|
|
107072
|
+
}
|
|
107073
|
+
if (input.projectId !== void 0) updateValues.projectId = input.projectId;
|
|
107074
|
+
if (input.customerId !== void 0) {
|
|
107075
|
+
updateValues.customerId = input.customerId;
|
|
107076
|
+
}
|
|
107077
|
+
if (input.assigneeId !== void 0) updateValues.userId = input.assigneeId;
|
|
107078
|
+
if (input.type !== void 0) {
|
|
107079
|
+
updateValues.type = mapInputType(input.type);
|
|
107080
|
+
}
|
|
107081
|
+
if (input.dueDate !== void 0 || input.startsAt !== void 0 || input.endsAt !== void 0) {
|
|
107082
|
+
const currentType = input.type ?? mapOutputType(existing.type, existing.metadata);
|
|
107083
|
+
const { startTime, endTime, allDay } = resolveEventTimes({
|
|
107084
|
+
dueDate: input.dueDate,
|
|
107085
|
+
startsAt: input.startsAt ?? existing.startTime,
|
|
107086
|
+
endsAt: input.endsAt ?? existing.endTime ?? void 0,
|
|
107087
|
+
type: currentType
|
|
107088
|
+
});
|
|
107089
|
+
updateValues.startTime = startTime;
|
|
107090
|
+
updateValues.endTime = endTime;
|
|
107091
|
+
updateValues.allDay = allDay;
|
|
107092
|
+
}
|
|
107093
|
+
const existingMeta = existing.metadata && typeof existing.metadata === "object" && !Array.isArray(existing.metadata) ? existing.metadata : {};
|
|
107094
|
+
const nextMeta = { ...existingMeta };
|
|
107095
|
+
if (input.type !== void 0) nextMeta.calendarType = input.type;
|
|
107096
|
+
if (input.status === "completed") {
|
|
107097
|
+
nextMeta.completed = true;
|
|
107098
|
+
updateValues.status = "confirmed";
|
|
107099
|
+
} else if (input.status === "cancelled") {
|
|
107100
|
+
await db.update(schema_exports.timesheetEvents).set({
|
|
107101
|
+
isDeleted: true,
|
|
107102
|
+
deletedAt: sql`NOW()`,
|
|
107103
|
+
updatedAt: sql`NOW()`
|
|
107104
|
+
}).where(
|
|
107105
|
+
and(
|
|
107106
|
+
eq(schema_exports.timesheetEvents.id, input.id),
|
|
107107
|
+
eq(schema_exports.timesheetEvents.teamId, teamId)
|
|
107108
|
+
)
|
|
107109
|
+
);
|
|
107110
|
+
const cancelled = await db.select({ id: schema_exports.timesheetEvents.id }).from(schema_exports.timesheetEvents).where(eq(schema_exports.timesheetEvents.id, input.id)).limit(1);
|
|
107111
|
+
if (cancelled[0]) {
|
|
107112
|
+
return {
|
|
107113
|
+
content: [
|
|
107114
|
+
{
|
|
107115
|
+
type: "text",
|
|
107116
|
+
text: `\u2705 Calendar item ${input.id} cancelled (soft-deleted).`
|
|
107117
|
+
}
|
|
107118
|
+
]
|
|
107119
|
+
};
|
|
107120
|
+
}
|
|
107121
|
+
} else if (input.status === "draft") {
|
|
107122
|
+
updateValues.status = "draft";
|
|
107123
|
+
delete nextMeta.completed;
|
|
107124
|
+
} else if (input.status === "scheduled") {
|
|
107125
|
+
updateValues.status = "confirmed";
|
|
107126
|
+
delete nextMeta.completed;
|
|
107127
|
+
}
|
|
107128
|
+
updateValues.metadata = nextMeta;
|
|
107129
|
+
await db.update(schema_exports.timesheetEvents).set(updateValues).where(
|
|
107130
|
+
and(
|
|
107131
|
+
eq(schema_exports.timesheetEvents.id, input.id),
|
|
107132
|
+
eq(schema_exports.timesheetEvents.teamId, teamId)
|
|
107133
|
+
)
|
|
107134
|
+
);
|
|
107135
|
+
if (input.ticketId !== void 0) {
|
|
107136
|
+
await db.delete(schema_exports.timesheetEventTickets).where(eq(schema_exports.timesheetEventTickets.timesheetEventId, input.id));
|
|
107137
|
+
if (input.ticketId) {
|
|
107138
|
+
const access = await loadAccessibleTicket(input.teamId, input.ticketId);
|
|
107139
|
+
if (!access.ok) return access.response;
|
|
107140
|
+
await db.insert(schema_exports.timesheetEventTickets).values({
|
|
107141
|
+
timesheetEventId: input.id,
|
|
107142
|
+
ticketId: input.ticketId
|
|
107143
|
+
});
|
|
107144
|
+
}
|
|
107145
|
+
}
|
|
107146
|
+
const item = await loadCalendarItemById(teamId, input.id);
|
|
107147
|
+
return {
|
|
107148
|
+
content: [
|
|
107149
|
+
{
|
|
107150
|
+
type: "text",
|
|
107151
|
+
text: `\u2705 **Calendar item updated**
|
|
107152
|
+
|
|
107153
|
+
` + JSON.stringify(formatCalendarItem(item), null, 2)
|
|
107154
|
+
}
|
|
107155
|
+
]
|
|
107156
|
+
};
|
|
107157
|
+
}
|
|
107158
|
+
async function syncTicketDeadline(teamId, ticket, dueDate) {
|
|
107159
|
+
const ctx = getAuthContext();
|
|
107160
|
+
if (dueDate === null) {
|
|
107161
|
+
const existingId = await findExistingTicketDeadline(teamId, ticket.id);
|
|
107162
|
+
if (existingId) {
|
|
107163
|
+
await db.update(schema_exports.timesheetEvents).set({ isDeleted: true, deletedAt: sql`NOW()`, updatedAt: sql`NOW()` }).where(eq(schema_exports.timesheetEvents.id, existingId));
|
|
107164
|
+
}
|
|
107165
|
+
return "deadline removed";
|
|
107166
|
+
}
|
|
107167
|
+
if (dueDate) {
|
|
107168
|
+
const title = `Deadline: ${ticket.title}`;
|
|
107169
|
+
const { startTime, endTime, allDay } = parseDueDate(dueDate);
|
|
107170
|
+
const assigneeId = ticket.assigneeId ?? ctx.userId;
|
|
107171
|
+
const existingId = await findExistingTicketDeadline(teamId, ticket.id);
|
|
107172
|
+
if (existingId) {
|
|
107173
|
+
await db.update(schema_exports.timesheetEvents).set({
|
|
107174
|
+
title,
|
|
107175
|
+
startTime,
|
|
107176
|
+
endTime,
|
|
107177
|
+
allDay,
|
|
107178
|
+
projectId: ticket.projectId,
|
|
107179
|
+
customerId: ticket.customerId,
|
|
107180
|
+
userId: assigneeId,
|
|
107181
|
+
type: "deadline",
|
|
107182
|
+
status: "confirmed",
|
|
107183
|
+
metadata: {
|
|
107184
|
+
calendarType: "deadline",
|
|
107185
|
+
source: "mcp_ticket_due_date"
|
|
107186
|
+
},
|
|
107187
|
+
updatedAt: sql`NOW()`
|
|
107188
|
+
}).where(eq(schema_exports.timesheetEvents.id, existingId));
|
|
107189
|
+
return "deadline updated";
|
|
107190
|
+
}
|
|
107191
|
+
const [created] = await db.insert(schema_exports.timesheetEvents).values({
|
|
107192
|
+
teamId,
|
|
107193
|
+
userId: assigneeId,
|
|
107194
|
+
title,
|
|
107195
|
+
type: "deadline",
|
|
107196
|
+
status: "confirmed",
|
|
107197
|
+
startTime,
|
|
107198
|
+
endTime,
|
|
107199
|
+
allDay,
|
|
107200
|
+
projectId: ticket.projectId,
|
|
107201
|
+
customerId: ticket.customerId,
|
|
107202
|
+
metadata: {
|
|
107203
|
+
calendarType: "deadline",
|
|
107204
|
+
source: "mcp_ticket_due_date"
|
|
107205
|
+
},
|
|
107206
|
+
billingStatus: "unbillable"
|
|
107207
|
+
}).returning({ id: schema_exports.timesheetEvents.id });
|
|
107208
|
+
if (created) {
|
|
107209
|
+
await db.insert(schema_exports.timesheetEventTickets).values({
|
|
107210
|
+
timesheetEventId: created.id,
|
|
107211
|
+
ticketId: ticket.id
|
|
107212
|
+
});
|
|
107213
|
+
}
|
|
107214
|
+
return "deadline created";
|
|
107215
|
+
}
|
|
107216
|
+
if (ticket.status === "resolved" || ticket.status === "closed") {
|
|
107217
|
+
const existingId = await findExistingTicketDeadline(teamId, ticket.id);
|
|
107218
|
+
if (existingId) {
|
|
107219
|
+
const [existing] = await db.select({ metadata: schema_exports.timesheetEvents.metadata }).from(schema_exports.timesheetEvents).where(eq(schema_exports.timesheetEvents.id, existingId)).limit(1);
|
|
107220
|
+
const meta5 = existing?.metadata && typeof existing.metadata === "object" && !Array.isArray(existing.metadata) ? existing.metadata : {};
|
|
107221
|
+
await db.update(schema_exports.timesheetEvents).set({
|
|
107222
|
+
metadata: { ...meta5, completed: true },
|
|
107223
|
+
updatedAt: sql`NOW()`
|
|
107224
|
+
}).where(eq(schema_exports.timesheetEvents.id, existingId));
|
|
107225
|
+
return "deadline marked completed";
|
|
107226
|
+
}
|
|
107227
|
+
}
|
|
107228
|
+
return null;
|
|
107229
|
+
}
|
|
107230
|
+
|
|
107231
|
+
// src/tools/customers.ts
|
|
107232
|
+
async function handleGetCustomers(input) {
|
|
107233
|
+
const { q: q3, pageSize = 20 } = input;
|
|
107234
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
107235
|
+
if (!resolved.ok) return resolved.response;
|
|
107236
|
+
const customerIds = await getAccessibleCustomerIds(resolved.teamId);
|
|
107237
|
+
if (customerIds.length === 0) {
|
|
107238
|
+
return {
|
|
107239
|
+
content: [
|
|
107240
|
+
{
|
|
107241
|
+
type: "text",
|
|
107242
|
+
text: "No customers found or no access to any customers."
|
|
107243
|
+
}
|
|
107244
|
+
]
|
|
107245
|
+
};
|
|
107246
|
+
}
|
|
107247
|
+
const filters = [inArray(schema_exports.customers.id, customerIds)];
|
|
107248
|
+
if (q3) {
|
|
107249
|
+
const pattern = `%${q3}%`;
|
|
107250
|
+
filters.push(
|
|
107251
|
+
or(
|
|
107252
|
+
ilike(schema_exports.customers.name, pattern),
|
|
107253
|
+
ilike(schema_exports.customers.email, pattern)
|
|
107254
|
+
)
|
|
107255
|
+
);
|
|
107256
|
+
}
|
|
107257
|
+
const rows = await db.select({
|
|
107258
|
+
id: schema_exports.customers.id,
|
|
107259
|
+
name: schema_exports.customers.name,
|
|
107260
|
+
email: schema_exports.customers.email,
|
|
107261
|
+
website: schema_exports.customers.website,
|
|
107262
|
+
createdAt: schema_exports.customers.createdAt
|
|
107263
|
+
}).from(schema_exports.customers).where(and(...filters)).orderBy(asc(schema_exports.customers.name)).limit(Math.min(pageSize, 100));
|
|
107264
|
+
return {
|
|
107265
|
+
content: [
|
|
107266
|
+
{
|
|
107267
|
+
type: "text",
|
|
107268
|
+
text: `Found ${rows.length} customers:
|
|
107269
|
+
|
|
107270
|
+
${rows.map(
|
|
107271
|
+
(c6) => `**${c6.name}**
|
|
107272
|
+
${c6.email ? `Email: ${c6.email}
|
|
107273
|
+
` : ""}${c6.website ? `Website: ${c6.website}
|
|
107274
|
+
` : ""}Created: ${new Date(c6.createdAt).toLocaleDateString()}
|
|
107275
|
+
`
|
|
107276
|
+
).join("\n") || "No customers found."}`
|
|
107277
|
+
}
|
|
107278
|
+
]
|
|
107279
|
+
};
|
|
107280
|
+
}
|
|
107281
|
+
async function handleCreateCustomer(input) {
|
|
107282
|
+
const { name: name21, email: email5, website } = input;
|
|
107283
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
107284
|
+
if (!resolved.ok) return resolved.response;
|
|
107285
|
+
await db.insert(schema_exports.customers).values({
|
|
107286
|
+
teamId: resolved.teamId,
|
|
107287
|
+
name: name21,
|
|
107288
|
+
email: email5 ?? "",
|
|
107289
|
+
website: website ?? null
|
|
107290
|
+
});
|
|
107291
|
+
return {
|
|
107292
|
+
content: [
|
|
107293
|
+
{
|
|
107294
|
+
type: "text",
|
|
107295
|
+
text: `\u2705 **Customer Created Successfully!**
|
|
107296
|
+
|
|
107297
|
+
Name: ${name21}
|
|
107298
|
+
${email5 ? `Email: ${email5}
|
|
107299
|
+
` : ""}${website ? `Website: ${website}
|
|
107300
|
+
` : ""}`
|
|
107301
|
+
}
|
|
107302
|
+
]
|
|
107303
|
+
};
|
|
107304
|
+
}
|
|
107305
|
+
|
|
107306
|
+
// ../document/src/humanizer/rules.ts
|
|
107307
|
+
var REPLACEMENTS = [
|
|
107308
|
+
// --- Dutch clichés ---
|
|
107309
|
+
{ pattern: /\bbovendien\b/gi, replacement: "daarnaast", rule: "nl-cliche" },
|
|
107310
|
+
{ pattern: /\btevens\b/gi, replacement: "ook", rule: "nl-cliche" },
|
|
107311
|
+
{ pattern: /\bkortom\b/gi, replacement: "samengevat", rule: "nl-cliche" },
|
|
107312
|
+
{ pattern: /\bnaadloze\b/gi, replacement: "soepele", rule: "nl-cliche" },
|
|
107313
|
+
{ pattern: /\bnaadloos\b/gi, replacement: "soepel", rule: "nl-cliche" },
|
|
107314
|
+
{ pattern: /\brobuuste\b/gi, replacement: "solide", rule: "nl-cliche" },
|
|
107315
|
+
{ pattern: /\brobuust\b/gi, replacement: "solide", rule: "nl-cliche" },
|
|
107316
|
+
{ pattern: /\bcruciale\b/gi, replacement: "belangrijke", rule: "nl-cliche" },
|
|
107317
|
+
{ pattern: /\bcruciaal\b/gi, replacement: "belangrijk", rule: "nl-cliche" },
|
|
107318
|
+
{
|
|
107319
|
+
pattern: /\bessentiële\b/gi,
|
|
107320
|
+
replacement: "belangrijke",
|
|
107321
|
+
rule: "nl-cliche"
|
|
107322
|
+
},
|
|
107323
|
+
{ pattern: /\bessentieel\b/gi, replacement: "belangrijk", rule: "nl-cliche" },
|
|
107324
|
+
{
|
|
107325
|
+
pattern: /\been breed scala aan\b/gi,
|
|
107326
|
+
replacement: "veel",
|
|
107327
|
+
rule: "nl-cliche"
|
|
107328
|
+
},
|
|
107329
|
+
{
|
|
107330
|
+
pattern: /\boptimaal benutten\b/gi,
|
|
107331
|
+
replacement: "goed benutten",
|
|
107332
|
+
rule: "nl-cliche"
|
|
107333
|
+
},
|
|
107334
|
+
{
|
|
107335
|
+
pattern: /\bin de wereld van vandaag\b/gi,
|
|
107336
|
+
replacement: "vandaag de dag",
|
|
107337
|
+
rule: "nl-cliche"
|
|
107338
|
+
},
|
|
107339
|
+
{
|
|
107340
|
+
pattern: /\bin het huidige digitale tijdperk\b/gi,
|
|
107341
|
+
replacement: "tegenwoordig",
|
|
107342
|
+
rule: "nl-cliche"
|
|
107343
|
+
},
|
|
107344
|
+
{
|
|
106611
107345
|
pattern: /\bstate-of-the-art\b/gi,
|
|
106612
107346
|
replacement: "moderne",
|
|
106613
107347
|
rule: "nl-cliche"
|
|
@@ -118800,38 +119534,6 @@ var storage = new Proxy({}, {
|
|
|
118800
119534
|
}
|
|
118801
119535
|
});
|
|
118802
119536
|
|
|
118803
|
-
// src/tools/ticket-access.ts
|
|
118804
|
-
function notFoundResponse(ticketId) {
|
|
118805
|
-
return {
|
|
118806
|
-
content: [
|
|
118807
|
-
{
|
|
118808
|
-
type: "text",
|
|
118809
|
-
text: `Ticket not found or no access: ${ticketId}. Call get-tickets to find the correct ticket.`
|
|
118810
|
-
}
|
|
118811
|
-
]
|
|
118812
|
-
};
|
|
118813
|
-
}
|
|
118814
|
-
async function loadAccessibleTicket(requestedTeamId, ticketId) {
|
|
118815
|
-
const scope = await resolveTeamScope(requestedTeamId);
|
|
118816
|
-
if (!scope.ok) return scope;
|
|
118817
|
-
const [ticket] = await db.select({
|
|
118818
|
-
id: schema_exports.tickets.id,
|
|
118819
|
-
teamId: schema_exports.tickets.teamId,
|
|
118820
|
-
projectId: schema_exports.tickets.projectId,
|
|
118821
|
-
customerId: schema_exports.tickets.customerId,
|
|
118822
|
-
ticketNumber: schema_exports.tickets.ticketNumber,
|
|
118823
|
-
title: schema_exports.tickets.title,
|
|
118824
|
-
status: schema_exports.tickets.status,
|
|
118825
|
-
priority: schema_exports.tickets.priority,
|
|
118826
|
-
type: schema_exports.tickets.type,
|
|
118827
|
-
assigneeId: schema_exports.tickets.assigneeId
|
|
118828
|
-
}).from(schema_exports.tickets).where(eq(schema_exports.tickets.id, ticketId)).limit(1);
|
|
118829
|
-
if (!ticket) return { ok: false, response: notFoundResponse(ticketId) };
|
|
118830
|
-
const hasAccess = scope.teamIds.includes(ticket.teamId) || !!ticket.projectId && scope.projectIds.includes(ticket.projectId) || !!ticket.customerId && scope.customerIds.includes(ticket.customerId);
|
|
118831
|
-
if (!hasAccess) return { ok: false, response: notFoundResponse(ticketId) };
|
|
118832
|
-
return { ok: true, ticket };
|
|
118833
|
-
}
|
|
118834
|
-
|
|
118835
119537
|
// src/tools/ticket-attachments.ts
|
|
118836
119538
|
var ALLOWED_IMAGE_TYPES = [
|
|
118837
119539
|
"image/jpeg",
|
|
@@ -119182,6 +119884,262 @@ ${rendered}`
|
|
|
119182
119884
|
};
|
|
119183
119885
|
}
|
|
119184
119886
|
|
|
119887
|
+
// src/tools/ticket-tags.ts
|
|
119888
|
+
function normalizeTagName(name21) {
|
|
119889
|
+
return name21.toLowerCase().trim();
|
|
119890
|
+
}
|
|
119891
|
+
function formatTagList(tags2) {
|
|
119892
|
+
if (tags2.length === 0) return "";
|
|
119893
|
+
return tags2.map((t8) => t8.name).join(", ");
|
|
119894
|
+
}
|
|
119895
|
+
async function getTagsForTickets(ticketIds) {
|
|
119896
|
+
const result = /* @__PURE__ */ new Map();
|
|
119897
|
+
if (ticketIds.length === 0) return result;
|
|
119898
|
+
const rows = await db.select({
|
|
119899
|
+
ticketId: schema_exports.ticketTags.ticketId,
|
|
119900
|
+
id: schema_exports.tags.id,
|
|
119901
|
+
name: schema_exports.tags.name
|
|
119902
|
+
}).from(schema_exports.ticketTags).innerJoin(schema_exports.tags, eq(schema_exports.tags.id, schema_exports.ticketTags.tagId)).where(inArray(schema_exports.ticketTags.ticketId, ticketIds)).orderBy(schema_exports.tags.name);
|
|
119903
|
+
for (const row of rows) {
|
|
119904
|
+
const existing = result.get(row.ticketId) ?? [];
|
|
119905
|
+
existing.push({ id: row.id, name: row.name });
|
|
119906
|
+
result.set(row.ticketId, existing);
|
|
119907
|
+
}
|
|
119908
|
+
return result;
|
|
119909
|
+
}
|
|
119910
|
+
async function resolveTagFilterIds(teamId, input) {
|
|
119911
|
+
const ids = new Set(input.tagIds ?? []);
|
|
119912
|
+
const names = [
|
|
119913
|
+
...input.tag ? [input.tag] : [],
|
|
119914
|
+
...input.tags ?? []
|
|
119915
|
+
].map(normalizeTagName).filter(Boolean);
|
|
119916
|
+
if (names.length > 0) {
|
|
119917
|
+
const nameConditions = names.map(
|
|
119918
|
+
(name21) => sql`lower(${schema_exports.tags.name}) = ${name21}`
|
|
119919
|
+
);
|
|
119920
|
+
const rows = await db.select({ id: schema_exports.tags.id }).from(schema_exports.tags).where(and(eq(schema_exports.tags.teamId, teamId), or(...nameConditions)));
|
|
119921
|
+
for (const row of rows) ids.add(row.id);
|
|
119922
|
+
}
|
|
119923
|
+
return [...ids];
|
|
119924
|
+
}
|
|
119925
|
+
async function resolveTags(teamId, input) {
|
|
119926
|
+
const tags2 = [];
|
|
119927
|
+
const errors = [];
|
|
119928
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
119929
|
+
if (input.tagIds?.length) {
|
|
119930
|
+
const rows = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(
|
|
119931
|
+
and(
|
|
119932
|
+
eq(schema_exports.tags.teamId, teamId),
|
|
119933
|
+
inArray(schema_exports.tags.id, input.tagIds)
|
|
119934
|
+
)
|
|
119935
|
+
);
|
|
119936
|
+
for (const row of rows) {
|
|
119937
|
+
if (seenIds.has(row.id)) continue;
|
|
119938
|
+
seenIds.add(row.id);
|
|
119939
|
+
tags2.push(row);
|
|
119940
|
+
}
|
|
119941
|
+
for (const id of input.tagIds) {
|
|
119942
|
+
if (!seenIds.has(id)) errors.push(`Unknown tag ID: ${id}`);
|
|
119943
|
+
}
|
|
119944
|
+
}
|
|
119945
|
+
const rawNames = input.tagNames ?? [];
|
|
119946
|
+
if (rawNames.length === 0) return { tags: tags2, errors };
|
|
119947
|
+
const normalizedNames = [
|
|
119948
|
+
...new Set(rawNames.map(normalizeTagName).filter(Boolean))
|
|
119949
|
+
];
|
|
119950
|
+
if (normalizedNames.length === 0) return { tags: tags2, errors };
|
|
119951
|
+
const nameConditions = normalizedNames.map(
|
|
119952
|
+
(name21) => sql`lower(${schema_exports.tags.name}) = ${name21}`
|
|
119953
|
+
);
|
|
119954
|
+
const existing = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(and(eq(schema_exports.tags.teamId, teamId), or(...nameConditions)));
|
|
119955
|
+
const existingByNorm = /* @__PURE__ */ new Map();
|
|
119956
|
+
for (const tag of existing) {
|
|
119957
|
+
existingByNorm.set(normalizeTagName(tag.name), tag);
|
|
119958
|
+
}
|
|
119959
|
+
for (const rawName of rawNames) {
|
|
119960
|
+
const norm = normalizeTagName(rawName);
|
|
119961
|
+
if (!norm) continue;
|
|
119962
|
+
const found = existingByNorm.get(norm);
|
|
119963
|
+
if (found) {
|
|
119964
|
+
if (!seenIds.has(found.id)) {
|
|
119965
|
+
seenIds.add(found.id);
|
|
119966
|
+
tags2.push(found);
|
|
119967
|
+
}
|
|
119968
|
+
continue;
|
|
119969
|
+
}
|
|
119970
|
+
if (!input.createMissing) {
|
|
119971
|
+
errors.push(`Tag not found: ${rawName}`);
|
|
119972
|
+
continue;
|
|
119973
|
+
}
|
|
119974
|
+
try {
|
|
119975
|
+
const [created] = await db.insert(schema_exports.tags).values({
|
|
119976
|
+
teamId,
|
|
119977
|
+
name: norm,
|
|
119978
|
+
projectId: input.projectId ?? null
|
|
119979
|
+
}).returning({ id: schema_exports.tags.id, name: schema_exports.tags.name });
|
|
119980
|
+
if (created) {
|
|
119981
|
+
seenIds.add(created.id);
|
|
119982
|
+
tags2.push(created);
|
|
119983
|
+
existingByNorm.set(norm, created);
|
|
119984
|
+
}
|
|
119985
|
+
} catch {
|
|
119986
|
+
const [retry2] = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(
|
|
119987
|
+
and(
|
|
119988
|
+
eq(schema_exports.tags.teamId, teamId),
|
|
119989
|
+
sql`lower(${schema_exports.tags.name}) = ${norm}`,
|
|
119990
|
+
input.projectId ? or(
|
|
119991
|
+
eq(schema_exports.tags.projectId, input.projectId),
|
|
119992
|
+
isNull(schema_exports.tags.projectId)
|
|
119993
|
+
) : isNull(schema_exports.tags.projectId)
|
|
119994
|
+
)
|
|
119995
|
+
).limit(1);
|
|
119996
|
+
if (retry2 && !seenIds.has(retry2.id)) {
|
|
119997
|
+
seenIds.add(retry2.id);
|
|
119998
|
+
tags2.push(retry2);
|
|
119999
|
+
existingByNorm.set(norm, retry2);
|
|
120000
|
+
} else {
|
|
120001
|
+
errors.push(`Failed to create tag: ${rawName}`);
|
|
120002
|
+
}
|
|
120003
|
+
}
|
|
120004
|
+
}
|
|
120005
|
+
return { tags: tags2, errors };
|
|
120006
|
+
}
|
|
120007
|
+
async function syncTicketTags(ticketId, teamId, tagIds, mode = "replace") {
|
|
120008
|
+
const current = await db.select({ tagId: schema_exports.ticketTags.tagId }).from(schema_exports.ticketTags).where(eq(schema_exports.ticketTags.ticketId, ticketId));
|
|
120009
|
+
const currentIds = new Set(current.map((row) => row.tagId));
|
|
120010
|
+
let targetIds;
|
|
120011
|
+
if (mode === "remove") {
|
|
120012
|
+
targetIds = new Set(
|
|
120013
|
+
[...currentIds].filter((id) => !tagIds.includes(id))
|
|
120014
|
+
);
|
|
120015
|
+
} else if (mode === "merge") {
|
|
120016
|
+
targetIds = /* @__PURE__ */ new Set([...currentIds, ...tagIds]);
|
|
120017
|
+
} else {
|
|
120018
|
+
targetIds = new Set(tagIds);
|
|
120019
|
+
}
|
|
120020
|
+
const toInsert = [...targetIds].filter((id) => !currentIds.has(id));
|
|
120021
|
+
const toDelete = [...currentIds].filter((id) => !targetIds.has(id));
|
|
120022
|
+
if (toDelete.length > 0) {
|
|
120023
|
+
await db.delete(schema_exports.ticketTags).where(
|
|
120024
|
+
and(
|
|
120025
|
+
eq(schema_exports.ticketTags.ticketId, ticketId),
|
|
120026
|
+
inArray(schema_exports.ticketTags.tagId, toDelete)
|
|
120027
|
+
)
|
|
120028
|
+
);
|
|
120029
|
+
}
|
|
120030
|
+
if (toInsert.length > 0) {
|
|
120031
|
+
await db.insert(schema_exports.ticketTags).values(
|
|
120032
|
+
toInsert.map((tagId) => ({
|
|
120033
|
+
ticketId,
|
|
120034
|
+
tagId,
|
|
120035
|
+
teamId
|
|
120036
|
+
}))
|
|
120037
|
+
);
|
|
120038
|
+
}
|
|
120039
|
+
const tagIdsToDescribe = [.../* @__PURE__ */ new Set([...toInsert, ...toDelete])];
|
|
120040
|
+
const tagNamesById = /* @__PURE__ */ new Map();
|
|
120041
|
+
if (tagIdsToDescribe.length > 0) {
|
|
120042
|
+
const rows = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(inArray(schema_exports.tags.id, tagIdsToDescribe));
|
|
120043
|
+
for (const row of rows) tagNamesById.set(row.id, row.name);
|
|
120044
|
+
}
|
|
120045
|
+
return {
|
|
120046
|
+
added: toInsert.map((id) => tagNamesById.get(id) ?? id),
|
|
120047
|
+
removed: toDelete.map((id) => tagNamesById.get(id) ?? id)
|
|
120048
|
+
};
|
|
120049
|
+
}
|
|
120050
|
+
|
|
120051
|
+
// src/tools/tags.ts
|
|
120052
|
+
async function handleGetTags(input) {
|
|
120053
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
120054
|
+
if (!resolved.ok) return resolved.response;
|
|
120055
|
+
const filters = [eq(schema_exports.tags.teamId, resolved.teamId)];
|
|
120056
|
+
if (input.projectId !== void 0) {
|
|
120057
|
+
filters.push(
|
|
120058
|
+
or(
|
|
120059
|
+
eq(schema_exports.tags.projectId, input.projectId),
|
|
120060
|
+
isNull(schema_exports.tags.projectId)
|
|
120061
|
+
)
|
|
120062
|
+
);
|
|
120063
|
+
}
|
|
120064
|
+
const rows = await db.select({
|
|
120065
|
+
id: schema_exports.tags.id,
|
|
120066
|
+
name: schema_exports.tags.name,
|
|
120067
|
+
projectId: schema_exports.tags.projectId,
|
|
120068
|
+
createdAt: schema_exports.tags.createdAt
|
|
120069
|
+
}).from(schema_exports.tags).where(and(...filters)).orderBy(asc(schema_exports.tags.name)).limit(Math.min(input.pageSize ?? 100, 200));
|
|
120070
|
+
return {
|
|
120071
|
+
content: [
|
|
120072
|
+
{
|
|
120073
|
+
type: "text",
|
|
120074
|
+
text: `Found ${rows.length} tags (team-specific${input.projectId ? ", including general tags for this project" : ""}):
|
|
120075
|
+
|
|
120076
|
+
` + (rows.map(
|
|
120077
|
+
(tag) => `**${tag.name}** (id: ${tag.id})${tag.projectId ? " [project-specific]" : " [general]"}`
|
|
120078
|
+
).join("\n") || "No tags found.")
|
|
120079
|
+
}
|
|
120080
|
+
]
|
|
120081
|
+
};
|
|
120082
|
+
}
|
|
120083
|
+
async function handleCreateTag(input) {
|
|
120084
|
+
const name21 = input.name.trim();
|
|
120085
|
+
if (!name21) {
|
|
120086
|
+
return {
|
|
120087
|
+
content: [{ type: "text", text: "Tag name is required." }]
|
|
120088
|
+
};
|
|
120089
|
+
}
|
|
120090
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
120091
|
+
if (!resolved.ok) return resolved.response;
|
|
120092
|
+
const normalized = normalizeTagName(name21);
|
|
120093
|
+
const scopeFilter = input.projectId ? eq(schema_exports.tags.projectId, input.projectId) : isNull(schema_exports.tags.projectId);
|
|
120094
|
+
const [existing] = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(
|
|
120095
|
+
and(
|
|
120096
|
+
eq(schema_exports.tags.teamId, resolved.teamId),
|
|
120097
|
+
scopeFilter,
|
|
120098
|
+
sql`lower(${schema_exports.tags.name}) = ${normalized}`
|
|
120099
|
+
)
|
|
120100
|
+
).limit(1);
|
|
120101
|
+
if (existing) {
|
|
120102
|
+
return {
|
|
120103
|
+
content: [
|
|
120104
|
+
{
|
|
120105
|
+
type: "text",
|
|
120106
|
+
text: `\u2139\uFE0F Tag already exists (case-insensitive match):
|
|
120107
|
+
|
|
120108
|
+
Name: **${existing.name}**
|
|
120109
|
+
ID: ${existing.id}`
|
|
120110
|
+
}
|
|
120111
|
+
]
|
|
120112
|
+
};
|
|
120113
|
+
}
|
|
120114
|
+
const [created] = await db.insert(schema_exports.tags).values({
|
|
120115
|
+
teamId: resolved.teamId,
|
|
120116
|
+
name: name21,
|
|
120117
|
+
projectId: input.projectId ?? null
|
|
120118
|
+
}).returning({
|
|
120119
|
+
id: schema_exports.tags.id,
|
|
120120
|
+
name: schema_exports.tags.name,
|
|
120121
|
+
projectId: schema_exports.tags.projectId
|
|
120122
|
+
});
|
|
120123
|
+
if (!created) {
|
|
120124
|
+
return {
|
|
120125
|
+
content: [{ type: "text", text: "Failed to create tag." }]
|
|
120126
|
+
};
|
|
120127
|
+
}
|
|
120128
|
+
return {
|
|
120129
|
+
content: [
|
|
120130
|
+
{
|
|
120131
|
+
type: "text",
|
|
120132
|
+
text: `\u2705 **Tag Created**
|
|
120133
|
+
|
|
120134
|
+
Name: **${created.name}**
|
|
120135
|
+
ID: ${created.id}
|
|
120136
|
+
${created.projectId ? `Project ID: ${created.projectId}
|
|
120137
|
+
` : "Scope: general (team-wide)\n"}`
|
|
120138
|
+
}
|
|
120139
|
+
]
|
|
120140
|
+
};
|
|
120141
|
+
}
|
|
120142
|
+
|
|
119185
120143
|
// src/tools/ticket-update.ts
|
|
119186
120144
|
async function handleUpdateTicket(input) {
|
|
119187
120145
|
const ctx = getAuthContext();
|
|
@@ -119227,6 +120185,38 @@ async function handleUpdateTicket(input) {
|
|
|
119227
120185
|
if (input.estimatedHours !== void 0) {
|
|
119228
120186
|
updateValues.estimatedHours = input.estimatedHours;
|
|
119229
120187
|
}
|
|
120188
|
+
if (input.dueDate !== void 0) {
|
|
120189
|
+
updateValues.dueDate = input.dueDate;
|
|
120190
|
+
updateValues.dueDateAllDay = input.dueDate ? true : false;
|
|
120191
|
+
}
|
|
120192
|
+
let deadlineChange = null;
|
|
120193
|
+
if (input.dueDate !== void 0) {
|
|
120194
|
+
deadlineChange = await syncTicketDeadline(
|
|
120195
|
+
ticket.teamId,
|
|
120196
|
+
{
|
|
120197
|
+
id: ticket.id,
|
|
120198
|
+
title: input.title ?? ticket.title,
|
|
120199
|
+
status: input.status ?? ticket.status,
|
|
120200
|
+
projectId: input.projectId ?? ticket.projectId,
|
|
120201
|
+
customerId: input.customerId ?? ticket.customerId,
|
|
120202
|
+
assigneeId: input.assigneeId !== void 0 ? input.assigneeId : ticket.assigneeId
|
|
120203
|
+
},
|
|
120204
|
+
input.dueDate
|
|
120205
|
+
);
|
|
120206
|
+
} else if (input.status !== void 0 && (input.status === "resolved" || input.status === "closed") && input.status !== ticket.status) {
|
|
120207
|
+
deadlineChange = await syncTicketDeadline(
|
|
120208
|
+
ticket.teamId,
|
|
120209
|
+
{
|
|
120210
|
+
id: ticket.id,
|
|
120211
|
+
title: input.title ?? ticket.title,
|
|
120212
|
+
status: input.status,
|
|
120213
|
+
projectId: input.projectId ?? ticket.projectId,
|
|
120214
|
+
customerId: input.customerId ?? ticket.customerId,
|
|
120215
|
+
assigneeId: input.assigneeId !== void 0 ? input.assigneeId : ticket.assigneeId
|
|
120216
|
+
},
|
|
120217
|
+
void 0
|
|
120218
|
+
);
|
|
120219
|
+
}
|
|
119230
120220
|
await db.update(schema_exports.tickets).set(updateValues).where(eq(schema_exports.tickets.id, ticket.id));
|
|
119231
120221
|
const changes = [];
|
|
119232
120222
|
const activities2 = [];
|
|
@@ -119271,6 +120261,61 @@ async function handleUpdateTicket(input) {
|
|
|
119271
120261
|
if (input.projectId !== void 0) changes.push("project updated");
|
|
119272
120262
|
if (input.customerId !== void 0) changes.push("customer updated");
|
|
119273
120263
|
if (input.estimatedHours !== void 0) changes.push("estimated hours updated");
|
|
120264
|
+
if (deadlineChange) changes.push(deadlineChange);
|
|
120265
|
+
if (input.tags !== void 0 || input.tagIds !== void 0) {
|
|
120266
|
+
const tagMode = input.tagMode ?? "replace";
|
|
120267
|
+
const createMissing = tagMode === "replace" || tagMode === "merge";
|
|
120268
|
+
const resolvedTags = await resolveTags(ticket.teamId, {
|
|
120269
|
+
tagNames: input.tags,
|
|
120270
|
+
tagIds: input.tagIds,
|
|
120271
|
+
projectId: input.projectId ?? ticket.projectId,
|
|
120272
|
+
createMissing
|
|
120273
|
+
});
|
|
120274
|
+
if (resolvedTags.errors.length > 0) {
|
|
120275
|
+
return {
|
|
120276
|
+
content: [
|
|
120277
|
+
{
|
|
120278
|
+
type: "text",
|
|
120279
|
+
text: `Cannot update tags on ${ticket.ticketNumber}:
|
|
120280
|
+
` + resolvedTags.errors.map((e6) => ` \u2022 ${e6}`).join("\n")
|
|
120281
|
+
}
|
|
120282
|
+
]
|
|
120283
|
+
};
|
|
120284
|
+
}
|
|
120285
|
+
const { added, removed } = await syncTicketTags(
|
|
120286
|
+
ticket.id,
|
|
120287
|
+
ticket.teamId,
|
|
120288
|
+
resolvedTags.tags.map((t8) => t8.id),
|
|
120289
|
+
tagMode
|
|
120290
|
+
);
|
|
120291
|
+
if (added.length > 0) {
|
|
120292
|
+
changes.push(`tags added: ${added.join(", ")}`);
|
|
120293
|
+
for (const tagName of added) {
|
|
120294
|
+
await db.insert(schema_exports.ticketActivity).values({
|
|
120295
|
+
ticketId: ticket.id,
|
|
120296
|
+
teamId: ticket.teamId,
|
|
120297
|
+
userId: ctx.userId,
|
|
120298
|
+
activityType: "tag_added",
|
|
120299
|
+
newValue: tagName
|
|
120300
|
+
});
|
|
120301
|
+
}
|
|
120302
|
+
}
|
|
120303
|
+
if (removed.length > 0) {
|
|
120304
|
+
changes.push(`tags removed: ${removed.join(", ")}`);
|
|
120305
|
+
for (const tagName of removed) {
|
|
120306
|
+
await db.insert(schema_exports.ticketActivity).values({
|
|
120307
|
+
ticketId: ticket.id,
|
|
120308
|
+
teamId: ticket.teamId,
|
|
120309
|
+
userId: ctx.userId,
|
|
120310
|
+
activityType: "tag_removed",
|
|
120311
|
+
newValue: tagName
|
|
120312
|
+
});
|
|
120313
|
+
}
|
|
120314
|
+
}
|
|
120315
|
+
if (tagMode === "replace" && added.length === 0 && removed.length === 0) {
|
|
120316
|
+
changes.push("tags cleared");
|
|
120317
|
+
}
|
|
120318
|
+
}
|
|
119274
120319
|
for (const activity of activities2) {
|
|
119275
120320
|
await db.insert(schema_exports.ticketActivity).values({
|
|
119276
120321
|
ticketId: ticket.id,
|
|
@@ -119330,7 +120375,17 @@ async function downloadImageAsBase64(storageKey) {
|
|
|
119330
120375
|
}
|
|
119331
120376
|
async function handleGetTickets(input) {
|
|
119332
120377
|
const ctx = getAuthContext();
|
|
119333
|
-
const {
|
|
120378
|
+
const {
|
|
120379
|
+
status,
|
|
120380
|
+
priority,
|
|
120381
|
+
projectId,
|
|
120382
|
+
customerId,
|
|
120383
|
+
q: q3,
|
|
120384
|
+
tag,
|
|
120385
|
+
tags: tags2,
|
|
120386
|
+
tagIds,
|
|
120387
|
+
pageSize = 20
|
|
120388
|
+
} = input;
|
|
119334
120389
|
const resolved = await resolveTeamId(input.teamId);
|
|
119335
120390
|
if (!resolved.ok) return resolved.response;
|
|
119336
120391
|
const teamId = resolved.teamId;
|
|
@@ -119357,6 +120412,29 @@ async function handleGetTickets(input) {
|
|
|
119357
120412
|
)
|
|
119358
120413
|
);
|
|
119359
120414
|
}
|
|
120415
|
+
const filterTagIds = await resolveTagFilterIds(teamId, { tag, tags: tags2, tagIds });
|
|
120416
|
+
if (tag || tags2?.length || tagIds?.length) {
|
|
120417
|
+
if (filterTagIds.length === 0) {
|
|
120418
|
+
return {
|
|
120419
|
+
content: [
|
|
120420
|
+
{
|
|
120421
|
+
type: "text",
|
|
120422
|
+
text: "No tickets found (no matching tags for the given filter)."
|
|
120423
|
+
}
|
|
120424
|
+
]
|
|
120425
|
+
};
|
|
120426
|
+
}
|
|
120427
|
+
filters.push(
|
|
120428
|
+
sql`EXISTS (
|
|
120429
|
+
SELECT 1 FROM ${schema_exports.ticketTags}
|
|
120430
|
+
WHERE ${schema_exports.ticketTags.ticketId} = ${schema_exports.tickets.id}
|
|
120431
|
+
AND ${schema_exports.ticketTags.tagId} IN (${sql.join(
|
|
120432
|
+
filterTagIds.map((id) => sql`${id}`),
|
|
120433
|
+
sql`, `
|
|
120434
|
+
)})
|
|
120435
|
+
)`
|
|
120436
|
+
);
|
|
120437
|
+
}
|
|
119360
120438
|
const rows = await db.select({
|
|
119361
120439
|
id: schema_exports.tickets.id,
|
|
119362
120440
|
ticketNumber: schema_exports.tickets.ticketNumber,
|
|
@@ -119374,20 +120452,23 @@ async function handleGetTickets(input) {
|
|
|
119374
120452
|
schema_exports.customers,
|
|
119375
120453
|
eq(schema_exports.customers.id, schema_exports.tickets.customerId)
|
|
119376
120454
|
).where(and(...filters)).orderBy(desc(schema_exports.tickets.createdAt)).limit(Math.min(pageSize, 100));
|
|
120455
|
+
const tagsByTicket = await getTagsForTickets(rows.map((t8) => t8.id));
|
|
119377
120456
|
return {
|
|
119378
120457
|
content: [
|
|
119379
120458
|
{
|
|
119380
120459
|
type: "text",
|
|
119381
120460
|
text: `Found ${rows.length} tickets:
|
|
119382
120461
|
|
|
119383
|
-
${rows.map(
|
|
119384
|
-
(t8)
|
|
120462
|
+
${rows.map((t8) => {
|
|
120463
|
+
const ticketTags2 = tagsByTicket.get(t8.id) ?? [];
|
|
120464
|
+
return `**${t8.ticketNumber}**: ${t8.title}
|
|
119385
120465
|
Status: ${t8.status} | Priority: ${t8.priority}
|
|
119386
|
-
${
|
|
120466
|
+
${ticketTags2.length > 0 ? `Tags: ${formatTagList(ticketTags2)}
|
|
120467
|
+
` : ""}${t8.projectName ? `Project: ${t8.projectName}
|
|
119387
120468
|
` : ""}${t8.customerName ? `Customer: ${t8.customerName}
|
|
119388
120469
|
` : ""}Created: ${new Date(t8.createdAt).toLocaleDateString()}
|
|
119389
|
-
|
|
119390
|
-
).join("\n") || "No tickets found."}`
|
|
120470
|
+
`;
|
|
120471
|
+
}).join("\n") || "No tickets found."}`
|
|
119391
120472
|
}
|
|
119392
120473
|
]
|
|
119393
120474
|
};
|
|
@@ -119500,6 +120581,11 @@ ${text3.split("\n").map((l4) => ` ${l4}`).join("\n")}`;
|
|
|
119500
120581
|
const assigneeLine = ticketRow.assignee ? `Assignee: ${ticketRow.assignee.fullName || "Unknown"} [id: ${ticketRow.assignee.id}]
|
|
119501
120582
|
` : ticketRow.requester ? `Assignee: (unassigned) \u2014 use requester id ${ticketRow.requester.id} for review handoff
|
|
119502
120583
|
` : `Assignee: (unassigned)
|
|
120584
|
+
`;
|
|
120585
|
+
const ticketTagRows = await getTagsForTickets([id]);
|
|
120586
|
+
const ticketTags2 = ticketTagRows.get(id) ?? [];
|
|
120587
|
+
const tagsLine = ticketTags2.length > 0 ? `Tags: ${formatTagList(ticketTags2)}
|
|
120588
|
+
` : `Tags: (none)
|
|
119503
120589
|
`;
|
|
119504
120590
|
const content = [
|
|
119505
120591
|
{
|
|
@@ -119512,10 +120598,11 @@ Team id: ${ticketRow.teamId}
|
|
|
119512
120598
|
Status: ${ticketRow.status}
|
|
119513
120599
|
Priority: ${ticketRow.priority}
|
|
119514
120600
|
Type: ${ticketRow.type}
|
|
119515
|
-
${ticketRow.
|
|
120601
|
+
${ticketRow.dueDate ? `Deadline: ${new Date(ticketRow.dueDate).toLocaleDateString()}
|
|
120602
|
+
` : ""}${ticketRow.description ? `Description: ${tiptapToPlainText(ticketRow.description)}
|
|
119516
120603
|
` : ""}${ticketRow.project?.name ? `Project: ${ticketRow.project.name}
|
|
119517
120604
|
` : ""}${ticketRow.customer?.name ? `Customer: ${ticketRow.customer.name}
|
|
119518
|
-
` : ""}` + assigneeLine + requesterLine + `Created: ${new Date(ticketRow.createdAt).toLocaleDateString()}
|
|
120605
|
+
` : ""}` + tagsLine + assigneeLine + requesterLine + `Created: ${new Date(ticketRow.createdAt).toLocaleDateString()}
|
|
119519
120606
|
` + attachmentList + commentList
|
|
119520
120607
|
}
|
|
119521
120608
|
];
|
|
@@ -119592,7 +120679,10 @@ async function handleCreateTicket(input) {
|
|
|
119592
120679
|
priority = "medium",
|
|
119593
120680
|
type = "task",
|
|
119594
120681
|
projectId,
|
|
119595
|
-
customerId
|
|
120682
|
+
customerId,
|
|
120683
|
+
dueDate,
|
|
120684
|
+
tags: tags2,
|
|
120685
|
+
tagIds
|
|
119596
120686
|
} = input;
|
|
119597
120687
|
const resolved = await resolveTeamId(input.teamId);
|
|
119598
120688
|
if (!resolved.ok) return resolved.response;
|
|
@@ -119649,7 +120739,7 @@ async function handleCreateTicket(input) {
|
|
|
119649
120739
|
const count = Number(countRow?.n ?? 0);
|
|
119650
120740
|
ticketNumber = `${year2}-${String(count + 1).padStart(3, "0")}`;
|
|
119651
120741
|
}
|
|
119652
|
-
await db.insert(schema_exports.tickets).values({
|
|
120742
|
+
const [created] = await db.insert(schema_exports.tickets).values({
|
|
119653
120743
|
teamId: resolvedTeamId,
|
|
119654
120744
|
ticketNumber,
|
|
119655
120745
|
title,
|
|
@@ -119659,20 +120749,54 @@ async function handleCreateTicket(input) {
|
|
|
119659
120749
|
type,
|
|
119660
120750
|
projectId: projectId ?? null,
|
|
119661
120751
|
customerId: resolvedCustomerId ?? null,
|
|
119662
|
-
requesterId: ctx.userId
|
|
120752
|
+
requesterId: ctx.userId,
|
|
120753
|
+
dueDate: dueDate ?? null,
|
|
120754
|
+
dueDateAllDay: dueDate ? true : false
|
|
120755
|
+
}).returning({
|
|
120756
|
+
id: schema_exports.tickets.id,
|
|
120757
|
+
ticketNumber: schema_exports.tickets.ticketNumber
|
|
119663
120758
|
});
|
|
120759
|
+
if (!created) {
|
|
120760
|
+
throw new Error("Failed to create ticket");
|
|
120761
|
+
}
|
|
120762
|
+
let appliedTags = [];
|
|
120763
|
+
const tagErrors = [];
|
|
120764
|
+
if ((tags2?.length ?? 0) > 0 || (tagIds?.length ?? 0) > 0) {
|
|
120765
|
+
const resolvedTags = await resolveTags(resolvedTeamId, {
|
|
120766
|
+
tagNames: tags2,
|
|
120767
|
+
tagIds,
|
|
120768
|
+
projectId: projectId ?? null,
|
|
120769
|
+
createMissing: true
|
|
120770
|
+
});
|
|
120771
|
+
tagErrors.push(...resolvedTags.errors);
|
|
120772
|
+
if (resolvedTags.tags.length > 0) {
|
|
120773
|
+
await syncTicketTags(
|
|
120774
|
+
created.id,
|
|
120775
|
+
resolvedTeamId,
|
|
120776
|
+
resolvedTags.tags.map((t8) => t8.id),
|
|
120777
|
+
"replace"
|
|
120778
|
+
);
|
|
120779
|
+
appliedTags = resolvedTags.tags;
|
|
120780
|
+
}
|
|
120781
|
+
}
|
|
119664
120782
|
return {
|
|
119665
120783
|
content: [
|
|
119666
120784
|
{
|
|
119667
120785
|
type: "text",
|
|
119668
120786
|
text: `\u2705 **Ticket Created Successfully!**
|
|
119669
120787
|
|
|
119670
|
-
Ticket Number: **${ticketNumber}**
|
|
120788
|
+
Ticket Number: **${created.ticketNumber}**
|
|
120789
|
+
ID: ${created.id}
|
|
119671
120790
|
Title: ${title}
|
|
119672
120791
|
Status: ${status}
|
|
119673
120792
|
Priority: ${priority}
|
|
119674
120793
|
Type: ${type}
|
|
119675
|
-
`
|
|
120794
|
+
${dueDate ? `Deadline: ${new Date(dueDate).toLocaleDateString()}
|
|
120795
|
+
` : ""}${appliedTags.length > 0 ? `Tags: ${formatTagList(appliedTags)}
|
|
120796
|
+
` : ""}${tagErrors.length > 0 ? `
|
|
120797
|
+
\u26A0\uFE0F Tag warnings:
|
|
120798
|
+
${tagErrors.map((e6) => ` \u2022 ${e6}`).join("\n")}
|
|
120799
|
+
` : ""}`
|
|
119676
120800
|
}
|
|
119677
120801
|
]
|
|
119678
120802
|
};
|
|
@@ -119723,6 +120847,22 @@ function createMcpServer() {
|
|
|
119723
120847
|
return await handleCreateTicket(asToolArgs(toolArgs));
|
|
119724
120848
|
case "update-ticket":
|
|
119725
120849
|
return await handleUpdateTicket(asToolArgs(toolArgs));
|
|
120850
|
+
case "get-tags":
|
|
120851
|
+
return await handleGetTags(asToolArgs(toolArgs));
|
|
120852
|
+
case "create-tag":
|
|
120853
|
+
return await handleCreateTag(asToolArgs(toolArgs));
|
|
120854
|
+
case "get-calendar-items":
|
|
120855
|
+
return await handleGetCalendarItems(
|
|
120856
|
+
asToolArgs(toolArgs)
|
|
120857
|
+
);
|
|
120858
|
+
case "create-calendar-item":
|
|
120859
|
+
return await handleCreateCalendarItem(
|
|
120860
|
+
asToolArgs(toolArgs)
|
|
120861
|
+
);
|
|
120862
|
+
case "update-calendar-item":
|
|
120863
|
+
return await handleUpdateCalendarItem(
|
|
120864
|
+
asToolArgs(toolArgs)
|
|
120865
|
+
);
|
|
119726
120866
|
case "add-ticket-comment":
|
|
119727
120867
|
return await handleAddTicketComment(
|
|
119728
120868
|
asToolArgs(toolArgs)
|