@kadoa/mcp 0.6.6 → 0.7.0-rc.2
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 +263 -199
- package/dist/index.js +4262 -49433
- package/package.json +11 -9
package/README.md
CHANGED
|
@@ -2,87 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Use [Kadoa](https://kadoa.com) from ChatGPT, Claude.ai, Claude Code, Cursor, and other MCP clients.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Public API JWKS, enforces the exact issuer and resource audience, and exchanges
|
|
10
|
-
each resource token for a request-local Public API token using a dedicated service secret.
|
|
11
|
-
It does not mount authorization, registration, token, refresh, or revocation
|
|
12
|
-
routes and does not store end-user credentials.
|
|
13
|
-
|
|
14
|
-
This runtime targets the separate `mcp-server-beta` deployment. Production MCP
|
|
15
|
-
at `https://mcp.kadoa.com/mcp` remains on legacy authentication until a separate
|
|
16
|
-
approved migration.
|
|
17
|
-
|
|
18
|
-
```sh
|
|
19
|
-
MCP_AUTH_ISSUER=https://api.kadoa.com/v5/auth
|
|
20
|
-
MCP_AUTH_JWKS_URL=https://api.kadoa.com/v5/auth/.well-known/jwks.json
|
|
21
|
-
MCP_AUTH_AUDIENCE=https://beta.mcp.kadoa.com/mcp
|
|
22
|
-
MCP_DOWNSTREAM_TOKEN_ENDPOINT=https://api.kadoa.com/v5/auth/mcp/downstream-token
|
|
23
|
-
# Inject MCP_DOWNSTREAM_EXCHANGE_SECRET from a dedicated secret store.
|
|
24
|
-
MCP_ALLOWED_ORIGIN_HOSTNAMES=beta.mcp.kadoa.com
|
|
25
|
-
MCP_LEGACY_TRANSPORT_ENABLED=true
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
`MCP_LEGACY_TRANSPORT_ENABLED` controls protocol-version compatibility only.
|
|
29
|
-
When `true`, supported 2025 protocol requests use the same central verifier and
|
|
30
|
-
downstream exchange as modern requests. It never selects another authentication
|
|
31
|
-
implementation. Missing central configuration fails startup.
|
|
32
|
-
|
|
33
|
-
The exchange secret must contain at least 32 characters without whitespace.
|
|
34
|
-
Generate at least 32 random bytes, encode as hex, and inject the same value into
|
|
35
|
-
Public API as `KADOA_MCP_DOWNSTREAM_EXCHANGE_SECRET`. Never reuse the worker or
|
|
36
|
-
legacy registry credentials. Secret rotation requires restarting both consumers.
|
|
37
|
-
The service secret does not replace the user token or its authorization checks.
|
|
38
|
-
No Google service account or metadata-server access is required.
|
|
39
|
-
|
|
40
|
-
### Historical local central-mode fixture
|
|
41
|
-
|
|
42
|
-
This fixture depends on the earlier backend local-issuer configuration, which
|
|
43
|
-
is absent from the narrowed beta rollout candidate. The commands below are not
|
|
44
|
-
a validated proof for that candidate. Do not restore browser OAuth or local-issuer
|
|
45
|
-
support to run this rollout.
|
|
46
|
-
|
|
47
|
-
The normal `dev` entrypoint authenticates downstream exchange with
|
|
48
|
-
`MCP_DOWNSTREAM_EXCHANGE_SECRET`. The test-only launcher imports the backend
|
|
49
|
-
exchange owner and uses a local-only fixture secret. It requires
|
|
50
|
-
`KADOA_ENVIRONMENT=local`, binds to `127.0.0.1:3002`, and is not included in the
|
|
51
|
-
published package build.
|
|
52
|
-
|
|
53
|
-
Start the backend services without the regular MCP child. The shared environment
|
|
54
|
-
pins the backend issuer, Assistant MCP URL, resource audience, and downstream
|
|
55
|
-
Public API audience to literal loopback addresses. If the backend root `.env`
|
|
56
|
-
defines `KADOA_MCP_TOKEN_ALLOWED_RESOURCES`, set it to
|
|
57
|
-
`http://127.0.0.1:3002/mcp` as well. Backend package processes load that file and
|
|
58
|
-
a conflicting value makes the Assistant exchange fail with `invalid_target`.
|
|
59
|
-
The backend `turbo.json` dev task must propagate every variable exported by
|
|
60
|
-
`local-central-env.sh`; the launcher checks this before it starts.
|
|
61
|
-
|
|
62
|
-
```sh
|
|
63
|
-
cd /home/gabrielziegler/kadoa/kadoa-backend
|
|
64
|
-
direnv exec . bash -lc '
|
|
65
|
-
source /home/gabrielziegler/kadoa/kadoa-mcp/tests/integration/local-central-env.sh
|
|
66
|
-
./start-kadoa.sh --quick --services=dashboard,api,shelly,events-gateway,operations
|
|
67
|
-
'
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
In a second terminal, start the local central resource server with the backend's
|
|
71
|
-
local environment:
|
|
72
|
-
|
|
73
|
-
```sh
|
|
74
|
-
direnv exec /home/gabrielziegler/kadoa/kadoa-backend bash -lc '
|
|
75
|
-
source /home/gabrielziegler/kadoa/kadoa-mcp/tests/integration/local-central-env.sh
|
|
76
|
-
cd /home/gabrielziegler/kadoa/kadoa-mcp
|
|
77
|
-
KADOA_BACKEND_ROOT=/home/gabrielziegler/kadoa/kadoa-backend bun run dev:central-local
|
|
78
|
-
'
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Open a new Dashboard Assistant session and request a workflow list. The proof
|
|
82
|
-
covers the central resource server, backend-owned token exchange, live team
|
|
83
|
-
membership checks, downstream Public API credentials, and real tool calls. It
|
|
84
|
-
does not prove the current beta candidate, browser OAuth consent, or a deployed
|
|
85
|
-
network boundary.
|
|
5
|
+
This repo builds the Kadoa MCP server: an HTTP-only [MCP](https://modelcontextprotocol.io)
|
|
6
|
+
resource server backed by Public API central authentication. There is no stdio
|
|
7
|
+
transport and no CLI entry point — see [Deployment & auth](#deployment--auth)
|
|
8
|
+
for what that means in practice.
|
|
86
9
|
|
|
87
10
|
## Remote Server (no install needed)
|
|
88
11
|
|
|
@@ -143,32 +66,108 @@ Point your client to `https://mcp.kadoa.com/mcp` with OAuth authentication.
|
|
|
143
66
|
|
|
144
67
|
| Tool | Description |
|
|
145
68
|
|------|-------------|
|
|
146
|
-
| `scrape` | Immediately fetch one URL as markdown or raw HTML (shown only for enabled workspaces) |
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
69
|
+
| `scrape` | Immediately fetch one URL as markdown or raw HTML (shown only for scrape-enabled workspaces) |
|
|
70
|
+
|
|
71
|
+
### Workflows
|
|
72
|
+
|
|
73
|
+
| Tool | Description |
|
|
74
|
+
|------|-------------|
|
|
75
|
+
| `create_workflow` | Create a one-time or scheduled agentic-navigation extraction workflow, optionally from a template |
|
|
150
76
|
| `get_workflow` | Get canonical intent, Assistant/session, template ownership, run health, schedule, location, monitoring, and validation details |
|
|
151
|
-
| `
|
|
77
|
+
| `update_workflow` | Update deterministic configuration such as name, URLs, schema, schedule timezone, monitoring, and unlimited rows |
|
|
78
|
+
| `run_workflow` | Run a workflow, including retrying a failed one-time or scheduled run |
|
|
79
|
+
| `delete_workflow` | Delete a workflow permanently |
|
|
80
|
+
| `approve_workflow` | Approve and activate a workflow that is in PREVIEW, PAUSED, or ERROR state |
|
|
81
|
+
| `pause_workflow` | Pause an ACTIVE workflow's schedule |
|
|
82
|
+
| `fetch_data` | Get a page of extracted data from a workflow (previews and small slices) |
|
|
83
|
+
| `export_data` | Get the full extracted dataset via a signed download URL |
|
|
84
|
+
| `list_workflows` | List workflows with status, Assistant session availability, template drift, realtime health, and `awaitingUserInput`; filter with `statusFilters: ["group:attention"]` for everything waiting on you |
|
|
85
|
+
| `list_workflow_runs` | List a workflow's execution run history (status, start/finish, record count, errors) |
|
|
86
|
+
| `get_workflow_history` | Get the workflow's configuration revision history (audit log) |
|
|
87
|
+
|
|
88
|
+
### Realtime monitoring
|
|
89
|
+
|
|
90
|
+
| Tool | Description |
|
|
91
|
+
|------|-------------|
|
|
92
|
+
| `create_realtime_monitor` | Create an asynchronous realtime monitoring workflow after persisting notification channels |
|
|
93
|
+
| `list_changes` | List detected data changes across one or more realtime monitoring workflows |
|
|
94
|
+
| `get_change` | Get detailed information about one detected change, including the data snapshot (cut to whole rows past ~80 KB) and diff |
|
|
95
|
+
|
|
96
|
+
### Workflow Assistant
|
|
97
|
+
|
|
98
|
+
| Tool | Description |
|
|
99
|
+
|------|-------------|
|
|
152
100
|
| `get_workflow_assistant` | Get the workflow Assistant's current activity and any pending clarification question |
|
|
153
|
-
| `get_workflow_assistant_timeline` | Read paginated customer-visible Assistant messages and questions
|
|
101
|
+
| `get_workflow_assistant_timeline` | Read paginated customer-visible Assistant messages and questions |
|
|
154
102
|
| `answer_workflow_assistant_question` | Answer the workflow Assistant's current clarification question and resume it |
|
|
103
|
+
| `request_workflow_update` | Ask the workflow Assistant to change extraction intent, navigation, sourcing, pagination, or generated behavior without changing the workflow ID |
|
|
155
104
|
| `interrupt_workflow_assistant` | Safely interrupt active Assistant work without pausing the workflow schedule |
|
|
156
105
|
| `resume_workflow_assistant` | Resume an idle/interrupted workflow Assistant with its persisted role and conversation |
|
|
157
106
|
| `stop_workflow_assistant` | Stop active Assistant work without deleting or replacing the workflow |
|
|
158
107
|
| `get_workflow_strategy` | Get the current customer-safe extraction/build strategy for an Assistant or custom-script workflow |
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
108
|
+
|
|
109
|
+
### Templates
|
|
110
|
+
|
|
111
|
+
| Tool | Description |
|
|
112
|
+
|------|-------------|
|
|
113
|
+
| `list_templates` | List all templates in the current team |
|
|
114
|
+
| `get_template` | Get a template by ID, including all published versions and schemas |
|
|
115
|
+
| `create_template` | Create a new (unpublished) template |
|
|
116
|
+
| `update_template` | Update a template's name and/or description only |
|
|
117
|
+
| `delete_template` | Delete (archive) a template |
|
|
118
|
+
| `create_template_version` | Publish a new immutable version of a template (prompt, schema, notifications) |
|
|
119
|
+
| `apply_template_version` | Push a template version onto its linked workflows |
|
|
120
|
+
| `save_workflow_as_template` | Create a template (or a new version of one) from an existing workflow's configuration |
|
|
121
|
+
| `list_template_schemas` | List schemas associated with a template |
|
|
122
|
+
| `list_template_workflows` | List the workflows linked to a template, flagging which are outdated |
|
|
123
|
+
| `link_template_to_workflows` | Link existing workflows to a template so they inherit and stay in sync with it |
|
|
124
|
+
| `unlink_template_from_workflows` | Unlink workflows from a template, keeping their current configuration |
|
|
125
|
+
|
|
126
|
+
### Variables
|
|
127
|
+
|
|
128
|
+
| Tool | Description |
|
|
129
|
+
|------|-------------|
|
|
130
|
+
| `create_variable` | Create a variable (key/value pair referenced in prompts as `@key`) |
|
|
131
|
+
| `list_variables` | List all variables in the current team scope |
|
|
132
|
+
| `get_variable` | Get a specific variable by its ID |
|
|
133
|
+
| `update_variable` | Update an existing variable |
|
|
134
|
+
| `delete_variable` | Delete a variable permanently |
|
|
135
|
+
|
|
136
|
+
### Notifications
|
|
137
|
+
|
|
138
|
+
| Tool | Description |
|
|
139
|
+
|------|-------------|
|
|
140
|
+
| `create_notification_channel` | Create a notification channel (email, webhook, Slack, or WebSocket), reusing an existing one for the same destination |
|
|
141
|
+
| `list_notification_channels` | List notification channels configured for a workflow or across the workspace |
|
|
142
|
+
| `delete_notification_channel` | Delete a notification channel by ID |
|
|
143
|
+
| `configure_notifications` | Create channels and event-to-channel settings for a workflow or workspace in one step |
|
|
144
|
+
| `list_notification_settings` | List notification settings (event-to-channel mappings) |
|
|
145
|
+
| `delete_notification_setting` | Delete a notification setting (the event-to-channel mapping, not the channel) |
|
|
146
|
+
|
|
147
|
+
### Observability & activity
|
|
148
|
+
|
|
149
|
+
| Tool | Description |
|
|
150
|
+
|------|-------------|
|
|
162
151
|
| `get_observability` | Team-wide workflow health for a window of days: uptime, failed runs, MTTR, records delivered, health counts, and a ranked list of down or degraded workflows (the Observability page data) |
|
|
163
|
-
| `
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
|
168
|
-
|
|
152
|
+
| `list_activity` | List the team's Activity log (audit trail) across all workflows and resources: actor email, resource name, action, interface, timestamp, change summary |
|
|
153
|
+
|
|
154
|
+
### Support
|
|
155
|
+
|
|
156
|
+
| Tool | Description |
|
|
157
|
+
|------|-------------|
|
|
158
|
+
| `create_support_ticket` | File a support ticket for one workflow so the Kadoa team investigates it |
|
|
159
|
+
|
|
160
|
+
### Account & team
|
|
161
|
+
|
|
162
|
+
| Tool | Description |
|
|
163
|
+
|------|-------------|
|
|
169
164
|
| `whoami` | Show current user details, auth method, and team memberships |
|
|
170
165
|
| `team_list` | List all teams you belong to and see which is active |
|
|
171
|
-
|
|
166
|
+
|
|
167
|
+
Team switching (a team_switch tool) exists in the code but is currently
|
|
168
|
+
disabled and hidden from every client's tool list — a credential is fixed to
|
|
169
|
+
one team for its lifetime under central auth; reconnect to choose a different
|
|
170
|
+
team.
|
|
172
171
|
|
|
173
172
|
## Workflow controls and behavior
|
|
174
173
|
|
|
@@ -350,130 +349,189 @@ delete_workflow for each, confirming before proceeding.
|
|
|
350
349
|
- Restart your MCP client
|
|
351
350
|
- Re-authenticate via OAuth if prompted
|
|
352
351
|
|
|
353
|
-
##
|
|
354
|
-
|
|
355
|
-
This repo ships two surfaces, and a "release" usually touches both:
|
|
356
|
-
|
|
357
|
-
- **npm package `@kadoa/mcp`** — stdio CLI users install locally (`npx @kadoa/mcp`). Cut by [Release Please](https://github.com/googleapis/release-please) from Conventional Commits on `main`.
|
|
358
|
-
- **Hosted server at `mcp.kadoa.com`** — Docker container on GKE, built and deployed from the [`kadoa-backend`](https://github.com/kadoa-org/kadoa-backend) monorepo, which pins a specific `@kadoa/mcp` version.
|
|
352
|
+
## Deployment & auth
|
|
359
353
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
354
|
+
This server is an MCP **resource server**, not an OAuth authorization server:
|
|
355
|
+
Public API is the only OAuth authority. This code publishes protected-resource
|
|
356
|
+
metadata, verifies RS256 resource tokens against Public API's JWKS, enforces
|
|
357
|
+
the exact issuer and resource audience, and exchanges each resource token for
|
|
358
|
+
a request-local Public API token using a dedicated service secret
|
|
359
|
+
(`src/kadoa-access-token.ts`, `src/downstream-token.ts`). It never mounts
|
|
360
|
+
authorization, registration, token, refresh, or revocation routes and never
|
|
361
|
+
stores end-user credentials. Old opaque MCP bearer credentials are rejected —
|
|
362
|
+
this build supports **central auth only**.
|
|
364
363
|
|
|
365
|
-
|
|
364
|
+
There is no stdio transport (`src/http.ts` is the only server entry point) and
|
|
365
|
+
no `bin` in `package.json`, so `npx @kadoa/mcp` does not start a usable server.
|
|
366
366
|
|
|
367
|
-
|
|
368
|
-
2. Merge the release PR. The [`release-please.yml`](.github/workflows/release-please.yml) workflow tags, drafts a GitHub Release, and publishes to npm through GitHub Actions OIDC trusted publishing (`latest` dist-tag).
|
|
369
|
-
3. After npm publication, `release-please.yml` opens or updates a deterministic PR in `kadoa-backend` that bumps `infra/docker/mcp/package.json` and refreshes `bun.lock`. The PR validates a frozen production install and Docker image build, and remains manually mergeable.
|
|
370
|
-
4. Merge the backend PR to `main`. CI (`main-build-deploy.yml`) builds and pushes `europe-west3-docker.pkg.dev/oceanic-base-310208/kadoa-artifacts/mcp-server:<IMAGE_TAG>` (tag shown in the build summary).
|
|
371
|
-
5. Trigger the **Deploy to Production** workflow ([`deploy-prod.yml`](https://github.com/kadoa-org/kadoa-backend/actions/workflows/deploy-prod.yml)) with:
|
|
372
|
-
- **Target cluster:** `gcp`
|
|
373
|
-
- **Deployment scope:** `mcp`
|
|
374
|
-
- **Image tag:** the tag from step 4
|
|
375
|
-
- **Method:** `kubectl`
|
|
367
|
+
### Where this code runs
|
|
376
368
|
|
|
377
|
-
|
|
369
|
+
Two deployments exist in `kadoa-backend` (see `infra/cdk8s/clusters/kadprod1.ts`
|
|
370
|
+
and `infra/cdk8s/staging/services.ts`):
|
|
378
371
|
|
|
379
|
-
|
|
372
|
+
- **`https://beta.mcp.kadoa.com/mcp`** — runs the image built from this repo,
|
|
373
|
+
with central auth (`auth.mode: "central"`). It has carried all production
|
|
374
|
+
Dashboard Assistant traffic since 2026-09-23.
|
|
375
|
+
- **`https://mcp.kadoa.com/mcp`** (and staging) — still run a pinned legacy
|
|
376
|
+
image (`LEGACY_MCP_IMAGE_DIGEST` in `infra/cdk8s/mcp/images.ts`) built from
|
|
377
|
+
older code, with `auth.mode: "legacy"`. Promoting these to this repo's code
|
|
378
|
+
and central auth is a separate, approved migration.
|
|
380
379
|
|
|
381
|
-
|
|
380
|
+
The `@kadoa/mcp` npm package (this repo's build output plus `README.md`, per
|
|
381
|
+
`package.json` `files`) is not distributed as an end-user CLI — it is the
|
|
382
|
+
server bundle the backend image installs (`infra/docker/mcp/package.json`
|
|
383
|
+
pins the version).
|
|
382
384
|
|
|
383
|
-
|
|
385
|
+
## Configuration
|
|
384
386
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
+
All auth and downstream-exchange settings below are required for the server
|
|
388
|
+
to start (`bun run dev` fails immediately without them); there is no fallback
|
|
389
|
+
auth mode.
|
|
387
390
|
|
|
388
|
-
|
|
391
|
+
| Variable | Description |
|
|
392
|
+
|---|---|
|
|
393
|
+
| `PORT` | HTTP port to listen on (default `3000`) |
|
|
394
|
+
| `MCP_SERVER_URL` | This deployment's own base URL; used only to derive a default for `MCP_ALLOWED_ORIGIN_HOSTNAMES` when that variable is unset and the hostname is a loopback address (default `http://localhost:<PORT>`) |
|
|
395
|
+
| `KADOA_PUBLIC_API_URI` | Public API base URL the server calls through `@kadoa/node-sdk` (default `https://api.kadoa.com`) |
|
|
396
|
+
| `KADOA_DASHBOARD_URL` | Base URL used to build `dashboardUrl` links in tool responses (default `https://www.kadoa.com`) |
|
|
397
|
+
| `MCP_AUTH_ISSUER` | Exact Public API OAuth issuer, e.g. `https://api.kadoa.com/v5/auth` |
|
|
398
|
+
| `MCP_AUTH_JWKS_URL` | JWKS URL for the issuer |
|
|
399
|
+
| `MCP_AUTH_AUDIENCE` | Exact MCP resource URL served by this deployment; must be HTTPS (loopback HTTP allowed) and its path must be `/mcp` |
|
|
400
|
+
| `MCP_DOWNSTREAM_TOKEN_ENDPOINT` | Public API's service-authenticated downstream exchange endpoint |
|
|
401
|
+
| `MCP_DOWNSTREAM_EXCHANGE_SECRET` | Dedicated service credential shared only with the downstream exchange; must be at least 32 characters with no whitespace, and must match Public API's `KADOA_MCP_DOWNSTREAM_EXCHANGE_SECRET` |
|
|
402
|
+
| `MCP_ALLOWED_ORIGIN_HOSTNAMES` | Comma-separated hostnames admitted when an HTTP `Origin` header is present; required outside loopback |
|
|
403
|
+
| `MCP_ALLOWED_HOSTNAMES` | Comma-separated `Host` header allow-list for DNS-rebinding protection (opt-in); loopback and this pod's own interface addresses are always added |
|
|
404
|
+
| `MCP_LEGACY_TRANSPORT_ENABLED` | `true` (default) serves supported 2025 MCP protocol versions through the same central verifier and downstream exchange as modern requests; `false` rejects them. It never selects a different authentication implementation |
|
|
405
|
+
| `LOG_LEVEL` | `debug`, `info` (default), `warn` or `error`; filters the structured JSON log lines written to stdout |
|
|
406
|
+
| `MCP_METRICS_PORT` | Port for a separate Prometheus `GET /metrics` listener (unset disables it). The Kadoa deployment uses `35536`, the port its pod scrape annotations point at; it is not exposed through the Service or ingress |
|
|
407
|
+
|
|
408
|
+
**Request logs and metrics.** Every `/mcp` request writes one JSON line to stdout
|
|
409
|
+
(`event: "mcp.request"`) with `requestId`, `era` (`modern`, `legacy`, `unknown`),
|
|
410
|
+
`httpMethod`, `method`, `toolName`, `clientInfo.name`, `protocolVersion`, `clientId`,
|
|
411
|
+
`userId`, `teamId`, `status`, `latencyMs`, `isError`, `rpcErrorCode`, `authReason`,
|
|
412
|
+
`traceId` and `aborted`. `isError` is true when the HTTP status is >= 400, the
|
|
413
|
+
outgoing JSON-RPC message carries an `error` object, or a tool result sets
|
|
414
|
+
`isError: true`; `rpcErrorCode` is the JSON-RPC error's `code` when the response
|
|
415
|
+
was a JSON-RPC error. `authReason` mirrors the reason class of a bearer-auth
|
|
416
|
+
rejection (see below) on that same request's line. `traceId` is the 32-hex-char
|
|
417
|
+
trace id from an incoming W3C `traceparent` (header, or the SEP-414 `_meta` trace
|
|
418
|
+
context if the request body carries one — that takes precedence), when present.
|
|
419
|
+
`aborted` is `true` only when the connection closed before the response finished
|
|
420
|
+
writing; it is omitted otherwise. Bearer-auth failures additionally write a
|
|
421
|
+
separate `auth.rejected` line with a reason class only. Tokens, request bodies and
|
|
422
|
+
tool arguments are never logged. The request id comes from a well-formed incoming
|
|
423
|
+
`x-request-id` (otherwise it is generated) and is returned in the response
|
|
424
|
+
header. `MCP_METRICS_PORT` exposes `mcp_http_requests_total{era,method,status}`
|
|
425
|
+
and `mcp_auth_rejections_total{reason}`; it must be a different port from `PORT`,
|
|
426
|
+
or the server refuses to start.
|
|
427
|
+
|
|
428
|
+
**Turning off legacy transport.** Set `MCP_LEGACY_TRANSPORT_ENABLED=false` only when,
|
|
429
|
+
over a trailing 14 days: the legacy share of successful requests,
|
|
430
|
+
`sum(increase(mcp_http_requests_total{era="legacy",status=~"2.."}[14d])) / sum(increase(mcp_http_requests_total{status=~"2.."}[14d]))`,
|
|
431
|
+
is below 1%; no first-party or top-10 client still appears in
|
|
432
|
+
`jsonPayload.event="mcp.request" AND jsonPayload.era="legacy"` grouped by
|
|
433
|
+
`jsonPayload.clientInfo.name` (legacy clients send it on `initialize`); and delegated
|
|
434
|
+
Dashboard Assistant traffic (`clientId` `kadoa-delegated:shelly-worker`) is modern
|
|
435
|
+
only. After switching, watch `sum(increase(mcp_http_requests_total{era="legacy"}[1d]))`
|
|
436
|
+
overall for a week, not only `status="400"`: with legacy transport off, a legacy
|
|
437
|
+
notification (no JSON-RPC response expected, e.g. `notifications/initialized`) is
|
|
438
|
+
answered `202 Accepted` rather than rejected, so most straggling legacy traffic shows
|
|
439
|
+
up as `era="legacy",status="202"`, and only a legacy request that expects a reply
|
|
440
|
+
(e.g. `initialize`) gets a `4xx` rejection. Setting the flag back to `true` restores
|
|
441
|
+
legacy serving.
|
|
442
|
+
|
|
443
|
+
Secret rotation requires restarting both this server and Public API. The
|
|
444
|
+
service secret does not replace the user's own token or its authorization
|
|
445
|
+
checks. No Google service account or metadata-server access is required.
|
|
389
446
|
|
|
390
|
-
|
|
447
|
+
## Development
|
|
391
448
|
|
|
392
449
|
```bash
|
|
393
|
-
|
|
394
|
-
bun
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
450
|
+
bun install
|
|
451
|
+
bun run dev # Run locally (requires all vars above to be set)
|
|
452
|
+
bun run dev:central-local # Recommended local workflow (see below)
|
|
453
|
+
bun run test # Run every discovered test (BUN_TEST=1 bun test)
|
|
454
|
+
bun run test:unit # Run the curated unit suite used in CI
|
|
455
|
+
bun run check-types # tsc --noEmit
|
|
456
|
+
bun run lint # bunx biome check
|
|
457
|
+
bun run build # Bundle for distribution
|
|
458
|
+
bun run smoke:dist # Boots dist/index.js and checks /health (lands with KAD-24989)
|
|
398
459
|
```
|
|
399
460
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
Promote later with: `npm dist-tag add @kadoa/mcp@0.5.7 latest` (run **after** the matching stable version has been published the normal way).
|
|
461
|
+
### Local development (`dev:central-local`)
|
|
403
462
|
|
|
404
|
-
|
|
463
|
+
`bun run dev` boots the same central-auth code path as production, so it
|
|
464
|
+
requires every `MCP_AUTH_*` / `MCP_DOWNSTREAM_*` variable above to already be
|
|
465
|
+
set and fails startup otherwise. `bun run dev:central-local`
|
|
466
|
+
(`tests/integration/local-central-server.ts`) wires that configuration up for
|
|
467
|
+
you against a **local** Kadoa backend instead of hand-setting each variable:
|
|
405
468
|
|
|
406
|
-
1.
|
|
407
|
-
|
|
469
|
+
1. Start the backend with `KADOA_ENVIRONMENT=local` so its local Public API
|
|
470
|
+
(`public-api`) listens on `http://127.0.0.1:12380`; source
|
|
471
|
+
`tests/integration/local-central-env.sh` in that shell first, and make sure
|
|
472
|
+
the backend's `turbo.json` `dev` task propagates the variables it sets.
|
|
473
|
+
2. From this repo, with `KADOA_BACKEND_ROOT` pointing at that backend
|
|
474
|
+
checkout and `KADOA_ENVIRONMENT=local`:
|
|
408
475
|
|
|
409
476
|
```bash
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
kubectl set image deployment/mcp-server-test \
|
|
414
|
-
mcp-server=europe-west3-docker.pkg.dev/oceanic-base-310208/kadoa-artifacts/mcp-server:<IMAGE_TAG>
|
|
477
|
+
KADOA_BACKEND_ROOT=/path/to/kadoa-backend KADOA_ENVIRONMENT=local \
|
|
478
|
+
bun run dev:central-local
|
|
415
479
|
```
|
|
416
480
|
|
|
417
|
-
3.
|
|
481
|
+
3. The server listens on `http://127.0.0.1:3002/mcp`, using a local-only
|
|
482
|
+
exchange secret and the backend's own token-exchange and team-membership
|
|
483
|
+
code (imported directly from `KADOA_BACKEND_ROOT`). It refuses to start if
|
|
484
|
+
the backend checkout, its `turbo.json` env propagation, or the local Public
|
|
485
|
+
API at `12380` aren't in place.
|
|
486
|
+
|
|
487
|
+
See `tests/integration/README.md` for the deeper integration proof this local
|
|
488
|
+
flow builds on.
|
|
489
|
+
|
|
490
|
+
### Build
|
|
491
|
+
|
|
492
|
+
`bun run build` bundles `src/index.ts` to `dist/index.js` with all
|
|
493
|
+
dependencies kept external (`--packages=external`), so `dist/` resolves
|
|
494
|
+
packages from `node_modules` at run time instead of bundling them in. That is
|
|
495
|
+
what lets the backend image's `overrides` in `infra/docker/mcp/package.json`
|
|
496
|
+
(for example pinning `@kadoa/node-sdk`) take effect for the built server.
|
|
497
|
+
That override must be kept in the SDK range this package depends on; the
|
|
498
|
+
automated backend PR that bumps `@kadoa/mcp` after a release only bumps that
|
|
499
|
+
dependency, not the `overrides` block, so a mismatch there has to be fixed by
|
|
500
|
+
hand.
|
|
501
|
+
`bun run smoke:dist` boots the built `dist/index.js` and checks `/health` as
|
|
502
|
+
part of verifying a build.
|
|
418
503
|
|
|
419
|
-
|
|
420
|
-
kubectl port-forward svc/mcp-server-test 3000:3000
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
4. Point your MCP client at `http://localhost:3000/mcp` and exercise the change.
|
|
424
|
-
|
|
425
|
-
**Auth gotcha:** `auth.kadoa.com` enforces an `ALLOWED_CALLBACK_ORIGINS` allowlist (`app.kadoa.com`, `mcp.kadoa.com`, `kadoa.com`) plus a loopback bypass for `127.0.0.1` when `NODE_ENV !== production`. `localhost` is **not** treated the same as `127.0.0.1` by that check. The test pod is configured with `MCP_SERVER_URL=http://localhost:3000` so the port-forwarded session resolves correctly. If you change the local port, update the deployment env var too:
|
|
426
|
-
|
|
427
|
-
```bash
|
|
428
|
-
kubectl set env deployment/mcp-server-test MCP_SERVER_URL=http://localhost:<PORT>
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
#### Promotion checklist
|
|
432
|
-
|
|
433
|
-
Before flipping `latest` / deploying to prod:
|
|
434
|
-
|
|
435
|
-
- [ ] RC consumed locally (`npx @kadoa/mcp@rc`) — stdio tools work
|
|
436
|
-
- [ ] RC consumed via `mcp-server-test` — OAuth login succeeds end-to-end
|
|
437
|
-
- [ ] No new errors in the test pod's logs (`kubectl logs deployment/mcp-server-test -f`)
|
|
438
|
-
- [ ] Release Please PR open with the stable version
|
|
439
|
-
|
|
440
|
-
## Development
|
|
441
|
-
|
|
442
|
-
```bash
|
|
443
|
-
bun install
|
|
444
|
-
bun run dev # Run locally
|
|
445
|
-
bun run test # Run tests
|
|
446
|
-
bun run build # Build for distribution
|
|
447
|
-
```
|
|
504
|
+
## Releases
|
|
448
505
|
|
|
449
|
-
|
|
506
|
+
This repo ships two surfaces, and a "release" usually touches both:
|
|
450
507
|
|
|
451
|
-
|
|
508
|
+
- **npm package `@kadoa/mcp`** — the server bundle the hosted deployments
|
|
509
|
+
install. Cut by [Release Please](https://github.com/googleapis/release-please) from Conventional Commits on `main`.
|
|
510
|
+
- **Hosted server at `beta.mcp.kadoa.com`** (production traffic) and
|
|
511
|
+
`mcp.kadoa.com` (legacy, separate migration) — Docker containers on GKE,
|
|
512
|
+
built and deployed from the [`kadoa-backend`](https://github.com/kadoa-org/kadoa-backend) monorepo, which pins a specific `@kadoa/mcp` version for the non-legacy deployment.
|
|
452
513
|
|
|
453
|
-
|
|
514
|
+
Relevant files in `kadoa-backend`:
|
|
515
|
+
- `infra/docker/mcp/Dockerfile.mcp-server` — image definition
|
|
516
|
+
- `infra/docker/mcp/package.json` — pinned `@kadoa/mcp` version and dependency `overrides`
|
|
517
|
+
- `infra/cdk8s/clusters/kadprod1.ts` — k8s manifest source for `mcp-server` (legacy) and `mcp-server-beta` (this repo's code, central auth)
|
|
454
518
|
|
|
455
|
-
|
|
519
|
+
### Production release
|
|
456
520
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
521
|
+
1. Merge PRs to `main` using Conventional Commits (`feat:`, `fix:`, etc.). Release Please opens/maintains a `chore(main): release mcp x.y.z` PR.
|
|
522
|
+
2. Merge the release PR. The [`release-please.yml`](.github/workflows/release-please.yml) workflow tags, drafts a GitHub Release, and publishes to npm through GitHub Actions OIDC trusted publishing (`latest` dist-tag).
|
|
523
|
+
3. After npm publication, the same workflow opens or updates a deterministic PR in `kadoa-backend` that bumps `infra/docker/mcp/package.json` and refreshes its lockfile, validating a production install and Docker image build. It remains manually mergeable, and is skipped with a warning (not a failure) if the `BACKEND_REPO_TOKEN` secret isn't provisioned.
|
|
524
|
+
4. Merge the backend PR to `main`. CI builds and pushes the `mcp-server` image.
|
|
525
|
+
5. Deploy the new image tag to the target deployment (`mcp-server-beta` for this repo's code) through the backend's deploy workflow.
|
|
460
526
|
|
|
461
|
-
|
|
527
|
+
### RC / test release
|
|
462
528
|
|
|
463
|
-
|
|
529
|
+
Use this to validate a change end-to-end before promoting to the `latest` npm dist-tag. Trigger the [`release-please.yml`](.github/workflows/release-please.yml) workflow manually (`workflow_dispatch`) with an `rc_version` like `0.6.0-rc.1`; it publishes that version under the npm `rc` dist-tag using the same trusted-publishing flow as a stable release.
|
|
464
530
|
|
|
465
|
-
|
|
466
|
-
|---|---|
|
|
467
|
-
| `MCP_AUTH_ISSUER` | Exact Public API OAuth issuer, such as `https://api.kadoa.com/v5/auth` |
|
|
468
|
-
| `MCP_AUTH_JWKS_URL` | JWKS URL for the issuer |
|
|
469
|
-
| `MCP_AUTH_AUDIENCE` | Exact MCP resource URL served by this deployment; its path must be `/mcp` |
|
|
470
|
-
| `MCP_DOWNSTREAM_TOKEN_ENDPOINT` | Public API service-authenticated downstream exchange endpoint |
|
|
471
|
-
| `MCP_DOWNSTREAM_EXCHANGE_SECRET` | Dedicated service credential shared only with the downstream exchange; must satisfy the same validation as Public API `KADOA_MCP_DOWNSTREAM_EXCHANGE_SECRET` |
|
|
472
|
-
| `MCP_ALLOWED_ORIGIN_HOSTNAMES` | Comma-separated hostnames admitted when an HTTP Origin header is present; required outside loopback |
|
|
473
|
-
| `MCP_LEGACY_TRANSPORT_ENABLED` | `true` serves supported 2025 protocol versions through central auth; `false` rejects them |
|
|
531
|
+
Verify: `npm view @kadoa/mcp dist-tags`. The `rc` tag should now point to your version.
|
|
474
532
|
|
|
475
|
-
|
|
476
|
-
|
|
533
|
+
Because there is no CLI entry point, consuming an RC means pointing a hosted
|
|
534
|
+
test deployment at the new image tag, not `npx`ing the package locally.
|
|
477
535
|
|
|
478
536
|
## Contributing
|
|
479
537
|
|
|
@@ -487,7 +545,7 @@ A typical feature touches three repos — **`kadoa-backend` → `kadoa-sdks` →
|
|
|
487
545
|
| SDK low-level | `kadoa-sdks/sdks/node/src/generated/api/*` | Auto-generated axios client. Raw HTTP, typed from the OpenAPI spec. **Do not hand-edit.** |
|
|
488
546
|
| SDK domain (high-level) | `kadoa-sdks/sdks/node/src/domains/<thing>/<thing>.service.ts` | Thin domain wrapper over the generated client. `client.<thing>.<action>(...)`. This is what callers use. |
|
|
489
547
|
| CLI | `kadoa-cli/src/commands/<thing>.ts` | Commander action: flag parsing, spinner, table/json output. Calls SDK domain. |
|
|
490
|
-
| MCP (this repo) | `kadoa-mcp/src/tools
|
|
548
|
+
| MCP (this repo) | `kadoa-mcp/src/tools/<domain>.ts` | Zod input schema + tool description tuned for LLMs. Calls SDK domain. |
|
|
491
549
|
|
|
492
550
|
Mental model:
|
|
493
551
|
- **SDK** = pure function over HTTP.
|
|
@@ -515,30 +573,36 @@ CLI and MCP both call **identical** SDK domain methods. A new feature added to t
|
|
|
515
573
|
|
|
516
574
|
3. **MCP** (this repo)
|
|
517
575
|
- Bump `@kadoa/node-sdk` in `package.json` to the new version, `bun install`.
|
|
518
|
-
- Add the tool in `src/tools.ts
|
|
519
|
-
- Zod input schema (
|
|
576
|
+
- Add the tool in the matching `src/tools/<domain>.ts` register function (keep `src/tools/index.ts` order in mind — it is the `tools/list` order):
|
|
577
|
+
- Zod input schema (coercion helpers for inputs LLM clients sometimes send as strings, e.g. numbers/bools, live in `src/tools/shared/coercion.ts`; shared shapes in `src/tools/shared/schemas.ts`).
|
|
520
578
|
- Description string written **for the LLM**: when to call it, what inputs mean, what the response shape is.
|
|
521
579
|
- Implementation calls `ctx.client.<thing>.<action>(...)` — never raw HTTP.
|
|
522
580
|
- Add a unit test under `tests/unit/`.
|
|
581
|
+
- If the change intentionally alters a tool's name, title, description, schema, outputSchema or annotations, regenerate the snapshot: `UPDATE_TOOLS_SNAPSHOT=1 BUN_TEST=1 bun test tests/unit/tools-list-snapshot.test.ts`, then `./node_modules/.bin/biome format --write tests/unit/fixtures/tools-list.snapshot.json`.
|
|
523
582
|
- Use a Conventional Commit message (`feat(mcp): ...`, `fix(mcp): ...`). Release Please will cut the next version on merge to `main`.
|
|
524
583
|
|
|
525
584
|
4. **CLI** (`kadoa-cli`), if the feature is user-facing on the command line: mirror the MCP step in `src/commands/<thing>.ts`. Same SDK call, different UX.
|
|
526
585
|
|
|
527
586
|
### Anti-patterns
|
|
528
587
|
|
|
529
|
-
- ❌ Raw `axios` calls or hand-rolled HTTP in `src/tools
|
|
588
|
+
- ❌ Raw `axios` calls or hand-rolled HTTP in `src/tools/`. Always go through `ctx.client.<thing>`.
|
|
530
589
|
- ❌ Putting tool-shaped logic (input validation, response shaping) in the MCP layer when the SDK should expose it. If two clients would need the same logic, it belongs in the SDK.
|
|
531
590
|
- ❌ Editing files under `sdks/node/src/generated/` by hand. They get overwritten by `kadoa-codegen generate`.
|
|
532
591
|
- ❌ Bundling a spec update and an SDK feature in the same commit — keep `specs/` PRs separate from domain-method PRs for clean review.
|
|
533
592
|
|
|
534
593
|
### Where to find things in this repo
|
|
535
594
|
|
|
536
|
-
- `src/tools
|
|
537
|
-
- `src/
|
|
595
|
+
- `src/tools/` — MCP tools, one module per domain (`workflows`, `assistant`, `templates`, `notifications`, `variables`, `observability`, `team`, `scrape`, `support`); shared zod schemas, coercion, errors, results and dashboard URLs in `src/tools/shared/`. `src/tools/index.ts` fixes registration order.
|
|
596
|
+
- `src/server.ts` — assembles the `McpServer` (tool registration, capability gating, instructions); `src/index.ts` is the CLI entry.
|
|
597
|
+
- `src/http.ts` — Express resource server, MCP transport mounting, and graceful shutdown.
|
|
598
|
+
- `src/kadoa-access-token.ts` — verifies Public API resource tokens (central auth config, JWT verification).
|
|
538
599
|
- `src/downstream-token.ts` — exchanges resource tokens for request-local Public API credentials.
|
|
539
600
|
- `src/client.ts` — wires `@kadoa/node-sdk` per request.
|
|
540
|
-
- `src/
|
|
541
|
-
- `
|
|
601
|
+
- `src/principal.ts` — the `McpPrincipal` type carried through auth and tool context.
|
|
602
|
+
- `src/notification-channels.ts` — channel identity/reuse logic shared by the notification tools.
|
|
603
|
+
- `src/error-summary.ts` — redacts credentials from error messages before they're logged.
|
|
604
|
+
- `tests/unit/` — bun tests; run with `bun run test:unit` (curated) or `bun run test` (everything discovered).
|
|
605
|
+
- `tests/integration/` — local and CI-owned integration fixtures against a real backend; not part of `test:unit`.
|
|
542
606
|
|
|
543
607
|
## License
|
|
544
608
|
|