@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.
@@ -0,0 +1,643 @@
1
+ import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
2
+ import { EventsServer } from "../server.js";
3
+ import axios from "axios";
4
+
5
+ // Mock axios
6
+ vi.mock("axios");
7
+
8
+ describe("EventsServer", () => {
9
+ let server: EventsServer;
10
+ let mockHttpClient: any;
11
+
12
+ beforeEach(() => {
13
+ server = new EventsServer();
14
+
15
+ // Set up mock HTTP client
16
+ mockHttpClient = {
17
+ get: vi.fn(),
18
+ };
19
+
20
+ // Override the httpClient getter
21
+ Object.defineProperty(server, "httpClient", {
22
+ get: () => mockHttpClient,
23
+ configurable: true,
24
+ });
25
+ });
26
+
27
+ afterEach(() => {
28
+ vi.clearAllMocks();
29
+ });
30
+
31
+ describe("Server Initialization", () => {
32
+ it("should initialize with correct server name and version", () => {
33
+ expect(server).toBeDefined();
34
+ expect(server["serverName"]).toBe("access-mcp-events");
35
+ expect(server["version"]).toBe("0.1.0");
36
+ expect(server["baseURL"]).toBe("https://support.access-ci.org");
37
+ });
38
+
39
+ it("should provide the correct tools", () => {
40
+ const tools = server["getTools"]();
41
+
42
+ expect(tools).toHaveLength(4);
43
+ expect(tools.map((t) => t.name)).toContain("get_events");
44
+ expect(tools.map((t) => t.name)).toContain("get_upcoming_events");
45
+ expect(tools.map((t) => t.name)).toContain("search_events");
46
+ expect(tools.map((t) => t.name)).toContain("get_events_by_tag");
47
+ });
48
+
49
+ it("should provide the correct resources", () => {
50
+ const resources = server["getResources"]();
51
+
52
+ expect(resources).toHaveLength(4);
53
+ expect(resources.map((r) => r.uri)).toContain("accessci://events");
54
+ expect(resources.map((r) => r.uri)).toContain(
55
+ "accessci://events/upcoming",
56
+ );
57
+ expect(resources.map((r) => r.uri)).toContain(
58
+ "accessci://events/workshops",
59
+ );
60
+ expect(resources.map((r) => r.uri)).toContain(
61
+ "accessci://events/webinars",
62
+ );
63
+ });
64
+ });
65
+
66
+ describe("URL Building", () => {
67
+ it("should build correct URLs with relative date filters", () => {
68
+ const url = server["buildEventsUrl"]({
69
+ beginning_date_relative: "today",
70
+ end_date_relative: "+1week",
71
+ });
72
+
73
+ expect(url).toContain("beginning_date_relative=today");
74
+ expect(url).toContain("end_date_relative=%2B1week");
75
+ });
76
+
77
+ it("should build correct URLs with absolute date filters", () => {
78
+ const url = server["buildEventsUrl"]({
79
+ beginning_date: "2024-01-01",
80
+ end_date: "2024-12-31",
81
+ });
82
+
83
+ expect(url).toContain("beginning_date=2024-01-01");
84
+ expect(url).toContain("end_date=2024-12-31");
85
+ });
86
+
87
+ it("should build correct URLs with faceted filters", () => {
88
+ const url = server["buildEventsUrl"]({
89
+ event_type: "workshop",
90
+ skill_level: "beginner",
91
+ event_tags: "python",
92
+ event_affiliation: "ACCESS",
93
+ });
94
+
95
+ expect(url).toContain("f%5B0%5D=custom_event_type%3Aworkshop");
96
+ expect(url).toContain("f%5B1%5D=custom_event_affiliation%3AACCESS");
97
+ expect(url).toContain("f%5B2%5D=skill_level%3Abeginner");
98
+ expect(url).toContain("f%5B3%5D=custom_event_tags%3Apython");
99
+ });
100
+
101
+ it("should build correct URLs with mixed filters", () => {
102
+ const url = server["buildEventsUrl"]({
103
+ beginning_date_relative: "today",
104
+ end_date: "2024-12-31",
105
+ event_type: "webinar",
106
+ skill_level: "intermediate",
107
+ });
108
+
109
+ expect(url).toContain("beginning_date_relative=today");
110
+ expect(url).toContain("end_date=2024-12-31");
111
+ expect(url).toContain("f%5B0%5D=custom_event_type%3Awebinar");
112
+ expect(url).toContain("f%5B1%5D=skill_level%3Aintermediate");
113
+ });
114
+ });
115
+
116
+ describe("Tool Methods", () => {
117
+ const mockEventsData = [
118
+ {
119
+ id: "1",
120
+ title: "Python Workshop",
121
+ description: "Learn Python basics",
122
+ date: "2024-08-30T09:00:00",
123
+ date_1: "2024-08-30T17:00:00",
124
+ location: "Online",
125
+ event_type: "workshop",
126
+ event_affiliation: "ACCESS",
127
+ custom_event_tags: "python,programming,beginner",
128
+ skill_level: "beginner",
129
+ speakers: "Dr. Smith",
130
+ contact: "events@example.com",
131
+ registration: "https://example.com/register",
132
+ field_video: "",
133
+ created: "2024-08-01T10:00:00-0400",
134
+ changed: "2024-08-15T14:30:00-0400",
135
+ },
136
+ {
137
+ id: "2",
138
+ title: "Machine Learning Webinar",
139
+ description: "Introduction to ML",
140
+ date: "2024-09-01T14:00:00",
141
+ date_1: "2024-09-01T15:30:00",
142
+ location: "Virtual",
143
+ event_type: "webinar",
144
+ event_affiliation: "Community",
145
+ custom_event_tags: "machine-learning,ai,python",
146
+ skill_level: "intermediate",
147
+ speakers: "Prof. Johnson",
148
+ contact: "ml@example.com",
149
+ registration: "https://example.com/ml-register",
150
+ field_video: "",
151
+ created: "2024-08-01T10:00:00-0400",
152
+ changed: "2024-08-20T11:00:00-0400",
153
+ },
154
+ ];
155
+
156
+ describe("get_events", () => {
157
+ it("should get events with no filters", async () => {
158
+ mockHttpClient.get.mockResolvedValue({
159
+ status: 200,
160
+ data: mockEventsData,
161
+ });
162
+
163
+ const result = await server["handleToolCall"]({
164
+ params: {
165
+ name: "get_events",
166
+ arguments: {},
167
+ },
168
+ });
169
+
170
+ expect(mockHttpClient.get).toHaveBeenCalled();
171
+ const responseData = JSON.parse(result.content[0].text);
172
+ expect(responseData.total_events).toBe(2);
173
+ expect(responseData.events).toHaveLength(2);
174
+ });
175
+
176
+ it("should get events with date filters", async () => {
177
+ mockHttpClient.get.mockResolvedValue({
178
+ status: 200,
179
+ data: mockEventsData,
180
+ });
181
+
182
+ const result = await server["handleToolCall"]({
183
+ params: {
184
+ name: "get_events",
185
+ arguments: {
186
+ beginning_date_relative: "today",
187
+ end_date_relative: "+1month",
188
+ },
189
+ },
190
+ });
191
+
192
+ const calledUrl = mockHttpClient.get.mock.calls[0][0];
193
+ expect(calledUrl).toContain("beginning_date_relative=today");
194
+ expect(calledUrl).toContain("end_date_relative=%2B1month");
195
+ });
196
+
197
+ it("should get events with faceted filters", async () => {
198
+ mockHttpClient.get.mockResolvedValue({
199
+ status: 200,
200
+ data: [mockEventsData[0]], // Only workshop
201
+ });
202
+
203
+ const result = await server["handleToolCall"]({
204
+ params: {
205
+ name: "get_events",
206
+ arguments: {
207
+ event_type: "workshop",
208
+ skill_level: "beginner",
209
+ },
210
+ },
211
+ });
212
+
213
+ const calledUrl = mockHttpClient.get.mock.calls[0][0];
214
+ expect(calledUrl).toContain("custom_event_type%3Aworkshop");
215
+ expect(calledUrl).toContain("skill_level%3Abeginner");
216
+ });
217
+
218
+ it("should apply limit to events", async () => {
219
+ mockHttpClient.get.mockResolvedValue({
220
+ status: 200,
221
+ data: mockEventsData,
222
+ });
223
+
224
+ const result = await server["handleToolCall"]({
225
+ params: {
226
+ name: "get_events",
227
+ arguments: {
228
+ limit: 1,
229
+ },
230
+ },
231
+ });
232
+
233
+ const responseData = JSON.parse(result.content[0].text);
234
+ expect(responseData.events).toHaveLength(1);
235
+ });
236
+
237
+ it("should enhance events with calculated fields", async () => {
238
+ mockHttpClient.get.mockResolvedValue({
239
+ status: 200,
240
+ data: mockEventsData,
241
+ });
242
+
243
+ const result = await server["handleToolCall"]({
244
+ params: {
245
+ name: "get_events",
246
+ arguments: {},
247
+ },
248
+ });
249
+
250
+ const responseData = JSON.parse(result.content[0].text);
251
+ const event = responseData.events[0];
252
+
253
+ // Check enhanced fields
254
+ expect(event.tags).toEqual(["python", "programming", "beginner"]);
255
+ expect(event.duration_hours).toBe(8); // 9am to 5pm
256
+ expect(event.starts_in_hours).toBeDefined();
257
+ });
258
+
259
+ it("should extract popular tags", async () => {
260
+ mockHttpClient.get.mockResolvedValue({
261
+ status: 200,
262
+ data: mockEventsData,
263
+ });
264
+
265
+ const result = await server["handleToolCall"]({
266
+ params: {
267
+ name: "get_events",
268
+ arguments: {},
269
+ },
270
+ });
271
+
272
+ const responseData = JSON.parse(result.content[0].text);
273
+ expect(responseData.popular_tags).toContain("python");
274
+ expect(responseData.popular_tags).toContain("machine-learning");
275
+ });
276
+ });
277
+
278
+ describe("get_upcoming_events", () => {
279
+ it("should get upcoming events with default limit", async () => {
280
+ mockHttpClient.get.mockResolvedValue({
281
+ status: 200,
282
+ data: mockEventsData,
283
+ });
284
+
285
+ const result = await server["handleToolCall"]({
286
+ params: {
287
+ name: "get_upcoming_events",
288
+ arguments: {},
289
+ },
290
+ });
291
+
292
+ const calledUrl = mockHttpClient.get.mock.calls[0][0];
293
+ expect(calledUrl).toContain("beginning_date_relative=today");
294
+ const responseData = JSON.parse(result.content[0].text);
295
+ expect(responseData.events).toBeDefined();
296
+ });
297
+
298
+ it("should filter upcoming events by type", async () => {
299
+ mockHttpClient.get.mockResolvedValue({
300
+ status: 200,
301
+ data: [mockEventsData[1]], // Only webinar
302
+ });
303
+
304
+ const result = await server["handleToolCall"]({
305
+ params: {
306
+ name: "get_upcoming_events",
307
+ arguments: {
308
+ event_type: "webinar",
309
+ limit: 10,
310
+ },
311
+ },
312
+ });
313
+
314
+ const calledUrl = mockHttpClient.get.mock.calls[0][0];
315
+ expect(calledUrl).toContain("custom_event_type%3Awebinar");
316
+ });
317
+ });
318
+
319
+ describe("search_events", () => {
320
+ it("should search events by query in title", async () => {
321
+ mockHttpClient.get.mockResolvedValue({
322
+ status: 200,
323
+ data: mockEventsData,
324
+ });
325
+
326
+ const result = await server["handleToolCall"]({
327
+ params: {
328
+ name: "search_events",
329
+ arguments: {
330
+ query: "Python",
331
+ },
332
+ },
333
+ });
334
+
335
+ const responseData = JSON.parse(result.content[0].text);
336
+ expect(responseData.search_query).toBe("Python");
337
+ expect(responseData.total_matches).toBe(2); // Both events have 'python' in tags
338
+ expect(responseData.events[0].title).toContain("Python");
339
+ });
340
+
341
+ it("should search events by query in description", async () => {
342
+ mockHttpClient.get.mockResolvedValue({
343
+ status: 200,
344
+ data: mockEventsData,
345
+ });
346
+
347
+ const result = await server["handleToolCall"]({
348
+ params: {
349
+ name: "search_events",
350
+ arguments: {
351
+ query: "ML",
352
+ },
353
+ },
354
+ });
355
+
356
+ const responseData = JSON.parse(result.content[0].text);
357
+ expect(responseData.total_matches).toBe(1);
358
+ expect(responseData.events[0].description).toContain("ML");
359
+ });
360
+
361
+ it("should search events by query in tags", async () => {
362
+ mockHttpClient.get.mockResolvedValue({
363
+ status: 200,
364
+ data: mockEventsData,
365
+ });
366
+
367
+ const result = await server["handleToolCall"]({
368
+ params: {
369
+ name: "search_events",
370
+ arguments: {
371
+ query: "machine-learning",
372
+ },
373
+ },
374
+ });
375
+
376
+ const responseData = JSON.parse(result.content[0].text);
377
+ expect(responseData.total_matches).toBe(1);
378
+ });
379
+
380
+ it("should search with custom date range", async () => {
381
+ mockHttpClient.get.mockResolvedValue({
382
+ status: 200,
383
+ data: mockEventsData,
384
+ });
385
+
386
+ const result = await server["handleToolCall"]({
387
+ params: {
388
+ name: "search_events",
389
+ arguments: {
390
+ query: "workshop",
391
+ beginning_date_relative: "-1month",
392
+ limit: 5,
393
+ },
394
+ },
395
+ });
396
+
397
+ const calledUrl = mockHttpClient.get.mock.calls[0][0];
398
+ expect(calledUrl).toContain("beginning_date_relative=-1month");
399
+ });
400
+ });
401
+
402
+ describe("get_events_by_tag", () => {
403
+ it("should get events by tag for upcoming time range", async () => {
404
+ mockHttpClient.get.mockResolvedValue({
405
+ status: 200,
406
+ data: mockEventsData,
407
+ });
408
+
409
+ const result = await server["handleToolCall"]({
410
+ params: {
411
+ name: "get_events_by_tag",
412
+ arguments: {
413
+ tag: "python",
414
+ time_range: "upcoming",
415
+ },
416
+ },
417
+ });
418
+
419
+ const calledUrl = mockHttpClient.get.mock.calls[0][0];
420
+ expect(calledUrl).toContain("custom_event_tags%3Apython");
421
+ expect(calledUrl).toContain("beginning_date_relative=today");
422
+
423
+ const responseData = JSON.parse(result.content[0].text);
424
+ expect(responseData.tag).toBe("python");
425
+ expect(responseData.time_range).toBe("upcoming");
426
+ });
427
+
428
+ it("should get events by tag for this week", async () => {
429
+ mockHttpClient.get.mockResolvedValue({
430
+ status: 200,
431
+ data: mockEventsData,
432
+ });
433
+
434
+ const result = await server["handleToolCall"]({
435
+ params: {
436
+ name: "get_events_by_tag",
437
+ arguments: {
438
+ tag: "ai",
439
+ time_range: "this_week",
440
+ },
441
+ },
442
+ });
443
+
444
+ const calledUrl = mockHttpClient.get.mock.calls[0][0];
445
+ expect(calledUrl).toContain("beginning_date_relative=today");
446
+ expect(calledUrl).toContain("end_date_relative=%2B1week");
447
+ });
448
+
449
+ it("should get events by tag for this month", async () => {
450
+ mockHttpClient.get.mockResolvedValue({
451
+ status: 200,
452
+ data: mockEventsData,
453
+ });
454
+
455
+ const result = await server["handleToolCall"]({
456
+ params: {
457
+ name: "get_events_by_tag",
458
+ arguments: {
459
+ tag: "programming",
460
+ time_range: "this_month",
461
+ },
462
+ },
463
+ });
464
+
465
+ const calledUrl = mockHttpClient.get.mock.calls[0][0];
466
+ expect(calledUrl).toContain("beginning_date_relative=today");
467
+ expect(calledUrl).toContain("end_date_relative=%2B1month");
468
+ });
469
+
470
+ it("should get all events by tag", async () => {
471
+ mockHttpClient.get.mockResolvedValue({
472
+ status: 200,
473
+ data: mockEventsData,
474
+ });
475
+
476
+ const result = await server["handleToolCall"]({
477
+ params: {
478
+ name: "get_events_by_tag",
479
+ arguments: {
480
+ tag: "beginner",
481
+ time_range: "all",
482
+ },
483
+ },
484
+ });
485
+
486
+ const calledUrl = mockHttpClient.get.mock.calls[0][0];
487
+ expect(calledUrl).not.toContain("beginning_date_relative");
488
+ expect(calledUrl).not.toContain("end_date_relative");
489
+ });
490
+ });
491
+
492
+ describe("Error Handling", () => {
493
+ it("should handle API errors gracefully", async () => {
494
+ mockHttpClient.get.mockResolvedValue({
495
+ status: 404,
496
+ statusText: "Not Found",
497
+ });
498
+
499
+ const result = await server["handleToolCall"]({
500
+ params: {
501
+ name: "get_events",
502
+ arguments: {},
503
+ },
504
+ });
505
+
506
+ expect(result.content[0].text).toContain("Error");
507
+ expect(result.content[0].text).toContain("404");
508
+ });
509
+
510
+ it("should handle network errors", async () => {
511
+ mockHttpClient.get.mockRejectedValue(new Error("Network error"));
512
+
513
+ const result = await server["handleToolCall"]({
514
+ params: {
515
+ name: "get_events",
516
+ arguments: {},
517
+ },
518
+ });
519
+
520
+ expect(result.content[0].text).toContain("Error");
521
+ });
522
+
523
+ it("should handle unknown tools", async () => {
524
+ const result = await server["handleToolCall"]({
525
+ params: {
526
+ name: "unknown_tool",
527
+ arguments: {},
528
+ },
529
+ });
530
+
531
+ expect(result.content[0].text).toContain("Unknown tool");
532
+ });
533
+ });
534
+ });
535
+
536
+ describe("Resource Handling", () => {
537
+ const mockEventsData = [
538
+ {
539
+ id: "1",
540
+ title: "Test Event",
541
+ event_type: "workshop",
542
+ date: "2024-08-30T09:00:00",
543
+ date_1: "2024-08-30T17:00:00",
544
+ custom_event_tags: "test",
545
+ },
546
+ ];
547
+
548
+ it("should handle accessci://events resource", async () => {
549
+ mockHttpClient.get.mockResolvedValue({
550
+ status: 200,
551
+ data: mockEventsData,
552
+ });
553
+
554
+ const result = await server["handleResourceRead"]({
555
+ params: { uri: "accessci://events" },
556
+ });
557
+
558
+ expect(result.contents[0].mimeType).toBe("application/json");
559
+ expect(result.contents[0].text).toBeDefined();
560
+ });
561
+
562
+ it("should handle accessci://events/upcoming resource", async () => {
563
+ mockHttpClient.get.mockResolvedValue({
564
+ status: 200,
565
+ data: mockEventsData,
566
+ });
567
+
568
+ const result = await server["handleResourceRead"]({
569
+ params: { uri: "accessci://events/upcoming" },
570
+ });
571
+
572
+ expect(result.contents[0].mimeType).toBe("application/json");
573
+ });
574
+
575
+ it("should handle accessci://events/workshops resource", async () => {
576
+ mockHttpClient.get.mockResolvedValue({
577
+ status: 200,
578
+ data: mockEventsData,
579
+ });
580
+
581
+ const result = await server["handleResourceRead"]({
582
+ params: { uri: "accessci://events/workshops" },
583
+ });
584
+
585
+ expect(result.contents[0].mimeType).toBe("application/json");
586
+ });
587
+
588
+ it("should handle accessci://events/webinars resource", async () => {
589
+ mockHttpClient.get.mockResolvedValue({
590
+ status: 200,
591
+ data: [],
592
+ });
593
+
594
+ const result = await server["handleResourceRead"]({
595
+ params: { uri: "accessci://events/webinars" },
596
+ });
597
+
598
+ expect(result.contents[0].mimeType).toBe("application/json");
599
+ });
600
+
601
+ it("should handle unknown resources", async () => {
602
+ await expect(async () => {
603
+ await server["handleResourceRead"]({
604
+ params: { uri: "accessci://unknown" },
605
+ });
606
+ }).rejects.toThrow("Unknown resource");
607
+ });
608
+ });
609
+
610
+ describe("Utility Methods", () => {
611
+ it("should extract popular tags correctly", () => {
612
+ const events = [
613
+ { tags: ["python", "ai", "machine-learning"] },
614
+ { tags: ["python", "data-science"] },
615
+ { tags: ["ai", "gpu"] },
616
+ { tags: ["python"] },
617
+ { tags: ["machine-learning"] },
618
+ ];
619
+
620
+ const popularTags = server["getPopularTags"](events);
621
+ expect(popularTags[0]).toBe("python"); // Most frequent (3 times)
622
+ expect(popularTags[1]).toBe("ai"); // Second most frequent (2 times)
623
+ expect(popularTags[2]).toBe("machine-learning"); // Also 2 times
624
+ expect(popularTags).toHaveLength(Math.min(10, 5)); // Should return up to 10 tags
625
+ });
626
+
627
+ it("should handle empty events for popular tags", () => {
628
+ const popularTags = server["getPopularTags"]([]);
629
+ expect(popularTags).toEqual([]);
630
+ });
631
+
632
+ it("should handle events without tags", () => {
633
+ const events = [
634
+ { title: "Event 1" },
635
+ { title: "Event 2", tags: null },
636
+ { title: "Event 3", tags: undefined },
637
+ ];
638
+
639
+ const popularTags = server["getPopularTags"](events);
640
+ expect(popularTags).toEqual([]);
641
+ });
642
+ });
643
+ });
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { EventsServer } from "./server.js";
4
+
5
+ async function main() {
6
+ const server = new EventsServer();
7
+ await server.start();
8
+ }
9
+
10
+ if (import.meta.url === `file://${process.argv[1]}`) {
11
+ main().catch((error) => {
12
+ console.error("Server error:", error);
13
+ process.exit(1);
14
+ });
15
+ }