Skip to content

Commit 5b1f49a

Browse files
committed
feat: add mcp dev summit tool
1 parent bddf067 commit 5b1f49a

File tree

4 files changed

+200
-16
lines changed

4 files changed

+200
-16
lines changed

src/const.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export enum HelperTools {
4444
DOCS_SEARCH = 'search-apify-docs',
4545
DOCS_FETCH = 'fetch-apify-docs',
4646
GET_HTML_SKELETON = 'get-html-skeleton',
47+
GET_MCP_DEV_SUMMIT_SCHEDULE = 'get-mcp-dev-summit-schedule',
4748
}
4849

4950
export const RAG_WEB_BROWSER = 'apify/rag-web-browser';
@@ -72,8 +73,10 @@ export const APIFY_DOCS_CACHE_MAX_SIZE = 500;
7273
export const APIFY_DOCS_CACHE_TTL_SECS = 60 * 60; // 1 hour
7374
export const GET_HTML_SKELETON_CACHE_TTL_SECS = 5 * 60; // 5 minutes
7475
export const GET_HTML_SKELETON_CACHE_MAX_SIZE = 200;
75-
export const MCP_SERVER_CACHE_MAX_SIZE = 500;
76-
export const MCP_SERVER_CACHE_TTL_SECS = 30 * 60; // 30 minutes
76+
export const MCP_SERVER_CACHE_MAX_SIZE = 500;
77+
export const MCP_SERVER_CACHE_TTL_SECS = 30 * 60; // 30 minutes
78+
export const MCP_DEV_SUMMIT_SCHEDULE_CACHE_MAX_SIZE = 100;
79+
export const MCP_DEV_SUMMIT_SCHEDULE_CACHE_TTL_SECS = 30 * 60; // 30 minutes
7780

