@access-mcp/events 0.3.0 → 0.3.1

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 CHANGED
@@ -1,266 +1,74 @@
1
1
  # ACCESS-CI Events MCP Server
2
2
 
3
- A Model Context Protocol server providing access to ACCESS-CI events data including workshops, webinars, and training sessions with comprehensive filtering capabilities.
4
-
5
- ## Features
6
-
7
- ### 🔧 Tools
8
-
9
- - **`search_events`** - Comprehensive event search and filtering with native full-text search, date filters, topic/tag filtering, and more
10
-
11
- ### 📊 Resources
12
-
13
- - **`accessci://events`** - All events data
14
- - **`accessci://events/upcoming`** - Upcoming events only
15
- - **`accessci://events/workshops`** - Workshop events only
16
- - **`accessci://events/webinars`** - Webinar events only
17
-
18
- ## Installation
19
-
20
- ```bash
21
- npm install -g @access-mcp/events
22
- ```
23
-
24
- ## Usage
25
-
26
- ### Claude Desktop Configuration
27
-
28
- Add to your Claude Desktop configuration:
29
-
30
- ```json
31
- {
32
- "mcpServers": {
33
- "access-events": {
34
- "command": "npx",
35
- "args": ["@access-mcp/events"]
36
- }
37
- }
38
- }
39
- ```
3
+ MCP server for ACCESS-CI events including workshops, webinars, and training sessions.
40
4
 
41
5
  ## Usage Examples
42
6
 
43
- ### **Search & Discovery**
44
-
7
+ ### Search & Discovery
45
8
  ```
46
- "Upcoming events next week"
9
+ "Upcoming ACCESS events"
47
10
  "Python workshops"
48
- "Machine learning events"
11
+ "Machine learning training"
49
12
  "Office hours this week"
50
13
  ```
51
14
 
52
- ### **Filtering**
53
-
15
+ ### Filtering
54
16
  ```
55
- "Beginner-level events this month"
56
- "GPU computing events"
57
- "Webinars in December"
17
+ "Beginner events this month"
18
+ "GPU computing workshops"
58
19
  "Advanced training sessions"
59
20
  ```
60
21
 
61
- ## Key Improvements
62
-
63
- ### 🚀 Enhanced Search (v2.1)
64
-
65
- **Native API Full-Text Search:**
66
- - Searches across titles, descriptions, speakers, tags, location, and event type
67
- - Supports multi-word queries (e.g., "machine learning", "office hours")
68
- - Much more comprehensive results than previous tag-only filtering
69
- - Server-side indexing for better performance
70
-
71
- **Search Examples:**
72
- - `"python"` - Find Python programming events
73
- - `"machine learning"` - Find ML-related content in any field
74
- - `"gpu computing"` - Find GPU-related events
75
- - `"office hours"` - Find all office hours sessions
76
-
77
- ### 🌍 Timezone Support (v2.1)
78
-
79
- **Smart Timezone Handling:**
80
- - All timestamps returned in UTC (ISO 8601 format with Z suffix)
81
- - Timezone parameter controls relative date calculations
82
- - Common timezone examples: `America/New_York`, `Europe/London`, `Asia/Tokyo`
83
- - Default: UTC calculations
84
-
85
- ## Filtering Capabilities
86
-
87
- ### Date Filtering
88
-
89
- **Relative Dates (Dynamic):**
90
-
91
- - `today` - Current date
92
- - `+1week`, `+2week` - Future weeks
93
- - `+1month`, `+2month` - Future months
94
- - `+1year` - Future year
95
- - `-1week`, `-1month` - Past periods
96
-
97
- **Absolute Dates (Fixed):**
98
-
99
- - `YYYY-MM-DD` format (e.g., "2024-08-30")
100
- - `YYYY-MM-DD HH:MM:SS` format with time
101
-
102
- **Mixed Filtering:**
103
- You can combine relative and absolute dates in the same query.
104
-
105
- ### Faceted Search Filters
22
+ ## Tools
106
23
 
107
- - **Event Type:** workshop, webinar, etc.
108
- - **Event Affiliation:** Community, ACCESS, etc.
109
- - **Skill Level:** beginner, intermediate, advanced
110
- - **Event Tags:** python, big-data, machine-learning, gpu, etc.
24
+ ### `search_events`
111
25
 
