@access-mcp/events 0.2.0 → 0.3.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/README.md +99 -75
- package/dist/index.js +2 -1
- package/dist/server.d.ts +9 -148
- package/dist/server.js +87 -373
- package/package.json +2 -2
- package/src/__tests__/server.integration.test.ts +60 -145
- package/src/__tests__/server.test.ts +108 -466
- package/src/index.ts +2 -1
- package/src/server.ts +94 -433
package/dist/server.js
CHANGED
|
@@ -3,7 +3,7 @@ import axios from "axios";
|
|
|
3
3
|
export class EventsServer extends BaseAccessServer {
|
|
4
4
|
_eventsHttpClient;
|
|
5
5
|
constructor() {
|
|
6
|
-
super("access-mcp-events", "0.
|
|
6
|
+
super("access-mcp-events", "0.3.0", "https://support.access-ci.org");
|
|
7
7
|
}
|
|
8
8
|
get httpClient() {
|
|
9
9
|
if (!this._eventsHttpClient) {
|
|
@@ -26,172 +26,42 @@ export class EventsServer extends BaseAccessServer {
|
|
|
26
26
|
}
|
|
27
27
|
getTools() {
|
|
28
28
|
return [
|
|
29
|
-
{
|
|
30
|
-
name: "get_events",
|
|
31
|
-
description: "Get ACCESS-CI events with comprehensive filtering capabilities. Returns events in UTC timezone with enhanced metadata.",
|
|
32
|
-
inputSchema: {
|
|
33
|
-
type: "object",
|
|
34
|
-
properties: {
|
|
35
|
-
// Relative date filtering
|
|
36
|
-
beginning_date_relative: {
|
|
37
|
-
type: "string",
|
|
38
|
-
description: "Start date using relative values. Calculated in UTC by default, or use 'timezone' parameter for local calculations.",
|
|
39
|
-
enum: [
|
|
40
|
-
"today",
|
|
41
|
-
"+1week",
|
|
42
|
-
"+2week",
|
|
43
|
-
"+1month",
|
|
44
|
-
"+2month",
|
|
45
|
-
"+1year",
|
|
46
|
-
"-1week",
|
|
47
|
-
"-1month",
|
|
48
|
-
"-1year",
|
|
49
|
-
],
|
|
50
|
-
},
|
|
51
|
-
end_date_relative: {
|
|
52
|
-
type: "string",
|
|
53
|
-
description: "End date using relative values. Calculated in UTC by default, or use 'timezone' parameter for local calculations.",
|
|
54
|
-
enum: [
|
|
55
|
-
"today",
|
|
56
|
-
"+1week",
|
|
57
|
-
"+2week",
|
|
58
|
-
"+1month",
|
|
59
|
-
"+2month",
|
|
60
|
-
"+1year",
|
|
61
|
-
"-1week",
|
|
62
|
-
"-1month",
|
|
63
|
-
"-1year",
|
|
64
|
-
],
|
|
65
|
-
},
|
|
66
|
-
// Absolute date filtering
|
|
67
|
-
beginning_date: {
|
|
68
|
-
type: "string",
|
|
69
|
-
description: "Start date in YYYY-MM-DD or YYYY-MM-DD HH:MM:SS format. Always interpreted as provided (no timezone conversion).",
|
|
70
|
-
},
|
|
71
|
-
end_date: {
|
|
72
|
-
type: "string",
|
|
73
|
-
description: "End date in YYYY-MM-DD or YYYY-MM-DD HH:MM:SS format. Always interpreted as provided (no timezone conversion).",
|
|
74
|
-
},
|
|
75
|
-
// Timezone parameter for relative date calculations
|
|
76
|
-
timezone: {
|
|
77
|
-
type: "string",
|
|
78
|
-
description: "Timezone for relative date calculations (default: UTC). Common values: UTC, America/New_York (Eastern), America/Chicago (Central), America/Denver (Mountain), America/Los_Angeles (Pacific), Europe/London (British), Europe/Berlin (CET). Only affects relative dates, not absolute dates. Invalid timezones default to UTC.",
|
|
79
|
-
default: "UTC",
|
|
80
|
-
},
|
|
81
|
-
// Faceted search filters
|
|
82
|
-
event_type: {
|
|
83
|
-
type: "string",
|
|
84
|
-
description: "Filter by event type (workshop, webinar, etc.)",
|
|
85
|
-
},
|
|
86
|
-
event_affiliation: {
|
|
87
|
-
type: "string",
|
|
88
|
-
description: "Filter by organizational affiliation (Community, ACCESS, etc.)",
|
|
89
|
-
},
|
|
90
|
-
skill_level: {
|
|
91
|
-
type: "string",
|
|
92
|
-
description: "Filter by required skill level (beginner, intermediate, advanced)",
|
|
93
|
-
enum: ["beginner", "intermediate", "advanced"],
|
|
94
|
-
},
|
|
95
|
-
event_tags: {
|
|
96
|
-
type: "string",
|
|
97
|
-
description: "Filter by event tags (python, big-data, machine-learning, etc.)",
|
|
98
|
-
},
|
|
99
|
-
limit: {
|
|
100
|
-
type: "number",
|
|
101
|
-
description: "Maximum number of events to return (default: 100)",
|
|
102
|
-
minimum: 1,
|
|
103
|
-
maximum: 1000,
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
required: [],
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
name: "get_upcoming_events",
|
|
111
|
-
description: "Get upcoming ACCESS-CI events (from today onward in UTC). Convenient shortcut for get_events with beginning_date_relative=today.",
|
|
112
|
-
inputSchema: {
|
|
113
|
-
type: "object",
|
|
114
|
-
properties: {
|
|
115
|
-
limit: {
|
|
116
|
-
type: "number",
|
|
117
|
-
description: "Maximum number of events to return (default: 50)",
|
|
118
|
-
minimum: 1,
|
|
119
|
-
maximum: 100,
|
|
120
|
-
},
|
|
121
|
-
event_type: {
|
|
122
|
-
type: "string",
|
|
123
|
-
description: "Filter by event type (workshop, webinar, Office Hours, Training, etc.)",
|
|
124
|
-
},
|
|
125
|
-
timezone: {
|
|
126
|
-
type: "string",
|
|
127
|
-
description: "Timezone for 'today' calculation (default: UTC). Use user's local timezone for better relevance.",
|
|
128
|
-
default: "UTC",
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
required: [],
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
29
|
{
|
|
135
30
|
name: "search_events",
|
|
136
|
-
description: "Search
|
|
31
|
+
description: "Search ACCESS-CI events (workshops, webinars, training). Returns {total, items}.",
|
|
137
32
|
inputSchema: {
|
|
138
33
|
type: "object",
|
|
139
34
|
properties: {
|
|
140
35
|
query: {
|
|
141
36
|
type: "string",
|
|
142
|
-
description: "Search
|
|
37
|
+
description: "Search titles, descriptions, speakers, tags"
|
|
143
38
|
},
|
|
144
|
-
|
|
39
|
+
type: {
|
|
145
40
|
type: "string",
|
|
146
|
-
description: "
|
|
147
|
-
default: "today",
|
|
41
|
+
description: "Filter: workshop, webinar, training"
|
|
148
42
|
},
|
|
149
|
-
|
|
43
|
+
tags: {
|
|
150
44
|
type: "string",
|
|
151
|
-
description: "
|
|
152
|
-
default: "UTC",
|
|
45
|
+
description: "Filter: python, gpu, hpc, ml"
|
|
153
46
|
},
|
|
154
|
-
|
|
155
|
-
type: "number",
|
|
156
|
-
description: "Maximum number of events to return (default: 25)",
|
|
157
|
-
minimum: 1,
|
|
158
|
-
maximum: 100,
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
required: ["query"],
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
name: "get_events_by_tag",
|
|
166
|
-
description: "Get events filtered by specific tags. Useful for finding events on topics like 'python', 'ai', 'machine-learning', 'gpu', etc.",
|
|
167
|
-
inputSchema: {
|
|
168
|
-
type: "object",
|
|
169
|
-
properties: {
|
|
170
|
-
tag: {
|
|
171
|
-
type: "string",
|
|
172
|
-
description: "Event tag to filter by. Common tags: python, ai, machine-learning, gpu, deep-learning, neural-networks, big-data, hpc, jetstream, neocortex",
|
|
173
|
-
},
|
|
174
|
-
time_range: {
|
|
47
|
+
date: {
|
|
175
48
|
type: "string",
|
|
176
|
-
description: "
|
|
177
|
-
enum: ["upcoming", "
|
|
178
|
-
default: "upcoming",
|
|
49
|
+
description: "Filter by time period",
|
|
50
|
+
enum: ["today", "upcoming", "past", "this_week", "this_month"]
|
|
179
51
|
},
|
|
180
|
-
|
|
52
|
+
skill: {
|
|
181
53
|
type: "string",
|
|
182
|
-
description: "
|
|
183
|
-
|
|
54
|
+
description: "Skill level filter",
|
|
55
|
+
enum: ["beginner", "intermediate", "advanced"]
|
|
184
56
|
},
|
|
185
57
|
limit: {
|
|
186
58
|
type: "number",
|
|
187
|
-
description: "
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
},
|
|
194
|
-
},
|
|
59
|
+
description: "Max results (default: 50)",
|
|
60
|
+
default: 50
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
195
65
|
];
|
|
196
66
|
}
|
|
197
67
|
getResources() {
|
|
@@ -226,119 +96,80 @@ export class EventsServer extends BaseAccessServer {
|
|
|
226
96
|
const { name, arguments: args = {} } = request.params;
|
|
227
97
|
try {
|
|
228
98
|
switch (name) {
|
|
229
|
-
case "get_events":
|
|
230
|
-
return await this.getEvents(args);
|
|
231
|
-
case "get_upcoming_events":
|
|
232
|
-
return await this.getUpcomingEvents(args);
|
|
233
99
|
case "search_events":
|
|
234
100
|
return await this.searchEvents(args);
|
|
235
|
-
case "get_events_by_tag":
|
|
236
|
-
return await this.getEventsByTag(args);
|
|
237
101
|
default:
|
|
238
|
-
|
|
102
|
+
return this.errorResponse(`Unknown tool: ${name}`);
|
|
239
103
|
}
|
|
240
104
|
}
|
|
241
105
|
catch (error) {
|
|
242
|
-
return
|
|
243
|
-
content: [
|
|
244
|
-
{
|
|
245
|
-
type: "text",
|
|
246
|
-
text: `Error: ${handleApiError(error)}`,
|
|
247
|
-
},
|
|
248
|
-
],
|
|
249
|
-
};
|
|
106
|
+
return this.errorResponse(handleApiError(error));
|
|
250
107
|
}
|
|
251
108
|
}
|
|
252
109
|
async handleResourceRead(request) {
|
|
253
110
|
const { uri } = request.params;
|
|
254
111
|
switch (uri) {
|
|
255
112
|
case "accessci://events":
|
|
256
|
-
const allEvents = await this.
|
|
257
|
-
return
|
|
258
|
-
contents: [
|
|
259
|
-
{
|
|
260
|
-
uri,
|
|
261
|
-
mimeType: "application/json",
|
|
262
|
-
text: allEvents.content[0].text,
|
|
263
|
-
},
|
|
264
|
-
],
|
|
265
|
-
};
|
|
113
|
+
const allEvents = await this.searchEvents({});
|
|
114
|
+
return this.createJsonResource(uri, JSON.parse(allEvents.content[0].text));
|
|
266
115
|
case "accessci://events/upcoming":
|
|
267
|
-
const upcomingEvents = await this.
|
|
268
|
-
return
|
|
269
|
-
contents: [
|
|
270
|
-
{
|
|
271
|
-
uri,
|
|
272
|
-
mimeType: "application/json",
|
|
273
|
-
text: upcomingEvents.content[0].text,
|
|
274
|
-
},
|
|
275
|
-
],
|
|
276
|
-
};
|
|
116
|
+
const upcomingEvents = await this.searchEvents({ date: "upcoming" });
|
|
117
|
+
return this.createJsonResource(uri, JSON.parse(upcomingEvents.content[0].text));
|
|
277
118
|
case "accessci://events/workshops":
|
|
278
|
-
const workshops = await this.
|
|
279
|
-
return
|
|
280
|
-
contents: [
|
|
281
|
-
{
|
|
282
|
-
uri,
|
|
283
|
-
mimeType: "application/json",
|
|
284
|
-
text: workshops.content[0].text,
|
|
285
|
-
},
|
|
286
|
-
],
|
|
287
|
-
};
|
|
119
|
+
const workshops = await this.searchEvents({ type: "workshop" });
|
|
120
|
+
return this.createJsonResource(uri, JSON.parse(workshops.content[0].text));
|
|
288
121
|
case "accessci://events/webinars":
|
|
289
|
-
const webinars = await this.
|
|
290
|
-
return
|
|
291
|
-
contents: [
|
|
292
|
-
{
|
|
293
|
-
uri,
|
|
294
|
-
mimeType: "application/json",
|
|
295
|
-
text: webinars.content[0].text,
|
|
296
|
-
},
|
|
297
|
-
],
|
|
298
|
-
};
|
|
122
|
+
const webinars = await this.searchEvents({ type: "webinar" });
|
|
123
|
+
return this.createJsonResource(uri, JSON.parse(webinars.content[0].text));
|
|
299
124
|
default:
|
|
300
125
|
throw new Error(`Unknown resource: ${uri}`);
|
|
301
126
|
}
|
|
302
127
|
}
|
|
303
128
|
buildEventsUrl(params) {
|
|
304
|
-
const url = new URL("/api/2.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
129
|
+
const url = new URL("/api/2.2/events", this.baseURL);
|
|
130
|
+
const limit = params.limit || 50;
|
|
131
|
+
let itemsPerPage = 50;
|
|
132
|
+
if (limit >= 100)
|
|
133
|
+
itemsPerPage = 100;
|
|
134
|
+
else if (limit >= 75)
|
|
135
|
+
itemsPerPage = 75;
|
|
136
|
+
else if (limit >= 50)
|
|
137
|
+
itemsPerPage = 50;
|
|
138
|
+
else if (limit >= 25)
|
|
139
|
+
itemsPerPage = 25;
|
|
140
|
+
else
|
|
141
|
+
itemsPerPage = 25;
|
|
142
|
+
url.searchParams.set("items_per_page", String(itemsPerPage));
|
|
143
|
+
if (params.query) {
|
|
144
|
+
url.searchParams.set("search_api_fulltext", params.query);
|
|
145
|
+
}
|
|
146
|
+
// Map universal 'date' to API params
|
|
147
|
+
const dateMap = {
|
|
148
|
+
today: { start: "today" },
|
|
149
|
+
upcoming: { start: "today" },
|
|
150
|
+
past: { start: "-1year", end: "today" },
|
|
151
|
+
this_week: { start: "today", end: "+1week" },
|
|
152
|
+
this_month: { start: "today", end: "+1month" }
|
|
153
|
+
};
|
|
154
|
+
if (params.date && dateMap[params.date]) {
|
|
155
|
+
const dateMapping = dateMap[params.date];
|
|
156
|
+
url.searchParams.set("beginning_date_relative", dateMapping.start);
|
|
157
|
+
if (dateMapping.end) {
|
|
158
|
+
url.searchParams.set("end_date_relative", dateMapping.end);
|
|
159
|
+
}
|
|
325
160
|
}
|
|
326
|
-
//
|
|
161
|
+
// Faceted filters
|
|
327
162
|
let filterIndex = 0;
|
|
328
|
-
if (params.
|
|
329
|
-
url.searchParams.set(`f[${filterIndex}]`, `custom_event_type:${params.
|
|
163
|
+
if (params.type) {
|
|
164
|
+
url.searchParams.set(`f[${filterIndex}]`, `custom_event_type:${params.type}`);
|
|
330
165
|
filterIndex++;
|
|
331
166
|
}
|
|
332
|
-
if (params.
|
|
333
|
-
url.searchParams.set(`f[${filterIndex}]`, `
|
|
167
|
+
if (params.tags) {
|
|
168
|
+
url.searchParams.set(`f[${filterIndex}]`, `custom_event_tags:${params.tags}`);
|
|
334
169
|
filterIndex++;
|
|
335
170
|
}
|
|
336
|
-
if (params.
|
|
337
|
-
url.searchParams.set(`f[${filterIndex}]`, `skill_level:${params.
|
|
338
|
-
filterIndex++;
|
|
339
|
-
}
|
|
340
|
-
if (params.event_tags) {
|
|
341
|
-
url.searchParams.set(`f[${filterIndex}]`, `custom_event_tags:${params.event_tags}`);
|
|
171
|
+
if (params.skill) {
|
|
172
|
+
url.searchParams.set(`f[${filterIndex}]`, `skill_level:${params.skill}`);
|
|
342
173
|
filterIndex++;
|
|
343
174
|
}
|
|
344
175
|
return url.toString();
|
|
@@ -347,157 +178,40 @@ export class EventsServer extends BaseAccessServer {
|
|
|
347
178
|
const url = this.buildEventsUrl(params);
|
|
348
179
|
const response = await this.httpClient.get(url);
|
|
349
180
|
if (response.status !== 200) {
|
|
350
|
-
throw new Error(`
|
|
181
|
+
throw new Error(`API error ${response.status}`);
|
|
351
182
|
}
|
|
352
183
|
let events = response.data || [];
|
|
353
|
-
// Apply limit if specified
|
|
354
184
|
if (params.limit && events.length > params.limit) {
|
|
355
185
|
events = events.slice(0, params.limit);
|
|
356
186
|
}
|
|
357
|
-
// Enhance events with additional metadata
|
|
358
187
|
const enhancedEvents = events.map((event) => ({
|
|
359
188
|
...event,
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
// Split tags into array
|
|
364
|
-
tags: event.custom_event_tags
|
|
365
|
-
? event.custom_event_tags.split(",").map((tag) => tag.trim())
|
|
366
|
-
: [],
|
|
367
|
-
// Calculate duration if both dates present
|
|
368
|
-
duration_hours: event.date_1
|
|
369
|
-
? Math.round((new Date(event.date_1).getTime() -
|
|
370
|
-
new Date(event.date).getTime()) /
|
|
371
|
-
(1000 * 60 * 60))
|
|
189
|
+
tags: Array.isArray(event.tags) ? event.tags : [],
|
|
190
|
+
duration_hours: event.end_date
|
|
191
|
+
? Math.round((new Date(event.end_date).getTime() - new Date(event.start_date).getTime()) / 3600000)
|
|
372
192
|
: null,
|
|
373
|
-
|
|
374
|
-
starts_in_hours: Math.max(0, Math.round((new Date(event.date).getTime() - Date.now()) / (1000 * 60 * 60))),
|
|
193
|
+
starts_in_hours: Math.max(0, Math.round((new Date(event.start_date).getTime() - Date.now()) / 3600000))
|
|
375
194
|
}));
|
|
376
|
-
const summary = {
|
|
377
|
-
total_events: enhancedEvents.length,
|
|
378
|
-
upcoming_events: enhancedEvents.filter((e) => e.starts_in_hours >= 0)
|
|
379
|
-
.length,
|
|
380
|
-
events_this_week: enhancedEvents.filter((e) => e.starts_in_hours <= 168 && e.starts_in_hours >= 0).length,
|
|
381
|
-
api_info: {
|
|
382
|
-
endpoint_version: "2.1",
|
|
383
|
-
timezone_handling: "All timestamps in UTC (Z suffix). Relative dates calculated using timezone parameter (default: UTC).",
|
|
384
|
-
timezone_used: params.timezone || "UTC",
|
|
385
|
-
},
|
|
386
|
-
event_types: [
|
|
387
|
-
...new Set(enhancedEvents.map((e) => e.event_type).filter(Boolean)),
|
|
388
|
-
],
|
|
389
|
-
affiliations: [
|
|
390
|
-
...new Set(enhancedEvents.map((e) => e.event_affiliation).filter(Boolean)),
|
|
391
|
-
],
|
|
392
|
-
skill_levels: [
|
|
393
|
-
...new Set(enhancedEvents.map((e) => e.skill_level).filter(Boolean)),
|
|
394
|
-
],
|
|
395
|
-
popular_tags: this.getPopularTags(enhancedEvents),
|
|
396
|
-
events: enhancedEvents,
|
|
397
|
-
};
|
|
398
195
|
return {
|
|
399
|
-
content: [
|
|
400
|
-
{
|
|
196
|
+
content: [{
|
|
401
197
|
type: "text",
|
|
402
|
-
text: JSON.stringify(
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
async getUpcomingEvents(params) {
|
|
408
|
-
const upcomingParams = {
|
|
409
|
-
...params,
|
|
410
|
-
beginning_date_relative: "today",
|
|
411
|
-
limit: params.limit || 50,
|
|
412
|
-
// Pass through timezone if provided
|
|
413
|
-
...(params.timezone && { timezone: params.timezone }),
|
|
198
|
+
text: JSON.stringify({
|
|
199
|
+
total: enhancedEvents.length,
|
|
200
|
+
items: enhancedEvents
|
|
201
|
+
})
|
|
202
|
+
}]
|
|
414
203
|
};
|
|
415
|
-
return this.getEvents(upcomingParams);
|
|
416
204
|
}
|
|
417
205
|
async searchEvents(params) {
|
|
418
|
-
//
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
// Pass through timezone if provided
|
|
424
|
-
...(params.timezone && { timezone: params.timezone }),
|
|
425
|
-
};
|
|
426
|
-
// Use the API's native search capabilities
|
|
427
|
-
const eventsResponse = await this.getEvents(searchParams);
|
|
428
|
-
const eventsData = JSON.parse(eventsResponse.content[0].text);
|
|
429
|
-
// API returns already filtered results, no need for client-side filtering
|
|
430
|
-
const summary = {
|
|
431
|
-
search_query: params.query,
|
|
432
|
-
total_matches: eventsData.total_events,
|
|
433
|
-
search_method: "API native full-text search",
|
|
434
|
-
search_scope: "titles, descriptions, speakers, tags, location, event type",
|
|
435
|
-
events: eventsData.events,
|
|
436
|
-
};
|
|
437
|
-
return {
|
|
438
|
-
content: [
|
|
439
|
-
{
|
|
440
|
-
type: "text",
|
|
441
|
-
text: JSON.stringify(summary, null, 2),
|
|
442
|
-
},
|
|
443
|
-
],
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
async getEventsByTag(params) {
|
|
447
|
-
const { tag, time_range = "upcoming", limit = 25, timezone } = params;
|
|
448
|
-
let dateParams = {};
|
|
449
|
-
switch (time_range) {
|
|
450
|
-
case "upcoming":
|
|
451
|
-
dateParams.beginning_date_relative = "today";
|
|
452
|
-
break;
|
|
453
|
-
case "this_week":
|
|
454
|
-
dateParams.beginning_date_relative = "today";
|
|
455
|
-
dateParams.end_date_relative = "+1week";
|
|
456
|
-
break;
|
|
457
|
-
case "this_month":
|
|
458
|
-
dateParams.beginning_date_relative = "today";
|
|
459
|
-
dateParams.end_date_relative = "+1month";
|
|
460
|
-
break;
|
|
461
|
-
case "all":
|
|
462
|
-
// No date restrictions
|
|
463
|
-
break;
|
|
206
|
+
// Validate enum parameters
|
|
207
|
+
const validDateValues = ["today", "upcoming", "past", "this_week", "this_month"];
|
|
208
|
+
const validSkillValues = ["beginner", "intermediate", "advanced"];
|
|
209
|
+
if (params.date && !validDateValues.includes(params.date)) {
|
|
210
|
+
return this.errorResponse(`Invalid date value: '${params.date}'`, `Valid values are: ${validDateValues.join(", ")}`);
|
|
464
211
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
// Pass through timezone if provided
|
|
470
|
-
...(timezone && { timezone }),
|
|
471
|
-
};
|
|
472
|
-
const eventsResponse = await this.getEvents(taggedParams);
|
|
473
|
-
const eventsData = JSON.parse(eventsResponse.content[0].text);
|
|
474
|
-
const summary = {
|
|
475
|
-
tag: tag,
|
|
476
|
-
time_range: time_range,
|
|
477
|
-
total_events: eventsData.events.length,
|
|
478
|
-
events: eventsData.events,
|
|
479
|
-
};
|
|
480
|
-
return {
|
|
481
|
-
content: [
|
|
482
|
-
{
|
|
483
|
-
type: "text",
|
|
484
|
-
text: JSON.stringify(summary, null, 2),
|
|
485
|
-
},
|
|
486
|
-
],
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
getPopularTags(events) {
|
|
490
|
-
const tagCounts = {};
|
|
491
|
-
events.forEach((event) => {
|
|
492
|
-
if (event.tags) {
|
|
493
|
-
event.tags.forEach((tag) => {
|
|
494
|
-
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
});
|
|
498
|
-
return Object.entries(tagCounts)
|
|
499
|
-
.sort(([, a], [, b]) => b - a)
|
|
500
|
-
.slice(0, 10)
|
|
501
|
-
.map(([tag]) => tag);
|
|
212
|
+
if (params.skill && !validSkillValues.includes(params.skill)) {
|
|
213
|
+
return this.errorResponse(`Invalid skill value: '${params.skill}'`, `Valid values are: ${validSkillValues.join(", ")}`);
|
|
214
|
+
}
|
|
215
|
+
return await this.getEvents(params);
|
|
502
216
|
}
|
|
503
217
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@access-mcp/events",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "ACCESS-CI Events MCP Server - Get information about workshops, webinars, and training events",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"test:coverage": "vitest run --coverage --exclude '**/**.integration.test.ts'"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@access-mcp/shared": "
|
|
22
|
+
"@access-mcp/shared": "^0.3.3",
|
|
23
23
|
"@modelcontextprotocol/sdk": "^0.5.0",
|
|
24
24
|
"axios": "^1.7.0"
|
|
25
25
|
},
|