7881
export const ACTOR_PRICING_MODEL = {
7982
/** Rental Actors */

src/state.ts

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import {
2-
ACTOR_CACHE_MAX_SIZE,
3-
ACTOR_CACHE_TTL_SECS,
4-
APIFY_DOCS_CACHE_MAX_SIZE,
5-
APIFY_DOCS_CACHE_TTL_SECS,
6-
GET_HTML_SKELETON_CACHE_MAX_SIZE,
7-
GET_HTML_SKELETON_CACHE_TTL_SECS,
8-
MCP_SERVER_CACHE_MAX_SIZE,
9-
MCP_SERVER_CACHE_TTL_SECS,
10-
} from './const.js';
1+
import {
2+
ACTOR_CACHE_MAX_SIZE,
3+
ACTOR_CACHE_TTL_SECS,
4+
APIFY_DOCS_CACHE_MAX_SIZE,
5+
APIFY_DOCS_CACHE_TTL_SECS,
6+
GET_HTML_SKELETON_CACHE_MAX_SIZE,
7+
GET_HTML_SKELETON_CACHE_TTL_SECS,
8+
MCP_SERVER_CACHE_MAX_SIZE,
9+
MCP_SERVER_CACHE_TTL_SECS,
10+
MCP_DEV_SUMMIT_SCHEDULE_CACHE_MAX_SIZE,
11+
MCP_DEV_SUMMIT_SCHEDULE_CACHE_TTL_SECS,
12+
} from './const.js';
1113
import type { ActorDefinitionPruned, ApifyDocsSearchResult } from './types.js';
1214
import { TTLLRUCache } from './utils/ttl-lru.js';
1315

@@ -17,9 +19,14 @@ export const searchApifyDocsCache = new TTLLRUCache<ApifyDocsSearchResult[]>(API
1719
export const fetchApifyDocsCache = new TTLLRUCache<string>(APIFY_DOCS_CACHE_MAX_SIZE, APIFY_DOCS_CACHE_TTL_SECS);
1820
/** Stores HTML content per URL so we can paginate the tool output */
1921
export const getHtmlSkeletonCache = new TTLLRUCache<string>(GET_HTML_SKELETON_CACHE_MAX_SIZE, GET_HTML_SKELETON_CACHE_TTL_SECS);
22+
/**
23+
* Stores MCP server resolution per actor:
24+
* - false: not an MCP server
25+
* - string: MCP server URL
26+
*/
27+
export const mcpServerCache = new TTLLRUCache<boolean | string>(MCP_SERVER_CACHE_MAX_SIZE, MCP_SERVER_CACHE_TTL_SECS);
28+
2029
/**
21-
* Stores MCP server resolution per actor:
22-
* - false: not an MCP server
23-
* - string: MCP server URL
30+
* Stores MCP Dev Summit schedule data
2431
*/
25-
export const mcpServerCache = new TTLLRUCache<boolean | string>(MCP_SERVER_CACHE_MAX_SIZE, MCP_SERVER_CACHE_TTL_SECS);
32+
export const mcpDevSummitScheduleCache = new TTLLRUCache<string[]>(MCP_DEV_SUMMIT_SCHEDULE_CACHE_MAX_SIZE, MCP_DEV_SUMMIT_SCHEDULE_CACHE_TTL_SECS);

src/tools/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { getHtmlSkeleton } from './get-html-skeleton.js';
1111
import { addTool } from './helpers.js';
1212
import { getKeyValueStore, getKeyValueStoreKeys, getKeyValueStoreRecord } from './key_value_store.js';
1313
import { getUserKeyValueStoresList } from './key_value_store_collection.js';
14+
import { getMcpDevSummitSchedule } from './mcp-dev-summit-schedule.js';
1415
import { getActorRun, getActorRunLog } from './run.js';
1516
import { getUserRunsList } from './run_collection.js';
1617
import { searchApifyDocsTool } from './search-apify-docs.js';
@@ -48,10 +49,14 @@ export const toolCategories = {
4849
dev: [
4950
getHtmlSkeleton,
5051
],
52+
mcpDevSummit: [
53+
getMcpDevSummitSchedule,
54+
]
5155
};
5256
export const toolCategoriesEnabledByDefault: ToolCategory[] = [
5357
'actors',
5458
'docs',
59+
'mcpDevSummit'
5560
];
5661

5762
export const defaultTools = getExpectedToolsByCategories(toolCategoriesEnabledByDefault);
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import { z } from 'zod';
2+
import zodToJsonSchema from 'zod-to-json-schema';
3+
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';
10+
11+
// Local backup variable to store the latest data in case cache expires
12+
let latestScheduleData: string[] | null = null;
13+
14+
// Helper function to fetch schedule data from Apify Actor
15+
async function fetchScheduleData(): Promise<string[]> {
16+
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
17+
18+
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": [
30+
{
31+
"glob": "https://mcpdevsummiteurope2025.sched.com/event/**"
32+
}
33+
],
34+
"keepUrlFragments": false,
35+
"proxyConfiguration": {
36+
"useApifyProxy": true
37+
},
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": [
49+
{
50+
"url": "https://mcpdevsummiteurope2025.sched.com/list/simple",
51+
"method": "GET"
52+
}
53+
],
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
72+
};
73+
74+
const run = await client.actor('apify/website-content-crawler').call(input);
75+
const { items } = await client.dataset(run.defaultDatasetId).listItems();
76+
77+
// The crawled markdown already contains all the event details
78+
const data = items.map((item: any) => item.text || '');
79+
80+
// Update the local backup variable
81+
latestScheduleData = data;
82+
83+
return data;
84+
}
85+
86+
// Helper function to schedule background refresh
87+
function scheduleBackgroundRefresh(): void {
88+
// Use setTimeout to schedule refresh after response is sent
89+
setTimeout(async () => {
90+
try {
91+
// Remove expired entry
92+
(mcpDevSummitScheduleCache as any).cache.remove('mcp-dev-summit-schedule');
93+
const freshData = await fetchScheduleData();
94+
mcpDevSummitScheduleCache.set('mcp-dev-summit-schedule', freshData);
95+
// Update local backup as well
96+
latestScheduleData = freshData;
97+
} catch (error) {
98+
console.error('Background refresh of MCP Dev Summit schedule failed:', error);
99+
}
100+
}, 0);
101+
}
102+
103+
// Custom cache check that serves expired data and refreshes in background
104+
function getCachedOrFetch(): { data: string[] | null, isExpired: boolean } {
105+
const cacheKey = 'mcp-dev-summit-schedule';
106+
const entry = (mcpDevSummitScheduleCache as any).cache.get(cacheKey);
107+
108+
if (!entry) {
109+
return { data: null, isExpired: false };
110+
}
111+
112+
const isExpired = entry.expiresAt <= Date.now();
113+
114+
if (isExpired) {
115+
// Return expired data
116+
return { data: entry.value, isExpired: true };
117+
}
118+
119+
return { data: entry.value, isExpired: false };
120+
}
121+
122+
123+
124+
export const getMcpDevSummitSchedule: ToolEntry = {
125+
type: 'internal',
126+
tool: {
127+
name: HelperTools.GET_MCP_DEV_SUMMIT_SCHEDULE,
128+
actorFullName: HelperTools.GET_MCP_DEV_SUMMIT_SCHEDULE,
129+
description: `Retrieve the schedule for the MCP Dev Summit Europe 2025.
130+
Fetches and parses the schedule from https://mcpdevsummiteurope2025.sched.com/list/simple to provide
131+
structured information about sessions, speakers, and timing.
132+
133+
USAGE:
134+
- Use when you need information about MCP Dev Summit sessions, schedule, or speakers.
135+
136+
USAGE EXAMPLES:
137+
- user_input: What sessions are scheduled for the MCP Dev Summit?
138+
- user_input: Who are the speakers at the MCP Dev Summit?`,
139+
inputSchema: zodToJsonSchema(z.object({})),
140+
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+
},
168+
} as InternalTool,
169+
};

0 commit comments

Comments
 (0)