112
- ## API Details
113
-
114
- ### Event Object Structure
115
-
116
- Each event contains:
117
-
118
- - `id` - Unique identifier
119
- - `title` - Event title
120
- - `description` - Event description
121
- - `date` - Start date/time (ISO 8601)
122
- - `date_1` - End date/time (ISO 8601)
123
- - `location` - Event location
124
- - `event_type` - Type of event
125
- - `event_affiliation` - Organizational affiliation
126
- - `custom_event_tags` - Comma-separated tags
127
- - `skill_level` - Required skill level
128
- - `speakers` - Event speakers
129
- - `contact` - Contact information
130
- - `registration` - Registration URL/info
131
- - `created` - Creation timestamp
132
- - `changed` - Last modified timestamp
133
-
134
- ### Enhanced Fields
135
-
136
- The server adds these computed fields:
137
-
138
- - `start_date` - Parsed start date object
139
- - `end_date` - Parsed end date object
140
- - `tags` - Tags split into array
141
- - `duration_hours` - Calculated event duration
142
- - `starts_in_hours` - Hours until event starts
143
-
144
- ## Tool Reference
145
-
146
- ### search_events
147
-
148
- Comprehensive event search and filtering with native full-text search, date filters, and topic/tag filtering.
26
+ Search and filter ACCESS-CI events.
149
27
 
150
28
  **Parameters:**
151
- - `query` (string, optional): Search titles, descriptions, speakers, and tags
152
- - `type` (string, optional): Filter by event type (workshop, webinar, training)
153
- - `tags` (string, optional): Filter by tags (python, gpu, hpc, ml)
154
- - `date` (string, optional): Filter by time period. Valid values: `today`, `upcoming`, `past`, `this_week`, `this_month`
155
- - `skill` (string, optional): Filter by skill level. Valid values: `beginner`, `intermediate`, `advanced`
156
- - `limit` (number, optional): Maximum results (default: 50, automatically rounded to API page sizes: 25, 50, 75, or 100)
157
-
158
- ## Usage Examples
159
-
160
- ### Full-Text Search
161
-
29
+ | Parameter | Type | Description |
30
+ |-----------|------|-------------|
31
+ | `query` | string | Search titles, descriptions, speakers, tags |
32
+ | `type` | string | Filter: `workshop`, `webinar`, `training` |
33
+ | `tags` | string | Filter: `python`, `gpu`, `hpc`, `ml` |
34
+ | `date` | enum | Time period: `today`, `upcoming`, `past`, `this_week`, `this_month` |
35
+ | `skill` | enum | Skill level: `beginner`, `intermediate`, `advanced` |
36
+ | `limit` | number | Max results (default: 50) |
37
+
38
+ **Examples:**
162
39
  ```javascript
163
40
  // Upcoming Python events
164
- search_events({
165
- query: "python",
166
- date: "upcoming",
167
- limit: 10
168
- })
41
+ search_events({ query: "python", date: "upcoming", limit: 10 })
169
42
 
170
43
  // Machine learning workshops this month
171
- search_events({
172
- query: "machine learning",
173
- date: "this_month",
174
- type: "workshop",
175
- limit: 25
176
- })
177
-
178
- // Past office hours
179
- search_events({
180
- query: "office hours",
181
- date: "past",
182
- limit: 30
183
- })
184
- ```
44
+ search_events({ query: "machine learning", date: "this_month", type: "workshop" })
185
45
 
186
- ### Filter by Tags and Type
187
-
188
- ```javascript
189
- // All upcoming GPU events
190
- search_events({
191
- tags: "gpu",
192
- date: "upcoming",
193
- limit: 20
194
- })
195
-
196
- // Beginner training sessions
197
- search_events({
198
- type: "training",
199
- skill: "beginner",
200
- date: "upcoming",
201
- limit: 15
202
- })
203
-
204
- // Python workshops for beginners
205
- search_events({
206
- date: "this_month",
207
- type: "workshop",
208
- skill: "beginner",
209
- tags: "python",
210
- limit: 25
211
- })
46
+ // Beginner GPU training
47
+ search_events({ tags: "gpu", skill: "beginner", date: "upcoming" })
212
48
  ```
213
49
 
214
- ### Timezone-Aware Searches
215
-
216
- ```javascript
217
- // Get events in New York timezone
218
- search_events({
219
- beginning_date_relative: "today",
220
- end_date_relative: "+1week",
221
- timezone: "America/New_York",
222
- limit: 30
223
- })
224
-
225
- // Get events in Europe/London timezone
226
- search_events({
227
- query: "workshop",
228
- beginning_date_relative: "today",
229
- timezone: "Europe/London",
230
- limit: 25
231
- })
232
- ```
233
-
234
- ## Development
50
+ ## Installation
235
51
 
