{"id":51298687,"url":"https://github.com/jeeftor/ics-mcp","last_synced_at":"2026-06-30T17:01:42.628Z","repository":{"id":368244388,"uuid":"1284198331","full_name":"jeeftor/ics-mcp","owner":"jeeftor","description":"ICS MCP Server","archived":false,"fork":false,"pushed_at":"2026-06-29T18:12:53.000Z","size":75,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-29T18:22:32.388Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jeeftor.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-29T16:07:50.000Z","updated_at":"2026-06-29T18:12:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jeeftor/ics-mcp","commit_stats":null,"previous_names":["jeeftor/ics-mcp"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/jeeftor/ics-mcp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeeftor%2Fics-mcp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeeftor%2Fics-mcp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeeftor%2Fics-mcp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeeftor%2Fics-mcp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jeeftor","download_url":"https://codeload.github.com/jeeftor/ics-mcp/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeeftor%2Fics-mcp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34975672,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-30T02:00:05.919Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-30T17:01:39.360Z","updated_at":"2026-06-30T17:01:42.617Z","avatar_url":"https://github.com/jeeftor.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ICS MCP\n\nICS MCP is a small Go MCP server for homelab calendar feeds. It watches ICS URLs, stores normalized upcoming event instances in SQLite, exposes a Streamable HTTP MCP endpoint, and includes a simple embedded admin UI.\n\nThe v1 server is intentionally unauthenticated. Run it only on a trusted local or homelab network, or put it behind your own reverse proxy and access control.\n\n## Run Locally\n\n```bash\ngo run main.go serve\n```\n\nDefaults:\n\n- HTTP listen address: `127.0.0.1:3333`\n- MCP endpoint: `http://127.0.0.1:3333/mcp`\n- Admin UI: `http://127.0.0.1:3333/`\n- Config directory: `./data`\n- SQLite database: `./data/icsmcp.sqlite3`\n- Refresh interval: `5m`\n- Log level: `info`\n- Display timezone: `ICSMCP_TIMEZONE`, then `UTC`\n\nPrint build metadata:\n\n```bash\ngo run main.go version\n```\n\nUseful flags:\n\n```bash\ngo run main.go serve \\\n  --http-addr 0.0.0.0:3333 \\\n  --config-dir ./data \\\n  --refresh-interval 5m \\\n  --timezone America/Denver \\\n  --external-url https://ics-mcp.vookie.net \\\n  --log-level debug \\\n  --calendar MITRE=https://example.invalid/calendar.ics\n```\n\n## Calendar Config\n\nKeep `.env` private because ICS feed URLs often contain bearer-style access tokens.\n\nThe server loads an optional `.env` from the config directory first, then an optional `.env` from the current working directory. Existing environment variables are not overwritten, so runtime env values still win over files.\n\nFor Docker, put persistent config in `/config`:\n\n```text\n/config/.env\n/config/icsmcp.sqlite3\n```\n\nStartup calendars can be loaded from environment variables:\n\n```dotenv\nICSMCP_CALENDAR_MITRE=https://example.invalid/mitre.ics\nICSMCP_CALENDAR_EMILY=https://example.invalid/emily.ics\nICSMCP_TIMEZONE=America/Denver\nICSMCP_EXTERNAL_URL=http://192.168.1.112:3333\nICSMCP_LOG_LEVEL=info\nICSMCP_LOG_COLOR=true\n```\n\nThe suffix after `ICSMCP_CALENDAR_` is the stable key. Underscores are shown as spaces by default. Calendars from `.env` and `--calendar name=url` are upserted on startup; calendars added in the UI, API, or MCP tools are not deleted just because they are absent from startup config.\n\nEach calendar also has an `include_in_general_queries` setting, exposed in the Calendars tab, `PATCH /api/calendars/{id}`, and `update_calendar`. It defaults to `true`. Set it to `false` when a calendar should keep refreshing and remain explicitly queryable, but should not appear in default/general upcoming meeting results. Explicit `calendar_id` or `calendar_ids` filters still return that calendar.\n\nAfter startup, SQLite is the runtime source of truth for display names, enabled state, refresh state, and cached event instances.\n\nOutlook / Exchange feeds commonly use Windows timezone IDs such as `Eastern Standard Time`, `Mountain Standard Time`, and `Pacific Standard Time`; those are mapped to IANA zones during parsing before events are cached.\n\n`ICSMCP_TIMEZONE` accepts IANA names such as `America/Denver`, `UTC`, and common Outlook / Windows timezone IDs such as `Mountain Standard Time`. If a configured timezone is not recognized, startup logs a warning and falls back to `UTC`.\n\nLocal config-directory smoke test:\n\n```bash\nmkdir -p config\n$EDITOR config/.env\ngo run main.go serve --config-dir ./config --log-level debug\n```\n\nThe startup output prints the Admin UI, MCP endpoint, status URL, display timezone, and external URL when configured. The SQLite database should appear at `./config/icsmcp.sqlite3`.\n\n## HTTP API\n\n- `GET /healthz`\n- `GET /readyz`\n- `GET /metrics`\n- `GET /api/status`\n- `GET /api/meetings`\n- `GET /api/meetings/by-calendar`\n- `GET /api/tools`\n- `POST /api/tools/{name}/call`\n- `GET /api/calendars`\n- `POST /api/calendars`\n- `POST /api/calendars/validate`\n- `PATCH /api/calendars/{id}`\n- `DELETE /api/calendars/{id}`\n- `POST /api/calendars/{id}/refresh`\n\nMeeting preview endpoints accept `limit`, `lookahead_days`, repeated `calendar_id`, `query`, `timezone`, `only_ongoing`, `exclude_all_day`, `exclude_cancelled`, `include_description`, `description_max_chars`, `after`, and `before`. When no `calendar_id` is supplied, calendars with `include_in_general_queries=false` are omitted. `timezone` is optional and accepts IANA names such as `America/Denver` or `UTC`; when omitted, output uses the configured display timezone. `after` and `before` use RFC3339 timestamps.\n\n`/healthz` is the liveness endpoint and `/readyz` is the readiness endpoint. The `z` suffix is a common convention from Kubernetes-style health checks.\n\n## MCP Tools\n\n- `upcoming_meetings`\n- `upcoming_meetings_by_calendar`\n- `next_meetings`\n- `current_meetings`\n- `search_meetings`\n- `server_status`\n- `list_calendars`\n- `add_calendar`\n- `validate_calendar`\n- `update_calendar`\n- `remove_calendar`\n- `refresh_calendar`\n- `refresh_all_calendars`\n\n`upcoming_meetings` returns ongoing meetings plus future meetings, sorted by start time. It defaults to 10 meetings and a 30 day lookahead. Day labels are compact (`Mon`, `Tue`, etc.), display times are rendered in the configured timezone, and descriptions are omitted by default. Calendars opted out of general queries are omitted unless `calendar_ids` is supplied. Use `timezone` to render a specific query in another IANA timezone. Use `include_description: true` and optional `description_max_chars` when details are needed. Optional filters include `query`, `only_ongoing`, `exclude_all_day`, `exclude_cancelled`, `calendar_ids`, `after`, and `before`.\n\n`upcoming_meetings_by_calendar` returns the same meeting fields grouped by calendar for clients that prefer a calendar-first view. Its `limit` applies per calendar, so the default is 10 meetings per calendar.\n\n`next_meetings` is the opinionated, token-conscious preset for normal meeting prep. It returns the same shape as `upcoming_meetings`, but always excludes all-day blocks and cancelled events.\n\n`current_meetings` returns only events that have already started and have not ended yet. Ongoing events are marked with `ongoing: true`.\n\n`search_meetings` uses the same cached event data and filters as `upcoming_meetings`; pass `query` to match title, description, or calendar name.\n\n`server_status` returns build metadata, timezone, optional external URL, and calendar refresh state.\n\nThe admin tools use one canonical verb-first naming style: `list_calendars`, `add_calendar`, `validate_calendar`, `update_calendar`, `remove_calendar`, `refresh_calendar`, and `refresh_all_calendars`.\n\n`validate_calendar` fetches and parses an ICS feed without saving it. It returns fetch status, event count, and a small upcoming-meeting preview so you can test a URL before adding it.\n\nMeeting outputs include `timezone`, `all_day`, `cancelled`, `meeting_url`, and `meeting_url_type`. `meeting_url` is set when an online join link can be extracted from ICS `URL`, `LOCATION`, or `DESCRIPTION` fields. Known providers such as Teams, Zoom, Google Meet, and Webex are preferred over generic links.\n\nMCP tool discovery exposes each tool name, description, and JSON input schema. For example, `upcoming_meetings`, `next_meetings`, and `search_meetings` all advertise the `limit`, `calendar_ids`, `lookahead_days`, `timezone`, description, all-day, cancelled, and time-window options through the official `tools/list` response.\n\n## Debug UI\n\nThe admin page at `/` is also the local debug interface. It shows the exact same-origin MCP endpoint (`/mcp`), optional external endpoint from `ICSMCP_EXTERNAL_URL`, status endpoint, health endpoint, readiness endpoint, metrics endpoint, runtime config, build version, calendar refresh state, per-calendar general-query inclusion, copy buttons for endpoint/setup values, setup snippets for MCP clients, a next-meetings preview grouped by calendar, and a tool runner that lists every exposed MCP tool. Select a tool, edit JSON arguments, run it, and inspect syntax-highlighted JSON output.\n\n## Docker\n\nTagged releases publish multi-architecture images to GitHub Container Registry:\n\n```bash\nmkdir -p config\n$EDITOR config/.env\ndocker pull ghcr.io/jeeftor/ics-mcp:latest\ndocker run --rm -p 3333:3333 \\\n  -v \"$PWD/config:/config\" \\\n  ghcr.io/jeeftor/ics-mcp:latest \\\n  serve --http-addr 0.0.0.0:3333 --config-dir /config --log-level info\n```\n\nCreate `config/.env` before running the container, or pass `ICSMCP_CALENDAR_\u003cKEY\u003e` values through your container runtime. Put `ICSMCP_TIMEZONE` and `ICSMCP_EXTERNAL_URL` in `config/.env` too when the container is reached through a LAN IP, reverse proxy, or non-default port. The `/config` mount preserves the SQLite database and UI/API changes across restarts.\n\nUse `ICSMCP_TIMEZONE` for ics-mcp display times. The app defaults to `UTC` when no app timezone is configured and intentionally ignores the generic container `TZ` variable, so the container image does not need a `TZ` environment variable.\n\nThe repository also includes `compose.yaml`. It does not set `ICSMCP_TIMEZONE` itself, so values from `config/.env` are honored inside the container:\n\n```bash\nmkdir -p config\n$EDITOR config/.env\ndocker compose up -d\n```\n\nThe published image is built from the Go binary into a minimal `scratch` runtime image. It includes the normal public CA certificate bundle so outbound HTTPS calendar feeds work; it does not include private corporate certificate material.\n\nBuild the image locally when you want to test the Dockerfile before a release:\n\n```bash\ngo build -trimpath -o icsmcp ./main.go\ndocker build -t ics-mcp:local .\ndocker run --rm -p 3333:3333 \\\n  -v \"$PWD/config:/config\" \\\n  ics-mcp:local\n```\n\n## Releases\n\nGitHub Actions runs GoReleaser for tags matching `v*`.\n\n```bash\ngit tag -a vX.Y.Z -m \"Release vX.Y.Z\"\ngit push origin vX.Y.Z\n```\n\nThe release workflow runs tests, builds Linux, macOS, and Windows archives/checksums with GoReleaser, and publishes Docker images for `linux/amd64` and `linux/arm64`.\n\nRelease artifacts:\n\n- GitHub Release archives: `ics-mcp_\u003cversion\u003e_\u003cos\u003e_\u003carch\u003e.tar.gz` or `.zip`\n- Docker images: `ghcr.io/jeeftor/ics-mcp:\u003csemver-without-v\u003e` and `ghcr.io/jeeftor/ics-mcp:latest`, for example `ghcr.io/jeeftor/ics-mcp:1.2.0`\n- Checksums: `checksums.txt`\n\nUpdate `CHANGELOG.md` before tagging a release.\n\nRelease builds inject version, commit, and build date into `icsmcp version`, `/api/status`, the MCP implementation metadata, and the debug UI.\n\n## Development\n\n```bash\nmake test\n```\n\nThe default `make` target prints help and does not mutate state.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeeftor%2Fics-mcp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjeeftor%2Fics-mcp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeeftor%2Fics-mcp/lists"}