@access-mcp/events 0.1.0 → 0.2.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 +49 -6
- package/dist/server.d.ts +20 -0
- package/dist/server.js +64 -25
- package/package.json +1 -1
- package/src/__tests__/server.integration.test.ts +82 -0
- package/src/__tests__/server.test.ts +216 -6
- package/src/server.ts +67 -29
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ A Model Context Protocol server providing access to ACCESS-CI events data includ
|
|
|
8
8
|
|
|
9
9
|
- **`get_events`** - Get ACCESS-CI events with comprehensive filtering
|
|
10
10
|
- **`get_upcoming_events`** - Get upcoming events (today onward)
|
|
11
|
-
- **`search_events`** - Search events
|
|
11
|
+
- **`search_events`** - **Enhanced!** Search events using API's native full-text search across all content
|
|
12
12
|
- **`get_events_by_tag`** - Get events filtered by specific tags
|
|
13
13
|
|
|
14
14
|
### 📊 Resources
|
|
@@ -49,10 +49,12 @@ Add to your Claude Desktop configuration:
|
|
|
49
49
|
What events are coming up in the next week?
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
**Search for specific topics:**
|
|
52
|
+
**Search for specific topics (now much more powerful!):**
|
|
53
53
|
|
|
54
54
|
```
|
|
55
55
|
Find upcoming Python workshops
|
|
56
|
+
Show me machine learning events
|
|
57
|
+
Search for "office hours" this week
|
|
56
58
|
```
|
|
57
59
|
|
|
58
60
|
**Filter by skill level:**
|
|
@@ -64,9 +66,33 @@ Show me beginner-level events this month
|
|
|
64
66
|
**Get events by tags:**
|
|
65
67
|
|
|
66
68
|
```
|
|
67
|
-
Find all
|
|
69
|
+
Find all GPU computing events
|
|
68
70
|
```
|
|
69
71
|
|
|
72
|
+
## Key Improvements
|
|
73
|
+
|
|
74
|
+
### 🚀 Enhanced Search (v2.1)
|
|
75
|
+
|
|
76
|
+
**Native API Full-Text Search:**
|
|
77
|
+
- Searches across titles, descriptions, speakers, tags, location, and event type
|
|
78
|
+
- Supports multi-word queries (e.g., "machine learning", "office hours")
|
|
79
|
+
- Much more comprehensive results than previous tag-only filtering
|
|
80
|
+
- Server-side indexing for better performance
|
|
81
|
+
|
|
82
|
+
**Search Examples:**
|
|
83
|
+
- `"python"` - Find Python programming events
|
|
84
|
+
- `"machine learning"` - Find ML-related content in any field
|
|
85
|
+
- `"gpu computing"` - Find GPU-related events
|
|
86
|
+
- `"office hours"` - Find all office hours sessions
|
|
87
|
+
|
|
88
|
+
### 🌍 Timezone Support (v2.1)
|
|
89
|
+
|
|
90
|
+
**Smart Timezone Handling:**
|
|
91
|
+
- All timestamps returned in UTC (ISO 8601 format with Z suffix)
|
|
92
|
+
- Timezone parameter controls relative date calculations
|
|
93
|
+
- Common timezone examples: `America/New_York`, `Europe/London`, `Asia/Tokyo`
|
|
94
|
+
- Default: UTC calculations
|
|
95
|
+
|
|
70
96
|
## Filtering Capabilities
|
|
71
97
|
|
|
72
98
|
### Date Filtering
|
|
@@ -144,18 +170,28 @@ The server adds these computed fields:
|
|
|
144
170
|
}
|
|
145
171
|
```
|
|
146
172
|
|
|
147
|
-
### Search Events
|
|
173
|
+
### Search Events (Enhanced API Search)
|
|
148
174
|
|
|
149
175
|
```typescript
|
|
150
|
-
// Search for GPU-related events
|
|
176
|
+
// Search for GPU-related events using native API search
|
|
151
177
|
{
|
|
152
178
|
"tool": "search_events",
|
|
153
179
|
"arguments": {
|
|
154
180
|
"query": "GPU computing",
|
|
155
181
|
"beginning_date_relative": "today",
|
|
182
|
+
"timezone": "America/New_York",
|
|
156
183
|
"limit": 10
|
|
157
184
|
}
|
|
158
185
|
}
|
|
186
|
+
|
|
187
|
+
// Multi-word search examples
|
|
188
|
+
{
|
|
189
|
+
"tool": "search_events",
|
|
190
|
+
"arguments": {
|
|
191
|
+
"query": "machine learning",
|
|
192
|
+
"beginning_date_relative": "-1month"
|
|
193
|
+
}
|
|
194
|
+
}
|
|
159
195
|
```
|
|
160
196
|
|
|
161
197
|
### Get Events by Tag
|
|
@@ -187,10 +223,17 @@ npm test
|
|
|
187
223
|
|
|
188
224
|
## Base URL
|
|
189
225
|
|
|
190
|
-
The server connects to: `https://support.access-ci.org/api/2.
|
|
226
|
+
The server connects to: `https://support.access-ci.org/api/2.1/events` (v2.1 with UTC timestamps and enhanced search)
|
|
191
227
|
|
|
192
228
|
## Technical Notes
|
|
193
229
|
|
|
230
|
+
### API Version 2.1 Features
|
|
231
|
+
- **UTC timestamps**: All dates returned in UTC with Z suffix (e.g., `2024-08-30T13:00:00Z`)
|
|
232
|
+
- **Native search**: Uses `search_api_fulltext` parameter for comprehensive searching
|
|
233
|
+
- **Timezone support**: Relative dates calculated using specified timezone
|
|
234
|
+
- **Enhanced metadata**: Responses include API version and timezone info
|
|
235
|
+
|
|
236
|
+
### General
|
|
194
237
|
- All date comparisons use the event's start date (`date` field)
|
|
195
238
|
- Results include both upcoming and past events unless date filtered
|
|
196
239
|
- Faceted search filters use AND logic when combined
|
package/dist/server.d.ts
CHANGED
|
@@ -29,6 +29,11 @@ export declare class EventsServer extends BaseAccessServer {
|
|
|
29
29
|
type: string;
|
|
30
30
|
description: string;
|
|
31
31
|
};
|
|
32
|
+
timezone: {
|
|
33
|
+
type: string;
|
|
34
|
+
description: string;
|
|
35
|
+
default: string;
|
|
36
|
+
};
|
|
32
37
|
event_type: {
|
|
33
38
|
type: string;
|
|
34
39
|
description: string;
|
|
@@ -74,6 +79,11 @@ export declare class EventsServer extends BaseAccessServer {
|
|
|
74
79
|
type: string;
|
|
75
80
|
description: string;
|
|
76
81
|
};
|
|
82
|
+
timezone: {
|
|
83
|
+
type: string;
|
|
84
|
+
description: string;
|
|
85
|
+
default: string;
|
|
86
|
+
};
|
|
77
87
|
beginning_date_relative?: undefined;
|
|
78
88
|
end_date_relative?: undefined;
|
|
79
89
|
beginning_date?: undefined;
|
|
@@ -103,6 +113,11 @@ export declare class EventsServer extends BaseAccessServer {
|
|
|
103
113
|
default: string;
|
|
104
114
|
enum?: undefined;
|
|
105
115
|
};
|
|
116
|
+
timezone: {
|
|
117
|
+
type: string;
|
|
118
|
+
description: string;
|
|
119
|
+
default: string;
|
|
120
|
+
};
|
|
106
121
|
limit: {
|
|
107
122
|
type: string;
|
|
108
123
|
description: string;
|
|
@@ -137,6 +152,11 @@ export declare class EventsServer extends BaseAccessServer {
|
|
|
137
152
|
enum: string[];
|
|
138
153
|
default: string;
|
|
139
154
|
};
|
|
155
|
+
timezone: {
|
|
156
|
+
type: string;
|
|
157
|
+
description: string;
|
|
158
|
+
default: string;
|
|
159
|
+
};
|
|
140
160
|
limit: {
|
|
141
161
|
type: string;
|
|
142
162
|
description: string;
|
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.2.0", "https://support.access-ci.org");
|
|
7
7
|
}
|
|
8
8
|
get httpClient() {
|
|
9
9
|
if (!this._eventsHttpClient) {
|
|
@@ -28,14 +28,14 @@ export class EventsServer extends BaseAccessServer {
|
|
|
28
28
|
return [
|
|
29
29
|
{
|
|
30
30
|
name: "get_events",
|
|
31
|
-
description: "Get ACCESS-CI events with comprehensive filtering capabilities",
|
|
31
|
+
description: "Get ACCESS-CI events with comprehensive filtering capabilities. Returns events in UTC timezone with enhanced metadata.",
|
|
32
32
|
inputSchema: {
|
|
33
33
|
type: "object",
|
|
34
34
|
properties: {
|
|
35
35
|
// Relative date filtering
|
|
36
36
|
beginning_date_relative: {
|
|
37
37
|
type: "string",
|
|
38
|
-
description: "Start date using relative values
|
|
38
|
+
description: "Start date using relative values. Calculated in UTC by default, or use 'timezone' parameter for local calculations.",
|
|
39
39
|
enum: [
|
|
40
40
|
"today",
|
|
41
41
|
"+1week",
|
|
@@ -50,7 +50,7 @@ export class EventsServer extends BaseAccessServer {
|
|
|
50
50
|
},
|
|
51
51
|
end_date_relative: {
|
|
52
52
|
type: "string",
|
|
53
|
-
description: "End date using relative values
|
|
53
|
+
description: "End date using relative values. Calculated in UTC by default, or use 'timezone' parameter for local calculations.",
|
|
54
54
|
enum: [
|
|
55
55
|
"today",
|
|
56
56
|
"+1week",
|
|
@@ -66,11 +66,17 @@ export class EventsServer extends BaseAccessServer {
|
|
|
66
66
|
// Absolute date filtering
|
|
67
67
|
beginning_date: {
|
|
68
68
|
type: "string",
|
|
69
|
-
description: "Start date in YYYY-MM-DD or YYYY-MM-DD HH:MM:SS format",
|
|
69
|
+
description: "Start date in YYYY-MM-DD or YYYY-MM-DD HH:MM:SS format. Always interpreted as provided (no timezone conversion).",
|
|
70
70
|
},
|
|
71
71
|
end_date: {
|
|
72
72
|
type: "string",
|
|
73
|
-
description: "End date in YYYY-MM-DD or YYYY-MM-DD HH:MM:SS format",
|
|
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",
|
|
74
80
|
},
|
|
75
81
|
// Faceted search filters
|
|
76
82
|
event_type: {
|
|
@@ -102,7 +108,7 @@ export class EventsServer extends BaseAccessServer {
|
|
|
102
108
|
},
|
|
103
109
|
{
|
|
104
110
|
name: "get_upcoming_events",
|
|
105
|
-
description: "Get upcoming ACCESS-CI events (today onward)",
|
|
111
|
+
description: "Get upcoming ACCESS-CI events (from today onward in UTC). Convenient shortcut for get_events with beginning_date_relative=today.",
|
|
106
112
|
inputSchema: {
|
|
107
113
|
type: "object",
|
|
108
114
|
properties: {
|
|
@@ -114,7 +120,12 @@ export class EventsServer extends BaseAccessServer {
|
|
|
114
120
|
},
|
|
115
121
|
event_type: {
|
|
116
122
|
type: "string",
|
|
117
|
-
description: "Filter by event type (workshop, webinar, etc.)",
|
|
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",
|
|
118
129
|
},
|
|
119
130
|
},
|
|
120
131
|
required: [],
|
|
@@ -122,19 +133,24 @@ export class EventsServer extends BaseAccessServer {
|
|
|
122
133
|
},
|
|
123
134
|
{
|
|
124
135
|
name: "search_events",
|
|
125
|
-
description: "Search events
|
|
136
|
+
description: "Search events using API's native full-text search. Searches across titles, descriptions, speakers, tags, location, and event type. Much more powerful than tag filtering.",
|
|
126
137
|
inputSchema: {
|
|
127
138
|
type: "object",
|
|
128
139
|
properties: {
|
|
129
140
|
query: {
|
|
130
141
|
type: "string",
|
|
131
|
-
description: "Search query for event
|
|
142
|
+
description: "Search query (case-insensitive). Use spaces for multiple words (e.g., 'machine learning', 'office hours'). Searches across all event content including descriptions.",
|
|
132
143
|
},
|
|
133
144
|
beginning_date_relative: {
|
|
134
145
|
type: "string",
|
|
135
|
-
description: "Start date using relative values (default: today)",
|
|
146
|
+
description: "Start date using relative values (default: today). Use '-1month' or '-1year' to search past events, or omit for all-time search.",
|
|
136
147
|
default: "today",
|
|
137
148
|
},
|
|
149
|
+
timezone: {
|
|
150
|
+
type: "string",
|
|
151
|
+
description: "Timezone for relative date calculation (default: UTC)",
|
|
152
|
+
default: "UTC",
|
|
153
|
+
},
|
|
138
154
|
limit: {
|
|
139
155
|
type: "number",
|
|
140
156
|
description: "Maximum number of events to return (default: 25)",
|
|
@@ -147,20 +163,25 @@ export class EventsServer extends BaseAccessServer {
|
|
|
147
163
|
},
|
|
148
164
|
{
|
|
149
165
|
name: "get_events_by_tag",
|
|
150
|
-
description: "Get events filtered by specific tags",
|
|
166
|
+
description: "Get events filtered by specific tags. Useful for finding events on topics like 'python', 'ai', 'machine-learning', 'gpu', etc.",
|
|
151
167
|
inputSchema: {
|
|
152
168
|
type: "object",
|
|
153
169
|
properties: {
|
|
154
170
|
tag: {
|
|
155
171
|
type: "string",
|
|
156
|
-
description: "Event tag to filter by
|
|
172
|
+
description: "Event tag to filter by. Common tags: python, ai, machine-learning, gpu, deep-learning, neural-networks, big-data, hpc, jetstream, neocortex",
|
|
157
173
|
},
|
|
158
174
|
time_range: {
|
|
159
175
|
type: "string",
|
|
160
|
-
description: "Time range for events",
|
|
176
|
+
description: "Time range for events (upcoming=today onward, this_week=next 7 days, this_month=next 30 days, all=no date filter)",
|
|
161
177
|
enum: ["upcoming", "this_week", "this_month", "all"],
|
|
162
178
|
default: "upcoming",
|
|
163
179
|
},
|
|
180
|
+
timezone: {
|
|
181
|
+
type: "string",
|
|
182
|
+
description: "Timezone for time_range calculations (default: UTC)",
|
|
183
|
+
default: "UTC",
|
|
184
|
+
},
|
|
164
185
|
limit: {
|
|
165
186
|
type: "number",
|
|
166
187
|
description: "Maximum number of events to return (default: 25)",
|
|
@@ -280,7 +301,11 @@ export class EventsServer extends BaseAccessServer {
|
|
|
280
301
|
}
|
|
281
302
|
}
|
|
282
303
|
buildEventsUrl(params) {
|
|
283
|
-
const url = new URL("/api/2.
|
|
304
|
+
const url = new URL("/api/2.1/events", this.baseURL);
|
|
305
|
+
// Add full-text search parameter (API native search)
|
|
306
|
+
if (params.search_api_fulltext) {
|
|
307
|
+
url.searchParams.set("search_api_fulltext", params.search_api_fulltext);
|
|
308
|
+
}
|
|
284
309
|
// Add date filtering parameters
|
|
285
310
|
if (params.beginning_date_relative) {
|
|
286
311
|
url.searchParams.set("beginning_date_relative", params.beginning_date_relative);
|
|
@@ -294,6 +319,10 @@ export class EventsServer extends BaseAccessServer {
|
|
|
294
319
|
if (params.end_date) {
|
|
295
320
|
url.searchParams.set("end_date", params.end_date);
|
|
296
321
|
}
|
|
322
|
+
// Add timezone parameter for relative date calculations
|
|
323
|
+
if (params.timezone) {
|
|
324
|
+
url.searchParams.set("timezone", params.timezone);
|
|
325
|
+
}
|
|
297
326
|
// Add faceted search filters
|
|
298
327
|
let filterIndex = 0;
|
|
299
328
|
if (params.event_type) {
|
|
@@ -349,6 +378,11 @@ export class EventsServer extends BaseAccessServer {
|
|
|
349
378
|
upcoming_events: enhancedEvents.filter((e) => e.starts_in_hours >= 0)
|
|
350
379
|
.length,
|
|
351
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
|
+
},
|
|
352
386
|
event_types: [
|
|
353
387
|
...new Set(enhancedEvents.map((e) => e.event_type).filter(Boolean)),
|
|
354
388
|
],
|
|
@@ -375,27 +409,30 @@ export class EventsServer extends BaseAccessServer {
|
|
|
375
409
|
...params,
|
|
376
410
|
beginning_date_relative: "today",
|
|
377
411
|
limit: params.limit || 50,
|
|
412
|
+
// Pass through timezone if provided
|
|
413
|
+
...(params.timezone && { timezone: params.timezone }),
|
|
378
414
|
};
|
|
379
415
|
return this.getEvents(upcomingParams);
|
|
380
416
|
}
|
|
381
417
|
async searchEvents(params) {
|
|
418
|
+
// Use API's native full-text search instead of client-side filtering
|
|
382
419
|
const searchParams = {
|
|
420
|
+
search_api_fulltext: params.query,
|
|
383
421
|
beginning_date_relative: params.beginning_date_relative || "today",
|
|
384
422
|
limit: params.limit || 25,
|
|
423
|
+
// Pass through timezone if provided
|
|
424
|
+
...(params.timezone && { timezone: params.timezone }),
|
|
385
425
|
};
|
|
386
|
-
//
|
|
426
|
+
// Use the API's native search capabilities
|
|
387
427
|
const eventsResponse = await this.getEvents(searchParams);
|
|
388
428
|
const eventsData = JSON.parse(eventsResponse.content[0].text);
|
|
389
|
-
|
|
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)));
|
|
429
|
+
// API returns already filtered results, no need for client-side filtering
|
|
394
430
|
const summary = {
|
|
395
431
|
search_query: params.query,
|
|
396
|
-
total_matches:
|
|
397
|
-
|
|
398
|
-
|
|
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,
|
|
399
436
|
};
|
|
400
437
|
return {
|
|
401
438
|
content: [
|
|
@@ -407,7 +444,7 @@ export class EventsServer extends BaseAccessServer {
|
|
|
407
444
|
};
|
|
408
445
|
}
|
|
409
446
|
async getEventsByTag(params) {
|
|
410
|
-
const { tag, time_range = "upcoming", limit = 25 } = params;
|
|
447
|
+
const { tag, time_range = "upcoming", limit = 25, timezone } = params;
|
|
411
448
|
let dateParams = {};
|
|
412
449
|
switch (time_range) {
|
|
413
450
|
case "upcoming":
|
|
@@ -429,6 +466,8 @@ export class EventsServer extends BaseAccessServer {
|
|
|
429
466
|
...dateParams,
|
|
430
467
|
event_tags: tag,
|
|
431
468
|
limit,
|
|
469
|
+
// Pass through timezone if provided
|
|
470
|
+
...(timezone && { timezone }),
|
|
432
471
|
};
|
|
433
472
|
const eventsResponse = await this.getEvents(taggedParams);
|
|
434
473
|
const eventsData = JSON.parse(eventsResponse.content[0].text);
|
package/package.json
CHANGED
|
@@ -164,6 +164,88 @@ describe("EventsServer Integration Tests", () => {
|
|
|
164
164
|
});
|
|
165
165
|
}
|
|
166
166
|
}, 10000);
|
|
167
|
+
|
|
168
|
+
it("should handle timezone parameter with relative dates", async () => {
|
|
169
|
+
const result = await server["handleToolCall"]({
|
|
170
|
+
params: {
|
|
171
|
+
name: "get_events",
|
|
172
|
+
arguments: {
|
|
173
|
+
beginning_date_relative: "today",
|
|
174
|
+
timezone: "America/New_York",
|
|
175
|
+
limit: 5,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const responseData = JSON.parse(result.content[0].text);
|
|
181
|
+
|
|
182
|
+
expect(responseData).toHaveProperty("events");
|
|
183
|
+
expect(responseData).toHaveProperty("total_events");
|
|
184
|
+
expect(responseData).toHaveProperty("api_info");
|
|
185
|
+
|
|
186
|
+
// Should successfully handle timezone parameter (v2.1 API feature)
|
|
187
|
+
expect(typeof responseData.total_events).toBe("number");
|
|
188
|
+
expect(responseData.api_info.timezone_used).toBe("America/New_York");
|
|
189
|
+
expect(responseData.api_info.endpoint_version).toBe("2.1");
|
|
190
|
+
}, 10000);
|
|
191
|
+
|
|
192
|
+
it("should handle upcoming events with timezone", async () => {
|
|
193
|
+
const result = await server["handleToolCall"]({
|
|
194
|
+
params: {
|
|
195
|
+
name: "get_upcoming_events",
|
|
196
|
+
arguments: {
|
|
197
|
+
timezone: "Europe/London",
|
|
198
|
+
limit: 3,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const responseData = JSON.parse(result.content[0].text);
|
|
204
|
+
|
|
205
|
+
expect(responseData).toHaveProperty("events");
|
|
206
|
+
expect(responseData).toHaveProperty("api_info");
|
|
207
|
+
expect(responseData.api_info.timezone_used).toBe("Europe/London");
|
|
208
|
+
}, 10000);
|
|
209
|
+
|
|
210
|
+
it("should handle search with Pacific timezone", async () => {
|
|
211
|
+
const result = await server["handleToolCall"]({
|
|
212
|
+
params: {
|
|
213
|
+
name: "search_events",
|
|
214
|
+
arguments: {
|
|
215
|
+
query: "office",
|
|
216
|
+
timezone: "America/Los_Angeles",
|
|
217
|
+
limit: 2,
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const responseData = JSON.parse(result.content[0].text);
|
|
223
|
+
|
|
224
|
+
expect(responseData).toHaveProperty("search_query");
|
|
225
|
+
expect(responseData.search_query).toBe("office");
|
|
226
|
+
expect(responseData).toHaveProperty("total_matches");
|
|
227
|
+
}, 10000);
|
|
228
|
+
|
|
229
|
+
it("should handle events by tag with timezone", async () => {
|
|
230
|
+
const result = await server["handleToolCall"]({
|
|
231
|
+
params: {
|
|
232
|
+
name: "get_events_by_tag",
|
|
233
|
+
arguments: {
|
|
234
|
+
tag: "ai",
|
|
235
|
+
time_range: "this_week",
|
|
236
|
+
timezone: "Asia/Tokyo",
|
|
237
|
+
limit: 3,
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const responseData = JSON.parse(result.content[0].text);
|
|
243
|
+
|
|
244
|
+
expect(responseData).toHaveProperty("tag");
|
|
245
|
+
expect(responseData.tag).toBe("ai");
|
|
246
|
+
expect(responseData).toHaveProperty("time_range");
|
|
247
|
+
expect(responseData.time_range).toBe("this_week");
|
|
248
|
+
}, 10000);
|
|
167
249
|
});
|
|
168
250
|
|
|
169
251
|
describe("Error Handling with Real API", () => {
|
|
@@ -32,7 +32,7 @@ describe("EventsServer", () => {
|
|
|
32
32
|
it("should initialize with correct server name and version", () => {
|
|
33
33
|
expect(server).toBeDefined();
|
|
34
34
|
expect(server["serverName"]).toBe("access-mcp-events");
|
|
35
|
-
expect(server["version"]).toBe("0.
|
|
35
|
+
expect(server["version"]).toBe("0.2.0");
|
|
36
36
|
expect(server["baseURL"]).toBe("https://support.access-ci.org");
|
|
37
37
|
});
|
|
38
38
|
|
|
@@ -64,6 +64,11 @@ describe("EventsServer", () => {
|
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
describe("URL Building", () => {
|
|
67
|
+
it("should build correct URLs with v2.1 endpoint", () => {
|
|
68
|
+
const url = server["buildEventsUrl"]({});
|
|
69
|
+
expect(url).toContain("/api/2.1/events");
|
|
70
|
+
});
|
|
71
|
+
|
|
67
72
|
it("should build correct URLs with relative date filters", () => {
|
|
68
73
|
const url = server["buildEventsUrl"]({
|
|
69
74
|
beginning_date_relative: "today",
|
|
@@ -74,6 +79,16 @@ describe("EventsServer", () => {
|
|
|
74
79
|
expect(url).toContain("end_date_relative=%2B1week");
|
|
75
80
|
});
|
|
76
81
|
|
|
82
|
+
it("should build correct URLs with timezone parameter", () => {
|
|
83
|
+
const url = server["buildEventsUrl"]({
|
|
84
|
+
beginning_date_relative: "today",
|
|
85
|
+
timezone: "America/New_York",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(url).toContain("beginning_date_relative=today");
|
|
89
|
+
expect(url).toContain("timezone=America%2FNew_York");
|
|
90
|
+
});
|
|
91
|
+
|
|
77
92
|
it("should build correct URLs with absolute date filters", () => {
|
|
78
93
|
const url = server["buildEventsUrl"]({
|
|
79
94
|
beginning_date: "2024-01-01",
|
|
@@ -111,6 +126,66 @@ describe("EventsServer", () => {
|
|
|
111
126
|
expect(url).toContain("f%5B0%5D=custom_event_type%3Awebinar");
|
|
112
127
|
expect(url).toContain("f%5B1%5D=skill_level%3Aintermediate");
|
|
113
128
|
});
|
|
129
|
+
|
|
130
|
+
it("should build correct URLs with timezone and mixed parameters", () => {
|
|
131
|
+
const url = server["buildEventsUrl"]({
|
|
132
|
+
beginning_date_relative: "today",
|
|
133
|
+
end_date_relative: "+1month",
|
|
134
|
+
timezone: "Europe/Berlin",
|
|
135
|
+
event_type: "workshop",
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(url).toContain("beginning_date_relative=today");
|
|
139
|
+
expect(url).toContain("end_date_relative=%2B1month");
|
|
140
|
+
expect(url).toContain("timezone=Europe%2FBerlin");
|
|
141
|
+
expect(url).toContain("f%5B0%5D=custom_event_type%3Aworkshop");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should not include timezone parameter when not provided", () => {
|
|
145
|
+
const url = server["buildEventsUrl"]({
|
|
146
|
+
beginning_date_relative: "today",
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(url).toContain("beginning_date_relative=today");
|
|
150
|
+
expect(url).not.toContain("timezone=");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should handle various timezone formats", () => {
|
|
154
|
+
const timezones = [
|
|
155
|
+
"UTC",
|
|
156
|
+
"America/New_York",
|
|
157
|
+
"America/Los_Angeles",
|
|
158
|
+
"Europe/London",
|
|
159
|
+
"Asia/Tokyo"
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
timezones.forEach(tz => {
|
|
163
|
+
const url = server["buildEventsUrl"]({
|
|
164
|
+
beginning_date_relative: "today",
|
|
165
|
+
timezone: tz,
|
|
166
|
+
});
|
|
167
|
+
expect(url).toContain(`timezone=${encodeURIComponent(tz)}`);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("should include search_api_fulltext parameter", () => {
|
|
172
|
+
const url = server["buildEventsUrl"]({
|
|
173
|
+
search_api_fulltext: "python machine learning",
|
|
174
|
+
beginning_date_relative: "today",
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
expect(url).toContain("search_api_fulltext=python+machine+learning");
|
|
178
|
+
expect(url).toContain("beginning_date_relative=today");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("should not include search parameter when not provided", () => {
|
|
182
|
+
const url = server["buildEventsUrl"]({
|
|
183
|
+
beginning_date_relative: "today",
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expect(url).not.toContain("search_api_fulltext");
|
|
187
|
+
expect(url).toContain("beginning_date_relative=today");
|
|
188
|
+
});
|
|
114
189
|
});
|
|
115
190
|
|
|
116
191
|
describe("Tool Methods", () => {
|
|
@@ -273,6 +348,68 @@ describe("EventsServer", () => {
|
|
|
273
348
|
expect(responseData.popular_tags).toContain("python");
|
|
274
349
|
expect(responseData.popular_tags).toContain("machine-learning");
|
|
275
350
|
});
|
|
351
|
+
|
|
352
|
+
it("should include timezone parameter in URL when provided", async () => {
|
|
353
|
+
mockHttpClient.get.mockResolvedValue({
|
|
354
|
+
status: 200,
|
|
355
|
+
data: mockEventsData,
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const result = await server["handleToolCall"]({
|
|
359
|
+
params: {
|
|
360
|
+
name: "get_events",
|
|
361
|
+
arguments: {
|
|
362
|
+
beginning_date_relative: "today",
|
|
363
|
+
timezone: "America/New_York",
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
369
|
+
expect(calledUrl).toContain("timezone=America%2FNew_York");
|
|
370
|
+
expect(calledUrl).toContain("beginning_date_relative=today");
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("should include API info with timezone used", async () => {
|
|
374
|
+
mockHttpClient.get.mockResolvedValue({
|
|
375
|
+
status: 200,
|
|
376
|
+
data: mockEventsData,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const result = await server["handleToolCall"]({
|
|
380
|
+
params: {
|
|
381
|
+
name: "get_events",
|
|
382
|
+
arguments: {
|
|
383
|
+
beginning_date_relative: "today",
|
|
384
|
+
timezone: "Europe/London",
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const responseData = JSON.parse(result.content[0].text);
|
|
390
|
+
expect(responseData.api_info).toBeDefined();
|
|
391
|
+
expect(responseData.api_info.endpoint_version).toBe("2.1");
|
|
392
|
+
expect(responseData.api_info.timezone_used).toBe("Europe/London");
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("should default to UTC timezone when not specified", async () => {
|
|
396
|
+
mockHttpClient.get.mockResolvedValue({
|
|
397
|
+
status: 200,
|
|
398
|
+
data: mockEventsData,
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
const result = await server["handleToolCall"]({
|
|
402
|
+
params: {
|
|
403
|
+
name: "get_events",
|
|
404
|
+
arguments: {
|
|
405
|
+
beginning_date_relative: "today",
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const responseData = JSON.parse(result.content[0].text);
|
|
411
|
+
expect(responseData.api_info.timezone_used).toBe("UTC");
|
|
412
|
+
});
|
|
276
413
|
});
|
|
277
414
|
|
|
278
415
|
describe("get_upcoming_events", () => {
|
|
@@ -314,6 +451,27 @@ describe("EventsServer", () => {
|
|
|
314
451
|
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
315
452
|
expect(calledUrl).toContain("custom_event_type%3Awebinar");
|
|
316
453
|
});
|
|
454
|
+
|
|
455
|
+
it("should pass timezone parameter to get_events", async () => {
|
|
456
|
+
mockHttpClient.get.mockResolvedValue({
|
|
457
|
+
status: 200,
|
|
458
|
+
data: mockEventsData,
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const result = await server["handleToolCall"]({
|
|
462
|
+
params: {
|
|
463
|
+
name: "get_upcoming_events",
|
|
464
|
+
arguments: {
|
|
465
|
+
timezone: "America/Chicago",
|
|
466
|
+
limit: 5,
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
472
|
+
expect(calledUrl).toContain("beginning_date_relative=today");
|
|
473
|
+
expect(calledUrl).toContain("timezone=America%2FChicago");
|
|
474
|
+
});
|
|
317
475
|
});
|
|
318
476
|
|
|
319
477
|
describe("search_events", () => {
|
|
@@ -338,7 +496,7 @@ describe("EventsServer", () => {
|
|
|
338
496
|
expect(responseData.events[0].title).toContain("Python");
|
|
339
497
|
});
|
|
340
498
|
|
|
341
|
-
it("should search
|
|
499
|
+
it("should use API native search instead of client-side filtering", async () => {
|
|
342
500
|
mockHttpClient.get.mockResolvedValue({
|
|
343
501
|
status: 200,
|
|
344
502
|
data: mockEventsData,
|
|
@@ -353,12 +511,16 @@ describe("EventsServer", () => {
|
|
|
353
511
|
},
|
|
354
512
|
});
|
|
355
513
|
|
|
514
|
+
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
515
|
+
expect(calledUrl).toContain("search_api_fulltext=ML");
|
|
516
|
+
|
|
356
517
|
const responseData = JSON.parse(result.content[0].text);
|
|
357
|
-
expect(responseData.
|
|
358
|
-
expect(responseData.
|
|
518
|
+
expect(responseData.search_query).toBe("ML");
|
|
519
|
+
expect(responseData.search_method).toBe("API native full-text search");
|
|
520
|
+
expect(responseData.total_matches).toBe(mockEventsData.length); // API returns raw count
|
|
359
521
|
});
|
|
360
522
|
|
|
361
|
-
it("should search
|
|
523
|
+
it("should include search scope information", async () => {
|
|
362
524
|
mockHttpClient.get.mockResolvedValue({
|
|
363
525
|
status: 200,
|
|
364
526
|
data: mockEventsData,
|
|
@@ -373,8 +535,11 @@ describe("EventsServer", () => {
|
|
|
373
535
|
},
|
|
374
536
|
});
|
|
375
537
|
|
|
538
|
+
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
539
|
+
expect(calledUrl).toContain("search_api_fulltext=machine-learning");
|
|
540
|
+
|
|
376
541
|
const responseData = JSON.parse(result.content[0].text);
|
|
377
|
-
expect(responseData.
|
|
542
|
+
expect(responseData.search_scope).toBe("titles, descriptions, speakers, tags, location, event type");
|
|
378
543
|
});
|
|
379
544
|
|
|
380
545
|
it("should search with custom date range", async () => {
|
|
@@ -397,6 +562,27 @@ describe("EventsServer", () => {
|
|
|
397
562
|
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
398
563
|
expect(calledUrl).toContain("beginning_date_relative=-1month");
|
|
399
564
|
});
|
|
565
|
+
|
|
566
|
+
it("should include timezone parameter in search", async () => {
|
|
567
|
+
mockHttpClient.get.mockResolvedValue({
|
|
568
|
+
status: 200,
|
|
569
|
+
data: mockEventsData,
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
const result = await server["handleToolCall"]({
|
|
573
|
+
params: {
|
|
574
|
+
name: "search_events",
|
|
575
|
+
arguments: {
|
|
576
|
+
query: "python",
|
|
577
|
+
timezone: "Asia/Tokyo",
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
583
|
+
expect(calledUrl).toContain("timezone=Asia%2FTokyo");
|
|
584
|
+
expect(calledUrl).toContain("beginning_date_relative=today");
|
|
585
|
+
});
|
|
400
586
|
});
|
|
401
587
|
|
|
402
588
|
describe("get_events_by_tag", () => {
|
|
@@ -487,6 +673,30 @@ describe("EventsServer", () => {
|
|
|
487
673
|
expect(calledUrl).not.toContain("beginning_date_relative");
|
|
488
674
|
expect(calledUrl).not.toContain("end_date_relative");
|
|
489
675
|
});
|
|
676
|
+
|
|
677
|
+
it("should include timezone parameter for time-based ranges", async () => {
|
|
678
|
+
mockHttpClient.get.mockResolvedValue({
|
|
679
|
+
status: 200,
|
|
680
|
+
data: mockEventsData,
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
const result = await server["handleToolCall"]({
|
|
684
|
+
params: {
|
|
685
|
+
name: "get_events_by_tag",
|
|
686
|
+
arguments: {
|
|
687
|
+
tag: "ai",
|
|
688
|
+
time_range: "this_week",
|
|
689
|
+
timezone: "Australia/Sydney",
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
695
|
+
expect(calledUrl).toContain("custom_event_tags%3Aai");
|
|
696
|
+
expect(calledUrl).toContain("beginning_date_relative=today");
|
|
697
|
+
expect(calledUrl).toContain("end_date_relative=%2B1week");
|
|
698
|
+
expect(calledUrl).toContain("timezone=Australia%2FSydney");
|
|
699
|
+
});
|
|
490
700
|
});
|
|
491
701
|
|
|
492
702
|
describe("Error Handling", () => {
|
package/src/server.ts
CHANGED
|
@@ -5,7 +5,7 @@ export class EventsServer extends BaseAccessServer {
|
|
|
5
5
|
private _eventsHttpClient?: AxiosInstance;
|
|
6
6
|
|
|
7
7
|
constructor() {
|
|
8
|
-
super("access-mcp-events", "0.
|
|
8
|
+
super("access-mcp-events", "0.2.0", "https://support.access-ci.org");
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
protected get httpClient(): AxiosInstance {
|
|
@@ -35,7 +35,7 @@ export class EventsServer extends BaseAccessServer {
|
|
|
35
35
|
{
|
|
36
36
|
name: "get_events",
|
|
37
37
|
description:
|
|
38
|
-
"Get ACCESS-CI events with comprehensive filtering capabilities",
|
|
38
|
+
"Get ACCESS-CI events with comprehensive filtering capabilities. Returns events in UTC timezone with enhanced metadata.",
|
|
39
39
|
inputSchema: {
|
|
40
40
|
type: "object",
|
|
41
41
|
properties: {
|
|
@@ -43,7 +43,7 @@ export class EventsServer extends BaseAccessServer {
|
|
|
43
43
|
beginning_date_relative: {
|
|
44
44
|
type: "string",
|
|
45
45
|
description:
|
|
46
|
-
"Start date using relative values
|
|
46
|
+
"Start date using relative values. Calculated in UTC by default, or use 'timezone' parameter for local calculations.",
|
|
47
47
|
enum: [
|
|
48
48
|
"today",
|
|
49
49
|
"+1week",
|
|
@@ -59,7 +59,7 @@ export class EventsServer extends BaseAccessServer {
|
|
|
59
59
|
end_date_relative: {
|
|
60
60
|
type: "string",
|
|
61
61
|
description:
|
|
62
|
-
"End date using relative values
|
|
62
|
+
"End date using relative values. Calculated in UTC by default, or use 'timezone' parameter for local calculations.",
|
|
63
63
|
enum: [
|
|
64
64
|
"today",
|
|
65
65
|
"+1week",
|
|
@@ -76,12 +76,19 @@ export class EventsServer extends BaseAccessServer {
|
|
|
76
76
|
beginning_date: {
|
|
77
77
|
type: "string",
|
|
78
78
|
description:
|
|
79
|
-
"Start date in YYYY-MM-DD or YYYY-MM-DD HH:MM:SS format",
|
|
79
|
+
"Start date in YYYY-MM-DD or YYYY-MM-DD HH:MM:SS format. Always interpreted as provided (no timezone conversion).",
|
|
80
80
|
},
|
|
81
81
|
end_date: {
|
|
82
82
|
type: "string",
|
|
83
83
|
description:
|
|
84
|
-
"End date in YYYY-MM-DD or YYYY-MM-DD HH:MM:SS format",
|
|
84
|
+
"End date in YYYY-MM-DD or YYYY-MM-DD HH:MM:SS format. Always interpreted as provided (no timezone conversion).",
|
|
85
|
+
},
|
|
86
|
+
// Timezone parameter for relative date calculations
|
|
87
|
+
timezone: {
|
|
88
|
+
type: "string",
|
|
89
|
+
description:
|
|
90
|
+
"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.",
|
|
91
|
+
default: "UTC",
|
|
85
92
|
},
|
|
86
93
|
// Faceted search filters
|
|
87
94
|
event_type: {
|
|
@@ -116,7 +123,7 @@ export class EventsServer extends BaseAccessServer {
|
|
|
116
123
|
},
|
|
117
124
|
{
|
|
118
125
|
name: "get_upcoming_events",
|
|
119
|
-
description: "Get upcoming ACCESS-CI events (today onward)",
|
|
126
|
+
description: "Get upcoming ACCESS-CI events (from today onward in UTC). Convenient shortcut for get_events with beginning_date_relative=today.",
|
|
120
127
|
inputSchema: {
|
|
121
128
|
type: "object",
|
|
122
129
|
properties: {
|
|
@@ -128,7 +135,12 @@ export class EventsServer extends BaseAccessServer {
|
|
|
128
135
|
},
|
|
129
136
|
event_type: {
|
|
130
137
|
type: "string",
|
|
131
|
-
description: "Filter by event type (workshop, webinar, etc.)",
|
|
138
|
+
description: "Filter by event type (workshop, webinar, Office Hours, Training, etc.)",
|
|
139
|
+
},
|
|
140
|
+
timezone: {
|
|
141
|
+
type: "string",
|
|
142
|
+
description: "Timezone for 'today' calculation (default: UTC). Use user's local timezone for better relevance.",
|
|
143
|
+
default: "UTC",
|
|
132
144
|
},
|
|
133
145
|
},
|
|
134
146
|
required: [],
|
|
@@ -136,19 +148,24 @@ export class EventsServer extends BaseAccessServer {
|
|
|
136
148
|
},
|
|
137
149
|
{
|
|
138
150
|
name: "search_events",
|
|
139
|
-
description: "Search events
|
|
151
|
+
description: "Search events using API's native full-text search. Searches across titles, descriptions, speakers, tags, location, and event type. Much more powerful than tag filtering.",
|
|
140
152
|
inputSchema: {
|
|
141
153
|
type: "object",
|
|
142
154
|
properties: {
|
|
143
155
|
query: {
|
|
144
156
|
type: "string",
|
|
145
|
-
description: "Search query for event
|
|
157
|
+
description: "Search query (case-insensitive). Use spaces for multiple words (e.g., 'machine learning', 'office hours'). Searches across all event content including descriptions.",
|
|
146
158
|
},
|
|
147
159
|
beginning_date_relative: {
|
|
148
160
|
type: "string",
|
|
149
|
-
description: "Start date using relative values (default: today)",
|
|
161
|
+
description: "Start date using relative values (default: today). Use '-1month' or '-1year' to search past events, or omit for all-time search.",
|
|
150
162
|
default: "today",
|
|
151
163
|
},
|
|
164
|
+
timezone: {
|
|
165
|
+
type: "string",
|
|
166
|
+
description: "Timezone for relative date calculation (default: UTC)",
|
|
167
|
+
default: "UTC",
|
|
168
|
+
},
|
|
152
169
|
limit: {
|
|
153
170
|
type: "number",
|
|
154
171
|
description: "Maximum number of events to return (default: 25)",
|
|
@@ -161,21 +178,26 @@ export class EventsServer extends BaseAccessServer {
|
|
|
161
178
|
},
|
|
162
179
|
{
|
|
163
180
|
name: "get_events_by_tag",
|
|
164
|
-
description: "Get events filtered by specific tags",
|
|
181
|
+
description: "Get events filtered by specific tags. Useful for finding events on topics like 'python', 'ai', 'machine-learning', 'gpu', etc.",
|
|
165
182
|
inputSchema: {
|
|
166
183
|
type: "object",
|
|
167
184
|
properties: {
|
|
168
185
|
tag: {
|
|
169
186
|
type: "string",
|
|
170
187
|
description:
|
|
171
|
-
"Event tag to filter by
|
|
188
|
+
"Event tag to filter by. Common tags: python, ai, machine-learning, gpu, deep-learning, neural-networks, big-data, hpc, jetstream, neocortex",
|
|
172
189
|
},
|
|
173
190
|
time_range: {
|
|
174
191
|
type: "string",
|
|
175
|
-
description: "Time range for events",
|
|
192
|
+
description: "Time range for events (upcoming=today onward, this_week=next 7 days, this_month=next 30 days, all=no date filter)",
|
|
176
193
|
enum: ["upcoming", "this_week", "this_month", "all"],
|
|
177
194
|
default: "upcoming",
|
|
178
195
|
},
|
|
196
|
+
timezone: {
|
|
197
|
+
type: "string",
|
|
198
|
+
description: "Timezone for time_range calculations (default: UTC)",
|
|
199
|
+
default: "UTC",
|
|
200
|
+
},
|
|
179
201
|
limit: {
|
|
180
202
|
type: "number",
|
|
181
203
|
description: "Maximum number of events to return (default: 25)",
|
|
@@ -301,7 +323,12 @@ export class EventsServer extends BaseAccessServer {
|
|
|
301
323
|
}
|
|
302
324
|
|
|
303
325
|
private buildEventsUrl(params: any): string {
|
|
304
|
-
const url = new URL("/api/2.
|
|
326
|
+
const url = new URL("/api/2.1/events", this.baseURL);
|
|
327
|
+
|
|
328
|
+
// Add full-text search parameter (API native search)
|
|
329
|
+
if (params.search_api_fulltext) {
|
|
330
|
+
url.searchParams.set("search_api_fulltext", params.search_api_fulltext);
|
|
331
|
+
}
|
|
305
332
|
|
|
306
333
|
// Add date filtering parameters
|
|
307
334
|
if (params.beginning_date_relative) {
|
|
@@ -320,6 +347,11 @@ export class EventsServer extends BaseAccessServer {
|
|
|
320
347
|
url.searchParams.set("end_date", params.end_date);
|
|
321
348
|
}
|
|
322
349
|
|
|
350
|
+
// Add timezone parameter for relative date calculations
|
|
351
|
+
if (params.timezone) {
|
|
352
|
+
url.searchParams.set("timezone", params.timezone);
|
|
353
|
+
}
|
|
354
|
+
|
|
323
355
|
// Add faceted search filters
|
|
324
356
|
let filterIndex = 0;
|
|
325
357
|
if (params.event_type) {
|
|
@@ -405,6 +437,11 @@ export class EventsServer extends BaseAccessServer {
|
|
|
405
437
|
events_this_week: enhancedEvents.filter(
|
|
406
438
|
(e: any) => e.starts_in_hours <= 168 && e.starts_in_hours >= 0,
|
|
407
439
|
).length,
|
|
440
|
+
api_info: {
|
|
441
|
+
endpoint_version: "2.1",
|
|
442
|
+
timezone_handling: "All timestamps in UTC (Z suffix). Relative dates calculated using timezone parameter (default: UTC).",
|
|
443
|
+
timezone_used: params.timezone || "UTC",
|
|
444
|
+
},
|
|
408
445
|
event_types: [
|
|
409
446
|
...new Set(
|
|
410
447
|
enhancedEvents.map((e: any) => e.event_type).filter(Boolean),
|
|
@@ -439,35 +476,34 @@ export class EventsServer extends BaseAccessServer {
|
|
|
439
476
|
...params,
|
|
440
477
|
beginning_date_relative: "today",
|
|
441
478
|
limit: params.limit || 50,
|
|
479
|
+
// Pass through timezone if provided
|
|
480
|
+
...(params.timezone && { timezone: params.timezone }),
|
|
442
481
|
};
|
|
443
482
|
|
|
444
483
|
return this.getEvents(upcomingParams);
|
|
445
484
|
}
|
|
446
485
|
|
|
447
486
|
private async searchEvents(params: any) {
|
|
487
|
+
// Use API's native full-text search instead of client-side filtering
|
|
448
488
|
const searchParams = {
|
|
489
|
+
search_api_fulltext: params.query,
|
|
449
490
|
beginning_date_relative: params.beginning_date_relative || "today",
|
|
450
491
|
limit: params.limit || 25,
|
|
492
|
+
// Pass through timezone if provided
|
|
493
|
+
...(params.timezone && { timezone: params.timezone }),
|
|
451
494
|
};
|
|
452
495
|
|
|
453
|
-
//
|
|
496
|
+
// Use the API's native search capabilities
|
|
454
497
|
const eventsResponse = await this.getEvents(searchParams);
|
|
455
498
|
const eventsData = JSON.parse(eventsResponse.content[0].text);
|
|
456
499
|
|
|
457
|
-
|
|
458
|
-
const filteredEvents = eventsData.events.filter(
|
|
459
|
-
(event: any) =>
|
|
460
|
-
event.title?.toLowerCase().includes(query) ||
|
|
461
|
-
event.description?.toLowerCase().includes(query) ||
|
|
462
|
-
event.speakers?.toLowerCase().includes(query) ||
|
|
463
|
-
event.tags?.some((tag: string) => tag.toLowerCase().includes(query)),
|
|
464
|
-
);
|
|
465
|
-
|
|
500
|
+
// API returns already filtered results, no need for client-side filtering
|
|
466
501
|
const summary = {
|
|
467
502
|
search_query: params.query,
|
|
468
|
-
total_matches:
|
|
469
|
-
|
|
470
|
-
|
|
503
|
+
total_matches: eventsData.total_events,
|
|
504
|
+
search_method: "API native full-text search",
|
|
505
|
+
search_scope: "titles, descriptions, speakers, tags, location, event type",
|
|
506
|
+
events: eventsData.events,
|
|
471
507
|
};
|
|
472
508
|
|
|
473
509
|
return {
|
|
@@ -481,7 +517,7 @@ export class EventsServer extends BaseAccessServer {
|
|
|
481
517
|
}
|
|
482
518
|
|
|
483
519
|
private async getEventsByTag(params: any) {
|
|
484
|
-
const { tag, time_range = "upcoming", limit = 25 } = params;
|
|
520
|
+
const { tag, time_range = "upcoming", limit = 25, timezone } = params;
|
|
485
521
|
|
|
486
522
|
let dateParams: any = {};
|
|
487
523
|
switch (time_range) {
|
|
@@ -505,6 +541,8 @@ export class EventsServer extends BaseAccessServer {
|
|
|
505
541
|
...dateParams,
|
|
506
542
|
event_tags: tag,
|
|
507
543
|
limit,
|
|
544
|
+
// Pass through timezone if provided
|
|
545
|
+
...(timezone && { timezone }),
|
|
508
546
|
};
|
|
509
547
|
|
|
510
548
|
const eventsResponse = await this.getEvents(taggedParams);
|