236
52
  ```bash
237
- # Build the server
238
- npm run build
239
-
240
- # Run in development
241
- npm run dev
242
-
243
- # Test the server
244
- npm test
53
+ npm install -g @access-mcp/events
245
54
  ```
246
55
 
247
- ## Base URL
56
+ ## Configuration
248
57
 
249
- The server connects to: `https://support.access-ci.org/api/2.2/events`
250
-
251
- ## Technical Notes
58
+ ```json
59
+ {
60
+ "mcpServers": {
61
+ "access-events": {
62
+ "command": "npx",
63
+ "args": ["@access-mcp/events"]
64
+ }
65
+ }
66
+ }
67
+ ```
252
68
 
253
- ### API Version 2.2 Features
254
- - **UTC timestamps**: All dates returned in UTC with Z suffix (e.g., `2024-08-30T13:00:00Z`)
255
- - **Native search**: Uses `search_api_fulltext` parameter for comprehensive searching
256
- - **Timezone support**: Relative dates calculated using specified timezone
257
- - **Pagination**: API accepts specific page sizes (25, 50, 75, or 100). Requested limits are automatically rounded to nearest valid size.
258
- - **Enhanced metadata**: Responses include API version and timezone info
69
+ ## Resources
259
70
 
260
- ### General
261
- - All date comparisons use the event's start date (`date` field)
262
- - Results include both upcoming and past events unless date filtered
263
- - Faceted search filters use AND logic when combined
264
- - Response times are typically under 5 seconds
265
- - No pagination - all matching events are returned
266
- - URL encoding is handled automatically for special characters
71
+ - `accessci://events` - All events data
72
+ - `accessci://events/upcoming` - Upcoming events
73
+ - `accessci://events/workshops` - Workshop events
74
+ - `accessci://events/webinars` - Webinar events
package/dist/server.d.ts CHANGED
@@ -1,65 +1,31 @@
1
- import { BaseAccessServer } from "@access-mcp/shared";
1
+ import { BaseAccessServer, Tool, Resource, CallToolResult } from "@access-mcp/shared";
2
+ import { CallToolRequest, ReadResourceRequest, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
2
3
  import { AxiosInstance } from "axios";
3
4
  export declare class EventsServer extends BaseAccessServer {
4
5
  private _eventsHttpClient?;
6
+ private drupalAuth?;
5
7
  constructor();
8
+ /**
9
+ * Get or create the Drupal auth provider for authenticated operations.
10
+ * Requires DRUPAL_API_URL, DRUPAL_USERNAME, and DRUPAL_PASSWORD env vars.
11
+ */
12
+ private getDrupalAuth;
13
+ /**
14
+ * Get the acting user's ACCESS ID for filtering.
15
+ */
16
+ private getActingUserAccessId;
6
17
  protected get httpClient(): AxiosInstance;
7
- protected getTools(): {
8
- name: string;
9
- description: string;
10
- inputSchema: {
11
- type: string;
12
- properties: {
13
- query: {
14
- type: string;
15
- description: string;
16
- };
17
- type: {
18
- type: string;
19
- description: string;
20
- };
21
- tags: {
22
- type: string;
23
- description: string;
24
- };
25
- date: {
26
- type: string;
27
- description: string;
28
- enum: string[];
29
- };
30
- skill: {
31
- type: string;
32
- description: string;
33
- enum: string[];
34
- };
35
- limit: {
36
- type: string;
37
- description: string;
38
- default: number;
39
- };
40
- };
41
- };
42
- }[];
43
- protected getResources(): {
44
- uri: string;
45
- name: string;
46
- description: string;
47
- mimeType: string;
48
- }[];
49
- handleToolCall(request: any): Promise<{
50
- content: {
51
- type: string;
52
- text: string;
53
- }[];
54
- }>;
55
- handleResourceRead(request: any): Promise<{
56
- contents: {
57
- uri: string;
58
- mimeType: string;
59
- text: string;
60
- }[];
61
- }>;
18
+ protected getTools(): Tool[];
19
+ protected getResources(): Resource[];
20
+ protected handleToolCall(request: CallToolRequest): Promise<CallToolResult>;
21
+ protected handleResourceRead(request: ReadResourceRequest): Promise<ReadResourceResult>;
62
22
  private buildEventsUrl;
63
23
  private getEvents;
64
24
  private searchEvents;
25
+ /**
26
+ * Get events for the authenticated user via the unified Drupal view.
27
+ * Uses the /jsonapi/views/event_instance_mine/my_events_page endpoint
28
+ * which filters by X-Acting-User header.
29
+ */
30
+ private getMyEvents;
65
31
  }
package/dist/server.js CHANGED
@@ -1,9 +1,53 @@
1
- import { BaseAccessServer, handleApiError } from "@access-mcp/shared";
1
+ import { BaseAccessServer, handleApiError, DrupalAuthProvider, getRequestContext, } from "@access-mcp/shared";
2
2
  import axios from "axios";
3
+ import { createRequire } from "module";
4
+ const require = createRequire(import.meta.url);
5
+ const { version } = require("../package.json");
3
6
  export class EventsServer extends BaseAccessServer {
4
7
  _eventsHttpClient;
8
+ drupalAuth;
5
9
  constructor() {
6
- super("access-mcp-events", "0.3.0", "https://support.access-ci.org");
10
+ super("access-mcp-events", version, "https://support.access-ci.org", {
11
+ requireApiKey: true,
12
+ });
13
+ }
14
+ /**
15
+ * Get or create the Drupal auth provider for authenticated operations.
16
+ * Requires DRUPAL_API_URL, DRUPAL_USERNAME, and DRUPAL_PASSWORD env vars.
17
+ */
18
+ getDrupalAuth() {
19
+ if (!this.drupalAuth) {
20
+ const baseUrl = process.env.DRUPAL_API_URL;
21
+ const username = process.env.DRUPAL_USERNAME;
22
+ const password = process.env.DRUPAL_PASSWORD;
23
+ if (!baseUrl || !username || !password) {
24
+ throw new Error("Authenticated operations require DRUPAL_API_URL, DRUPAL_USERNAME, and DRUPAL_PASSWORD environment variables");
25
+ }
26
+ this.drupalAuth = new DrupalAuthProvider(baseUrl, username, password);
27
+ }
28
+ // Update acting user from request context or env var
29
+ const context = getRequestContext();
30
+ const actingUser = context?.actingUser || process.env.ACTING_USER;
31
+ if (actingUser) {
32
+ this.drupalAuth.setActingUser(actingUser);
33
+ }
34
+ return this.drupalAuth;
35
+ }
36
+ /**
37
+ * Get the acting user's ACCESS ID for filtering.
38
+ */
39
+ getActingUserAccessId() {
40
+ const context = getRequestContext();
41
+ if (context?.actingUser) {
42
+ return context.actingUser;
43
+ }
44
+ const envUser = process.env.ACTING_USER;
45
+ if (envUser) {
46
+ return envUser;
47
+ }
48
+ throw new Error("Cannot get user events: No acting user specified.\n\n" +
49
+ "Either set the X-Acting-User header or the ACTING_USER environment variable " +
50
+ "to the ACCESS ID (e.g., username@access-ci.org).");
7
51
  }
8
52
  get httpClient() {
9
53
  if (!this._eventsHttpClient) {
@@ -34,34 +78,53 @@ export class EventsServer extends BaseAccessServer {
34
78
  properties: {
35
79
  query: {
36
80
  type: "string",
37
- description: "Search titles, descriptions, speakers, tags"
81
+ description: "Search titles, descriptions, speakers, tags",
38
82
  },
39
83
  type: {
40
84
  type: "string",
41
- description: "Filter: workshop, webinar, training"
85
+ description: "Filter: workshop, webinar, training",
42
86
  },
43
87
  tags: {
44
88
  type: "string",
45
- description: "Filter: python, gpu, hpc, ml"
89
+ description: "Filter: python, gpu, hpc, ml",
46
90
  },
47
91
  date: {
48
92
  type: "string",
49
93
  description: "Filter by time period",
50
- enum: ["today", "upcoming", "past", "this_week", "this_month"]
94
+ enum: ["today", "upcoming", "past", "this_week", "this_month"],
51
95
  },
52
96
  skill: {
53
97
  type: "string",
54
98
  description: "Skill level filter",
55
- enum: ["beginner", "intermediate", "advanced"]
99
+ enum: ["beginner", "intermediate", "advanced"],
56
100
  },
57
101
  limit: {
58
102
  type: "number",
59
103
  description: "Max results (default: 50)",
60
- default: 50
61
- }
62
- }
63
- }
64
- }
104
+ default: 50,
105
+ },
106
+ },
107
+ },
108
+ },
109
+ {
110
+ name: "get_my_events",
111
+ description: `Get events created by or associated with the authenticated user.
112
+
113
+ Returns events the user has created or is associated with, including unpublished/draft events.
114
+ Requires authentication via X-Acting-User header or ACTING_USER environment variable.
115
+
116
+ Returns: {total, items: [{title, start_date, end_date, status, ...}]}`,
117
+ inputSchema: {
118
+ type: "object",
119
+ properties: {
120
+ limit: {
121
+ type: "number",
122
+ description: "Max results (default: 50)",
123
+ default: 50,
124
+ },
125
+ },
126
+ },
127
+ },
65
128
  ];
