---
title: Existing App
description: Step-by-step guide to add FullCalendar WebMCP to a FullCalendar React app you already ship without changing your UI or backend.
---

If FullCalendar already runs in your React app, add WebMCP without changing your plugins, themes, or backend.

## Keep as-is

- FullCalendar plugins, views, and renderers
- API routes, database, and auth
- React state, React Query, SWR, or similar

## 1. Install

```package-install
npm install @protocoltooling/fullcalendar
```

## 2. Wrap your API

```ts
import type { CalendarEventRepository } from "@protocoltooling/fullcalendar";
import * as api from "./events-api";

export const repository: CalendarEventRepository = {
  list: (query) => api.fetchEvents(query),
  get: (id) => api.fetchEventById(id),
  create: (input) => api.createEvent(input),
  update: (id, input) => api.updateEvent(id, input),
  delete: (id) => api.deleteEvent(id),
};
```

## 3. Register the hook

```tsx
import FullCalendar, { type CalendarRef } from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/react/daygrid";
import { useCallback, useEffect, useRef, useState } from "react";
import {
  useFullCalendarWebMCP,
  type CalendarEvent,
} from "@protocoltooling/fullcalendar";
import { repository } from "../services/calendar-adapter";

export function CalendarView() {
  const calendarRef = useRef<CalendarRef>(null);
  const [events, setEvents] = useState<CalendarEvent[]>([]);

  const refreshEvents = useCallback(async () => {
    setEvents(await repository.list());
  }, []);

  useEffect(() => {
    void refreshEvents();
  }, [refreshEvents]);

  useFullCalendarWebMCP({
    calendarRef,
    events: repository,
    onEventsChanged: refreshEvents,
  });

  return (
    <FullCalendar
      ref={calendarRef}
      plugins={[dayGridPlugin]}
      initialView="dayGridMonth"
      events={events.map((event) => ({
        ...event,
        end: event.end ?? undefined,
      }))}
      editable
      selectable
    />
  );
}
```

Then follow [Quickstart → Verify](/integrations/fullcalendar/quickstart#verify).

## Persist human edits

FullCalendar WebMCP does **not** listen to FullCalendar drag/resize/edit callbacks. If humans can move events in the UI, your app must persist those changes through the same repository agents use:

```tsx
<FullCalendar
  ref={calendarRef}
  editable
  eventDrop={async (info) => {
    await repository.update(info.event.id, {
      start: info.event.start!.toISOString(),
      end: info.event.end?.toISOString() ?? null,
      allDay: info.event.allDay,
    });
    await refreshEvents();
  }}
  eventResize={async (info) => {
    await repository.update(info.event.id, {
      start: info.event.start!.toISOString(),
      end: info.event.end?.toISOString() ?? null,
      allDay: info.event.allDay,
    });
    await refreshEvents();
  }}
  {/* ... */}
/>
```

Optional convergence check: after a human drag, run `calendar_get_event` in DevTools WebMCP and confirm the times match. See [Testing → Optional host convergence](/integrations/fullcalendar/guides/testing#optional-host-human-and-agent-convergence).

## Related

- [Security](/integrations/fullcalendar/concepts/security) — auth boundaries and least-privilege tool exposure
- [Custom event models](/integrations/fullcalendar/guides/custom-event-model) — domain mapping and metadata
- [Next.js](/integrations/fullcalendar/guides/nextjs) — App Router patterns
- [Troubleshooting](/integrations/fullcalendar/troubleshooting)
