@protocoltooling/fullcalendar 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Robertonevarez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # FullCalendar WebMCP
2
+
3
+ **FullCalendar WebMCP** is published by **Protocol Tooling**.
4
+
5
+ It exposes standard WebMCP calendar tools (`calendar_get_context`, `calendar_list_events`, `calendar_get_event`, `calendar_create_event`, `calendar_update_event`, `calendar_delete_event`) over existing FullCalendar React instances and host persistence without altering the application's UI, mutation workflows, or database architecture.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @protocoltooling/fullcalendar
11
+ ```
12
+
13
+ ### Local / Tarball Installation (Development)
14
+
15
+ ```bash
16
+ npm pack
17
+ npm install /path/to/protocoltooling-fullcalendar-0.1.0.tgz
18
+ ```
19
+
20
+ ### Peer Dependencies
21
+
22
+ Ensure host peer dependencies are installed:
23
+
24
+ - `react`: `>=17 <20`
25
+ - `@fullcalendar/react`: `^6.0.0 || ^7.0.0`
26
+
27
+ ---
28
+
29
+ ## Minimal Integration
30
+
31
+ ```tsx
32
+ import FullCalendar from "@fullcalendar/react"; // v6 or v7
33
+ import { useCallback, useRef, useState } from "react";
34
+ import {
35
+ useFullCalendarWebMCP,
36
+ type CalendarEvent,
37
+ type CalendarEventRepository,
38
+ } from "@protocoltooling/fullcalendar";
39
+
40
+ export function MyCalendar() {
41
+ const calendarRef = useRef<FullCalendar>(null);
42
+ const [events, setEvents] = useState<CalendarEvent[]>([]);
43
+
44
+ const reloadEvents = useCallback(async () => {
45
+ const data = await eventRepository.list();
46
+ setEvents(data);
47
+ return data;
48
+ }, []);
49
+
50
+ useFullCalendarWebMCP({
51
+ calendarRef,
52
+ events: eventRepository,
53
+ onEventsChanged: reloadEvents,
54
+ });
55
+
56
+ return (
57
+ <FullCalendar
58
+ ref={calendarRef}
59
+ events={events}
60
+ /* ... other FullCalendar options */
61
+ />
62
+ );
63
+ }
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Repository Contract
69
+
70
+ The host provides an object implementing `CalendarEventRepository` mapped to its existing database or API:
71
+
72
+ ```ts
73
+ import type {
74
+ CalendarEvent,
75
+ CalendarEventQuery,
76
+ CalendarEventRepository,
77
+ CreateCalendarEventInput,
78
+ UpdateCalendarEventInput,
79
+ } from "@protocoltooling/fullcalendar";
80
+
81
+ export const eventRepository: CalendarEventRepository = {
82
+ async list(query?: CalendarEventQuery, options?: { signal?: AbortSignal }): Promise<CalendarEvent[]> {
83
+ return api.events.list(query, options);
84
+ },
85
+ async get(id: string, options?: { signal?: AbortSignal }): Promise<CalendarEvent | null> {
86
+ return api.events.get(id, options);
87
+ },
88
+ async create(input: CreateCalendarEventInput, options?: { signal?: AbortSignal }): Promise<CalendarEvent> {
89
+ return api.events.create(input, options);
90
+ },
91
+ async update(id: string, input: UpdateCalendarEventInput, options?: { signal?: AbortSignal }): Promise<CalendarEvent> {
92
+ return api.events.update(id, input, options);
93
+ },
94
+ async delete(id: string, options?: { signal?: AbortSignal }): Promise<void> {
95
+ return api.events.delete(id, options);
96
+ },
97
+ };
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Persistence Model
103
+
104
+ > **FullCalendar WebMCP does not persist calendar events itself. The host application's persistence remains authoritative.**
105
+
106
+ Agent mutations and human interactions (e.g. dragging, resizing, modal editing) converge on the host application's authoritative persistence. The integration maintains no secondary event ledger.
107
+
108
+ ---
109
+
110
+ ## Supported Architecture
111
+
112
+ - **React:** React 17, 18, and 19.
113
+ - **FullCalendar React:** FullCalendar React v6 class instances (`RefObject<FullCalendar | null>`) and FullCalendar React v7 handles (`RefObject<CalendarRef | null>`).
114
+ - **Runtime:** WebMCP runtime required for tool registration (`document.modelContext.registerTool`).
115
+ - **Framework Agnostic:** Compatible with Next.js (App Router & Pages Router), Vite, Remix / React Router, TanStack Start, or pure client SPAs.
116
+ - **Backend Agnostic:** Works with Server Actions, REST, GraphQL, Supabase / PostgreSQL, ORMs, or custom APIs.
117
+
118
+ ---
119
+
120
+ ## Server-Side Rendering (SSR) & Lifecycle
121
+
122
+ - **SSR-Safe:** The package contains no top-level browser global (`window`, `document`) access. Server rendering completes without error.
123
+ - **Client Lifecycle:** Tool registration occurs inside `useEffect` via an `AbortController`. React Strict Mode replay and unmount cleanup properly unregister tools with zero orphaned state.
124
+ - **Delayed Runtime:** Automatically waits for late-injected WebMCP runtime if not immediately present at mount time.
125
+ - **React Server Components (RSC):** The package entry includes the `'use client';` directive for seamless import into Next.js App Router client components.
126
+
127
+ ---
128
+
129
+ ## Package migration
130
+
131
+ Previous package: `protocoltooling`
132
+ Current package: `@protocoltooling/fullcalendar`
133
+ The old package is deprecated.
134
+
135
+ ---
136
+
137
+ ## Development & Verification
138
+
139
+ ```bash
140
+ # Build library package (dist/index.js, dist/index.d.ts)
141
+ npm run build:lib
142
+
143
+ # Run unit, integration, public type, and SSR tests
144
+ npm test
145
+
146
+ # Run typecheck & linter
147
+ npm run typecheck
148
+ npm run lint
149
+
150
+ # Build package tarball and test in external Vite and Next.js consumers
151
+ npm run test:pack
152
+
153
+ # Run interactive dev example
154
+ npm run dev
155
+ ```
156
+
157
+ ## License
158
+
159
+ MIT
@@ -0,0 +1,92 @@
1
+ import { RefObject } from 'react';
2
+
3
+ /**
4
+ * Normalized calendar event model.
5
+ */
6
+ interface CalendarEvent {
7
+ id: string;
8
+ title: string;
9
+ start: string;
10
+ end: string | null;
11
+ allDay: boolean;
12
+ }
13
+ /**
14
+ * Input payload for creating a new calendar event.
15
+ */
16
+ interface CreateCalendarEventInput {
17
+ title: string;
18
+ start: string;
19
+ end?: string | null;
20
+ allDay?: boolean;
21
+ }
22
+ /**
23
+ * Input payload for updating an existing calendar event.
24
+ */
25
+ interface UpdateCalendarEventInput {
26
+ title?: string;
27
+ start?: string;
28
+ end?: string | null;
29
+ allDay?: boolean;
30
+ }
31
+ /**
32
+ * Filter criteria for querying calendar events.
33
+ */
34
+ interface CalendarEventQuery {
35
+ start?: string;
36
+ end?: string;
37
+ text?: string;
38
+ }
39
+ /**
40
+ * Authoritative host repository contract for calendar event persistence.
41
+ * The host application implements this interface over its existing storage or backend API.
42
+ */
43
+ interface CalendarEventRepository {
44
+ list(query?: CalendarEventQuery, options?: {
45
+ signal?: AbortSignal;
46
+ }): Promise<CalendarEvent[]>;
47
+ get(id: string, options?: {
48
+ signal?: AbortSignal;
49
+ }): Promise<CalendarEvent | null>;
50
+ create(input: CreateCalendarEventInput, options?: {
51
+ signal?: AbortSignal;
52
+ }): Promise<CalendarEvent>;
53
+ update(id: string, input: UpdateCalendarEventInput, options?: {
54
+ signal?: AbortSignal;
55
+ }): Promise<CalendarEvent>;
56
+ delete(id: string, options?: {
57
+ signal?: AbortSignal;
58
+ }): Promise<void>;
59
+ }
60
+ /**
61
+ * Minimal structural FullCalendar surface required by WebMCP tools.
62
+ * Compatible with FullCalendar React v6 class instances and v7 CalendarRef handles.
63
+ */
64
+ interface FullCalendarHandle {
65
+ getApi(): {
66
+ getOption(name: string): unknown;
67
+ view: {
68
+ type: string;
69
+ activeStart: Date;
70
+ activeEnd: Date;
71
+ };
72
+ };
73
+ }
74
+ /**
75
+ * Options for the useFullCalendarWebMCP hook.
76
+ */
77
+ interface FullCalendarWebMCPOptions {
78
+ calendarRef: RefObject<FullCalendarHandle | null>;
79
+ events: CalendarEventRepository;
80
+ onEventsChanged: () => unknown | Promise<unknown>;
81
+ onRegistrationError?: (error: unknown) => void;
82
+ }
83
+
84
+ /**
85
+ * Primary React hook integrating FullCalendar with the browser WebMCP model context.
86
+ *
87
+ * Provides safe client-side registration, unmount cleanup, and continuous
88
+ * binding to host persistence callbacks without re-registering tools across renders.
89
+ */
90
+ declare function useFullCalendarWebMCP(options: FullCalendarWebMCPOptions): void;
91
+
92
+ export { type CalendarEvent, type CalendarEventQuery, type CalendarEventRepository, type CreateCalendarEventInput, type FullCalendarHandle, type FullCalendarWebMCPOptions, type UpdateCalendarEventInput, useFullCalendarWebMCP };
package/dist/index.js ADDED
@@ -0,0 +1,229 @@
1
+ 'use client';
2
+ import { useRef, useEffect } from 'react';
3
+
4
+ // src/use-fullcalendar-webmcp.ts
5
+
6
+ // src/tool-definitions.ts
7
+ var emptySchema = {
8
+ type: "object",
9
+ properties: {},
10
+ additionalProperties: false
11
+ };
12
+ var eventProperties = {
13
+ title: {
14
+ type: "string",
15
+ minLength: 1,
16
+ description: "The human-readable event title."
17
+ },
18
+ start: {
19
+ type: "string",
20
+ format: "date-time",
21
+ description: "Inclusive event start as ISO 8601 with an explicit offset."
22
+ },
23
+ end: {
24
+ type: ["string", "null"],
25
+ format: "date-time",
26
+ description: "Exclusive event end as ISO 8601 with an explicit offset, or null."
27
+ },
28
+ allDay: {
29
+ type: "boolean",
30
+ description: "Whether the event is an all-day event."
31
+ }
32
+ };
33
+ function readAnnotations() {
34
+ return { readOnlyHint: true, untrustedContentHint: true };
35
+ }
36
+ function writeAnnotations() {
37
+ return { readOnlyHint: false, untrustedContentHint: true };
38
+ }
39
+ function createCalendarTools(readOptions) {
40
+ return [
41
+ {
42
+ name: "calendar_get_context",
43
+ title: "Get calendar context",
44
+ description: "Get the current time, resolved browser timezone, FullCalendar timezone, visible date range, and active view. Call this before interpreting relative dates such as Wednesday or tomorrow.",
45
+ inputSchema: emptySchema,
46
+ annotations: readAnnotations(),
47
+ async execute() {
48
+ const api = readOptions().calendarRef.current?.getApi();
49
+ return {
50
+ now: (/* @__PURE__ */ new Date()).toISOString(),
51
+ browserTimeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
52
+ fullCalendarTimeZone: api?.getOption("timeZone") ?? "local",
53
+ view: api?.view.type ?? null,
54
+ visibleRange: api ? {
55
+ start: api.view.activeStart.toISOString(),
56
+ end: api.view.activeEnd.toISOString()
57
+ } : null
58
+ };
59
+ }
60
+ },
61
+ {
62
+ name: "calendar_list_events",
63
+ title: "List calendar events",
64
+ description: "List persisted calendar events, optionally filtered by an inclusive start, exclusive end, or case-insensitive title text. Use an end boundary one day after the requested day.",
65
+ inputSchema: {
66
+ type: "object",
67
+ properties: {
68
+ start: {
69
+ type: "string",
70
+ format: "date-time",
71
+ description: "Inclusive ISO 8601 lower bound."
72
+ },
73
+ end: {
74
+ type: "string",
75
+ format: "date-time",
76
+ description: "Exclusive ISO 8601 upper bound."
77
+ },
78
+ text: {
79
+ type: "string",
80
+ description: "Optional case-insensitive title substring."
81
+ }
82
+ },
83
+ additionalProperties: false
84
+ },
85
+ annotations: readAnnotations(),
86
+ async execute(query, { signal }) {
87
+ const events = await readOptions().events.list(query, { signal });
88
+ return { events };
89
+ }
90
+ },
91
+ {
92
+ name: "calendar_get_event",
93
+ title: "Get calendar event",
94
+ description: "Get one persisted calendar event by its stable event ID.",
95
+ inputSchema: {
96
+ type: "object",
97
+ properties: {
98
+ id: { type: "string", minLength: 1, description: "Stable event ID." }
99
+ },
100
+ required: ["id"],
101
+ additionalProperties: false
102
+ },
103
+ annotations: readAnnotations(),
104
+ async execute(input, { signal }) {
105
+ const { id } = input;
106
+ const event = await readOptions().events.get(id, { signal });
107
+ return { event };
108
+ }
109
+ },
110
+ {
111
+ name: "calendar_create_event",
112
+ title: "Create calendar event",
113
+ description: "Create and persist a generic calendar event. Times must be ISO 8601 values with explicit offsets. Returns the host-assigned stable event ID.",
114
+ inputSchema: {
115
+ type: "object",
116
+ properties: eventProperties,
117
+ required: ["title", "start"],
118
+ additionalProperties: false
119
+ },
120
+ annotations: writeAnnotations(),
121
+ async execute(rawInput, { signal }) {
122
+ const input = rawInput;
123
+ const options = readOptions();
124
+ const event = await options.events.create(input, { signal });
125
+ await options.onEventsChanged();
126
+ return { event };
127
+ }
128
+ },
129
+ {
130
+ name: "calendar_update_event",
131
+ title: "Update calendar event",
132
+ description: "Update selected fields on one persisted calendar event using its stable event ID. Omitted fields remain unchanged.",
133
+ inputSchema: {
134
+ type: "object",
135
+ properties: {
136
+ id: { type: "string", minLength: 1, description: "Stable event ID." },
137
+ ...eventProperties
138
+ },
139
+ required: ["id"],
140
+ minProperties: 2,
141
+ additionalProperties: false
142
+ },
143
+ annotations: writeAnnotations(),
144
+ async execute(rawInput, { signal }) {
145
+ const { id, ...input } = rawInput;
146
+ const options = readOptions();
147
+ const event = await options.events.update(id, input, { signal });
148
+ await options.onEventsChanged();
149
+ return { event };
150
+ }
151
+ },
152
+ {
153
+ name: "calendar_delete_event",
154
+ title: "Delete calendar event",
155
+ description: "Delete one persisted calendar event by its stable event ID.",
156
+ inputSchema: {
157
+ type: "object",
158
+ properties: {
159
+ id: { type: "string", minLength: 1, description: "Stable event ID." }
160
+ },
161
+ required: ["id"],
162
+ additionalProperties: false
163
+ },
164
+ annotations: writeAnnotations(),
165
+ async execute(input, { signal }) {
166
+ const { id } = input;
167
+ const options = readOptions();
168
+ await options.events.delete(id, { signal });
169
+ await options.onEventsChanged();
170
+ return { deleted: true, id };
171
+ }
172
+ }
173
+ ];
174
+ }
175
+
176
+ // src/register-tools.ts
177
+ var RUNTIME_POLL_INTERVAL_MS = 250;
178
+ function registerCalendarToolsWhenAvailable(readOptions, lifecycleSignal) {
179
+ let retryTimer;
180
+ const register = async () => {
181
+ if (lifecycleSignal.aborted) return;
182
+ const modelContext = document.modelContext;
183
+ if (!modelContext) {
184
+ retryTimer = window.setTimeout(register, RUNTIME_POLL_INTERVAL_MS);
185
+ return;
186
+ }
187
+ const registration = new AbortController();
188
+ const unregister = () => registration.abort(lifecycleSignal.reason);
189
+ lifecycleSignal.addEventListener("abort", unregister, { once: true });
190
+ try {
191
+ await Promise.all(
192
+ createCalendarTools(readOptions).map(
193
+ (tool) => modelContext.registerTool(tool, { signal: registration.signal })
194
+ )
195
+ );
196
+ } catch (error) {
197
+ registration.abort();
198
+ if (!lifecycleSignal.aborted) {
199
+ readOptions().onRegistrationError?.(error);
200
+ }
201
+ }
202
+ };
203
+ void register();
204
+ lifecycleSignal.addEventListener(
205
+ "abort",
206
+ () => {
207
+ if (retryTimer !== void 0) window.clearTimeout(retryTimer);
208
+ },
209
+ { once: true }
210
+ );
211
+ }
212
+
213
+ // src/use-fullcalendar-webmcp.ts
214
+ function useFullCalendarWebMCP(options) {
215
+ const latestOptions = useRef(options);
216
+ latestOptions.current = options;
217
+ useEffect(() => {
218
+ const lifecycle = new AbortController();
219
+ registerCalendarToolsWhenAvailable(
220
+ () => latestOptions.current,
221
+ lifecycle.signal
222
+ );
223
+ return () => lifecycle.abort();
224
+ }, []);
225
+ }
226
+
227
+ export { useFullCalendarWebMCP };
228
+ //# sourceMappingURL=index.js.map
229
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tool-definitions.ts","../src/register-tools.ts","../src/use-fullcalendar-webmcp.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/// <reference types=\"webmcp-types\" />\n\nimport type {\n CreateCalendarEventInput,\n FullCalendarWebMCPOptions,\n UpdateCalendarEventInput,\n} from \"./types\";\n\ntype OptionsReader = () => FullCalendarWebMCPOptions;\n\nconst emptySchema = {\n type: \"object\",\n properties: {},\n additionalProperties: false,\n} as const;\n\nconst eventProperties = {\n title: {\n type: \"string\",\n minLength: 1,\n description: \"The human-readable event title.\",\n },\n start: {\n type: \"string\",\n format: \"date-time\",\n description: \"Inclusive event start as ISO 8601 with an explicit offset.\",\n },\n end: {\n type: [\"string\", \"null\"],\n format: \"date-time\",\n description: \"Exclusive event end as ISO 8601 with an explicit offset, or null.\",\n },\n allDay: {\n type: \"boolean\",\n description: \"Whether the event is an all-day event.\",\n },\n} as const;\n\nfunction readAnnotations(): WebMCP.ToolAnnotations {\n return { readOnlyHint: true, untrustedContentHint: true };\n}\n\nfunction writeAnnotations(): WebMCP.ToolAnnotations {\n return { readOnlyHint: false, untrustedContentHint: true };\n}\n\nexport function createCalendarTools(\n readOptions: OptionsReader,\n): WebMCP.ModelContextTool[] {\n return [\n {\n name: \"calendar_get_context\",\n title: \"Get calendar context\",\n description:\n \"Get the current time, resolved browser timezone, FullCalendar timezone, visible date range, and active view. Call this before interpreting relative dates such as Wednesday or tomorrow.\",\n inputSchema: emptySchema,\n annotations: readAnnotations(),\n async execute() {\n const api = readOptions().calendarRef.current?.getApi();\n return {\n now: new Date().toISOString(),\n browserTimeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n fullCalendarTimeZone: api?.getOption(\"timeZone\") ?? \"local\",\n view: api?.view.type ?? null,\n visibleRange: api\n ? {\n start: api.view.activeStart.toISOString(),\n end: api.view.activeEnd.toISOString(),\n }\n : null,\n };\n },\n },\n {\n name: \"calendar_list_events\",\n title: \"List calendar events\",\n description:\n \"List persisted calendar events, optionally filtered by an inclusive start, exclusive end, or case-insensitive title text. Use an end boundary one day after the requested day.\",\n inputSchema: {\n type: \"object\",\n properties: {\n start: {\n type: \"string\",\n format: \"date-time\",\n description: \"Inclusive ISO 8601 lower bound.\",\n },\n end: {\n type: \"string\",\n format: \"date-time\",\n description: \"Exclusive ISO 8601 upper bound.\",\n },\n text: {\n type: \"string\",\n description: \"Optional case-insensitive title substring.\",\n },\n },\n additionalProperties: false,\n },\n annotations: readAnnotations(),\n async execute(\n query: { start?: string; end?: string; text?: string },\n { signal },\n ) {\n const events = await readOptions().events.list(query, { signal });\n return { events };\n },\n },\n {\n name: \"calendar_get_event\",\n title: \"Get calendar event\",\n description: \"Get one persisted calendar event by its stable event ID.\",\n inputSchema: {\n type: \"object\",\n properties: {\n id: { type: \"string\", minLength: 1, description: \"Stable event ID.\" },\n },\n required: [\"id\"],\n additionalProperties: false,\n },\n annotations: readAnnotations(),\n async execute(input, { signal }) {\n const { id } = input as { id: string };\n const event = await readOptions().events.get(id, { signal });\n return { event };\n },\n },\n {\n name: \"calendar_create_event\",\n title: \"Create calendar event\",\n description:\n \"Create and persist a generic calendar event. Times must be ISO 8601 values with explicit offsets. Returns the host-assigned stable event ID.\",\n inputSchema: {\n type: \"object\",\n properties: eventProperties,\n required: [\"title\", \"start\"],\n additionalProperties: false,\n },\n annotations: writeAnnotations(),\n async execute(rawInput, { signal }) {\n const input = rawInput as unknown as CreateCalendarEventInput;\n const options = readOptions();\n const event = await options.events.create(input, { signal });\n await options.onEventsChanged();\n return { event };\n },\n },\n {\n name: \"calendar_update_event\",\n title: \"Update calendar event\",\n description:\n \"Update selected fields on one persisted calendar event using its stable event ID. Omitted fields remain unchanged.\",\n inputSchema: {\n type: \"object\",\n properties: {\n id: { type: \"string\", minLength: 1, description: \"Stable event ID.\" },\n ...eventProperties,\n },\n required: [\"id\"],\n minProperties: 2,\n additionalProperties: false,\n },\n annotations: writeAnnotations(),\n async execute(rawInput, { signal }) {\n const { id, ...input } = rawInput as unknown as UpdateCalendarEventInput & {\n id: string;\n };\n const options = readOptions();\n const event = await options.events.update(id, input, { signal });\n await options.onEventsChanged();\n return { event };\n },\n },\n {\n name: \"calendar_delete_event\",\n title: \"Delete calendar event\",\n description: \"Delete one persisted calendar event by its stable event ID.\",\n inputSchema: {\n type: \"object\",\n properties: {\n id: { type: \"string\", minLength: 1, description: \"Stable event ID.\" },\n },\n required: [\"id\"],\n additionalProperties: false,\n },\n annotations: writeAnnotations(),\n async execute(input, { signal }) {\n const { id } = input as { id: string };\n const options = readOptions();\n await options.events.delete(id, { signal });\n await options.onEventsChanged();\n return { deleted: true, id };\n },\n },\n ];\n}\n","import { createCalendarTools } from \"./tool-definitions\";\nimport type { FullCalendarWebMCPOptions } from \"./types\";\n\nconst RUNTIME_POLL_INTERVAL_MS = 250;\n\nexport function registerCalendarToolsWhenAvailable(\n readOptions: () => FullCalendarWebMCPOptions,\n lifecycleSignal: AbortSignal,\n) {\n let retryTimer: number | undefined;\n\n const register = async () => {\n if (lifecycleSignal.aborted) return;\n\n const modelContext = document.modelContext;\n if (!modelContext) {\n retryTimer = window.setTimeout(register, RUNTIME_POLL_INTERVAL_MS);\n return;\n }\n\n const registration = new AbortController();\n const unregister = () => registration.abort(lifecycleSignal.reason);\n lifecycleSignal.addEventListener(\"abort\", unregister, { once: true });\n\n try {\n await Promise.all(\n createCalendarTools(readOptions).map((tool) =>\n modelContext.registerTool(tool, { signal: registration.signal }),\n ),\n );\n } catch (error) {\n registration.abort();\n if (!lifecycleSignal.aborted) {\n readOptions().onRegistrationError?.(error);\n }\n }\n };\n\n void register();\n\n lifecycleSignal.addEventListener(\n \"abort\",\n () => {\n if (retryTimer !== undefined) window.clearTimeout(retryTimer);\n },\n { once: true },\n );\n}\n","import { useEffect, useRef } from \"react\";\nimport { registerCalendarToolsWhenAvailable } from \"./register-tools\";\nimport type { FullCalendarWebMCPOptions } from \"./types\";\n\n/**\n * Primary React hook integrating FullCalendar with the browser WebMCP model context.\n *\n * Provides safe client-side registration, unmount cleanup, and continuous\n * binding to host persistence callbacks without re-registering tools across renders.\n */\nexport function useFullCalendarWebMCP(options: FullCalendarWebMCPOptions): void {\n const latestOptions = useRef(options);\n latestOptions.current = options;\n\n useEffect(() => {\n const lifecycle = new AbortController();\n registerCalendarToolsWhenAvailable(\n () => latestOptions.current,\n lifecycle.signal,\n );\n return () => lifecycle.abort();\n }, []);\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "@protocoltooling/fullcalendar",
3
+ "version": "0.1.0",
4
+ "description": "Add WebMCP tools to FullCalendar React applications.",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "keywords": [
9
+ "webmcp",
10
+ "fullcalendar",
11
+ "react",
12
+ "mcp",
13
+ "model-context-protocol",
14
+ "calendar",
15
+ "ai-agents"
16
+ ],
17
+ "type": "module",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/robertonevarez/protocoltooling.git"
22
+ },
23
+ "homepage": "https://github.com/robertonevarez/protocoltooling#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/robertonevarez/protocoltooling/issues"
26
+ },
27
+ "main": "./dist/index.js",
28
+ "module": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js",
34
+ "default": "./dist/index.js"
35
+ }
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "README.md",
40
+ "LICENSE"
41
+ ],
42
+ "sideEffects": false,
43
+ "scripts": {
44
+ "dev": "vite",
45
+ "build": "npm run build:lib && vite build",
46
+ "build:lib": "tsup",
47
+ "prepublishOnly": "npm run build:lib",
48
+ "docs:dev": "blume dev",
49
+ "docs:build": "blume build",
50
+ "docs:check": "blume check",
51
+ "docs:validate": "blume validate",
52
+ "docs:audit": "blume audit",
53
+ "lint": "eslint .",
54
+ "typecheck": "tsc --noEmit -p tsconfig.json",
55
+ "test": "vitest run",
56
+ "test:watch": "vitest",
57
+ "test:types": "tsc --noEmit -p tsconfig.json",
58
+ "test:pack": "node ./scripts/test-pack.js"
59
+ },
60
+ "peerDependencies": {
61
+ "@fullcalendar/react": "^6.0.0 || ^7.0.0",
62
+ "react": ">=17 <20"
63
+ },
64
+ "devDependencies": {
65
+ "@eslint/js": "10.0.1",
66
+ "@fullcalendar/react": "7.0.2",
67
+ "@testing-library/jest-dom": "7.0.1",
68
+ "@testing-library/react": "16.3.3",
69
+ "@types/node": "24.10.13",
70
+ "@types/react": "19.2.14",
71
+ "@types/react-dom": "19.2.3",
72
+ "@vitejs/plugin-react": "6.1.1",
73
+ "blume": "^1.5.3",
74
+ "eslint": "10.0.1",
75
+ "globals": "17.11.0",
76
+ "jsdom": "30.0.1",
77
+ "react": "19.2.8",
78
+ "react-dom": "19.2.8",
79
+ "temporal-polyfill": "1.0.1",
80
+ "tsup": "8.5.1",
81
+ "typescript": "6.0.3",
82
+ "typescript-eslint": "8.68.0",
83
+ "vite": "8.2.2",
84
+ "vitest": "4.1.11",
85
+ "webmcp-types": "0.1.5"
86
+ }
87
+ }