jobber-mcp-server 1.0.0

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.
Files changed (53) hide show
  1. package/.env.example +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +176 -0
  4. package/dist/auth/oauth.d.ts +19 -0
  5. package/dist/auth/oauth.d.ts.map +1 -0
  6. package/dist/auth/oauth.js +94 -0
  7. package/dist/auth/oauth.js.map +1 -0
  8. package/dist/graphql/queries.d.ts +32 -0
  9. package/dist/graphql/queries.d.ts.map +1 -0
  10. package/dist/graphql/queries.js +447 -0
  11. package/dist/graphql/queries.js.map +1 -0
  12. package/dist/index.d.ts +13 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +79 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/tools/clients.d.ts +6 -0
  17. package/dist/tools/clients.d.ts.map +1 -0
  18. package/dist/tools/clients.js +186 -0
  19. package/dist/tools/clients.js.map +1 -0
  20. package/dist/tools/invoices.d.ts +6 -0
  21. package/dist/tools/invoices.d.ts.map +1 -0
  22. package/dist/tools/invoices.js +64 -0
  23. package/dist/tools/invoices.js.map +1 -0
  24. package/dist/tools/jobs.d.ts +6 -0
  25. package/dist/tools/jobs.d.ts.map +1 -0
  26. package/dist/tools/jobs.js +205 -0
  27. package/dist/tools/jobs.js.map +1 -0
  28. package/dist/tools/quotes.d.ts +6 -0
  29. package/dist/tools/quotes.d.ts.map +1 -0
  30. package/dist/tools/quotes.js +113 -0
  31. package/dist/tools/quotes.js.map +1 -0
  32. package/dist/tools/requests.d.ts +6 -0
  33. package/dist/tools/requests.d.ts.map +1 -0
  34. package/dist/tools/requests.js +117 -0
  35. package/dist/tools/requests.js.map +1 -0
  36. package/dist/tools/scheduling.d.ts +6 -0
  37. package/dist/tools/scheduling.d.ts.map +1 -0
  38. package/dist/tools/scheduling.js +197 -0
  39. package/dist/tools/scheduling.js.map +1 -0
  40. package/dist/transport.d.ts +7 -0
  41. package/dist/transport.d.ts.map +1 -0
  42. package/dist/transport.js +75 -0
  43. package/dist/transport.js.map +1 -0
  44. package/dist/utils/errors.d.ts +42 -0
  45. package/dist/utils/errors.d.ts.map +1 -0
  46. package/dist/utils/errors.js +72 -0
  47. package/dist/utils/errors.js.map +1 -0
  48. package/dist/utils/pagination.d.ts +29 -0
  49. package/dist/utils/pagination.d.ts.map +1 -0
  50. package/dist/utils/pagination.js +49 -0
  51. package/dist/utils/pagination.js.map +1 -0
  52. package/package.json +55 -0
  53. package/scripts/extract-tokens.py +46 -0
@@ -0,0 +1,117 @@
1
+ /**
2
+ * MCP tools for Jobber service request operations.
3
+ */
4
+ import { z } from "zod";
5
+ import { jobberRequest, CREATE_REQUEST, LIST_REQUESTS, GET_REQUEST, CREATE_REQUEST_NOTE, } from "../graphql/queries.js";
6
+ import { extractUserErrors, formatErrorForMCP } from "../utils/errors.js";
7
+ export function registerRequestTools(server) {
8
+ // ── Create Request ───────────────────────────────────────────────
9
+ server.tool("jobber_create_request", "Create a new service request in Jobber. A request represents an incoming inquiry from a customer that hasn't been converted to a job yet. Optionally attach a note with details about the issue.", {
10
+ client_id: z
11
+ .string()
12
+ .describe("The Jobber client ID to create the request for. Search for or create the client first."),
13
+ title: z
14
+ .string()
15
+ .max(255)
16
+ .describe("Short title for the request (max 255 chars). E.g. 'Leaking kitchen faucet', 'Annual furnace maintenance'."),
17
+ details: z
18
+ .string()
19
+ .optional()
20
+ .describe("Detailed description of the service request. This gets added as a note on the request since Jobber doesn't have a details field on requests directly."),
21
+ property_id: z
22
+ .string()
23
+ .optional()
24
+ .describe("The property ID if the request is for a specific service address. Get this from the client's properties."),
25
+ }, async ({ client_id, title, details, property_id }) => {
26
+ try {
27
+ const input = {
28
+ clientId: client_id,
29
+ title,
30
+ };
31
+ if (property_id)
32
+ input.propertyId = property_id;
33
+ const data = await jobberRequest(CREATE_REQUEST, { input });
34
+ const result = data.requestCreate;
35
+ const userErr = extractUserErrors(result);
36
+ if (userErr) {
37
+ return {
38
+ content: [{ type: "text", text: `Failed to create request: ${userErr}` }],
39
+ isError: true,
40
+ };
41
+ }
42
+ const request = result.request;
43
+ // Add details as a note if provided
44
+ if (details && request.id) {
45
+ try {
46
+ await jobberRequest(CREATE_REQUEST_NOTE, {
47
+ requestId: request.id,
48
+ message: details,
49
+ });
50
+ }
51
+ catch {
52
+ // Non-fatal — request was created, note just failed
53
+ console.error("[jobber-mcp] Failed to add note to request");
54
+ }
55
+ }
56
+ return {
57
+ content: [{ type: "text", text: JSON.stringify(request, null, 2) }],
58
+ };
59
+ }
60
+ catch (error) {
61
+ return formatErrorForMCP(error);
62
+ }
63
+ });
64
+ // ── List Requests ────────────────────────────────────────────────
65
+ server.tool("jobber_list_requests", "List service requests in Jobber. Returns requests with their status, client, and property info. Supports pagination for large result sets.", {
66
+ limit: z
67
+ .number()
68
+ .min(1)
69
+ .max(50)
70
+ .default(20)
71
+ .describe("Maximum number of requests to return. Default 20, max 50."),
72
+ after: z
73
+ .string()
74
+ .optional()
75
+ .describe("Pagination cursor — pass the endCursor from a previous response to get the next page."),
76
+ }, async ({ limit, after }) => {
77
+ try {
78
+ const variables = { first: limit };
79
+ if (after)
80
+ variables.after = after;
81
+ const data = await jobberRequest(LIST_REQUESTS, variables);
82
+ const requests = data.requests;
83
+ return {
84
+ content: [
85
+ {
86
+ type: "text",
87
+ text: JSON.stringify({
88
+ total_count: requests.totalCount,
89
+ requests: requests.nodes,
90
+ page_info: requests.pageInfo,
91
+ }, null, 2),
92
+ },
93
+ ],
94
+ };
95
+ }
96
+ catch (error) {
97
+ return formatErrorForMCP(error);
98
+ }
99
+ });
100
+ // ── Get Request ──────────────────────────────────────────────────
101
+ server.tool("jobber_get_request", "Get full details for a specific service request by ID. Returns the request with client contact info and property address.", {
102
+ request_id: z.string().describe("The Jobber request ID"),
103
+ }, async ({ request_id }) => {
104
+ try {
105
+ const data = await jobberRequest(GET_REQUEST, { id: request_id });
106
+ return {
107
+ content: [
108
+ { type: "text", text: JSON.stringify(data.request, null, 2) },
109
+ ],
110
+ };
111
+ }
112
+ catch (error) {
113
+ return formatErrorForMCP(error);
114
+ }
115
+ });
116
+ }
117
+ //# sourceMappingURL=requests.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requests.js","sourceRoot":"","sources":["../../src/tools/requests.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EACL,aAAa,EACb,cAAc,EACd,aAAa,EACb,WAAW,EACX,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE1E,MAAM,UAAU,oBAAoB,CAAC,MAAiB;IACpD,oEAAoE;IACpE,MAAM,CAAC,IAAI,CACT,uBAAuB,EACvB,kMAAkM,EAClM;QACE,SAAS,EAAE,CAAC;aACT,MAAM,EAAE;aACR,QAAQ,CACP,wFAAwF,CACzF;QACH,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,GAAG,CAAC,GAAG,CAAC;aACR,QAAQ,CACP,2GAA2G,CAC5G;QACH,OAAO,EAAE,CAAC;aACP,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,uJAAuJ,CACxJ;QACH,WAAW,EAAE,CAAC;aACX,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,0GAA0G,CAC3G;KACJ,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE;QACnD,IAAI,CAAC;YACH,MAAM,KAAK,GAA4B;gBACrC,QAAQ,EAAE,SAAS;gBACnB,KAAK;aACN,CAAC;YACF,IAAI,WAAW;gBAAE,KAAK,CAAC,UAAU,GAAG,WAAW,CAAC;YAEhD,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,aAAwC,CAAC;YAC7D,MAAM,OAAO,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;YAC1C,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,6BAA6B,OAAO,EAAE,EAAE,CAAC;oBAClF,OAAO,EAAE,IAAI;iBACd,CAAC;YACJ,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,CAAC,OAAkC,CAAC;YAE1D,oCAAoC;YACpC,IAAI,OAAO,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;gBAC1B,IAAI,CAAC;oBACH,MAAM,aAAa,CAAC,mBAAmB,EAAE;wBACvC,SAAS,EAAE,OAAO,CAAC,EAAE;wBACrB,OAAO,EAAE,OAAO;qBACjB,CAAC,CAAC;gBACL,CAAC;gBAAC,MAAM,CAAC;oBACP,oDAAoD;oBACpD,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;gBAC9D,CAAC;YACH,CAAC;YAED,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;aAC7E,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;IACH,CAAC,CACF,CAAC;IAEF,oEAAoE;IACpE,MAAM,CAAC,IAAI,CACT,sBAAsB,EACtB,4IAA4I,EAC5I;QACE,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,EAAE,CAAC;aACP,OAAO,CAAC,EAAE,CAAC;aACX,QAAQ,CAAC,2DAA2D,CAAC;QACxE,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,uFAAuF,CACxF;KACJ,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;QACzB,IAAI,CAAC;YACH,MAAM,SAAS,GAA4B,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;YAC5D,IAAI,KAAK;gBAAE,SAAS,CAAC,KAAK,GAAG,KAAK,CAAC;YAEnC,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;YAC3D,MAAM,QAAQ,GAAG,IAAI,CAAC,QAIrB,CAAC;YAEF,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAClB;4BACE,WAAW,EAAE,QAAQ,CAAC,UAAU;4BAChC,QAAQ,EAAE,QAAQ,CAAC,KAAK;4BACxB,SAAS,EAAE,QAAQ,CAAC,QAAQ;yBAC7B,EACD,IAAI,EACJ,CAAC,CACF;qBACF;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;IACH,CAAC,CACF,CAAC;IAEF,oEAAoE;IACpE,MAAM,CAAC,IAAI,CACT,oBAAoB,EACpB,2HAA2H,EAC3H;QACE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;KACzD,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;QACvB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,WAAW,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;YAClE,OAAO;gBACL,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;iBACvE;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * MCP tools for Jobber scheduling and calendar operations.
3
+ */
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ export declare function registerSchedulingTools(server: McpServer): void;
6
+ //# sourceMappingURL=scheduling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduling.d.ts","sourceRoot":"","sources":["../../src/tools/scheduling.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAoBpE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAoP/D"}
@@ -0,0 +1,197 @@
1
+ /**
2
+ * MCP tools for Jobber scheduling and calendar operations.
3
+ */
4
+ import { z } from "zod";
5
+ import { jobberRequest, GET_SCHEDULE, CREATE_VISIT, LIST_USERS, } from "../graphql/queries.js";
6
+ import { extractUserErrors, formatErrorForMCP } from "../utils/errors.js";
7
+ export function registerSchedulingTools(server) {
8
+ // ── Get Schedule ─────────────────────────────────────────────────
9
+ server.tool("jobber_get_schedule", "Get the schedule of jobs and visits for a date range. Shows what's booked, who's assigned, and when. Essential for checking availability before booking new work.", {
10
+ start_date: z
11
+ .string()
12
+ .describe("Start date in ISO 8601 format (YYYY-MM-DD). E.g. '2026-03-28'."),
13
+ end_date: z
14
+ .string()
15
+ .describe("End date in ISO 8601 format (YYYY-MM-DD). E.g. '2026-04-04'."),
16
+ team_member_name: z
17
+ .string()
18
+ .optional()
19
+ .describe("Filter by team member name (partial match). Only shows visits assigned to this person."),
20
+ limit: z
21
+ .number()
22
+ .min(1)
23
+ .max(50)
24
+ .default(50)
25
+ .describe("Maximum number of jobs to return. Default 50."),
26
+ }, async ({ start_date, end_date, team_member_name, limit }) => {
27
+ try {
28
+ const data = await jobberRequest(GET_SCHEDULE, {
29
+ startAt: start_date,
30
+ endAt: end_date,
31
+ first: limit,
32
+ });
33
+ let jobs = (data.jobs?.nodes ?? []);
34
+ // Filter by team member if specified
35
+ if (team_member_name) {
36
+ const nameFilter = team_member_name.toLowerCase();
37
+ jobs = jobs.filter((job) => {
38
+ const visits = job.visits?.nodes ?? [];
39
+ return visits.some((v) => v.assignedUsers?.nodes?.some((u) => u.name.full.toLowerCase().includes(nameFilter)));
40
+ });
41
+ }
42
+ return {
43
+ content: [
44
+ {
45
+ type: "text",
46
+ text: JSON.stringify({
47
+ date_range: { start: start_date, end: end_date },
48
+ total_jobs: jobs.length,
49
+ jobs,
50
+ }, null, 2),
51
+ },
52
+ ],
53
+ };
54
+ }
55
+ catch (error) {
56
+ return formatErrorForMCP(error);
57
+ }
58
+ });
59
+ // ── Create Visit ─────────────────────────────────────────────────
60
+ server.tool("jobber_create_visit", "Schedule a visit (appointment) for an existing job. A visit is a specific time slot when a team member goes to do the work. The job must already exist — create it first with jobber_create_job.", {
61
+ job_id: z.string().describe("The Jobber job ID to schedule the visit for"),
62
+ start_at: z
63
+ .string()
64
+ .describe("Visit start time in ISO 8601 datetime format (e.g. '2026-03-28T09:00:00-04:00'). Include timezone offset."),
65
+ end_at: z
66
+ .string()
67
+ .describe("Visit end time in ISO 8601 datetime format (e.g. '2026-03-28T11:00:00-04:00'). Include timezone offset."),
68
+ team_member_ids: z
69
+ .array(z.string())
70
+ .optional()
71
+ .describe("Array of Jobber user IDs to assign to this visit. Get IDs from jobber_get_availability. Omit to leave unassigned."),
72
+ instructions: z
73
+ .string()
74
+ .optional()
75
+ .describe("Special instructions for this visit (shown to the assigned team member)"),
76
+ }, async ({ job_id, start_at, end_at, team_member_ids, instructions }) => {
77
+ try {
78
+ const input = {
79
+ jobId: job_id,
80
+ startAt: start_at,
81
+ endAt: end_at,
82
+ };
83
+ if (team_member_ids && team_member_ids.length > 0) {
84
+ input.assignedEntityIds = team_member_ids;
85
+ }
86
+ if (instructions) {
87
+ input.instructions = instructions;
88
+ }
89
+ const data = await jobberRequest(CREATE_VISIT, { input });
90
+ const result = data.visitCreate;
91
+ const userErr = extractUserErrors(result);
92
+ if (userErr) {
93
+ return {
94
+ content: [
95
+ { type: "text", text: `Failed to create visit: ${userErr}` },
96
+ ],
97
+ isError: true,
98
+ };
99
+ }
100
+ return {
101
+ content: [
102
+ {
103
+ type: "text",
104
+ text: JSON.stringify(result.visit, null, 2),
105
+ },
106
+ ],
107
+ };
108
+ }
109
+ catch (error) {
110
+ return formatErrorForMCP(error);
111
+ }
112
+ });
113
+ // ── Get Availability ─────────────────────────────────────────────
114
+ server.tool("jobber_get_availability", "Check available time slots for team members on a given date range. This derives availability by fetching the existing schedule and finding gaps. Returns team members and their booked/free time slots. Assumes a standard work day of 8:00-17:00.", {
115
+ start_date: z
116
+ .string()
117
+ .describe("Start date in YYYY-MM-DD format"),
118
+ end_date: z
119
+ .string()
120
+ .describe("End date in YYYY-MM-DD format"),
121
+ work_day_start: z
122
+ .string()
123
+ .default("08:00")
124
+ .describe("Work day start time in HH:MM format. Default '08:00'."),
125
+ work_day_end: z
126
+ .string()
127
+ .default("17:00")
128
+ .describe("Work day end time in HH:MM format. Default '17:00'."),
129
+ }, async ({ start_date, end_date, work_day_start, work_day_end }) => {
130
+ try {
131
+ // Fetch team members
132
+ const usersData = await jobberRequest(LIST_USERS, { first: 50 });
133
+ const users = (usersData.users?.nodes ??
134
+ []);
135
+ // Fetch schedule for the date range
136
+ const scheduleData = await jobberRequest(GET_SCHEDULE, {
137
+ startAt: start_date,
138
+ endAt: end_date,
139
+ first: 50,
140
+ });
141
+ const scheduledJobs = (scheduleData.jobs?.nodes ?? []);
142
+ // Build a map of team_member_id -> booked time slots
143
+ const bookedSlots = new Map();
144
+ for (const job of scheduledJobs) {
145
+ const visits = job.visits?.nodes ?? [];
146
+ for (const visit of visits) {
147
+ const assignedUsers = visit.assignedUsers?.nodes ?? [];
148
+ for (const user of assignedUsers) {
149
+ if (!bookedSlots.has(user.id)) {
150
+ bookedSlots.set(user.id, []);
151
+ }
152
+ bookedSlots.get(user.id).push({
153
+ start: visit.startAt,
154
+ end: visit.endAt,
155
+ job_title: job.title ?? "Untitled",
156
+ });
157
+ }
158
+ }
159
+ }
160
+ // Build availability per team member
161
+ const availability = users
162
+ .filter((u) => u.role !== "ACCOUNT_OWNER" || users.length <= 2) // Include owner for small teams
163
+ .map((user) => {
164
+ const booked = bookedSlots.get(user.id) ?? [];
165
+ return {
166
+ id: user.id,
167
+ name: user.name.full,
168
+ role: user.role,
169
+ booked_slots: booked.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime()),
170
+ work_hours: { start: work_day_start, end: work_day_end },
171
+ total_booked_hours: booked.reduce((sum, slot) => {
172
+ const dur = (new Date(slot.end).getTime() - new Date(slot.start).getTime()) /
173
+ 3_600_000;
174
+ return sum + dur;
175
+ }, 0),
176
+ };
177
+ });
178
+ return {
179
+ content: [
180
+ {
181
+ type: "text",
182
+ text: JSON.stringify({
183
+ date_range: { start: start_date, end: end_date },
184
+ work_hours: { start: work_day_start, end: work_day_end },
185
+ team_availability: availability,
186
+ note: "Free time = work_hours minus booked_slots. Derive open windows from the gaps.",
187
+ }, null, 2),
188
+ },
189
+ ],
190
+ };
191
+ }
192
+ catch (error) {
193
+ return formatErrorForMCP(error);
194
+ }
195
+ });
196
+ }
197
+ //# sourceMappingURL=scheduling.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduling.js","sourceRoot":"","sources":["../../src/tools/scheduling.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Transport configuration for stdio and streamable-http modes.
3
+ */
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ export declare function startStdioTransport(server: McpServer): Promise<void>;
6
+ export declare function startHttpTransport(server: McpServer): Promise<void>;
7
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAMpE,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAI1E;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAsEzE"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Transport configuration for stdio and streamable-http modes.
3
+ */
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
6
+ import express from "express";
7
+ import { randomUUID } from "crypto";
8
+ export async function startStdioTransport(server) {
9
+ const transport = new StdioServerTransport();
10
+ await server.connect(transport);
11
+ console.error("[jobber-mcp] Server running on stdio transport");
12
+ }
13
+ export async function startHttpTransport(server) {
14
+ const app = express();
15
+ app.use(express.json());
16
+ // Store transports by session ID
17
+ const transports = new Map();
18
+ app.post("/mcp", async (req, res) => {
19
+ const sessionId = req.headers["mcp-session-id"];
20
+ if (sessionId && transports.has(sessionId)) {
21
+ // Existing session
22
+ const transport = transports.get(sessionId);
23
+ await transport.handleRequest(req, res, req.body);
24
+ }
25
+ else {
26
+ // New session
27
+ const newTransport = new StreamableHTTPServerTransport({
28
+ sessionIdGenerator: () => randomUUID(),
29
+ });
30
+ newTransport.onclose = () => {
31
+ const sid = newTransport.sessionId;
32
+ if (sid)
33
+ transports.delete(sid);
34
+ };
35
+ await server.connect(newTransport);
36
+ await newTransport.handleRequest(req, res, req.body);
37
+ // Store the transport using its session ID
38
+ const sid = res.getHeader("mcp-session-id");
39
+ if (sid) {
40
+ transports.set(sid, newTransport);
41
+ }
42
+ }
43
+ });
44
+ app.get("/mcp", async (req, res) => {
45
+ const sessionId = req.headers["mcp-session-id"];
46
+ if (sessionId && transports.has(sessionId)) {
47
+ const transport = transports.get(sessionId);
48
+ await transport.handleRequest(req, res);
49
+ }
50
+ else {
51
+ res.status(400).json({ error: "No session. Send a POST to /mcp first." });
52
+ }
53
+ });
54
+ app.delete("/mcp", async (req, res) => {
55
+ const sessionId = req.headers["mcp-session-id"];
56
+ if (sessionId && transports.has(sessionId)) {
57
+ const transport = transports.get(sessionId);
58
+ await transport.handleRequest(req, res);
59
+ transports.delete(sessionId);
60
+ }
61
+ else {
62
+ res.status(400).json({ error: "No session found." });
63
+ }
64
+ });
65
+ // Health check
66
+ app.get("/health", (_req, res) => {
67
+ res.json({ status: "ok", transport: "streamable-http" });
68
+ });
69
+ const port = parseInt(process.env.HTTP_PORT ?? "3000", 10);
70
+ const host = process.env.HTTP_HOST ?? "localhost";
71
+ app.listen(port, host, () => {
72
+ console.error(`[jobber-mcp] Server running on streamable-http at http://${host}:${port}/mcp`);
73
+ });
74
+ }
75
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Error handling utilities for Jobber GraphQL API responses.
3
+ *
4
+ * Maps raw GraphQL errors and userErrors into clear, actionable messages
5
+ * that Claude (or any MCP consumer) can understand and act on.
6
+ */
7
+ export declare class JobberAPIError extends Error {
8
+ readonly statusCode: number | undefined;
9
+ readonly graphqlErrors: unknown[];
10
+ readonly userErrors: Array<{
11
+ message: string;
12
+ path?: string[];
13
+ }>;
14
+ constructor(message: string, opts?: {
15
+ statusCode?: number;
16
+ graphqlErrors?: unknown[];
17
+ userErrors?: Array<{
18
+ message: string;
19
+ path?: string[];
20
+ }>;
21
+ });
22
+ }
23
+ /**
24
+ * Extract a clean error message from a Jobber GraphQL response.
25
+ * Handles both top-level `errors` array and mutation-level `userErrors`.
26
+ */
27
+ export declare function extractErrors(data: Record<string, unknown>): string | null;
28
+ /**
29
+ * Check a mutation result for userErrors. Returns the error string or null.
30
+ */
31
+ export declare function extractUserErrors(mutationResult: Record<string, unknown>): string | null;
32
+ /**
33
+ * Format a JobberAPIError for MCP tool response.
34
+ */
35
+ export declare function formatErrorForMCP(error: unknown): {
36
+ content: Array<{
37
+ type: "text";
38
+ text: string;
39
+ }>;
40
+ isError: true;
41
+ };
42
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAgB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/C,SAAgB,aAAa,EAAE,OAAO,EAAE,CAAC;IACzC,SAAgB,UAAU,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;gBAGtE,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;QACL,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC;QAC1B,UAAU,CAAC,EAAE,KAAK,CAAC;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC,CAAC;KAC1D;CAQJ;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CAU1E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACtC,MAAM,GAAG,IAAI,CAaf;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG;IACjD,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,OAAO,EAAE,IAAI,CAAC;CACf,CAqBA"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Error handling utilities for Jobber GraphQL API responses.
3
+ *
4
+ * Maps raw GraphQL errors and userErrors into clear, actionable messages
5
+ * that Claude (or any MCP consumer) can understand and act on.
6
+ */
7
+ export class JobberAPIError extends Error {
8
+ statusCode;
9
+ graphqlErrors;
10
+ userErrors;
11
+ constructor(message, opts) {
12
+ super(message);
13
+ this.name = "JobberAPIError";
14
+ this.statusCode = opts?.statusCode;
15
+ this.graphqlErrors = opts?.graphqlErrors ?? [];
16
+ this.userErrors = opts?.userErrors ?? [];
17
+ }
18
+ }
19
+ /**
20
+ * Extract a clean error message from a Jobber GraphQL response.
21
+ * Handles both top-level `errors` array and mutation-level `userErrors`.
22
+ */
23
+ export function extractErrors(data) {
24
+ // Top-level GraphQL errors
25
+ if (Array.isArray(data.errors) && data.errors.length > 0) {
26
+ const messages = data.errors
27
+ .map((e) => e.message ?? "Unknown error")
28
+ .join("; ");
29
+ return messages;
30
+ }
31
+ return null;
32
+ }
33
+ /**
34
+ * Check a mutation result for userErrors. Returns the error string or null.
35
+ */
36
+ export function extractUserErrors(mutationResult) {
37
+ const userErrors = mutationResult.userErrors;
38
+ if (!userErrors || userErrors.length === 0)
39
+ return null;
40
+ return userErrors
41
+ .map((e) => {
42
+ const path = e.path ? ` (${e.path.join(".")})` : "";
43
+ return `${e.message}${path}`;
44
+ })
45
+ .join("; ");
46
+ }
47
+ /**
48
+ * Format a JobberAPIError for MCP tool response.
49
+ */
50
+ export function formatErrorForMCP(error) {
51
+ let message;
52
+ if (error instanceof JobberAPIError) {
53
+ message = error.message;
54
+ if (error.userErrors.length > 0) {
55
+ const details = error.userErrors
56
+ .map((e) => `- ${e.message}`)
57
+ .join("\n");
58
+ message += `\n\nDetails:\n${details}`;
59
+ }
60
+ }
61
+ else if (error instanceof Error) {
62
+ message = error.message;
63
+ }
64
+ else {
65
+ message = String(error);
66
+ }
67
+ return {
68
+ content: [{ type: "text", text: `Error: ${message}` }],
69
+ isError: true,
70
+ };
71
+ }
72
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvB,UAAU,CAAqB;IAC/B,aAAa,CAAY;IACzB,UAAU,CAA8C;IAExE,YACE,OAAe,EACf,IAIC;QAED,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,UAAU,GAAG,IAAI,EAAE,UAAU,CAAC;QACnC,IAAI,CAAC,aAAa,GAAG,IAAI,EAAE,aAAa,IAAI,EAAE,CAAC;QAC/C,IAAI,CAAC,UAAU,GAAG,IAAI,EAAE,UAAU,IAAI,EAAE,CAAC;IAC3C,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAA6B;IACzD,2BAA2B;IAC3B,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM;aACzB,GAAG,CAAC,CAAC,CAAuB,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,eAAe,CAAC;aAC9D,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,cAAuC;IAEvC,MAAM,UAAU,GAAG,cAAc,CAAC,UAErB,CAAC;IAEd,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAExD,OAAO,UAAU;SACd,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACpD,OAAO,GAAG,CAAC,CAAC,OAAO,GAAG,IAAI,EAAE,CAAC;IAC/B,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAI9C,IAAI,OAAe,CAAC;IAEpB,IAAI,KAAK,YAAY,cAAc,EAAE,CAAC;QACpC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;QACxB,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU;iBAC7B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;iBAC5B,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,OAAO,IAAI,iBAAiB,OAAO,EAAE,CAAC;QACxC,CAAC;IACH,CAAC;SAAM,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAClC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC1B,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,OAAO,EAAE,EAAE,CAAC;QACtD,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Cursor-based pagination helper for Jobber's GraphQL API.
3
+ *
4
+ * Jobber uses the `first` / `after` pattern with `pageInfo.hasNextPage`
5
+ * and `pageInfo.endCursor`.
6
+ */
7
+ export interface PageInfo {
8
+ hasNextPage: boolean;
9
+ endCursor: string | null;
10
+ }
11
+ export interface PaginatedResult<T> {
12
+ nodes: T[];
13
+ totalCount: number | null;
14
+ pageInfo: PageInfo;
15
+ }
16
+ /**
17
+ * Fetch a single page from a paginated Jobber query.
18
+ *
19
+ * @param query - The GraphQL query string (must include $first and $after variables)
20
+ * @param variables - Query variables (first, after, plus any filters)
21
+ * @param rootField - The top-level field in the response (e.g. "clients", "jobs")
22
+ * @returns Parsed page with nodes, totalCount, and pageInfo
23
+ */
24
+ export declare function fetchPage<T>(query: string, variables: Record<string, unknown>, rootField: string): Promise<PaginatedResult<T>>;
25
+ /**
26
+ * Fetch all pages from a paginated query (up to maxPages to prevent runaway).
27
+ */
28
+ export declare function fetchAllPages<T>(query: string, variables: Record<string, unknown>, rootField: string, maxPages?: number): Promise<T[]>;
29
+ //# sourceMappingURL=pagination.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../../src/utils/pagination.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAqB7B;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,SAAS,EAAE,MAAM,EACjB,QAAQ,SAAK,GACZ,OAAO,CAAC,CAAC,EAAE,CAAC,CAmBd"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Cursor-based pagination helper for Jobber's GraphQL API.
3
+ *
4
+ * Jobber uses the `first` / `after` pattern with `pageInfo.hasNextPage`
5
+ * and `pageInfo.endCursor`.
6
+ */
7
+ import { jobberRequest } from "../graphql/queries.js";
8
+ /**
9
+ * Fetch a single page from a paginated Jobber query.
10
+ *
11
+ * @param query - The GraphQL query string (must include $first and $after variables)
12
+ * @param variables - Query variables (first, after, plus any filters)
13
+ * @param rootField - The top-level field in the response (e.g. "clients", "jobs")
14
+ * @returns Parsed page with nodes, totalCount, and pageInfo
15
+ */
16
+ export async function fetchPage(query, variables, rootField) {
17
+ const data = await jobberRequest(query, variables);
18
+ const root = data[rootField];
19
+ if (!root) {
20
+ return {
21
+ nodes: [],
22
+ totalCount: null,
23
+ pageInfo: { hasNextPage: false, endCursor: null },
24
+ };
25
+ }
26
+ return {
27
+ nodes: root.nodes ?? [],
28
+ totalCount: root.totalCount ?? null,
29
+ pageInfo: root.pageInfo ?? { hasNextPage: false, endCursor: null },
30
+ };
31
+ }
32
+ /**
33
+ * Fetch all pages from a paginated query (up to maxPages to prevent runaway).
34
+ */
35
+ export async function fetchAllPages(query, variables, rootField, maxPages = 10) {
36
+ const allNodes = [];
37
+ let cursor = null;
38
+ let page = 0;
39
+ while (page < maxPages) {
40
+ const result = await fetchPage(query, { ...variables, after: cursor }, rootField);
41
+ allNodes.push(...result.nodes);
42
+ if (!result.pageInfo.hasNextPage || !result.pageInfo.endCursor)
43
+ break;
44
+ cursor = result.pageInfo.endCursor;
45
+ page++;
46
+ }
47
+ return allNodes;
48
+ }
49
+ //# sourceMappingURL=pagination.js.map