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.
- package/.env.example +16 -0
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/dist/auth/oauth.d.ts +19 -0
- package/dist/auth/oauth.d.ts.map +1 -0
- package/dist/auth/oauth.js +94 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/graphql/queries.d.ts +32 -0
- package/dist/graphql/queries.d.ts.map +1 -0
- package/dist/graphql/queries.js +447 -0
- package/dist/graphql/queries.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +79 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/clients.d.ts +6 -0
- package/dist/tools/clients.d.ts.map +1 -0
- package/dist/tools/clients.js +186 -0
- package/dist/tools/clients.js.map +1 -0
- package/dist/tools/invoices.d.ts +6 -0
- package/dist/tools/invoices.d.ts.map +1 -0
- package/dist/tools/invoices.js +64 -0
- package/dist/tools/invoices.js.map +1 -0
- package/dist/tools/jobs.d.ts +6 -0
- package/dist/tools/jobs.d.ts.map +1 -0
- package/dist/tools/jobs.js +205 -0
- package/dist/tools/jobs.js.map +1 -0
- package/dist/tools/quotes.d.ts +6 -0
- package/dist/tools/quotes.d.ts.map +1 -0
- package/dist/tools/quotes.js +113 -0
- package/dist/tools/quotes.js.map +1 -0
- package/dist/tools/requests.d.ts +6 -0
- package/dist/tools/requests.d.ts.map +1 -0
- package/dist/tools/requests.js +117 -0
- package/dist/tools/requests.js.map +1 -0
- package/dist/tools/scheduling.d.ts +6 -0
- package/dist/tools/scheduling.d.ts.map +1 -0
- package/dist/tools/scheduling.js +197 -0
- package/dist/tools/scheduling.js.map +1 -0
- package/dist/transport.d.ts +7 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +75 -0
- package/dist/transport.js.map +1 -0
- package/dist/utils/errors.d.ts +42 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +72 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/pagination.d.ts +29 -0
- package/dist/utils/pagination.d.ts.map +1 -0
- package/dist/utils/pagination.js +49 -0
- package/dist/utils/pagination.js.map +1 -0
- package/package.json +55 -0
- package/scripts/extract-tokens.py +46 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tools for Jobber client/customer operations.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { jobberRequest, SEARCH_CLIENTS, GET_CLIENT, CREATE_CLIENT, UPDATE_CLIENT, } from "../graphql/queries.js";
|
|
6
|
+
import { extractUserErrors } from "../utils/errors.js";
|
|
7
|
+
import { formatErrorForMCP } from "../utils/errors.js";
|
|
8
|
+
export function registerClientTools(server) {
|
|
9
|
+
// ── Search Clients ───────────────────────────────────────────────
|
|
10
|
+
server.tool("jobber_search_clients", "Search for clients (customers) in Jobber by name, email, or phone number. Returns matching clients with their contact info, address, and tags. Use this to find existing clients before creating new ones.", {
|
|
11
|
+
search_term: z
|
|
12
|
+
.string()
|
|
13
|
+
.describe("The search query — can be a name, email address, or phone number. Jobber searches across all contact fields."),
|
|
14
|
+
limit: z
|
|
15
|
+
.number()
|
|
16
|
+
.min(1)
|
|
17
|
+
.max(50)
|
|
18
|
+
.default(10)
|
|
19
|
+
.describe("Maximum number of results to return. Default 10, max 50."),
|
|
20
|
+
}, async ({ search_term, limit }) => {
|
|
21
|
+
try {
|
|
22
|
+
const data = await jobberRequest(SEARCH_CLIENTS, {
|
|
23
|
+
searchTerm: search_term,
|
|
24
|
+
first: limit,
|
|
25
|
+
});
|
|
26
|
+
const clients = data.clients;
|
|
27
|
+
return {
|
|
28
|
+
content: [
|
|
29
|
+
{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: JSON.stringify({
|
|
32
|
+
total_count: clients.totalCount,
|
|
33
|
+
clients: clients.nodes,
|
|
34
|
+
}, null, 2),
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
return formatErrorForMCP(error);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
// ── Get Client ───────────────────────────────────────────────────
|
|
44
|
+
server.tool("jobber_get_client", "Get full details for a specific Jobber client by their ID. Returns contact info, properties (service addresses), and recent job history. Use this after searching to get complete client information.", {
|
|
45
|
+
client_id: z
|
|
46
|
+
.string()
|
|
47
|
+
.describe("The Jobber client ID (encoded ID format, e.g. 'Z2lkOi8vSm9iYmVyL0NsaWVudC8xMjM='). Get this from search results."),
|
|
48
|
+
}, async ({ client_id }) => {
|
|
49
|
+
try {
|
|
50
|
+
const data = await jobberRequest(GET_CLIENT, { id: client_id });
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{ type: "text", text: JSON.stringify(data.client, null, 2) },
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
return formatErrorForMCP(error);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
// ── Create Client ────────────────────────────────────────────────
|
|
62
|
+
server.tool("jobber_create_client", "Create a new client (customer) in Jobber. Always search for existing clients first to avoid duplicates. Returns the new client's ID and Jobber web URL.", {
|
|
63
|
+
first_name: z.string().describe("Client's first name"),
|
|
64
|
+
last_name: z.string().describe("Client's last name"),
|
|
65
|
+
email: z
|
|
66
|
+
.string()
|
|
67
|
+
.optional()
|
|
68
|
+
.describe("Client's email address. Recommended for sending quotes/invoices."),
|
|
69
|
+
phone: z
|
|
70
|
+
.string()
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("Client's phone number. Include country code if applicable (e.g. '+14165551234')."),
|
|
73
|
+
company_name: z
|
|
74
|
+
.string()
|
|
75
|
+
.optional()
|
|
76
|
+
.describe("Company name if this is a commercial/business client. Sets isCompany=true."),
|
|
77
|
+
street1: z.string().optional().describe("Street address line 1"),
|
|
78
|
+
street2: z.string().optional().describe("Street address line 2 (unit, suite, etc.)"),
|
|
79
|
+
city: z.string().optional().describe("City"),
|
|
80
|
+
province: z.string().optional().describe("Province/state (2-letter code, e.g. 'ON', 'CA')"),
|
|
81
|
+
postal_code: z.string().optional().describe("Postal/ZIP code"),
|
|
82
|
+
}, async ({ first_name, last_name, email, phone, company_name, street1, street2, city, province, postal_code, }) => {
|
|
83
|
+
try {
|
|
84
|
+
const input = {
|
|
85
|
+
firstName: first_name,
|
|
86
|
+
lastName: last_name,
|
|
87
|
+
};
|
|
88
|
+
if (company_name) {
|
|
89
|
+
input.companyName = company_name;
|
|
90
|
+
input.isCompany = true;
|
|
91
|
+
}
|
|
92
|
+
if (phone) {
|
|
93
|
+
input.phones = [
|
|
94
|
+
{ number: phone, description: "MAIN", primary: true },
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
if (email) {
|
|
98
|
+
input.emails = [
|
|
99
|
+
{ address: email, description: "MAIN", primary: true },
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
if (street1) {
|
|
103
|
+
input.billingAddress = {
|
|
104
|
+
street1,
|
|
105
|
+
street2: street2 ?? "",
|
|
106
|
+
city: city ?? "",
|
|
107
|
+
province: province ?? "",
|
|
108
|
+
postalCode: postal_code ?? "",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const data = await jobberRequest(CREATE_CLIENT, { input });
|
|
112
|
+
const result = data.clientCreate;
|
|
113
|
+
const userErr = extractUserErrors(result);
|
|
114
|
+
if (userErr) {
|
|
115
|
+
return {
|
|
116
|
+
content: [{ type: "text", text: `Failed to create client: ${userErr}` }],
|
|
117
|
+
isError: true,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
content: [
|
|
122
|
+
{
|
|
123
|
+
type: "text",
|
|
124
|
+
text: JSON.stringify(result.client, null, 2),
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return formatErrorForMCP(error);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
// ── Update Client ────────────────────────────────────────────────
|
|
134
|
+
server.tool("jobber_update_client", "Update an existing Jobber client's information. Only provide the fields you want to change — omitted fields remain unchanged.", {
|
|
135
|
+
client_id: z.string().describe("The Jobber client ID to update"),
|
|
136
|
+
first_name: z.string().optional().describe("Updated first name"),
|
|
137
|
+
last_name: z.string().optional().describe("Updated last name"),
|
|
138
|
+
email: z.string().optional().describe("Updated email address"),
|
|
139
|
+
phone: z.string().optional().describe("Updated phone number"),
|
|
140
|
+
company_name: z.string().optional().describe("Updated company name"),
|
|
141
|
+
}, async ({ client_id, first_name, last_name, email, phone, company_name }) => {
|
|
142
|
+
try {
|
|
143
|
+
const input = {};
|
|
144
|
+
if (first_name)
|
|
145
|
+
input.firstName = first_name;
|
|
146
|
+
if (last_name)
|
|
147
|
+
input.lastName = last_name;
|
|
148
|
+
if (company_name)
|
|
149
|
+
input.companyName = company_name;
|
|
150
|
+
if (phone) {
|
|
151
|
+
input.phones = [
|
|
152
|
+
{ number: phone, description: "MAIN", primary: true },
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
if (email) {
|
|
156
|
+
input.emails = [
|
|
157
|
+
{ address: email, description: "MAIN", primary: true },
|
|
158
|
+
];
|
|
159
|
+
}
|
|
160
|
+
const data = await jobberRequest(UPDATE_CLIENT, {
|
|
161
|
+
clientId: client_id,
|
|
162
|
+
input,
|
|
163
|
+
});
|
|
164
|
+
const result = data.clientUpdate;
|
|
165
|
+
const userErr = extractUserErrors(result);
|
|
166
|
+
if (userErr) {
|
|
167
|
+
return {
|
|
168
|
+
content: [{ type: "text", text: `Failed to update client: ${userErr}` }],
|
|
169
|
+
isError: true,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
content: [
|
|
174
|
+
{
|
|
175
|
+
type: "text",
|
|
176
|
+
text: JSON.stringify(result.client, null, 2),
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
return formatErrorForMCP(error);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=clients.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clients.js","sourceRoot":"","sources":["../../src/tools/clients.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EACL,aAAa,EACb,cAAc,EACd,UAAU,EACV,aAAa,EACb,aAAa,GACd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,oEAAoE;IACpE,MAAM,CAAC,IAAI,CACT,uBAAuB,EACvB,4MAA4M,EAC5M;QACE,WAAW,EAAE,CAAC;aACX,MAAM,EAAE;aACR,QAAQ,CACP,8GAA8G,CAC/G;QACH,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,EAAE,CAAC;aACP,OAAO,CAAC,EAAE,CAAC;aACX,QAAQ,CACP,0DAA0D,CAC3D;KACJ,EACD,KAAK,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,EAAE;QAC/B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,cAAc,EAAE;gBAC/C,UAAU,EAAE,WAAW;gBACvB,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAGpB,CAAC;YACF,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAClB;4BACE,WAAW,EAAE,OAAO,CAAC,UAAU;4BAC/B,OAAO,EAAE,OAAO,CAAC,KAAK;yBACvB,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,mBAAmB,EACnB,uMAAuM,EACvM;QACE,SAAS,EAAE,CAAC;aACT,MAAM,EAAE;aACR,QAAQ,CACP,kHAAkH,CACnH;KACJ,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE;QACtB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,UAAU,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;YAChE,OAAO;gBACL,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;iBACtE;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,sBAAsB,EACtB,yJAAyJ,EACzJ;QACE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qBAAqB,CAAC;QACtD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;QACpD,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,kEAAkE,CAAC;QAC/E,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,kFAAkF,CACnF;QACH,YAAY,EAAE,CAAC;aACZ,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,4EAA4E,CAC7E;QACH,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;QAChE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2CAA2C,CAAC;QACpF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC5C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iDAAiD,CAAC;QAC3F,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iBAAiB,CAAC;KAC/D,EACD,KAAK,EAAE,EACL,UAAU,EACV,SAAS,EACT,KAAK,EACL,KAAK,EACL,YAAY,EACZ,OAAO,EACP,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,WAAW,GACZ,EAAE,EAAE;QACH,IAAI,CAAC;YACH,MAAM,KAAK,GAA4B;gBACrC,SAAS,EAAE,UAAU;gBACrB,QAAQ,EAAE,SAAS;aACpB,CAAC;YAEF,IAAI,YAAY,EAAE,CAAC;gBACjB,KAAK,CAAC,WAAW,GAAG,YAAY,CAAC;gBACjC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC;YACzB,CAAC;YACD,IAAI,KAAK,EAAE,CAAC;gBACV,KAAK,CAAC,MAAM,GAAG;oBACb,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;iBACtD,CAAC;YACJ,CAAC;YACD,IAAI,KAAK,EAAE,CAAC;gBACV,KAAK,CAAC,MAAM,GAAG;oBACb,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;iBACvD,CAAC;YACJ,CAAC;YACD,IAAI,OAAO,EAAE,CAAC;gBACZ,KAAK,CAAC,cAAc,GAAG;oBACrB,OAAO;oBACP,OAAO,EAAE,OAAO,IAAI,EAAE;oBACtB,IAAI,EAAE,IAAI,IAAI,EAAE;oBAChB,QAAQ,EAAE,QAAQ,IAAI,EAAE;oBACxB,UAAU,EAAE,WAAW,IAAI,EAAE;iBAC9B,CAAC;YACJ,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YAC3D,MAAM,MAAM,GAAG,IAAI,CAAC,YAAuC,CAAC;YAC5D,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,4BAA4B,OAAO,EAAE,EAAE,CAAC;oBACjF,OAAO,EAAE,IAAI;iBACd,CAAC;YACJ,CAAC;YAED,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;qBAC7C;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,sBAAsB,EACtB,+HAA+H,EAC/H;QACE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gCAAgC,CAAC;QAChE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;QAChE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QAC9D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;QAC9D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QAC7D,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;KACrE,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,EAAE;QACzE,IAAI,CAAC;YACH,MAAM,KAAK,GAA4B,EAAE,CAAC;YAC1C,IAAI,UAAU;gBAAE,KAAK,CAAC,SAAS,GAAG,UAAU,CAAC;YAC7C,IAAI,SAAS;gBAAE,KAAK,CAAC,QAAQ,GAAG,SAAS,CAAC;YAC1C,IAAI,YAAY;gBAAE,KAAK,CAAC,WAAW,GAAG,YAAY,CAAC;YACnD,IAAI,KAAK,EAAE,CAAC;gBACV,KAAK,CAAC,MAAM,GAAG;oBACb,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;iBACtD,CAAC;YACJ,CAAC;YACD,IAAI,KAAK,EAAE,CAAC;gBACV,KAAK,CAAC,MAAM,GAAG;oBACb,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;iBACvD,CAAC;YACJ,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,aAAa,EAAE;gBAC9C,QAAQ,EAAE,SAAS;gBACnB,KAAK;aACN,CAAC,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,YAAuC,CAAC;YAC5D,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,4BAA4B,OAAO,EAAE,EAAE,CAAC;oBACjF,OAAO,EAAE,IAAI;iBACd,CAAC;YACJ,CAAC;YAED,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;qBAC7C;iBACF;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 @@
|
|
|
1
|
+
{"version":3,"file":"invoices.d.ts","sourceRoot":"","sources":["../../src/tools/invoices.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQpE,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA0E5D"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tools for Jobber invoice operations.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { jobberRequest, LIST_INVOICES, GET_INVOICE, } from "../graphql/queries.js";
|
|
6
|
+
import { formatErrorForMCP } from "../utils/errors.js";
|
|
7
|
+
export function registerInvoiceTools(server) {
|
|
8
|
+
// ── List Invoices ────────────────────────────────────────────────
|
|
9
|
+
server.tool("jobber_list_invoices", "List invoices in Jobber. Returns invoices with status (draft, sent, paid, overdue, etc.), amounts, dates, and client info. Supports pagination.", {
|
|
10
|
+
limit: z
|
|
11
|
+
.number()
|
|
12
|
+
.min(1)
|
|
13
|
+
.max(50)
|
|
14
|
+
.default(20)
|
|
15
|
+
.describe("Maximum number of invoices to return. Default 20, max 50."),
|
|
16
|
+
after: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Pagination cursor for the next page."),
|
|
20
|
+
}, async ({ limit, after }) => {
|
|
21
|
+
try {
|
|
22
|
+
const variables = { first: limit };
|
|
23
|
+
if (after)
|
|
24
|
+
variables.after = after;
|
|
25
|
+
const data = await jobberRequest(LIST_INVOICES, variables);
|
|
26
|
+
const invoices = data.invoices;
|
|
27
|
+
return {
|
|
28
|
+
content: [
|
|
29
|
+
{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: JSON.stringify({
|
|
32
|
+
total_count: invoices.totalCount,
|
|
33
|
+
invoices: invoices.nodes,
|
|
34
|
+
page_info: invoices.pageInfo,
|
|
35
|
+
}, null, 2),
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
return formatErrorForMCP(error);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
// ── Get Invoice ──────────────────────────────────────────────────
|
|
45
|
+
server.tool("jobber_get_invoice", "Get full details for a specific invoice by ID. Returns line items, amounts, payment status, due date, and client contact info.", {
|
|
46
|
+
invoice_id: z.string().describe("The Jobber invoice ID"),
|
|
47
|
+
}, async ({ invoice_id }) => {
|
|
48
|
+
try {
|
|
49
|
+
const data = await jobberRequest(GET_INVOICE, { id: invoice_id });
|
|
50
|
+
return {
|
|
51
|
+
content: [
|
|
52
|
+
{
|
|
53
|
+
type: "text",
|
|
54
|
+
text: JSON.stringify(data.invoice, null, 2),
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return formatErrorForMCP(error);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=invoices.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoices.js","sourceRoot":"","sources":["../../src/tools/invoices.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EACL,aAAa,EACb,aAAa,EACb,WAAW,GACZ,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,UAAU,oBAAoB,CAAC,MAAiB;IACpD,oEAAoE;IACpE,MAAM,CAAC,IAAI,CACT,sBAAsB,EACtB,iJAAiJ,EACjJ;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,CAAC,sCAAsC,CAAC;KACpD,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,gIAAgI,EAChI;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;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;qBAC5C;iBACF;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 @@
|
|
|
1
|
+
{"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../../src/tools/jobs.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAUpE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAoPxD"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tools for Jobber job operations.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { jobberRequest, LIST_JOBS, GET_JOB, CREATE_JOB, CREATE_JOB_NOTE, } from "../graphql/queries.js";
|
|
6
|
+
import { extractUserErrors, formatErrorForMCP } from "../utils/errors.js";
|
|
7
|
+
export function registerJobTools(server) {
|
|
8
|
+
// ── List Jobs ────────────────────────────────────────────────────
|
|
9
|
+
server.tool("jobber_list_jobs", "List jobs in Jobber with optional filters. Returns jobs with status, client, schedule, and total. Use this to find active work, check job history, or look up jobs for a specific client.", {
|
|
10
|
+
status: z
|
|
11
|
+
.array(z.enum([
|
|
12
|
+
"ACTIVE",
|
|
13
|
+
"IN_PROGRESS",
|
|
14
|
+
"COMPLETED",
|
|
15
|
+
"ARCHIVED",
|
|
16
|
+
"TODAY",
|
|
17
|
+
"UPCOMING",
|
|
18
|
+
"OVERDUE",
|
|
19
|
+
"UNSCHEDULED",
|
|
20
|
+
"LATE",
|
|
21
|
+
"ON_HOLD",
|
|
22
|
+
"ACTION_REQUIRED",
|
|
23
|
+
]))
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Filter by job status. Common values: ACTIVE, IN_PROGRESS, COMPLETED, ARCHIVED. Can pass multiple. Omit for all statuses."),
|
|
26
|
+
start_date: z
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Filter jobs starting on or after this date. ISO 8601 format (YYYY-MM-DD)."),
|
|
30
|
+
end_date: z
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Filter jobs starting on or before this date. ISO 8601 format (YYYY-MM-DD)."),
|
|
34
|
+
limit: z
|
|
35
|
+
.number()
|
|
36
|
+
.min(1)
|
|
37
|
+
.max(50)
|
|
38
|
+
.default(20)
|
|
39
|
+
.describe("Maximum number of jobs to return. Default 20, max 50."),
|
|
40
|
+
after: z
|
|
41
|
+
.string()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Pagination cursor for the next page."),
|
|
44
|
+
}, async ({ status, start_date, end_date, limit, after }) => {
|
|
45
|
+
try {
|
|
46
|
+
const filter = {};
|
|
47
|
+
if (status && status.length > 0) {
|
|
48
|
+
filter.status = status;
|
|
49
|
+
}
|
|
50
|
+
if (start_date || end_date) {
|
|
51
|
+
filter.startAt = {
|
|
52
|
+
between: {
|
|
53
|
+
start: start_date ?? "2000-01-01",
|
|
54
|
+
end: end_date ?? "2100-01-01",
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const variables = {
|
|
59
|
+
first: limit,
|
|
60
|
+
filter: Object.keys(filter).length > 0 ? filter : undefined,
|
|
61
|
+
};
|
|
62
|
+
if (after)
|
|
63
|
+
variables.after = after;
|
|
64
|
+
const data = await jobberRequest(LIST_JOBS, variables);
|
|
65
|
+
const jobs = data.jobs;
|
|
66
|
+
return {
|
|
67
|
+
content: [
|
|
68
|
+
{
|
|
69
|
+
type: "text",
|
|
70
|
+
text: JSON.stringify({
|
|
71
|
+
total_count: jobs.totalCount,
|
|
72
|
+
jobs: jobs.nodes,
|
|
73
|
+
page_info: jobs.pageInfo,
|
|
74
|
+
}, null, 2),
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
return formatErrorForMCP(error);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
// ── Get Job ──────────────────────────────────────────────────────
|
|
84
|
+
server.tool("jobber_get_job", "Get full details for a specific job by ID. Returns comprehensive info including line items, scheduled visits, assigned team members, client contact info, and property address.", {
|
|
85
|
+
job_id: z.string().describe("The Jobber job ID"),
|
|
86
|
+
}, async ({ job_id }) => {
|
|
87
|
+
try {
|
|
88
|
+
const data = await jobberRequest(GET_JOB, { id: job_id });
|
|
89
|
+
return {
|
|
90
|
+
content: [
|
|
91
|
+
{ type: "text", text: JSON.stringify(data.job, null, 2) },
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
return formatErrorForMCP(error);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
// ── Create Job ───────────────────────────────────────────────────
|
|
100
|
+
server.tool("jobber_create_job", "Create a new job in Jobber. A job represents actual work to be done for a client. Provide a client ID and job details. Line items define the billable work.", {
|
|
101
|
+
client_id: z.string().describe("The Jobber client ID this job is for"),
|
|
102
|
+
title: z
|
|
103
|
+
.string()
|
|
104
|
+
.describe("Job title (e.g. 'Kitchen faucet replacement', 'Drain cleaning')"),
|
|
105
|
+
description: z
|
|
106
|
+
.string()
|
|
107
|
+
.optional()
|
|
108
|
+
.describe("Detailed job description or instructions for the technician"),
|
|
109
|
+
line_items: z
|
|
110
|
+
.array(z.object({
|
|
111
|
+
name: z.string().describe("Line item name (e.g. 'Labour', 'Parts - Kitchen Faucet')"),
|
|
112
|
+
description: z.string().optional().describe("Line item description"),
|
|
113
|
+
quantity: z.number().default(1).describe("Quantity. Default 1."),
|
|
114
|
+
unit_price: z
|
|
115
|
+
.number()
|
|
116
|
+
.describe("Unit price in dollars (e.g. 150.00)"),
|
|
117
|
+
}))
|
|
118
|
+
.optional()
|
|
119
|
+
.describe("Line items for the job. Each has a name, optional description, quantity, and unit price."),
|
|
120
|
+
property_id: z
|
|
121
|
+
.string()
|
|
122
|
+
.optional()
|
|
123
|
+
.describe("Property ID for the service location. Get from client details."),
|
|
124
|
+
}, async ({ client_id, title, description, line_items, property_id }) => {
|
|
125
|
+
try {
|
|
126
|
+
const input = {
|
|
127
|
+
clientId: client_id,
|
|
128
|
+
title,
|
|
129
|
+
};
|
|
130
|
+
if (property_id)
|
|
131
|
+
input.propertyId = property_id;
|
|
132
|
+
if (line_items && line_items.length > 0) {
|
|
133
|
+
input.lineItems = line_items.map((li) => ({
|
|
134
|
+
name: li.name,
|
|
135
|
+
description: li.description ?? "",
|
|
136
|
+
qty: li.quantity,
|
|
137
|
+
unitPrice: li.unit_price,
|
|
138
|
+
saveToProductsAndServices: false,
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
const data = await jobberRequest(CREATE_JOB, { input });
|
|
142
|
+
const result = data.jobCreate;
|
|
143
|
+
const userErr = extractUserErrors(result);
|
|
144
|
+
if (userErr) {
|
|
145
|
+
return {
|
|
146
|
+
content: [{ type: "text", text: `Failed to create job: ${userErr}` }],
|
|
147
|
+
isError: true,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const job = result.job;
|
|
151
|
+
// Add description as a note if provided
|
|
152
|
+
if (description && job.id) {
|
|
153
|
+
try {
|
|
154
|
+
await jobberRequest(CREATE_JOB_NOTE, {
|
|
155
|
+
jobId: job.id,
|
|
156
|
+
message: description,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
console.error("[jobber-mcp] Failed to add note to job");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
content: [{ type: "text", text: JSON.stringify(job, null, 2) }],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
return formatErrorForMCP(error);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
// ── Update Job Status ────────────────────────────────────────────
|
|
172
|
+
server.tool("jobber_update_job_status", "Add a note to a job in Jobber. Use this to log status updates, technician notes, or diagnostic findings. Note: Jobber's GraphQL API doesn't support direct status transitions — use the Jobber web UI for that.", {
|
|
173
|
+
job_id: z.string().describe("The Jobber job ID"),
|
|
174
|
+
note: z
|
|
175
|
+
.string()
|
|
176
|
+
.describe("Note to add to the job (e.g. 'Customer confirmed appointment', 'Parts ordered — ETA 2 days')"),
|
|
177
|
+
}, async ({ job_id, note }) => {
|
|
178
|
+
try {
|
|
179
|
+
const data = await jobberRequest(CREATE_JOB_NOTE, {
|
|
180
|
+
jobId: job_id,
|
|
181
|
+
message: note,
|
|
182
|
+
});
|
|
183
|
+
const result = data.jobNoteCreate;
|
|
184
|
+
const userErr = extractUserErrors(result);
|
|
185
|
+
if (userErr) {
|
|
186
|
+
return {
|
|
187
|
+
content: [{ type: "text", text: `Failed to add note: ${userErr}` }],
|
|
188
|
+
isError: true,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
content: [
|
|
193
|
+
{
|
|
194
|
+
type: "text",
|
|
195
|
+
text: `Note added to job ${job_id} successfully.`,
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
return formatErrorForMCP(error);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=jobs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jobs.js","sourceRoot":"","sources":["../../src/tools/jobs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quotes.d.ts","sourceRoot":"","sources":["../../src/tools/quotes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQpE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA+H1D"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tools for Jobber quote operations.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { jobberRequest, CREATE_QUOTE, LIST_QUOTES, } from "../graphql/queries.js";
|
|
6
|
+
import { extractUserErrors, formatErrorForMCP } from "../utils/errors.js";
|
|
7
|
+
export function registerQuoteTools(server) {
|
|
8
|
+
// ── Create Quote ─────────────────────────────────────────────────
|
|
9
|
+
server.tool("jobber_create_quote", "Create a new quote (estimate) for a Jobber client. The quote is created as a draft — it won't be sent to the client automatically. Include line items with pricing to build the quote total.", {
|
|
10
|
+
client_id: z.string().describe("The Jobber client ID to create the quote for"),
|
|
11
|
+
title: z
|
|
12
|
+
.string()
|
|
13
|
+
.describe("Quote title (e.g. 'Kitchen faucet replacement estimate')"),
|
|
14
|
+
message: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Message to the client included with the quote. Supports plain text."),
|
|
18
|
+
line_items: z
|
|
19
|
+
.array(z.object({
|
|
20
|
+
name: z
|
|
21
|
+
.string()
|
|
22
|
+
.describe("Line item name (e.g. 'Labour - Faucet Installation')"),
|
|
23
|
+
description: z.string().optional().describe("Line item description"),
|
|
24
|
+
quantity: z.number().default(1).describe("Quantity. Default 1."),
|
|
25
|
+
unit_price: z
|
|
26
|
+
.number()
|
|
27
|
+
.describe("Unit price in dollars (e.g. 250.00)"),
|
|
28
|
+
}))
|
|
29
|
+
.min(1)
|
|
30
|
+
.describe("At least one line item is required for a quote."),
|
|
31
|
+
property_id: z
|
|
32
|
+
.string()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Property ID for the service location"),
|
|
35
|
+
}, async ({ client_id, title, message, line_items, property_id }) => {
|
|
36
|
+
try {
|
|
37
|
+
const attributes = {
|
|
38
|
+
clientId: client_id,
|
|
39
|
+
title,
|
|
40
|
+
lineItems: line_items.map((li) => ({
|
|
41
|
+
name: li.name,
|
|
42
|
+
description: li.description ?? "",
|
|
43
|
+
qty: li.quantity,
|
|
44
|
+
unitPrice: li.unit_price,
|
|
45
|
+
saveToProductsAndServices: false,
|
|
46
|
+
})),
|
|
47
|
+
};
|
|
48
|
+
if (message)
|
|
49
|
+
attributes.message = message;
|
|
50
|
+
if (property_id)
|
|
51
|
+
attributes.propertyId = property_id;
|
|
52
|
+
const data = await jobberRequest(CREATE_QUOTE, { attributes });
|
|
53
|
+
const result = data.quoteCreate;
|
|
54
|
+
const userErr = extractUserErrors(result);
|
|
55
|
+
if (userErr) {
|
|
56
|
+
return {
|
|
57
|
+
content: [
|
|
58
|
+
{ type: "text", text: `Failed to create quote: ${userErr}` },
|
|
59
|
+
],
|
|
60
|
+
isError: true,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: "text",
|
|
67
|
+
text: JSON.stringify(result.quote, null, 2),
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
return formatErrorForMCP(error);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
// ── List Quotes ──────────────────────────────────────────────────
|
|
77
|
+
server.tool("jobber_list_quotes", "List quotes in Jobber. Returns quotes with their status, total, client, and Jobber web URL. Supports pagination.", {
|
|
78
|
+
limit: z
|
|
79
|
+
.number()
|
|
80
|
+
.min(1)
|
|
81
|
+
.max(50)
|
|
82
|
+
.default(20)
|
|
83
|
+
.describe("Maximum number of quotes to return. Default 20, max 50."),
|
|
84
|
+
after: z
|
|
85
|
+
.string()
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("Pagination cursor for the next page."),
|
|
88
|
+
}, async ({ limit, after }) => {
|
|
89
|
+
try {
|
|
90
|
+
const variables = { first: limit };
|
|
91
|
+
if (after)
|
|
92
|
+
variables.after = after;
|
|
93
|
+
const data = await jobberRequest(LIST_QUOTES, variables);
|
|
94
|
+
const quotes = data.quotes;
|
|
95
|
+
return {
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
98
|
+
type: "text",
|
|
99
|
+
text: JSON.stringify({
|
|
100
|
+
total_count: quotes.totalCount,
|
|
101
|
+
quotes: quotes.nodes,
|
|
102
|
+
page_info: quotes.pageInfo,
|
|
103
|
+
}, null, 2),
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
return formatErrorForMCP(error);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=quotes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quotes.js","sourceRoot":"","sources":["../../src/tools/quotes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requests.d.ts","sourceRoot":"","sources":["../../src/tools/requests.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAUpE,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAgJ5D"}
|