66
129
  }
67
130
  getResources() {
@@ -98,6 +161,8 @@ export class EventsServer extends BaseAccessServer {
98
161
  switch (name) {
99
162
  case "search_events":
100
163
  return await this.searchEvents(args);
164
+ case "get_my_events":
165
+ return await this.getMyEvents(args);
101
166
  default:
102
167
  return this.errorResponse(`Unknown tool: ${name}`);
103
168
  }
@@ -109,18 +174,30 @@ export class EventsServer extends BaseAccessServer {
109
174
  async handleResourceRead(request) {
110
175
  const { uri } = request.params;
111
176
  switch (uri) {
112
- case "accessci://events":
177
+ case "accessci://events": {
113
178
  const allEvents = await this.searchEvents({});
114
- return this.createJsonResource(uri, JSON.parse(allEvents.content[0].text));
115
- case "accessci://events/upcoming":
179
+ const content = allEvents.content[0];
180
+ const text = content.type === "text" ? content.text : "";
181
+ return this.createJsonResource(uri, JSON.parse(text));
182
+ }
183
+ case "accessci://events/upcoming": {
116
184
  const upcomingEvents = await this.searchEvents({ date: "upcoming" });
117
- return this.createJsonResource(uri, JSON.parse(upcomingEvents.content[0].text));
118
- case "accessci://events/workshops":
185
+ const content = upcomingEvents.content[0];
186
+ const text = content.type === "text" ? content.text : "";
187
+ return this.createJsonResource(uri, JSON.parse(text));
188
+ }
189
+ case "accessci://events/workshops": {
119
190
  const workshops = await this.searchEvents({ type: "workshop" });
120
- return this.createJsonResource(uri, JSON.parse(workshops.content[0].text));
121
- case "accessci://events/webinars":
191
+ const content = workshops.content[0];
192
+ const text = content.type === "text" ? content.text : "";
193
+ return this.createJsonResource(uri, JSON.parse(text));
194
+ }
195
+ case "accessci://events/webinars": {
122
196
  const webinars = await this.searchEvents({ type: "webinar" });
123
- return this.createJsonResource(uri, JSON.parse(webinars.content[0].text));
197
+ const content = webinars.content[0];
198
+ const text = content.type === "text" ? content.text : "";
199
+ return this.createJsonResource(uri, JSON.parse(text));
200
+ }
124
201
  default:
125
202
  throw new Error(`Unknown resource: ${uri}`);
126
203
  }
@@ -149,7 +226,7 @@ export class EventsServer extends BaseAccessServer {
149
226
  upcoming: { start: "today" },
150
227
  past: { start: "-1year", end: "today" },
151
228
  this_week: { start: "today", end: "+1week" },
152
- this_month: { start: "today", end: "+1month" }
229
+ this_month: { start: "today", end: "+1month" },
153
230
  };
154
231
  if (params.date && dateMap[params.date]) {
155
232
  const dateMapping = dateMap[params.date];
@@ -158,20 +235,9 @@ export class EventsServer extends BaseAccessServer {
158
235
  url.searchParams.set("end_date_relative", dateMapping.end);
159
236
  }
160
237
  }
161
- // Faceted filters
162
- let filterIndex = 0;
163
- if (params.type) {
164
- url.searchParams.set(`f[${filterIndex}]`, `custom_event_type:${params.type}`);
165
- filterIndex++;
166
- }
167
- if (params.tags) {
168
- url.searchParams.set(`f[${filterIndex}]`, `custom_event_tags:${params.tags}`);
169
- filterIndex++;
170
- }
171
- if (params.skill) {
172
- url.searchParams.set(`f[${filterIndex}]`, `skill_level:${params.skill}`);
173
- filterIndex++;
174
- }
238
+ // Note: faceted filters (f[0]=field:value) are not supported on the
239
+ // data_export API display — they're bound to the page display in Drupal.
240
+ // Type, tags, and skill filters are applied client-side in getEvents().
175
241
  return url.toString();
176
242
  }
177
243
  async getEvents(params) {
@@ -180,7 +246,24 @@ export class EventsServer extends BaseAccessServer {
180
246
  if (response.status !== 200) {
181
247
  throw new Error(`API error ${response.status}`);
182
248
  }
183
- let events = response.data || [];
249
+ // Ensure response is an array (non-array means unexpected response like 403 HTML)
250
+ let events = Array.isArray(response.data) ? response.data : [];
251
+ // Client-side filters (faceted search not available on data_export display)
252
+ if (params.type) {
253
+ const typeFilter = params.type.toLowerCase();
254
+ events = events.filter((e) => (e.event_type || "").toLowerCase() === typeFilter);
255
+ }
256
+ if (params.tags) {
257
+ const tagFilter = params.tags.toLowerCase();
258
+ events = events.filter((e) => {
259
+ const tags = typeof e.tags === "string" ? e.tags : Array.isArray(e.tags) ? e.tags.join(",") : "";
260
+ return tags.toLowerCase().includes(tagFilter);
261
+ });
262
+ }
263
+ if (params.skill) {
264
+ const skillFilter = params.skill.toLowerCase();
265
+ events = events.filter((e) => (e.skill_level || "").toLowerCase() === skillFilter);
266
+ }
184
267
  if (params.limit && events.length > params.limit) {
185
268
  events = events.slice(0, params.limit);
186
269
  }
@@ -188,18 +271,21 @@ export class EventsServer extends BaseAccessServer {
188
271
  ...event,
189
272
  tags: Array.isArray(event.tags) ? event.tags : [],
190
273
  duration_hours: event.end_date
191
- ? Math.round((new Date(event.end_date).getTime() - new Date(event.start_date).getTime()) / 3600000)
274
+ ? Math.round((new Date(event.end_date).getTime() - new Date(event.start_date || "").getTime()) /
275
+ 3600000)
192
276
  : null,
193
- starts_in_hours: Math.max(0, Math.round((new Date(event.start_date).getTime() - Date.now()) / 3600000))
277
+ starts_in_hours: Math.max(0, Math.round((new Date(event.start_date || "").getTime() - Date.now()) / 3600000)),
194
278
  }));
195
279
  return {
196
- content: [{
280
+ content: [
281
+ {
197
282
  type: "text",
198
283
  text: JSON.stringify({
199
284
  total: enhancedEvents.length,
200
- items: enhancedEvents
201
- })
202
- }]
285
+ items: enhancedEvents,
286
+ }),
287
+ },
288
+ ],
203
289
  };
204
290
  }
