@access-mcp/events 0.1.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 +199 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +12 -0
- package/dist/server.d.ts +184 -0
- package/dist/server.js +464 -0
- package/package.json +47 -0
- package/src/__tests__/server.integration.test.ts +219 -0
- package/src/__tests__/server.test.ts +643 -0
- package/src/index.ts +15 -0
- package/src/server.ts +546 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +18 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { BaseAccessServer, handleApiError } from "@access-mcp/shared";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
export class EventsServer extends BaseAccessServer {
|
|
4
|
+
_eventsHttpClient;
|
|
5
|
+
constructor() {
|
|
6
|
+
super("access-mcp-events", "0.1.0", "https://support.access-ci.org");
|
|
7
|
+
}
|
|
8
|
+
get httpClient() {
|
|
9
|
+
if (!this._eventsHttpClient) {
|
|
10
|
+
const headers = {
|
|
11
|
+
"User-Agent": `${this.serverName}/${this.version}`,
|
|
12
|
+
};
|
|
13
|
+
// Add authentication if API key is provided
|
|
14
|
+
const apiKey = process.env.ACCESS_CI_API_KEY;
|
|
15
|
+
if (apiKey) {
|
|
16
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
17
|
+
}
|
|
18
|
+
this._eventsHttpClient = axios.create({
|
|
19
|
+
baseURL: this.baseURL,
|
|
20
|
+
timeout: 10000, // 10 seconds for events API (can be slower)
|
|
21
|
+
headers,
|
|
22
|
+
validateStatus: () => true, // Don't throw on HTTP errors
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return this._eventsHttpClient;
|
|
26
|
+
}
|
|
27
|
+
getTools() {
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
name: "get_events",
|
|
31
|
+
description: "Get ACCESS-CI events with comprehensive filtering capabilities",
|
|
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 (today, +1week, -1month, etc.)",
|
|
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 (today, +1week, -1month, etc.)",
|
|
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",
|
|
70
|
+
},
|
|
71
|
+
end_date: {
|
|
72
|
+
type: "string",
|
|
73
|
+
description: "End date in YYYY-MM-DD or YYYY-MM-DD HH:MM:SS format",
|
|
74
|
+
},
|
|
75
|
+
// Faceted search filters
|
|
76
|
+
event_type: {
|
|
77
|
+
type: "string",
|
|
78
|
+
description: "Filter by event type (workshop, webinar, etc.)",
|
|
79
|
+
},
|
|
80
|
+
event_affiliation: {
|
|
81
|
+
type: "string",
|
|
82
|
+
description: "Filter by organizational affiliation (Community, ACCESS, etc.)",
|
|
83
|
+
},
|
|
84
|
+
skill_level: {
|
|
85
|
+
type: "string",
|
|
86
|
+
description: "Filter by required skill level (beginner, intermediate, advanced)",
|
|
87
|
+
enum: ["beginner", "intermediate", "advanced"],
|
|
88
|
+
},
|
|
89
|
+
event_tags: {
|
|
90
|
+
type: "string",
|
|
91
|
+
description: "Filter by event tags (python, big-data, machine-learning, etc.)",
|
|
92
|
+
},
|
|
93
|
+
limit: {
|
|
94
|
+
type: "number",
|
|
95
|
+
description: "Maximum number of events to return (default: 100)",
|
|
96
|
+
minimum: 1,
|
|
97
|
+
maximum: 1000,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
required: [],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "get_upcoming_events",
|
|
105
|
+
description: "Get upcoming ACCESS-CI events (today onward)",
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: "object",
|
|
108
|
+
properties: {
|
|
109
|
+
limit: {
|
|
110
|
+
type: "number",
|
|
111
|
+
description: "Maximum number of events to return (default: 50)",
|
|
112
|
+
minimum: 1,
|
|
113
|
+
maximum: 100,
|
|
114
|
+
},
|
|
115
|
+
event_type: {
|
|
116
|
+
type: "string",
|
|
117
|
+
description: "Filter by event type (workshop, webinar, etc.)",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
required: [],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: "search_events",
|
|
125
|
+
description: "Search events by keywords in title and description",
|
|
126
|
+
inputSchema: {
|
|
127
|
+
type: "object",
|
|
128
|
+
properties: {
|
|
129
|
+
query: {
|
|
130
|
+
type: "string",
|
|
131
|
+
description: "Search query for event titles and descriptions",
|
|
132
|
+
},
|
|
133
|
+
beginning_date_relative: {
|
|
134
|
+
type: "string",
|
|
135
|
+
description: "Start date using relative values (default: today)",
|
|
136
|
+
default: "today",
|
|
137
|
+
},
|
|
138
|
+
limit: {
|
|
139
|
+
type: "number",
|
|
140
|
+
description: "Maximum number of events to return (default: 25)",
|
|
141
|
+
minimum: 1,
|
|
142
|
+
maximum: 100,
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
required: ["query"],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "get_events_by_tag",
|
|
150
|
+
description: "Get events filtered by specific tags",
|
|
151
|
+
inputSchema: {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: {
|
|
154
|
+
tag: {
|
|
155
|
+
type: "string",
|
|
156
|
+
description: "Event tag to filter by (e.g., python, machine-learning, gpu)",
|
|
157
|
+
},
|
|
158
|
+
time_range: {
|
|
159
|
+
type: "string",
|
|
160
|
+
description: "Time range for events",
|
|
161
|
+
enum: ["upcoming", "this_week", "this_month", "all"],
|
|
162
|
+
default: "upcoming",
|
|
163
|
+
},
|
|
164
|
+
limit: {
|
|
165
|
+
type: "number",
|
|
166
|
+
description: "Maximum number of events to return (default: 25)",
|
|
167
|
+
minimum: 1,
|
|
168
|
+
maximum: 100,
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
required: ["tag"],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
getResources() {
|
|
177
|
+
return [
|
|
178
|
+
{
|
|
179
|
+
uri: "accessci://events",
|
|
180
|
+
name: "ACCESS-CI Events",
|
|
181
|
+
description: "Comprehensive events data including workshops, webinars, and training",
|
|
182
|
+
mimeType: "application/json",
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
uri: "accessci://events/upcoming",
|
|
186
|
+
name: "Upcoming Events",
|
|
187
|
+
description: "Events scheduled for today and beyond",
|
|
188
|
+
mimeType: "application/json",
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
uri: "accessci://events/workshops",
|
|
192
|
+
name: "Workshops",
|
|
193
|
+
description: "Workshop events only",
|
|
194
|
+
mimeType: "application/json",
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
uri: "accessci://events/webinars",
|
|
198
|
+
name: "Webinars",
|
|
199
|
+
description: "Webinar events only",
|
|
200
|
+
mimeType: "application/json",
|
|
201
|
+
},
|
|
202
|
+
];
|
|
203
|
+
}
|
|
204
|
+
async handleToolCall(request) {
|
|
205
|
+
const { name, arguments: args = {} } = request.params;
|
|
206
|
+
try {
|
|
207
|
+
switch (name) {
|
|
208
|
+
case "get_events":
|
|
209
|
+
return await this.getEvents(args);
|
|
210
|
+
case "get_upcoming_events":
|
|
211
|
+
return await this.getUpcomingEvents(args);
|
|
212
|
+
case "search_events":
|
|
213
|
+
return await this.searchEvents(args);
|
|
214
|
+
case "get_events_by_tag":
|
|
215
|
+
return await this.getEventsByTag(args);
|
|
216
|
+
default:
|
|
217
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
return {
|
|
222
|
+
content: [
|
|
223
|
+
{
|
|
224
|
+
type: "text",
|
|
225
|
+
text: `Error: ${handleApiError(error)}`,
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async handleResourceRead(request) {
|
|
232
|
+
const { uri } = request.params;
|
|
233
|
+
switch (uri) {
|
|
234
|
+
case "accessci://events":
|
|
235
|
+
const allEvents = await this.getEvents({});
|
|
236
|
+
return {
|
|
237
|
+
contents: [
|
|
238
|
+
{
|
|
239
|
+
uri,
|
|
240
|
+
mimeType: "application/json",
|
|
241
|
+
text: allEvents.content[0].text,
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
};
|
|
245
|
+
case "accessci://events/upcoming":
|
|
246
|
+
const upcomingEvents = await this.getUpcomingEvents({});
|
|
247
|
+
return {
|
|
248
|
+
contents: [
|
|
249
|
+
{
|
|
250
|
+
uri,
|
|
251
|
+
mimeType: "application/json",
|
|
252
|
+
text: upcomingEvents.content[0].text,
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
};
|
|
256
|
+
case "accessci://events/workshops":
|
|
257
|
+
const workshops = await this.getEvents({ event_type: "workshop" });
|
|
258
|
+
return {
|
|
259
|
+
contents: [
|
|
260
|
+
{
|
|
261
|
+
uri,
|
|
262
|
+
mimeType: "application/json",
|
|
263
|
+
text: workshops.content[0].text,
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
};
|
|
267
|
+
case "accessci://events/webinars":
|
|
268
|
+
const webinars = await this.getEvents({ event_type: "webinar" });
|
|
269
|
+
return {
|
|
270
|
+
contents: [
|
|
271
|
+
{
|
|
272
|
+
uri,
|
|
273
|
+
mimeType: "application/json",
|
|
274
|
+
text: webinars.content[0].text,
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
};
|
|
278
|
+
default:
|
|
279
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
buildEventsUrl(params) {
|
|
283
|
+
const url = new URL("/api/2.0/events", this.baseURL);
|
|
284
|
+
// Add date filtering parameters
|
|
285
|
+
if (params.beginning_date_relative) {
|
|
286
|
+
url.searchParams.set("beginning_date_relative", params.beginning_date_relative);
|
|
287
|
+
}
|
|
288
|
+
if (params.end_date_relative) {
|
|
289
|
+
url.searchParams.set("end_date_relative", params.end_date_relative);
|
|
290
|
+
}
|
|
291
|
+
if (params.beginning_date) {
|
|
292
|
+
url.searchParams.set("beginning_date", params.beginning_date);
|
|
293
|
+
}
|
|
294
|
+
if (params.end_date) {
|
|
295
|
+
url.searchParams.set("end_date", params.end_date);
|
|
296
|
+
}
|
|
297
|
+
// Add faceted search filters
|
|
298
|
+
let filterIndex = 0;
|
|
299
|
+
if (params.event_type) {
|
|
300
|
+
url.searchParams.set(`f[${filterIndex}]`, `custom_event_type:${params.event_type}`);
|
|
301
|
+
filterIndex++;
|
|
302
|
+
}
|
|
303
|
+
if (params.event_affiliation) {
|
|
304
|
+
url.searchParams.set(`f[${filterIndex}]`, `custom_event_affiliation:${params.event_affiliation}`);
|
|
305
|
+
filterIndex++;
|
|
306
|
+
}
|
|
307
|
+
if (params.skill_level) {
|
|
308
|
+
url.searchParams.set(`f[${filterIndex}]`, `skill_level:${params.skill_level}`);
|
|
309
|
+
filterIndex++;
|
|
310
|
+
}
|
|
311
|
+
if (params.event_tags) {
|
|
312
|
+
url.searchParams.set(`f[${filterIndex}]`, `custom_event_tags:${params.event_tags}`);
|
|
313
|
+
filterIndex++;
|
|
314
|
+
}
|
|
315
|
+
return url.toString();
|
|
316
|
+
}
|
|
317
|
+
async getEvents(params) {
|
|
318
|
+
const url = this.buildEventsUrl(params);
|
|
319
|
+
const response = await this.httpClient.get(url);
|
|
320
|
+
if (response.status !== 200) {
|
|
321
|
+
throw new Error(`Events API returned ${response.status}: ${response.statusText}`);
|
|
322
|
+
}
|
|
323
|
+
let events = response.data || [];
|
|
324
|
+
// Apply limit if specified
|
|
325
|
+
if (params.limit && events.length > params.limit) {
|
|
326
|
+
events = events.slice(0, params.limit);
|
|
327
|
+
}
|
|
328
|
+
// Enhance events with additional metadata
|
|
329
|
+
const enhancedEvents = events.map((event) => ({
|
|
330
|
+
...event,
|
|
331
|
+
// Parse dates for better handling
|
|
332
|
+
start_date: new Date(event.date),
|
|
333
|
+
end_date: event.date_1 ? new Date(event.date_1) : null,
|
|
334
|
+
// Split tags into array
|
|
335
|
+
tags: event.custom_event_tags
|
|
336
|
+
? event.custom_event_tags.split(",").map((tag) => tag.trim())
|
|
337
|
+
: [],
|
|
338
|
+
// Calculate duration if both dates present
|
|
339
|
+
duration_hours: event.date_1
|
|
340
|
+
? Math.round((new Date(event.date_1).getTime() -
|
|
341
|
+
new Date(event.date).getTime()) /
|
|
342
|
+
(1000 * 60 * 60))
|
|
343
|
+
: null,
|
|
344
|
+
// Relative timing
|
|
345
|
+
starts_in_hours: Math.max(0, Math.round((new Date(event.date).getTime() - Date.now()) / (1000 * 60 * 60))),
|
|
346
|
+
}));
|
|
347
|
+
const summary = {
|
|
348
|
+
total_events: enhancedEvents.length,
|
|
349
|
+
upcoming_events: enhancedEvents.filter((e) => e.starts_in_hours >= 0)
|
|
350
|
+
.length,
|
|
351
|
+
events_this_week: enhancedEvents.filter((e) => e.starts_in_hours <= 168 && e.starts_in_hours >= 0).length,
|
|
352
|
+
event_types: [
|
|
353
|
+
...new Set(enhancedEvents.map((e) => e.event_type).filter(Boolean)),
|
|
354
|
+
],
|
|
355
|
+
affiliations: [
|
|
356
|
+
...new Set(enhancedEvents.map((e) => e.event_affiliation).filter(Boolean)),
|
|
357
|
+
],
|
|
358
|
+
skill_levels: [
|
|
359
|
+
...new Set(enhancedEvents.map((e) => e.skill_level).filter(Boolean)),
|
|
360
|
+
],
|
|
361
|
+
popular_tags: this.getPopularTags(enhancedEvents),
|
|
362
|
+
events: enhancedEvents,
|
|
363
|
+
};
|
|
364
|
+
return {
|
|
365
|
+
content: [
|
|
366
|
+
{
|
|
367
|
+
type: "text",
|
|
368
|
+
text: JSON.stringify(summary, null, 2),
|
|
369
|
+
},
|
|
370
|
+
],
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
async getUpcomingEvents(params) {
|
|
374
|
+
const upcomingParams = {
|
|
375
|
+
...params,
|
|
376
|
+
beginning_date_relative: "today",
|
|
377
|
+
limit: params.limit || 50,
|
|
378
|
+
};
|
|
379
|
+
return this.getEvents(upcomingParams);
|
|
380
|
+
}
|
|
381
|
+
async searchEvents(params) {
|
|
382
|
+
const searchParams = {
|
|
383
|
+
beginning_date_relative: params.beginning_date_relative || "today",
|
|
384
|
+
limit: params.limit || 25,
|
|
385
|
+
};
|
|
386
|
+
// Get events and filter by search query
|
|
387
|
+
const eventsResponse = await this.getEvents(searchParams);
|
|
388
|
+
const eventsData = JSON.parse(eventsResponse.content[0].text);
|
|
389
|
+
const query = params.query.toLowerCase();
|
|
390
|
+
const filteredEvents = eventsData.events.filter((event) => event.title?.toLowerCase().includes(query) ||
|
|
391
|
+
event.description?.toLowerCase().includes(query) ||
|
|
392
|
+
event.speakers?.toLowerCase().includes(query) ||
|
|
393
|
+
event.tags?.some((tag) => tag.toLowerCase().includes(query)));
|
|
394
|
+
const summary = {
|
|
395
|
+
search_query: params.query,
|
|
396
|
+
total_matches: filteredEvents.length,
|
|
397
|
+
searched_in: eventsData.total_events,
|
|
398
|
+
events: filteredEvents,
|
|
399
|
+
};
|
|
400
|
+
return {
|
|
401
|
+
content: [
|
|
402
|
+
{
|
|
403
|
+
type: "text",
|
|
404
|
+
text: JSON.stringify(summary, null, 2),
|
|
405
|
+
},
|
|
406
|
+
],
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
async getEventsByTag(params) {
|
|
410
|
+
const { tag, time_range = "upcoming", limit = 25 } = params;
|
|
411
|
+
let dateParams = {};
|
|
412
|
+
switch (time_range) {
|
|
413
|
+
case "upcoming":
|
|
414
|
+
dateParams.beginning_date_relative = "today";
|
|
415
|
+
break;
|
|
416
|
+
case "this_week":
|
|
417
|
+
dateParams.beginning_date_relative = "today";
|
|
418
|
+
dateParams.end_date_relative = "+1week";
|
|
419
|
+
break;
|
|
420
|
+
case "this_month":
|
|
421
|
+
dateParams.beginning_date_relative = "today";
|
|
422
|
+
dateParams.end_date_relative = "+1month";
|
|
423
|
+
break;
|
|
424
|
+
case "all":
|
|
425
|
+
// No date restrictions
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
const taggedParams = {
|
|
429
|
+
...dateParams,
|
|
430
|
+
event_tags: tag,
|
|
431
|
+
limit,
|
|
432
|
+
};
|
|
433
|
+
const eventsResponse = await this.getEvents(taggedParams);
|
|
434
|
+
const eventsData = JSON.parse(eventsResponse.content[0].text);
|
|
435
|
+
const summary = {
|
|
436
|
+
tag: tag,
|
|
437
|
+
time_range: time_range,
|
|
438
|
+
total_events: eventsData.events.length,
|
|
439
|
+
events: eventsData.events,
|
|
440
|
+
};
|
|
441
|
+
return {
|
|
442
|
+
content: [
|
|
443
|
+
{
|
|
444
|
+
type: "text",
|
|
445
|
+
text: JSON.stringify(summary, null, 2),
|
|
446
|
+
},
|
|
447
|
+
],
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
getPopularTags(events) {
|
|
451
|
+
const tagCounts = {};
|
|
452
|
+
events.forEach((event) => {
|
|
453
|
+
if (event.tags) {
|
|
454
|
+
event.tags.forEach((tag) => {
|
|
455
|
+
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
return Object.entries(tagCounts)
|
|
460
|
+
.sort(([, a], [, b]) => b - a)
|
|
461
|
+
.slice(0, 10)
|
|
462
|
+
.map(([tag]) => tag);
|
|
463
|
+
}
|
|
464
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@access-mcp/events",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ACCESS-CI Events MCP Server - Get information about workshops, webinars, and training events",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"access-mcp-events": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"dev": "npm run build && npm start",
|
|
14
|
+
"test": "vitest run --exclude '**/**.integration.test.ts'",
|
|
15
|
+
"test:integration": "vitest run src/**/*.integration.test.ts",
|
|
16
|
+
"test:all": "vitest run",
|
|
17
|
+
"test:watch": "vitest",
|
|
18
|
+
"test:ui": "vitest --ui",
|
|
19
|
+
"test:coverage": "vitest run --coverage --exclude '**/**.integration.test.ts'"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@access-mcp/shared": "file:../shared",
|
|
23
|
+
"@modelcontextprotocol/sdk": "^0.5.0",
|
|
24
|
+
"axios": "^1.7.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^22.0.0",
|
|
28
|
+
"@vitest/ui": "^2.1.9",
|
|
29
|
+
"c8": "^10.1.3",
|
|
30
|
+
"typescript": "^5.0.0",
|
|
31
|
+
"vitest": "^2.1.9"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"mcp",
|
|
35
|
+
"access-ci",
|
|
36
|
+
"events",
|
|
37
|
+
"workshops",
|
|
38
|
+
"training"
|
|
39
|
+
],
|
|
40
|
+
"author": "ACCESS-CI",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/access-ci-org/access-mcp-servers.git",
|
|
45
|
+
"directory": "packages/events"
|
|
46
|
+
}
|
|
47
|
+
}
|