Skip to content

Commit b20dca0

Browse files
committed
fetch from API
1 parent 0e9ee64 commit b20dca0

6 files changed

Lines changed: 394 additions & 122 deletions

File tree

bun.lock

Lines changed: 152 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"@tigerdata/mcp-boilerplate": "^0.8.0",
3131
"ai": "^5.0.94",
3232
"dotenv": "^17.2.3",
33+
"jsforce": "^3.10.14",
3334
"pg": "^8.16.3",
3435
"zod": "^3.25.76"
3536
},

src/apis/getCaseDetails.ts

Lines changed: 25 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1-
import { ApiFactory, InferSchema } from '@tigerdata/mcp-boilerplate';
1+
import { ApiFactory, InferSchema, log } from '@tigerdata/mcp-boilerplate';
22
import { z } from 'zod';
3-
import { ServerContext } from '../types.js';
3+
import {
4+
CaseDetails,
5+
caseDetailsFields,
6+
CaseDetailsWithUrl,
7+
CaseRow,
8+
ServerContext,
9+
zCaseDetailsWithUrl,
10+
} from '../types.js';
11+
import { getCaseDetails } from '../utils/salesforce.js';
412

513
// Pattern used to separate original message in Salesforce emails
614
const originalMessagePattern = /[-_]{10,}\s*Original Message\s*[-_]{10,}/i;
@@ -31,76 +39,6 @@ const inputSchema = {
3139
),
3240
} as const;
3341

34-
// Define the case fields schema
35-
const zCaseDetails = z.object({
36-
id: z.string().describe('The unique case identifier'),
37-
case_number: z.string().nullish().describe('The case number'),
38-
subject: z.string().nullish().describe('The case subject'),
39-
status: z.string().nullish().describe('The case status'),
40-
is_closed: z.boolean().nullish().describe('Whether the case is closed'),
41-
description: z.string().nullish().describe('The original email message'),
42-
supplied_email: z
43-
.string()
44-
.nullish()
45-
.describe('The email address of the person who submitted the case'),
46-
owner_id: z.string().nullish().describe('The ID of the case owner'),
47-
account_id: z
48-
.string()
49-
.nullish()
50-
.describe('The account ID associated with the case'),
51-
created_date: z.string().nullish().describe('When the case was created'),
52-
closed_date: z.string().nullish().describe('When the case was closed'),
53-
54-
// Cloud-related fields
55-
cloud_region_c: z.string().nullish().describe('Cloud region'),
56-
cloud_project_id_c: z.string().nullish().describe('Cloud project ID'),
57-
cloud_service_id_c: z.string().nullish().describe('Cloud service ID'),
58-
cloud_impact_c: z
59-
.string()
60-
.nullish()
61-
.describe(
62-
'Cloud impact level (Production Down / Production Impaired / Just a Question)',
63-
),
64-
cloud_is_production_c: z
65-
.boolean()
66-
.nullish()
67-
.describe('Whether this is a production issue'),
68-
69-
// Case details
70-
product_area_c: z.string().nullish().describe('Product area'),
71-
platform_name_c: z
72-
.string()
73-
.nullish()
74-
.describe('Platform name (Cloud / MST / PopSQL)'),
75-
impact_c: z.string().nullish().describe('Impact level'),
76-
supergeo_c: z.string().nullish().describe('Geographic identifier'),
77-
problem_description_c: z.string().nullish().describe('Problem description'),
78-
troubleshooting_steps_taken_c: z
79-
.string()
80-
.nullish()
81-
.describe('Troubleshooting steps taken'),
82-
final_resolution_c: z.string().nullish().describe('Final resolution'),
83-
84-
// Additional metadata
85-
csm_case_c: z.boolean().nullish().describe('Whether this is a CSM case'),
86-
dev_help_links_c: z.string().nullish().describe('Developer help links'),
87-
parent_case_c: z.string().nullish().describe('Parent case ID'),
88-
priority: z.string().nullish().describe('Case priority'),
89-
severity_c: z.string().nullish().describe('Case severity'),
90-
internal_status_c: z.string().nullish().describe('Internal status'),
91-
92-
// CSAT details
93-
csat_response_c: z.string().nullish().describe('CSAT response'),
94-
csatdetail_c: z.string().nullish().describe('CSAT details'),
95-
});
96-
type CaseDetails = z.infer<typeof zCaseDetails>;
97-
const caseDetailsFields = zCaseDetails.keyof().options;
98-
99-
const zCaseDetailsWithUrl = zCaseDetails.extend({
100-
url: z.string().optional().describe('The URL to view the case in Salesforce'),
101-
});
102-
type CaseDetailsWithUrl = z.infer<typeof zCaseDetailsWithUrl>;
103-
10442
// Define the email schema
10543
const zEmail = z.object({
10644
from_address: z.string().nullish().describe('The sender email address'),
@@ -118,47 +56,6 @@ const outputSchema = {
11856
.describe('Array of email messages in chronological order'),
11957
} as const;
12058

121-
type CaseRow = {
122-
// Case fields
123-
id: string;
124-
case_number: string | null;
125-
subject: string | null;
126-
status: string | null;
127-
is_closed: boolean | null;
128-
description: string | null;
129-
supplied_email: string | null;
130-
owner_id: string | null;
131-
account_id: string | null;
132-
created_date: Date | null;
133-
closed_date: Date | null;
134-
cloud_region_c: string | null;
135-
cloud_project_id_c: string | null;
136-
cloud_service_id_c: string | null;
137-
cloud_impact_c: string | null;
138-
cloud_is_production_c: boolean | null;
139-
product_area_c: string | null;
140-
platform_name_c: string | null;
141-
impact_c: string | null;
142-
supergeo_c: string | null;
143-
problem_description_c: string | null;
144-
troubleshooting_steps_taken_c: string | null;
145-
final_resolution_c: string | null;
146-
csm_case_c: boolean | null;
147-
dev_help_links_c: string | null;
148-
parent_case_c: string | null;
149-
priority: string | null;
150-
severity_c: string | null;
151-
internal_status_c: string | null;
152-
csat_response_c: string | null;
153-
csatdetail_c: string | null;
154-
155-
// Email fields (will be null if no emails)
156-
email_id: string | null;
157-
email_from_address: string | null;
158-
email_created_date: Date | null;
159-
email_text_body: string | null;
160-
};
161-
16259
export const getCaseDetailsFactory: ApiFactory<
16360
ServerContext,
16461
typeof inputSchema,
@@ -195,14 +92,25 @@ ORDER BY e.created_date ASC NULLS FIRST
19592
[case_id_or_number],
19693
);
19794

95+
let row: CaseRow | null = null;
19896
if (result.rows.length === 0) {
199-
throw new Error(
200-
`No case found with identifier: ${case_id_or_number}. Please verify the case ID/number and try again.`,
201-
);
97+
log.warn('Case not found in db, using Salesforce API', {
98+
caseIdOrNumber: case_id_or_number,
99+
});
100+
101+
row = await getCaseDetails(case_id_or_number);
102+
103+
if (!row) {
104+
throw new Error(
105+
`No case found with identifier: ${case_id_or_number}. Please verify the case ID/number and try again.`,
106+
);
107+
}
108+
} else {
109+
row = result.rows[0];
202110
}
203111

204112
// Extract case data from the first row (all rows have the same case data)
205-
const [row] = result.rows;
113+
206114
const caseData: CaseDetailsWithUrl = caseDetailsFields.reduce(
207115
(acc, key) => {
208116
const value = row[key as keyof CaseRow];

src/apis/getCaseSummary.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const getCaseSummaryFactory: ApiFactory<
2727
config: {
2828
title: 'Get Salesforce Case Summary',
2929
description:
30-
'This retrieves the summary for a specific Salesforce support case. Be sure to create a link to the case in your response, using the returned `url`.',
30+
'This retrieves the summary for a specific closed Salesforce support case. Be sure to create a link to the case in your response, using the returned `url`. Note: summaries are only created for cases that are closed. Use the get_case_details tool to retrieve information for a non-closed case.',
3131
inputSchema,
3232
outputSchema,
3333
},

src/types.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,3 +256,112 @@ export const zUserDetails = z.object({
256256
});
257257

258258
export type UserDetails = z.infer<typeof zUserDetails>;
259+
260+
export type CaseRow = {
261+
// Case fields
262+
id: string;
263+
case_number: string | null;
264+
subject: string | null;
265+
status: string | null;
266+
is_closed: boolean | null;
267+
description: string | null;
268+
supplied_email: string | null;
269+
owner_id: string | null;
270+
account_id: string | null;
271+
created_date: Date | null;
272+
closed_date: Date | null;
273+
cloud_region_c: string | null;
274+
cloud_project_id_c: string | null;
275+
cloud_service_id_c: string | null;
276+
cloud_impact_c: string | null;
277+
cloud_is_production_c: boolean | null;
278+
product_area_c: string | null;
279+
platform_name_c: string | null;
280+
impact_c: string | null;
281+
problem_description_c: string | null;
282+
troubleshooting_steps_taken_c: string | null;
283+
final_resolution_c: string | null;
284+
csm_case_c: boolean | null;
285+
dev_help_links_c: string | null;
286+
parent_case_c: string | null;
287+
priority: string | null;
288+
severity_c: string | null;
289+
internal_status_c: string | null;
290+
csat_response_c: string | null;
291+
csatdetail_c: string | null;
292+
293+
// Email fields (will be null if no emails)
294+
email_id: string | null;
295+
email_from_address: string | null;
296+
email_created_date: Date | null;
297+
email_text_body: string | null;
298+
};
299+
300+
// Define the case fields schema
301+
const zCaseDetails = z.object({
302+
id: z.string().describe('The unique case identifier'),
303+
case_number: z.string().nullish().describe('The case number'),
304+
subject: z.string().nullish().describe('The case subject'),
305+
status: z.string().nullish().describe('The case status'),
306+
is_closed: z.boolean().nullish().describe('Whether the case is closed'),
307+
description: z.string().nullish().describe('The original email message'),
308+
supplied_email: z
309+
.string()
310+
.nullish()
311+
.describe('The email address of the person who submitted the case'),
312+
owner_id: z.string().nullish().describe('The ID of the case owner'),
313+
account_id: z
314+
.string()
315+
.nullish()
316+
.describe('The account ID associated with the case'),
317+
created_date: z.string().nullish().describe('When the case was created'),
318+
closed_date: z.string().nullish().describe('When the case was closed'),
319+
320+
// Cloud-related fields
321+
cloud_region_c: z.string().nullish().describe('Cloud region'),
322+
cloud_project_id_c: z.string().nullish().describe('Cloud project ID'),
323+
cloud_service_id_c: z.string().nullish().describe('Cloud service ID'),
324+
cloud_impact_c: z
325+
.string()
326+
.nullish()
327+
.describe(
328+
'Cloud impact level (Production Down / Production Impaired / Just a Question)',
329+
),
330+
cloud_is_production_c: z
331+
.boolean()
332+
.nullish()
333+
.describe('Whether this is a production issue'),
334+
335+
// Case details
336+
product_area_c: z.string().nullish().describe('Product area'),
337+
platform_name_c: z
338+
.string()
339+
.nullish()
340+
.describe('Platform name (Cloud / MST / PopSQL)'),
341+
impact_c: z.string().nullish().describe('Impact level'),
342+
problem_description_c: z.string().nullish().describe('Problem description'),
343+
troubleshooting_steps_taken_c: z
344+
.string()
345+
.nullish()
346+
.describe('Troubleshooting steps taken'),
347+
final_resolution_c: z.string().nullish().describe('Final resolution'),
348+
349+
// Additional metadata
350+
csm_case_c: z.boolean().nullish().describe('Whether this is a CSM case'),
351+
dev_help_links_c: z.string().nullish().describe('Developer help links'),
352+
parent_case_c: z.string().nullish().describe('Parent case ID'),
353+
priority: z.string().nullish().describe('Case priority'),
354+
severity_c: z.string().nullish().describe('Case severity'),
355+
internal_status_c: z.string().nullish().describe('Internal status'),
356+
357+
// CSAT details
358+
csat_response_c: z.string().nullish().describe('CSAT response'),
359+
csatdetail_c: z.string().nullish().describe('CSAT details'),
360+
});
361+
export type CaseDetails = z.infer<typeof zCaseDetails>;
362+
export const caseDetailsFields = zCaseDetails.keyof().options;
363+
364+
export const zCaseDetailsWithUrl = zCaseDetails.extend({
365+
url: z.string().optional().describe('The URL to view the case in Salesforce'),
366+
});
367+
export type CaseDetailsWithUrl = z.infer<typeof zCaseDetailsWithUrl>;

0 commit comments

Comments
 (0)