service-bridge-mcp
by cayde-6
README.md
# service-bridge-mcp
A calendar-first, deny-by-default MCP service broker prototype. The agent is a caller, never the broker administrator. MCP provides interoperability; **it is not a security boundary by itself**.
**Status: local dummy-data prototype, not production-ready.** No real calendar credentials, calendars, or invitations were used. The default executable only stores temporary dummy data in memory. The CalDAV implementation is a protocol fixture exercised against an in-memory fake, and cannot make live network requests. There is no arbitrary-service execution, generic URL tool, proxy, shell tool, credential-reading tool, or admin tool.
## What works
- Official MCP TypeScript v2 SDK, pinned in the lockfile. Streamable HTTP request handler, compatible with current and supported legacy clients.
- OAuth resource-server bearer-token verification: asymmetric signature, allowed algorithms, exact issuer/audience, access-token type (`at+jwt`), expiry, issued-at, subject, and scopes. No token passthrough.
- Per-subject, per-calendar, server-owned grants intersected with token scopes. Unknown callers and missing permissions fail closed.
- Separate `calendar:read`, `calendar:create`, and `calendar:invite` capabilities. Create cannot carry attendees. Invite requires a separate preconfigured recipient allowlist and a version match.
- Canonical UTC timestamps, server-owned date bounds, maximum 31-day query/event window, strict schemas, bounded request sizes, minimal read fields.
- Idempotent creation scoped to subject/calendar/key, payload-conflict detection, ETag-guarded changes. Duplicate invite attempts with an old ETag fail rather than resend.
- Exact Host/Origin checks, standard OAuth protected-resource metadata, generic failure responses, no request/token/provider-body logging.
## Quick check
Requires Node.js 24 or newer and npm. No account or credentials are needed for tests.
```sh
npm ci --ignore-scripts
npm run check
npm run demo
```
The demo negotiates MCP, creates a dummy event, safely retries it, and reads it back without opening a network connection.
Tests create ephemeral signing keys in memory; they never persist them or provision credentials. They exercise signed/invalid access tokens, policy denials, MCP calls, idempotency, concurrency versions, field injection, fake CalDAV responses, and invitation non-delivery. `npm run build` compiles TypeScript; `npm test` runs Node's test runner.
## Surface
| Tool | Scope | Purpose |
| ----------------- | ----------------- | ------------------------------------------------------------------------ |
| `calendar_read` | `calendar:read` | At most 100 minimal events in an authorized UTC window |
| `calendar_create` | `calendar:create` | One event with title/start/end and an idempotency key; no attendees |
| `calendar_invite` | `calendar:invite` | Dummy attendee-list change only; recipient allowlist + ETag; no delivery |
Read responses expose only event ID, title, UTC start/end, and ETag. Calendar contents are untrusted data, never instructions. Invite responses expose only event ID, ETag, and `delivery: "not_attempted"`. An attendee list is not evidence that any invitation was sent or delivered. CalDAV scheduling returns `SCHEDULING_UNVERIFIED` without making a request.
## Operator-owned configuration
`createBroker(config, adapter)` is the embedding API. `npm start -- /path/to/operator-public-config.json` starts the **dummy-only** executable on `127.0.0.1:3000`. It reads only the explicitly supplied file, never `.env`. It refuses to start without a configuration. The repository intentionally contains no usable auth credentials or provisioned identity provider.
The JSON shape is:
```json
{
"issuer": "https://identity.example",
"resource": "https://broker.example/mcp",
"jwks": { "keys": ["REPLACE WITH AN EXISTING ISSUER PUBLIC JWK OBJECT"] },
"allowedOrigins": [],
"grants": [
{
"subject": "existing-subject-id",
"calendarId": "demo",
"scopes": ["calendar:read"],
"startUtc": "2026-10-01T00:00:00Z",
"endUtc": "2026-11-01T00:00:00Z",
"maxRangeDays": 7,
"allowedAttendees": []
}
]
}
```
The placeholder is intentionally not a runnable JWK. Supply only public verification keys from a separately managed issuer; symmetric or private key material is rejected. The broker does not issue tokens, provide login, refresh keys automatically, or manage users. Public JWKS are pinned at startup; key rotation currently requires an operator reload. A real deployment needs an OAuth authorization server with MCP client authorization and resource/audience support. Do not use an ID token as an access token.
The resource and issuer must be HTTPS. Local HTTP is restricted to loopback behind a separately managed TLS reverse proxy; preserve the configured public Host. No wildcard Origin is implicit; absent Origin is allowed for non-browser MCP clients, while a present Origin must exactly match the operator list. No forwarded-header identity is trusted. Remote deployment has **not** been performed or validated.
## Persistence and boundaries
The memory adapter holds at most 1,000 events and loses all data/idempotency records on restart. It is a single-process test adapter, not a durable calendar database. CalDAV create fixtures use deterministic object identifiers plus `If-None-Match: *`, and changes require a strong ETag. A retry of a changed payload under the same key conflicts. Actual CalDAV scheduling, timezone/recurrence semantics, provider permissions and durable multi-instance guarantees are future integration gates.
Adding another service means adding an explicit typed adapter, narrow schemas, independent capability checks, data minimization, provider-specific tests and security review. Do not let an agent register services or configure URLs/credentials. See [adapter contract](docs/ADAPTER_CONTRACT.md) and [threat model](docs/THREAT_MODEL.md).
## Before any production use
1. Deploy and test a real OAuth issuer/client flow, TLS termination, trusted Host handling, key rotation/revocation, request deadlines and rate limits.
2. Implement a reviewed live CalDAV transport: strict destination allowlist, no redirects, public-IP validation and connection pinning, DNS-rebinding protection, timeout/body limits, separate server-side service credentials. Do not substitute arbitrary `fetch`.
3. Verify read/write behavior against a disposable real provider and least-privilege account. Keep invitation sending disabled until provider scheduling behavior and approval requirements are verified.
4. Add durable transaction/idempotency storage, multi-process concurrency tests, resource quotas, audit-event design, operational monitoring, backup and retention policies.
5. Run independent deployment/security review and dependency scanning. Passing dummy tests is not evidence of production security.
## Provenance
Original implementation; no source from the imported `@cayde-6/icalendar` project was copied. Its sanitized package/license was located for comparison only, and no private configuration was read. Third-party dependency licenses remain with their packages. Licensed under MIT; see [LICENSE](LICENSE).
Official references checked during implementation:
- [MCP TypeScript v2](https://ts.sdk.modelcontextprotocol.io/v2/)
- [HTTP transport](https://ts.sdk.modelcontextprotocol.io/v2/serving/http.html)
- [Authorization](https://ts.sdk.modelcontextprotocol.io/v2/serving/authorization.html)
- [MCP authorization specification](https://modelcontextprotocol.io/specification/latest/basic/authorization)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues