1- import { z } from 'zod' ;
2- import zodToJsonSchema from 'zod-to-json-schema' ;
1+ import { z } from 'zod' ;
2+ import zodToJsonSchema from 'zod-to-json-schema' ;
33
4- import { ApifyClient } from '../apify-client.js' ;
5- import { HelperTools } from '../const.js' ;
6- import type { InternalTool , ToolEntry } from '../types .js' ;
7- import { ajv } from '../utils/ajv .js' ;
8- import { buildMCPResponse } from '../utils/mcp .js' ;
9- import { mcpDevSummitScheduleCache } from '../state .js' ;
4+ import { ApifyClient } from '../apify-client.js' ;
5+ import { HelperTools } from '../const.js' ;
6+ import { mcpDevSummitScheduleCache } from '../state .js' ;
7+ import type { InternalTool , ToolEntry } from '../types .js' ;
8+ import { ajv } from '../utils/ajv .js' ;
9+ import { buildMCPResponse } from '../utils/mcp .js' ;
1010
1111// Local backup variable to store the latest data in case cache expires
1212let latestScheduleData : string [ ] | null = null ;
@@ -16,66 +16,72 @@ async function fetchScheduleData(): Promise<string[]> {
1616 const client = new ApifyClient ( { token : process . env . APIFY_TOKEN } ) ;
1717
1818 const input = {
19- " aggressivePrune" : false ,
20- " blockMedia" : true ,
21- " clickElementsCssSelector" : " [aria-expanded=\ "false\"]" ,
22- " clientSideMinChangePercentage" : 15 ,
23- " crawlerType" : " cheerio" ,
24- " debugLog" : false ,
25- " debugMode" : false ,
26- " expandIframes" : true ,
27- " ignoreCanonicalUrl" : false ,
28- " ignoreHttpsErrors" : false ,
29- " includeUrlGlobs" : [
19+ aggressivePrune : false ,
20+ blockMedia : true ,
21+ clickElementsCssSelector : ' [aria-expanded="false"]' ,
22+ clientSideMinChangePercentage : 15 ,
23+ crawlerType : ' cheerio' ,
24+ debugLog : false ,
25+ debugMode : false ,
26+ expandIframes : true ,
27+ ignoreCanonicalUrl : false ,
28+ ignoreHttpsErrors : false ,
29+ includeUrlGlobs : [
3030 {
31- " glob" : " https://mcpdevsummiteurope2025.sched.com/event/**"
32- }
31+ glob : ' https://mcpdevsummiteurope2025.sched.com/event/**' ,
32+ } ,
3333 ] ,
34- " keepUrlFragments" : false ,
35- " proxyConfiguration" : {
36- " useApifyProxy" : true
34+ keepUrlFragments : false ,
35+ proxyConfiguration : {
36+ useApifyProxy : true ,
3737 } ,
38- "readableTextCharThreshold" : 100 ,
39- "removeCookieWarnings" : true ,
40- "removeElementsCssSelector" : "nav, footer, script, style, noscript, svg, img[src^='data:'],\n[role=\"alert\"],\n[role=\"banner\"],\n[role=\"dialog\"],\n[role=\"alertdialog\"],\n[role=\"region\"][aria-label*=\"skip\" i],\n[aria-modal=\"true\"]" ,
41- "renderingTypeDetectionPercentage" : 10 ,
42- "respectRobotsTxtFile" : false ,
43- "saveFiles" : false ,
44- "saveHtml" : false ,
45- "saveHtmlAsFile" : false ,
46- "saveMarkdown" : true ,
47- "saveScreenshots" : false ,
48- "startUrls" : [
38+ readableTextCharThreshold : 100 ,
39+ removeCookieWarnings : true ,
40+ removeElementsCssSelector : `nav, footer, script, style, noscript, svg, img[src^='data:'],
41+ [role="alert"],
42+ [role="banner"],
43+ [role="dialog"],
44+ [role="alertdialog"],
45+ [role="region"][aria-label*="skip" i],
46+ [aria-modal="true"]` ,
47+ renderingTypeDetectionPercentage : 10 ,
48+ respectRobotsTxtFile : false ,
49+ saveFiles : false ,
50+ saveHtml : false ,
51+ saveHtmlAsFile : false ,
52+ saveMarkdown : true ,
53+ saveScreenshots : false ,
54+ startUrls : [
4955 {
50- " url" : " https://mcpdevsummiteurope2025.sched.com/list/simple" ,
51- " method" : " GET"
52- }
56+ url : ' https://mcpdevsummiteurope2025.sched.com/list/simple' ,
57+ method : ' GET' ,
58+ } ,
5359 ] ,
54- " useSitemaps" : false ,
55- " excludeUrlGlobs" : [ ] ,
56- " maxCrawlDepth" : 20 ,
57- " maxCrawlPages" : 9999999 ,
58- " initialConcurrency" : 0 ,
59- " maxConcurrency" : 200 ,
60- " initialCookies" : [ ] ,
61- " maxSessionRotations" : 10 ,
62- " maxRequestRetries" : 3 ,
63- " requestTimeoutSecs" : 60 ,
64- " minFileDownloadSpeedKBps" : 128 ,
65- " dynamicContentWaitSecs" : 10 ,
66- " waitForSelector" : "" ,
67- " softWaitForSelector" : "" ,
68- " maxScrollHeightPixels" : 5000 ,
69- " keepElementsCssSelector" : "" ,
70- " htmlTransformer" : " readableText" ,
71- " maxResults" : 9999999
60+ useSitemaps : false ,
61+ excludeUrlGlobs : [ ] ,
62+ maxCrawlDepth : 20 ,
63+ maxCrawlPages : 9999999 ,
64+ initialConcurrency : 0 ,
65+ maxConcurrency : 200 ,
66+ initialCookies : [ ] ,
67+ maxSessionRotations : 10 ,
68+ maxRequestRetries : 3 ,
69+ requestTimeoutSecs : 60 ,
70+ minFileDownloadSpeedKBps : 128 ,
71+ dynamicContentWaitSecs : 10 ,
72+ waitForSelector : '' ,
73+ softWaitForSelector : '' ,
74+ maxScrollHeightPixels : 5000 ,
75+ keepElementsCssSelector : '' ,
76+ htmlTransformer : ' readableText' ,
77+ maxResults : 9999999 ,
7278 } ;
7379
7480 const run = await client . actor ( 'apify/website-content-crawler' ) . call ( input ) ;
7581 const { items } = await client . dataset ( run . defaultDatasetId ) . listItems ( ) ;
7682
7783 // The crawled markdown already contains all the event details
78- const data = items . map ( ( item : any ) => item . text || '' ) ;
84+ const data = items . map ( ( item : { text ?: string } ) => item . text || '' ) ;
7985
8086 // Update the local backup variable
8187 latestScheduleData = data ;
@@ -89,21 +95,23 @@ function scheduleBackgroundRefresh(): void {
8995 setTimeout ( async ( ) => {
9096 try {
9197 // Remove expired entry
92- ( mcpDevSummitScheduleCache as any ) . cache . remove ( 'mcp-dev-summit-schedule' ) ;
98+ ( mcpDevSummitScheduleCache as unknown as { cache : { remove : ( key : string ) => void } } ) . cache . remove ( 'mcp-dev-summit-schedule' ) ;
9399 const freshData = await fetchScheduleData ( ) ;
94100 mcpDevSummitScheduleCache . set ( 'mcp-dev-summit-schedule' , freshData ) ;
95101 // Update local backup as well
96102 latestScheduleData = freshData ;
97- } catch ( error ) {
98- console . error ( ' Background refresh of MCP Dev Summit schedule failed:' , error ) ;
103+ } catch {
104+ // Background refresh failed, but continue silently
99105 }
100106 } , 0 ) ;
101107}
102108
103109// Custom cache check that serves expired data and refreshes in background
104110function getCachedOrFetch ( ) : { data : string [ ] | null , isExpired : boolean } {
105111 const cacheKey = 'mcp-dev-summit-schedule' ;
106- const entry = ( mcpDevSummitScheduleCache as any ) . cache . get ( cacheKey ) ;
112+ const entry = ( mcpDevSummitScheduleCache as unknown as {
113+ cache : { get : ( key : string ) => { value : string [ ] , expiresAt : number } | undefined }
114+ } ) . cache . get ( cacheKey ) ;
107115
108116 if ( ! entry ) {
109117 return { data : null , isExpired : false } ;
@@ -119,8 +127,6 @@ function getCachedOrFetch(): { data: string[] | null, isExpired: boolean } {
119127 return { data : entry . value , isExpired : false } ;
120128}
121129
122-
123-
124130export const getMcpDevSummitSchedule : ToolEntry = {
125131 type : 'internal' ,
126132 tool : {
@@ -138,32 +144,32 @@ USAGE EXAMPLES:
138144- user_input: Who are the speakers at the MCP Dev Summit?` ,
139145 inputSchema : zodToJsonSchema ( z . object ( { } ) ) ,
140146 ajvValidate : ajv . compile ( zodToJsonSchema ( z . object ( { } ) ) ) ,
141- call : async ( ) => {
142- const { data : cachedData , isExpired } = getCachedOrFetch ( ) ;
143-
144- if ( cachedData ) {
145- // Serve cached data immediately
146- if ( isExpired ) {
147- // Schedule background refresh for expired data
148- scheduleBackgroundRefresh ( ) ;
149- }
150- return buildMCPResponse ( cachedData ) ;
151- }
152-
153- // No cached data, check local backup
154- if ( latestScheduleData ) {
155- // Serve local backup data immediately
156- scheduleBackgroundRefresh ( ) ;
157- return buildMCPResponse ( latestScheduleData ) ;
158- }
159-
160- // No cached or backup data, fetch fresh data
161- const freshData = await fetchScheduleData ( ) ;
162-
163- // Cache the fresh data
164- mcpDevSummitScheduleCache . set ( 'mcp-dev-summit-schedule' , freshData ) ;
165-
166- return buildMCPResponse ( freshData ) ;
167- } ,
147+ call : async ( ) => {
148+ const { data : cachedData , isExpired } = getCachedOrFetch ( ) ;
149+
150+ if ( cachedData ) {
151+ // Serve cached data immediately
152+ if ( isExpired ) {
153+ // Schedule background refresh for expired data
154+ scheduleBackgroundRefresh ( ) ;
155+ }
156+ return buildMCPResponse ( cachedData ) ;
157+ }
158+
159+ // No cached data, check local backup
160+ if ( latestScheduleData ) {
161+ // Serve local backup data immediately
162+ scheduleBackgroundRefresh ( ) ;
163+ return buildMCPResponse ( latestScheduleData ) ;
164+ }
165+
166+ // No cached or backup data, fetch fresh data
167+ const freshData = await fetchScheduleData ( ) ;
168+
169+ // Cache the fresh data
170+ mcpDevSummitScheduleCache . set ( 'mcp-dev-summit-schedule' , freshData ) ;
171+
172+ return buildMCPResponse ( freshData ) ;
173+ } ,
168174 } as InternalTool ,
169175} ;
0 commit comments