205
291
  async searchEvents(params) {
@@ -214,4 +300,39 @@ export class EventsServer extends BaseAccessServer {
214
300
  }
215
301
  return await this.getEvents(params);
216
302
  }
303
+ /**
304
+ * Get events for the authenticated user via the unified Drupal view.
305
+ * Uses the /jsonapi/views/event_instance_mine/my_events_page endpoint
306
+ * which filters by X-Acting-User header.
307
+ */
308
+ async getMyEvents(params) {
309
+ const auth = this.getDrupalAuth();
310
+ // Ensure we have an acting user
311
+ this.getActingUserAccessId();
312
+ const limit = params.limit || 50;
313
+ // Use the unified view endpoint - it respects X-Acting-User header
314
+ const result = await auth.get(`/jsonapi/views/event_instance_mine/my_events_page?page[limit]=${limit}`);
315
+ // JSON:API views return data in a different format
316
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- JSON:API response shape is dynamic
317
+ const events = (result.data || []).map((item) => ({
318
+ id: item.id,
319
+ type: item.type,
320
+ title: item.attributes?.title,
321
+ start_date: item.attributes?.field_start_date || item.attributes?.start_date,
322
+ end_date: item.attributes?.field_end_date || item.attributes?.end_date,
323
+ status: item.attributes?.status ? "published" : "draft",
324
+ ...item.attributes,
325
+ }));
326
+ return {
327
+ content: [
328
+ {
329
+ type: "text",
330
+ text: JSON.stringify({
331
+ total: events.length,
332
+ items: events,
333
+ }),
334
+ },
335
+ ],
336
+ };
337
+ }
217
338
  }