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,447 @@
1
+ /**
2
+ * Jobber GraphQL queries, mutations, and the core request function.
3
+ *
4
+ * All GraphQL operations are defined here as tagged template strings.
5
+ * The jobberRequest() function handles auth, retries, and rate limiting.
6
+ */
7
+ import { getTokens, isTokenExpiringSoon, refreshAccessToken, } from "../auth/oauth.js";
8
+ import { JobberAPIError, extractErrors } from "../utils/errors.js";
9
+ const API_URL = "https://api.getjobber.com/api/graphql";
10
+ const API_VERSION = "2025-04-16";
11
+ // Simple rate limiter: track request timestamps
12
+ const requestTimestamps = [];
13
+ const RATE_LIMIT = 60;
14
+ const RATE_WINDOW_MS = 60_000;
15
+ async function waitForRateLimit() {
16
+ const now = Date.now();
17
+ // Purge old timestamps
18
+ while (requestTimestamps.length > 0 && requestTimestamps[0] < now - RATE_WINDOW_MS) {
19
+ requestTimestamps.shift();
20
+ }
21
+ if (requestTimestamps.length >= RATE_LIMIT) {
22
+ const oldestInWindow = requestTimestamps[0];
23
+ const waitMs = oldestInWindow + RATE_WINDOW_MS - now + 100;
24
+ console.error(`[jobber-mcp] Rate limit approaching, waiting ${waitMs}ms`);
25
+ await new Promise((r) => setTimeout(r, waitMs));
26
+ }
27
+ requestTimestamps.push(Date.now());
28
+ }
29
+ /**
30
+ * Execute a GraphQL request against the Jobber API.
31
+ * Handles token refresh, rate limiting, and retries.
32
+ */
33
+ export async function jobberRequest(query, variables) {
34
+ // Refresh token if expiring soon
35
+ if (isTokenExpiringSoon()) {
36
+ await refreshAccessToken();
37
+ }
38
+ await waitForRateLimit();
39
+ const tokens = getTokens();
40
+ const headers = {
41
+ Authorization: `Bearer ${tokens.accessToken}`,
42
+ "Content-Type": "application/json",
43
+ "X-JOBBER-GRAPHQL-VERSION": API_VERSION,
44
+ };
45
+ const body = JSON.stringify({
46
+ query,
47
+ ...(variables ? { variables } : {}),
48
+ });
49
+ let lastError = null;
50
+ for (let attempt = 0; attempt < 3; attempt++) {
51
+ const resp = await fetch(API_URL, { method: "POST", headers, body });
52
+ if (resp.status === 429) {
53
+ const wait = (2 ** attempt) * 2000;
54
+ console.error(`[jobber-mcp] Rate limited (429), retrying in ${wait}ms (attempt ${attempt + 1})`);
55
+ await new Promise((r) => setTimeout(r, wait));
56
+ continue;
57
+ }
58
+ if (resp.status === 401 && attempt === 0) {
59
+ console.error("[jobber-mcp] Got 401, attempting token refresh...");
60
+ await refreshAccessToken();
61
+ const newTokens = getTokens();
62
+ headers.Authorization = `Bearer ${newTokens.accessToken}`;
63
+ continue;
64
+ }
65
+ if (!resp.ok) {
66
+ const text = await resp.text();
67
+ throw new JobberAPIError(`Jobber API returned ${resp.status}: ${text}`, {
68
+ statusCode: resp.status,
69
+ });
70
+ }
71
+ const data = (await resp.json());
72
+ const errorMsg = extractErrors(data);
73
+ if (errorMsg) {
74
+ throw new JobberAPIError(errorMsg, {
75
+ graphqlErrors: data.errors,
76
+ });
77
+ }
78
+ return (data.data ?? {});
79
+ }
80
+ throw lastError ?? new JobberAPIError("Request failed after 3 retries");
81
+ }
82
+ // ─── Client Queries ──────────────────────────────────────────────────
83
+ export const SEARCH_CLIENTS = `
84
+ query SearchClients($searchTerm: String!, $first: Int!, $after: String) {
85
+ clients(searchTerm: $searchTerm, first: $first, after: $after) {
86
+ nodes {
87
+ id
88
+ firstName
89
+ lastName
90
+ name
91
+ isCompany
92
+ companyName
93
+ phones { number description primary }
94
+ emails { address description primary }
95
+ billingAddress { street1 street2 city province postalCode country }
96
+ tags { nodes { label } }
97
+ createdAt
98
+ }
99
+ totalCount
100
+ pageInfo { hasNextPage endCursor }
101
+ }
102
+ }`;
103
+ export const GET_CLIENT = `
104
+ query GetClient($id: EncodedId!) {
105
+ client(id: $id) {
106
+ id
107
+ firstName
108
+ lastName
109
+ name
110
+ isCompany
111
+ companyName
112
+ phones { number description primary }
113
+ emails { address description primary }
114
+ billingAddress { street1 street2 city province postalCode country }
115
+ tags { nodes { label } }
116
+ createdAt
117
+ clientProperties(first: 10) {
118
+ nodes {
119
+ id
120
+ address { street1 street2 city province postalCode country }
121
+ }
122
+ }
123
+ jobs(first: 10, orderBy: { key: CREATED_AT, direction: DESC }) {
124
+ nodes {
125
+ id
126
+ title
127
+ jobStatus
128
+ createdAt
129
+ total
130
+ }
131
+ totalCount
132
+ }
133
+ }
134
+ }`;
135
+ export const CREATE_CLIENT = `
136
+ mutation CreateClient($input: ClientCreateInput!) {
137
+ clientCreate(input: $input) {
138
+ client {
139
+ id
140
+ firstName
141
+ lastName
142
+ name
143
+ jobberWebUri
144
+ }
145
+ userErrors { message path }
146
+ }
147
+ }`;
148
+ export const UPDATE_CLIENT = `
149
+ mutation UpdateClient($clientId: EncodedId!, $input: ClientUpdateInput!) {
150
+ clientUpdate(clientId: $clientId, input: $input) {
151
+ client {
152
+ id
153
+ firstName
154
+ lastName
155
+ name
156
+ phones { number description primary }
157
+ emails { address description primary }
158
+ }
159
+ userErrors { message path }
160
+ }
161
+ }`;
162
+ // ─── Request Queries ─────────────────────────────────────────────────
163
+ export const CREATE_REQUEST = `
164
+ mutation CreateRequest($input: RequestCreateInput!) {
165
+ requestCreate(input: $input) {
166
+ request {
167
+ id
168
+ title
169
+ requestStatus
170
+ jobberWebUri
171
+ createdAt
172
+ }
173
+ userErrors { message path }
174
+ }
175
+ }`;
176
+ export const LIST_REQUESTS = `
177
+ query ListRequests($first: Int!, $after: String) {
178
+ requests(first: $first, after: $after) {
179
+ nodes {
180
+ id
181
+ title
182
+ requestStatus
183
+ createdAt
184
+ client { id name }
185
+ property { address { street1 city province } }
186
+ jobberWebUri
187
+ }
188
+ totalCount
189
+ pageInfo { hasNextPage endCursor }
190
+ }
191
+ }`;
192
+ export const GET_REQUEST = `
193
+ query GetRequest($id: EncodedId!) {
194
+ request(id: $id) {
195
+ id
196
+ title
197
+ requestStatus
198
+ createdAt
199
+ client { id name phones { number } emails { address } }
200
+ property { address { street1 street2 city province postalCode } }
201
+ jobberWebUri
202
+ }
203
+ }`;
204
+ export const CREATE_REQUEST_NOTE = `
205
+ mutation CreateRequestNote($requestId: EncodedId!, $message: String!) {
206
+ requestNoteCreate(requestId: $requestId, message: $message) {
207
+ note { id }
208
+ userErrors { message path }
209
+ }
210
+ }`;
211
+ // ─── Job Queries ─────────────────────────────────────────────────────
212
+ export const LIST_JOBS = `
213
+ query ListJobs($first: Int!, $after: String, $filter: JobFilterAttributes) {
214
+ jobs(first: $first, after: $after, filter: $filter) {
215
+ nodes {
216
+ id
217
+ title
218
+ jobNumber
219
+ jobStatus
220
+ startAt
221
+ endAt
222
+ createdAt
223
+ total
224
+ client { id name }
225
+ property { address { street1 city province postalCode } }
226
+ jobberWebUri
227
+ }
228
+ totalCount
229
+ pageInfo { hasNextPage endCursor }
230
+ }
231
+ }`;
232
+ export const GET_JOB = `
233
+ query GetJob($id: EncodedId!) {
234
+ job(id: $id) {
235
+ id
236
+ title
237
+ jobNumber
238
+ jobStatus
239
+ startAt
240
+ endAt
241
+ createdAt
242
+ total
243
+ instructions
244
+ client {
245
+ id
246
+ name
247
+ firstName
248
+ lastName
249
+ phones { number description }
250
+ emails { address description }
251
+ }
252
+ property {
253
+ address { street1 street2 city province postalCode }
254
+ }
255
+ lineItems {
256
+ nodes {
257
+ name
258
+ description
259
+ quantity
260
+ unitPrice
261
+ totalPrice
262
+ }
263
+ }
264
+ visits(first: 20) {
265
+ nodes {
266
+ id
267
+ title
268
+ startAt
269
+ endAt
270
+ status
271
+ assignedUsers { nodes { id name { full } } }
272
+ }
273
+ }
274
+ jobberWebUri
275
+ }
276
+ }`;
277
+ export const CREATE_JOB = `
278
+ mutation CreateJob($input: JobCreateInput!) {
279
+ jobCreate(input: $input) {
280
+ job {
281
+ id
282
+ title
283
+ jobNumber
284
+ jobStatus
285
+ jobberWebUri
286
+ }
287
+ userErrors { message path }
288
+ }
289
+ }`;
290
+ export const CREATE_JOB_NOTE = `
291
+ mutation CreateJobNote($jobId: ID!, $message: String!) {
292
+ jobNoteCreate(jobId: $jobId, message: $message) {
293
+ note { id }
294
+ userErrors { message path }
295
+ }
296
+ }`;
297
+ // ─── Scheduling / Visit Queries ──────────────────────────────────────
298
+ export const GET_SCHEDULE = `
299
+ query GetSchedule($startAt: ISO8601Date!, $endAt: ISO8601Date!, $first: Int!, $after: String) {
300
+ jobs(
301
+ filter: {
302
+ startAt: { between: { start: $startAt, end: $endAt } }
303
+ status: [ACTIVE, IN_PROGRESS, TODAY, UPCOMING]
304
+ }
305
+ first: $first
306
+ after: $after
307
+ ) {
308
+ nodes {
309
+ id
310
+ title
311
+ jobStatus
312
+ startAt
313
+ endAt
314
+ client { id name }
315
+ property { address { street1 city province } }
316
+ visits(first: 20) {
317
+ nodes {
318
+ id
319
+ startAt
320
+ endAt
321
+ status
322
+ assignedUsers { nodes { id name { full } } }
323
+ }
324
+ }
325
+ jobberWebUri
326
+ }
327
+ totalCount
328
+ pageInfo { hasNextPage endCursor }
329
+ }
330
+ }`;
331
+ export const CREATE_VISIT = `
332
+ mutation CreateVisit($input: VisitCreateInput!) {
333
+ visitCreate(input: $input) {
334
+ visit {
335
+ id
336
+ startAt
337
+ endAt
338
+ }
339
+ userErrors { message path }
340
+ }
341
+ }`;
342
+ // ─── Quote Queries ───────────────────────────────────────────────────
343
+ export const CREATE_QUOTE = `
344
+ mutation CreateQuote($attributes: QuoteCreateAttributes!) {
345
+ quoteCreate(attributes: $attributes) {
346
+ quote {
347
+ id
348
+ quoteNumber
349
+ quoteStatus
350
+ total
351
+ jobberWebUri
352
+ }
353
+ userErrors { message path }
354
+ }
355
+ }`;
356
+ export const LIST_QUOTES = `
357
+ query ListQuotes($first: Int!, $after: String) {
358
+ quotes(first: $first, after: $after) {
359
+ nodes {
360
+ id
361
+ quoteNumber
362
+ quoteStatus
363
+ total
364
+ createdAt
365
+ client { id name }
366
+ jobberWebUri
367
+ }
368
+ totalCount
369
+ pageInfo { hasNextPage endCursor }
370
+ }
371
+ }`;
372
+ // ─── Invoice Queries ─────────────────────────────────────────────────
373
+ export const LIST_INVOICES = `
374
+ query ListInvoices($first: Int!, $after: String) {
375
+ invoices(first: $first, after: $after) {
376
+ nodes {
377
+ id
378
+ invoiceNumber
379
+ invoiceStatus
380
+ total
381
+ amountDue
382
+ issuedDate
383
+ dueDate
384
+ createdAt
385
+ client { id name }
386
+ jobberWebUri
387
+ }
388
+ totalCount
389
+ pageInfo { hasNextPage endCursor }
390
+ }
391
+ }`;
392
+ export const GET_INVOICE = `
393
+ query GetInvoice($id: EncodedId!) {
394
+ invoice(id: $id) {
395
+ id
396
+ invoiceNumber
397
+ invoiceStatus
398
+ total
399
+ amountDue
400
+ issuedDate
401
+ dueDate
402
+ createdAt
403
+ subject
404
+ message
405
+ client {
406
+ id
407
+ name
408
+ firstName
409
+ lastName
410
+ phones { number }
411
+ emails { address }
412
+ }
413
+ lineItems {
414
+ nodes {
415
+ name
416
+ description
417
+ quantity
418
+ unitPrice
419
+ totalPrice
420
+ }
421
+ }
422
+ jobberWebUri
423
+ }
424
+ }`;
425
+ // ─── Account ─────────────────────────────────────────────────────────
426
+ export const GET_ACCOUNT = `
427
+ query GetAccount {
428
+ account {
429
+ id
430
+ name
431
+ phone
432
+ industry
433
+ }
434
+ }`;
435
+ // ─── Users (for team member lookups in scheduling) ───────────────────
436
+ export const LIST_USERS = `
437
+ query ListUsers($first: Int!) {
438
+ users(first: $first) {
439
+ nodes {
440
+ id
441
+ name { first last full }
442
+ email { raw }
443
+ role
444
+ }
445
+ }
446
+ }`;
447
+ //# sourceMappingURL=queries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/graphql/queries.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Jobber MCP Server — provides Claude (or any MCP client) with tools
4
+ * to interact with a Jobber account via the GraphQL API.
5
+ *
6
+ * Supports two transport modes:
7
+ * - stdio (default): for Claude Code / local dev
8
+ * - streamable-http: for remote/hosted deployments
9
+ *
10
+ * Set TRANSPORT=http to use HTTP mode, or leave unset for stdio.
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;GASG"}
package/dist/index.js ADDED
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Jobber MCP Server — provides Claude (or any MCP client) with tools
4
+ * to interact with a Jobber account via the GraphQL API.
5
+ *
6
+ * Supports two transport modes:
7
+ * - stdio (default): for Claude Code / local dev
8
+ * - streamable-http: for remote/hosted deployments
9
+ *
10
+ * Set TRANSPORT=http to use HTTP mode, or leave unset for stdio.
11
+ */
12
+ import { readFileSync } from "fs";
13
+ import { resolve, dirname } from "path";
14
+ import { fileURLToPath } from "url";
15
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
16
+ import { loadTokensFromEnv } from "./auth/oauth.js";
17
+ // Load .env from project root (works for both src/ and dist/)
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+ const envPath = resolve(__dirname, "..", ".env");
20
+ try {
21
+ const envContent = readFileSync(envPath, "utf-8");
22
+ for (const line of envContent.split("\n")) {
23
+ const trimmed = line.trim();
24
+ if (!trimmed || trimmed.startsWith("#"))
25
+ continue;
26
+ const eqIdx = trimmed.indexOf("=");
27
+ if (eqIdx === -1)
28
+ continue;
29
+ const key = trimmed.slice(0, eqIdx).trim();
30
+ const val = trimmed.slice(eqIdx + 1).trim();
31
+ if (!process.env[key])
32
+ process.env[key] = val; // don't override existing env
33
+ }
34
+ }
35
+ catch {
36
+ // .env is optional — env vars can be passed directly
37
+ }
38
+ import { registerClientTools } from "./tools/clients.js";
39
+ import { registerRequestTools } from "./tools/requests.js";
40
+ import { registerJobTools } from "./tools/jobs.js";
41
+ import { registerSchedulingTools } from "./tools/scheduling.js";
42
+ import { registerQuoteTools } from "./tools/quotes.js";
43
+ import { registerInvoiceTools } from "./tools/invoices.js";
44
+ import { startStdioTransport, startHttpTransport } from "./transport.js";
45
+ async function main() {
46
+ // Validate auth tokens are available
47
+ try {
48
+ loadTokensFromEnv();
49
+ }
50
+ catch (error) {
51
+ console.error(`[jobber-mcp] ${error.message}`);
52
+ process.exit(1);
53
+ }
54
+ // Create MCP server
55
+ const server = new McpServer({
56
+ name: "jobber",
57
+ version: "1.0.0",
58
+ });
59
+ // Register all tool categories
60
+ registerClientTools(server);
61
+ registerRequestTools(server);
62
+ registerJobTools(server);
63
+ registerSchedulingTools(server);
64
+ registerQuoteTools(server);
65
+ registerInvoiceTools(server);
66
+ // Start the appropriate transport
67
+ const transport = process.env.TRANSPORT ?? "stdio";
68
+ if (transport === "http") {
69
+ await startHttpTransport(server);
70
+ }
71
+ else {
72
+ await startStdioTransport(server);
73
+ }
74
+ }
75
+ main().catch((error) => {
76
+ console.error("[jobber-mcp] Fatal error:", error);
77
+ process.exit(1);
78
+ });
79
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * MCP tools for Jobber client/customer operations.
3
+ */
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ export declare function registerClientTools(server: McpServer): void;
6
+ //# sourceMappingURL=clients.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clients.d.ts","sourceRoot":"","sources":["../../src/tools/clients.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAWpE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAkO3D"}