adf-mcp-server 1.0.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/CHANGELOG.md +163 -0
- package/LICENSE +21 -0
- package/README.md +276 -0
- package/index.js +1083 -0
- package/package.json +57 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [1.0.0]
|
|
11
|
+
|
|
12
|
+
First stable release. The 0.x line was a series of breaking surface
|
|
13
|
+
expansions (auth modes, write tools, destructive tools); 1.0.0 marks the
|
|
14
|
+
point where the tool, env-var, and gating contracts are committed and
|
|
15
|
+
SemVer kicks in.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- GitHub Actions **CI workflow** (`.github/workflows/ci.yml`) running
|
|
19
|
+
`npm run lint`, `npm run format:check`, `node --check index.js`, and
|
|
20
|
+
`npm audit --omit=dev` on every push and PR.
|
|
21
|
+
- GitHub Actions **release workflow** (`.github/workflows/release.yml`)
|
|
22
|
+
publishing to npm with provenance when a `v*` tag is pushed. Requires
|
|
23
|
+
a `NPM_TOKEN` repository secret.
|
|
24
|
+
- **`Dockerfile`** (multi-stage, alpine, non-root) for hosting the server
|
|
25
|
+
on Azure with managed identity. Companion `.dockerignore`.
|
|
26
|
+
- **`SECURITY.md`** with explicit disclosure process, in/out-of-scope
|
|
27
|
+
items, and a list of known design limitations.
|
|
28
|
+
- README sections: "Install from npm", "Docker / managed identity",
|
|
29
|
+
and a link to `SECURITY.md`.
|
|
30
|
+
- `package.json` gains `files`, `publishConfig`, and `keywords` for npm
|
|
31
|
+
discoverability and to keep the published tarball minimal.
|
|
32
|
+
- Plan-store capacity cap (`PLAN_STORE_MAX = 100`) — when full,
|
|
33
|
+
the oldest entry is evicted before insert. Prevents memory exhaustion
|
|
34
|
+
if a buggy client floods plans faster than the TTL sweep runs.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- Server version is now read from `package.json` at startup instead of
|
|
38
|
+
being hard-coded in `new McpServer({ version })`. Single source of truth.
|
|
39
|
+
|
|
40
|
+
### Known limitations (intentional)
|
|
41
|
+
- No automated test suite — manual smoke tests only.
|
|
42
|
+
- `list_*` tools don't follow ARM `nextLink`; factories with hundreds of
|
|
43
|
+
pipelines/datasets/etc. will see only the first ARM page.
|
|
44
|
+
- `AZURE_CLIENT_ID` is overloaded across auth modes (CLI public client /
|
|
45
|
+
SP app reg / user-assigned MI). Documented in `.env.example`.
|
|
46
|
+
- Plan/apply tokens are in-memory only and do not survive a restart.
|
|
47
|
+
|
|
48
|
+
## [0.4.0]
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
- `ADF_MCP_ALLOW_DELETE=true` (requires `ADF_MCP_MODE=write`) enables 8 new
|
|
52
|
+
destructive tools — 4 `create_or_update_*` and 4 `delete_*` for pipelines,
|
|
53
|
+
triggers, linked services, and datasets. Off by default. Setting the flag
|
|
54
|
+
without write mode logs a warning and is ignored.
|
|
55
|
+
- **Plan/apply confirmation pattern** for every destructive tool. First call
|
|
56
|
+
(`dry_run: true`, default) returns a before/after diff plus a single-use
|
|
57
|
+
`confirm_token` with a 10-minute TTL. Second call (`dry_run: false` +
|
|
58
|
+
matching `confirm_token`) applies the change.
|
|
59
|
+
- **Optimistic concurrency** via ETag captured at plan time and passed as
|
|
60
|
+
`If-Match` on apply. If the resource changed between plan and apply, ARM
|
|
61
|
+
returns 412 and the server surfaces "Resource changed since the plan;
|
|
62
|
+
request a new plan."
|
|
63
|
+
- Token-store hygiene: tokens are bound to `(tool, target, payload-hash)`,
|
|
64
|
+
single-use, expire after 10 minutes, and are swept every minute.
|
|
65
|
+
- README "Destructive mode" section covering the plan/apply rationale, the
|
|
66
|
+
ETag concurrency story, and why `create_or_update_*` is bundled with
|
|
67
|
+
`delete_*` under the same flag.
|
|
68
|
+
|
|
69
|
+
### Changed
|
|
70
|
+
- `armAt()` now accepts `extraHeaders` so callers can pass `If-Match`.
|
|
71
|
+
- Errors from ARM responses now expose `.status` so downstream code (e.g.
|
|
72
|
+
`fetchExistingOrNull`, 412 detection in apply) can branch on HTTP status.
|
|
73
|
+
|
|
74
|
+
## [0.3.0]
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
- Optional **write mode** opt-in via `ADF_MCP_MODE=write`. When unset (default)
|
|
78
|
+
the server is read-only — write tools are not registered at all, so an LLM
|
|
79
|
+
cannot call them regardless of prompt.
|
|
80
|
+
- Five write tools (registered only in write mode):
|
|
81
|
+
- `create_pipeline_run` — kick off a new run, optionally with parameters.
|
|
82
|
+
- `cancel_pipeline_run` — cancel an in-progress run; child runs too by default.
|
|
83
|
+
- `rerun_pipeline_run` — re-execute a previous run; defaults to resuming from
|
|
84
|
+
the failed activity.
|
|
85
|
+
- `start_trigger` / `stop_trigger` — toggle a trigger's runtime state.
|
|
86
|
+
- Audit logging to stderr for every write tool call. Each invocation emits
|
|
87
|
+
ATTEMPT + (SUCCESS | FAILURE) lines with timestamp, target, and caller
|
|
88
|
+
identity parsed from the Entra token (`upn`/`preferred_username`/`appid`/`oid`).
|
|
89
|
+
- Startup log line when write mode is enabled, so the operator can see in the
|
|
90
|
+
MCP server log whether mutations are possible.
|
|
91
|
+
- README "Write mode" section covering RBAC requirements, audit log format,
|
|
92
|
+
recommended SP pairing, and the separation from destructive ops (Stage 6).
|
|
93
|
+
|
|
94
|
+
### Changed
|
|
95
|
+
- `armAt()` now builds URLs via the `URL` constructor and accepts an
|
|
96
|
+
`extraQuery` argument, enabling endpoints that need query params beyond
|
|
97
|
+
`api-version` (rerun's `referencePipelineRunId`, cancel's `isRecursive`).
|
|
98
|
+
|
|
99
|
+
## [0.2.0]
|
|
100
|
+
|
|
101
|
+
### Added
|
|
102
|
+
- Five new read tools:
|
|
103
|
+
- `get_pipeline_run` — direct lookup of a single run by ID.
|
|
104
|
+
- `list_linked_services` — linked services and their types.
|
|
105
|
+
- `list_datasets` — datasets and their linked-service references.
|
|
106
|
+
- `list_integration_runtimes` — IRs and their state (spot offline self-hosted
|
|
107
|
+
IRs).
|
|
108
|
+
- `list_factories` — discover other factories in the current subscription.
|
|
109
|
+
- Pagination on `query_pipeline_runs` via `continuation_token` input and
|
|
110
|
+
`continuationToken` in the response.
|
|
111
|
+
- Automatic retry with `Retry-After`-aware backoff on ARM HTTP 429 (up to 3
|
|
112
|
+
retries, capped at 60 s per attempt). Retries are logged to stderr.
|
|
113
|
+
- `safeTool` wrapper converting thrown errors into structured MCP tool errors
|
|
114
|
+
(`isError: true`) so the LLM can see and react to the message.
|
|
115
|
+
- Startup validation that `ADF_FACTORY_RESOURCE_ID` parses as a valid ARM ID
|
|
116
|
+
with a `/subscriptions/<id>` prefix.
|
|
117
|
+
- `ADF_AUTH_MODE` env var selecting from six credential types: `interactive`
|
|
118
|
+
(default), `device-code`, `cli`, `service-principal`, `managed-identity`,
|
|
119
|
+
`default`. Default preserves prior behavior.
|
|
120
|
+
- `AZURE_CLIENT_SECRET` env var (required only for `service-principal` mode).
|
|
121
|
+
- README "Authentication modes" section with per-mode requirements and notes.
|
|
122
|
+
- `.env.example` documenting supported environment variables.
|
|
123
|
+
|
|
124
|
+
### Changed
|
|
125
|
+
- `query_activity_runs` truncates each activity's `input` and `output` to ~4 KB
|
|
126
|
+
by default to protect the LLM context window. Pass `full=true` to receive
|
|
127
|
+
the untruncated payloads. **Breaking** for callers that depended on the full
|
|
128
|
+
blob being returned by default.
|
|
129
|
+
- `.editorconfig` enforcing consistent indentation and line endings.
|
|
130
|
+
- `.gitattributes` enforcing LF line endings cross-platform.
|
|
131
|
+
- `ROADMAP.md` capturing the staged plan for upcoming work.
|
|
132
|
+
- README "Troubleshooting" section covering common auth and RBAC failures.
|
|
133
|
+
- ESLint (flat config) + Prettier with `lint`, `lint:fix`, `format`,
|
|
134
|
+
`format:check` npm scripts.
|
|
135
|
+
- `CONTRIBUTING.md` with branching, Conventional Commits, and PR conventions.
|
|
136
|
+
- `.github/` issue and pull request templates.
|
|
137
|
+
|
|
138
|
+
### Changed
|
|
139
|
+
- `index.js` reformatted by Prettier to match the new project style (no
|
|
140
|
+
behavior changes).
|
|
141
|
+
|
|
142
|
+
### Security
|
|
143
|
+
- Resolved 4 advisories (1 high, 3 moderate) in transitive dependencies of
|
|
144
|
+
`@modelcontextprotocol/sdk` via `npm audit fix`: `fast-uri`,
|
|
145
|
+
`hono`/`@hono/node-server`, `express-rate-limit`, `ip-address`. All affected
|
|
146
|
+
packages are HTTP-transport code paths that are not loaded at runtime by
|
|
147
|
+
this stdio-only server, so practical exposure was zero. Patch/minor bumps
|
|
148
|
+
only; no `package.json` changes.
|
|
149
|
+
|
|
150
|
+
## [0.1.0] - 2026-04-27
|
|
151
|
+
|
|
152
|
+
### Added
|
|
153
|
+
- Initial MCP server with read-only tools: `list_pipelines`, `get_pipeline`,
|
|
154
|
+
`query_pipeline_runs`, `query_activity_runs`, `list_triggers`.
|
|
155
|
+
- Interactive browser authentication via `@azure/identity`.
|
|
156
|
+
- MIT license and `package.json` metadata for distribution.
|
|
157
|
+
|
|
158
|
+
[Unreleased]: https://github.com/user-vik/adf-mcp-server/compare/v1.0.0...HEAD
|
|
159
|
+
[1.0.0]: https://github.com/user-vik/adf-mcp-server/compare/v0.4.0...v1.0.0
|
|
160
|
+
[0.4.0]: https://github.com/user-vik/adf-mcp-server/compare/v0.3.0...v0.4.0
|
|
161
|
+
[0.3.0]: https://github.com/user-vik/adf-mcp-server/compare/v0.2.0...v0.3.0
|
|
162
|
+
[0.2.0]: https://github.com/user-vik/adf-mcp-server/compare/v0.1.0...v0.2.0
|
|
163
|
+
[0.1.0]: https://github.com/user-vik/adf-mcp-server/releases/tag/v0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Victor Perez
|
|
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,276 @@
|
|
|
1
|
+
# adf-mcp-server
|
|
2
|
+
|
|
3
|
+
MCP server for classic Azure Data Factory (V2) pipeline troubleshooting and operations. Exposes ARM tools for pipelines, pipeline runs, activity runs, triggers, linked services, datasets, and integration runtimes over stdio. Supports six Entra auth modes — interactive browser, device code, Azure CLI session, service principal, managed identity, or auto-detect. **Read-only by default**, with two opt-in tiers: `ADF_MCP_MODE=write` for runtime ops (run/cancel/start/stop), plus `ADF_MCP_ALLOW_DELETE=true` for definition-changing ops (`create_or_update_*` and `delete_*`) gated by a plan/apply confirmation pattern.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Wraps the ADF REST API as MCP tools so an AI agent (Claude Code, Claude Desktop, Cursor, etc.) can investigate, run, and control a Data Factory. **Read-only by default** — write tools (kicking off pipeline runs, cancelling them, toggling triggers) are registered only when the operator sets `ADF_MCP_MODE=write` in the MCP client config. See **Write mode** below.
|
|
8
|
+
|
|
9
|
+
| Tool | Purpose |
|
|
10
|
+
| --------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
11
|
+
| `list_pipelines` | All pipelines in the factory + activity counts, parameters, folder |
|
|
12
|
+
| `get_pipeline` | Full JSON definition of a specific pipeline |
|
|
13
|
+
| `query_pipeline_runs` | Pipeline runs in a time window (default last 24h), filterable by pipeline/status, with pagination |
|
|
14
|
+
| `get_pipeline_run` | Full details for a single pipeline run by ID |
|
|
15
|
+
| `query_activity_runs` | Activity runs for a pipeline run — input/output truncated by default; pass `full=true` to opt out |
|
|
16
|
+
| `list_triggers` | All triggers + runtime state and recurrence |
|
|
17
|
+
| `list_linked_services` | Linked services (databases, storage, etc.) and their types |
|
|
18
|
+
| `list_datasets` | Datasets and the linked service each one belongs to |
|
|
19
|
+
| `list_integration_runtimes` | Integration runtimes and their state — useful for spotting offline self-hosted IRs |
|
|
20
|
+
| `list_factories` | All ADF v2 instances in the current subscription — discover other factories without their full ARM IDs |
|
|
21
|
+
|
|
22
|
+
**Write tools** — only registered when `ADF_MCP_MODE=write`:
|
|
23
|
+
|
|
24
|
+
| Tool | Purpose |
|
|
25
|
+
| --------------------- | ----------------------------------------------------------------------------------------------- |
|
|
26
|
+
| `create_pipeline_run` | Kick off a new run of a pipeline (optionally with parameters). Returns the new `runId`. |
|
|
27
|
+
| `cancel_pipeline_run` | Cancel an in-progress pipeline run. Cancels child runs too by default. |
|
|
28
|
+
| `rerun_pipeline_run` | Re-execute a previous run. Defaults to resuming from the failed activity (the common workflow). |
|
|
29
|
+
| `start_trigger` | Start a trigger so it begins firing on its schedule. |
|
|
30
|
+
| `stop_trigger` | Stop a trigger so it stops firing. |
|
|
31
|
+
|
|
32
|
+
**Destructive tools** — only registered when `ADF_MCP_MODE=write` AND `ADF_MCP_ALLOW_DELETE=true`. Every call uses a two-step plan/apply confirmation pattern (see **Destructive mode** below):
|
|
33
|
+
|
|
34
|
+
| Tool | Purpose |
|
|
35
|
+
| --------------------------------- | ----------------------------------------------------------------------- |
|
|
36
|
+
| `create_or_update_pipeline` | Create a new pipeline or overwrite an existing one. |
|
|
37
|
+
| `create_or_update_trigger` | Create or overwrite a trigger. New triggers start in Stopped state. |
|
|
38
|
+
| `create_or_update_linked_service` | Create or overwrite a linked service (database / storage connection). |
|
|
39
|
+
| `create_or_update_dataset` | Create or overwrite a dataset. |
|
|
40
|
+
| `delete_pipeline` | Delete a pipeline. |
|
|
41
|
+
| `delete_trigger` | Delete a trigger (must be stopped first via `stop_trigger`). |
|
|
42
|
+
| `delete_linked_service` | Delete a linked service. Datasets that reference it will start failing. |
|
|
43
|
+
| `delete_dataset` | Delete a dataset. Pipelines that reference it will start failing. |
|
|
44
|
+
|
|
45
|
+
## Prerequisites
|
|
46
|
+
|
|
47
|
+
- **Node.js >= 20** on PATH (`node -v` to verify). The Node MSI install may be UAC-blocked on locked-down corp Windows boxes — ask IT if needed.
|
|
48
|
+
- **Git** to clone the repo.
|
|
49
|
+
- **Azure RBAC**: at minimum **Reader** role on the target Data Factory resource. Assigned in Azure Portal → the ADF resource → **Access control (IAM)** → Role assignments. Without this every tool call returns 403 even when auth succeeds.
|
|
50
|
+
- **An MCP-aware client** (Claude Code, Claude Desktop, Cursor, etc.) to wire it into.
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
### From npm (recommended)
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
# One-off run via npx — no global install needed
|
|
58
|
+
npx adf-mcp-server
|
|
59
|
+
|
|
60
|
+
# Or install globally
|
|
61
|
+
npm install -g adf-mcp-server
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### From source (for development or to pin to a commit)
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
git clone https://github.com/user-vik/adf-mcp-server
|
|
68
|
+
cd adf-mcp-server
|
|
69
|
+
npm install
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Configuration
|
|
73
|
+
|
|
74
|
+
The server reads everything from environment variables — typically set inside your MCP client config rather than the shell.
|
|
75
|
+
|
|
76
|
+
| Variable | Required | Notes |
|
|
77
|
+
| ------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
78
|
+
| `ADF_FACTORY_RESOURCE_ID` | always | Full ARM resource ID, e.g. `/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.DataFactory/factories/<factory>` |
|
|
79
|
+
| `ADF_AUTH_MODE` | no | Auth credential to use. Defaults to `interactive`. See **Authentication modes** below. |
|
|
80
|
+
| `ADF_MCP_MODE` | no | `read` (default) or `write`. `write` registers run/cancel/start/stop tools. See **Write mode** below. |
|
|
81
|
+
| `ADF_MCP_ALLOW_DELETE` | no | When `true` AND `ADF_MCP_MODE=write`, also registers the 8 `create_or_update_*` and `delete_*` tools. See **Destructive mode** below. |
|
|
82
|
+
| `AZURE_TENANT_ID` | for `interactive` / `device-code` / `service-principal` | Entra tenant ID. |
|
|
83
|
+
| `AZURE_CLIENT_ID` | for `service-principal` | Optional for `interactive`/`device-code` (defaults to Azure CLI public client). For `managed-identity`, set only when targeting a user-assigned MI. |
|
|
84
|
+
| `AZURE_CLIENT_SECRET` | for `service-principal` | Treat as a secret. Never commit. |
|
|
85
|
+
|
|
86
|
+
## Wiring into an MCP client
|
|
87
|
+
|
|
88
|
+
Add an entry to your client's MCP config. Example (Claude Code / Claude Desktop format):
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"mcpServers": {
|
|
93
|
+
"ms-adf": {
|
|
94
|
+
"type": "stdio",
|
|
95
|
+
"command": "node",
|
|
96
|
+
"args": ["C:\\path\\to\\adf-mcp-server\\index.js"],
|
|
97
|
+
"env": {
|
|
98
|
+
"AZURE_TENANT_ID": "<your-tenant-id>",
|
|
99
|
+
"ADF_FACTORY_RESOURCE_ID": "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.DataFactory/factories/<factory>"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Restart the MCP client after editing the config.
|
|
107
|
+
|
|
108
|
+
## Authentication modes
|
|
109
|
+
|
|
110
|
+
Set `ADF_AUTH_MODE` to pick how the server obtains an Entra token. Default is `interactive`, which preserves prior behavior.
|
|
111
|
+
|
|
112
|
+
| Mode | Credential | Use case | Required env (beyond `ADF_FACTORY_RESOURCE_ID`) |
|
|
113
|
+
| ------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
114
|
+
| `interactive` _(default)_ | `InteractiveBrowserCredential` | Desktop devs — opens a browser tab on first call. | `AZURE_TENANT_ID` |
|
|
115
|
+
| `device-code` | `DeviceCodeCredential` | SSH / WSL / headless — prints a code + URL to stderr (the MCP client's server log). | `AZURE_TENANT_ID` |
|
|
116
|
+
| `cli` | `AzureCliCredential` | Devs already signed in via `az login`. Zero prompts. | _(none — uses CLI session)_ |
|
|
117
|
+
| `service-principal` | `ClientSecretCredential` | CI, shared servers, automation. | `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET` |
|
|
118
|
+
| `managed-identity` | `ManagedIdentityCredential` | MCP server hosted on an Azure VM, Container App, App Service, etc. | _(none — uses the host identity)_ |
|
|
119
|
+
| `default` | `DefaultAzureCredential` | Chain: env vars → managed identity → CLI → VS Code → interactive browser. Easiest "just works." | _(varies by what's available)_ |
|
|
120
|
+
|
|
121
|
+
On the first tool call, the chosen credential acquires a token at the `https://management.azure.com/.default` scope. Tokens are cached in memory for the lifetime of the process; subsequent calls reuse them.
|
|
122
|
+
|
|
123
|
+
For all user-flow modes (`interactive`, `device-code`, `cli`), the effective ARM permissions are _your own_ personal RBAC on the factory. For `service-principal` and `managed-identity`, they are the SP's or MI's RBAC — grant that identity at least **Reader** on the factory.
|
|
124
|
+
|
|
125
|
+
### Mode-specific notes
|
|
126
|
+
|
|
127
|
+
- **`device-code`**: the message containing the verification URL and one-time code is written to **stderr**, which most MCP clients route to their server log rather than the chat. In Claude Code, view it via `/mcp` → server logs. The auth call blocks until you complete the flow in a browser.
|
|
128
|
+
- **`service-principal`**: `AZURE_CLIENT_ID` here is the SP's app registration, _not_ the Azure CLI public client default.
|
|
129
|
+
- **`managed-identity`**: omit `AZURE_CLIENT_ID` for a system-assigned MI; set it to the MI's client ID for a user-assigned MI.
|
|
130
|
+
- **`default`**: opaque when something fails. If `DefaultAzureCredential` errors with "no credential was found", switch to a specific mode to see which one is actually failing.
|
|
131
|
+
|
|
132
|
+
## Write mode
|
|
133
|
+
|
|
134
|
+
By default the server is **read-only** — no MCP tool it exposes can mutate the factory. An LLM literally cannot call write operations because they aren't registered.
|
|
135
|
+
|
|
136
|
+
To enable mutations, set `ADF_MCP_MODE=write` in the MCP client's `env` block. The server logs `[adf-mcp] write mode enabled — pipeline run + trigger control tools are exposed` at startup so it's visible in the server log.
|
|
137
|
+
|
|
138
|
+
### What write mode unlocks
|
|
139
|
+
|
|
140
|
+
`create_pipeline_run`, `cancel_pipeline_run`, `rerun_pipeline_run`, `start_trigger`, `stop_trigger`. See the tool table above.
|
|
141
|
+
|
|
142
|
+
### What it does NOT unlock
|
|
143
|
+
|
|
144
|
+
Definition-changing operations — `create_or_update_*` and `delete_*` — are gated separately behind `ADF_MCP_ALLOW_DELETE=true` and use a two-step plan/apply confirmation. See **Destructive mode** below.
|
|
145
|
+
|
|
146
|
+
### RBAC
|
|
147
|
+
|
|
148
|
+
`Reader` is no longer enough. Grant the identity used by `ADF_AUTH_MODE` at least **Data Factory Contributor** on the factory, or a narrower custom role that includes the action `Microsoft.DataFactory/factories/pipelineruns/*` and the trigger start/stop actions.
|
|
149
|
+
|
|
150
|
+
### Audit log
|
|
151
|
+
|
|
152
|
+
Every write call is logged to **stderr** with a line like:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
[adf-mcp][AUDIT] 2026-05-19T15:42:01.123Z tool=create_pipeline_run target=pipeline=ETL_Daily caller=victor.perez@example.com status=ATTEMPT
|
|
156
|
+
[adf-mcp][AUDIT] 2026-05-19T15:42:01.987Z tool=create_pipeline_run target=pipeline=ETL_Daily caller=victor.perez@example.com status=SUCCESS
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The caller is parsed from the `upn` / `preferred_username` / `appid` / `oid` claims of the Entra access token. Forward your MCP client's server log somewhere durable if you need a long-term audit trail.
|
|
160
|
+
|
|
161
|
+
### Recommended pairing for shared / CI deployments
|
|
162
|
+
|
|
163
|
+
`ADF_MCP_MODE=write` + `ADF_AUTH_MODE=service-principal`. The SP gets exactly the RBAC it needs, the audit log identifies it consistently, and individual users don't need factory-Contributor on their personal accounts.
|
|
164
|
+
|
|
165
|
+
## Destructive mode
|
|
166
|
+
|
|
167
|
+
Setting `ADF_MCP_ALLOW_DELETE=true` in addition to `ADF_MCP_MODE=write` registers the 8 tools that mutate the factory's definition (`create_or_update_*` for pipelines, triggers, linked services, datasets, plus their `delete_*` counterparts). The server logs `[adf-mcp] destructive mode enabled` at startup so it's visible in the MCP server log. Setting `ADF_MCP_ALLOW_DELETE=true` without `ADF_MCP_MODE=write` logs a warning and is ignored.
|
|
168
|
+
|
|
169
|
+
### Why "destructive" covers create_or_update too
|
|
170
|
+
|
|
171
|
+
`create_or_update_*` can silently overwrite an existing resource — the LLM might not realize it exists. Bundling it with `delete_*` under the same flag keeps "anything that changes the factory's definition" behind a single, conscious opt-in.
|
|
172
|
+
|
|
173
|
+
### Plan/apply confirmation
|
|
174
|
+
|
|
175
|
+
Every destructive tool uses a two-step pattern that forces the LLM (and the human reading the chat) to look at the diff before applying it.
|
|
176
|
+
|
|
177
|
+
1. **Plan step** — the LLM calls the tool with `dry_run: true` (the default). The server fetches the existing resource, returns a `before` / `after` diff, and issues a one-time `confirm_token` with a 10-minute TTL.
|
|
178
|
+
|
|
179
|
+
```jsonc
|
|
180
|
+
{
|
|
181
|
+
"plan_type": "DRY_RUN",
|
|
182
|
+
"action": "create_or_update",
|
|
183
|
+
"target": "pipeline=ETL_Daily",
|
|
184
|
+
"before": {
|
|
185
|
+
"properties": {
|
|
186
|
+
/* current pipeline */
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
"after": {
|
|
190
|
+
"properties": {
|
|
191
|
+
/* proposed pipeline */
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
"confirm_token": "8c1f...e0a3",
|
|
195
|
+
"expires_at": "2026-05-19T15:52:00.000Z",
|
|
196
|
+
"hint": "To apply, call create_or_update_pipeline again with dry_run=false and confirm_token=\"8c1f...e0a3\".",
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
2. **Apply step** — the LLM calls the same tool again with `dry_run: false` and the `confirm_token` from step 1. The token is single-use, bound to the exact `(tool, target, payload)` triple, and tied to the resource's ETag at plan time. If anything changed since the plan was computed, ARM returns HTTP 412 Precondition Failed and the server surfaces "Resource changed since the plan; request a new plan."
|
|
201
|
+
|
|
202
|
+
### Why tokens, not just `dry_run=false`?
|
|
203
|
+
|
|
204
|
+
Without the token, an LLM could skip the plan step entirely. Requiring a token means the LLM must have seen a plan in its own context (and surfaced it to you in the chat) before it can apply. The token store is in-memory; restarting the MCP server invalidates all pending plans.
|
|
205
|
+
|
|
206
|
+
### RBAC for destructive mode
|
|
207
|
+
|
|
208
|
+
The identity needs **Data Factory Contributor** on the factory (same role as write mode — no additional permissions, because ADF doesn't model "can create but not delete" separately at the RBAC level).
|
|
209
|
+
|
|
210
|
+
## Docker / managed identity
|
|
211
|
+
|
|
212
|
+
A `Dockerfile` is included for hosting the server on Azure (Container Apps, App Service, AKS, or a VM) with a managed identity — no client secrets in env vars.
|
|
213
|
+
|
|
214
|
+
```sh
|
|
215
|
+
docker build -t adf-mcp-server .
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Typical deployment pattern on Azure Container Apps:
|
|
219
|
+
|
|
220
|
+
1. Grant the Container App's system-assigned managed identity **Data Factory Contributor** on the target factory.
|
|
221
|
+
2. Configure the container with:
|
|
222
|
+
```
|
|
223
|
+
ADF_FACTORY_RESOURCE_ID=/subscriptions/.../factories/...
|
|
224
|
+
ADF_AUTH_MODE=managed-identity
|
|
225
|
+
ADF_MCP_MODE=write # if you want write tools
|
|
226
|
+
ADF_MCP_ALLOW_DELETE=true # if you want destructive tools
|
|
227
|
+
```
|
|
228
|
+
3. Use an `azureContainerAppsApi`-style transport from your MCP client (out of scope for this server — stdio MCP usually runs locally; this section is for centralized deployments that proxy stdio via a sidecar).
|
|
229
|
+
|
|
230
|
+
The image runs as a non-root user (`adf`, uid auto-assigned). Stdio is the only entrypoint; no ports are exposed.
|
|
231
|
+
|
|
232
|
+
## Run standalone (for debugging)
|
|
233
|
+
|
|
234
|
+
```sh
|
|
235
|
+
ADF_FACTORY_RESOURCE_ID=... AZURE_TENANT_ID=... node index.js
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The server speaks MCP over stdio, so running it directly will just block waiting for an MCP client to connect via stdin/stdout. Useful only to confirm it starts without crashing.
|
|
239
|
+
|
|
240
|
+
## Troubleshooting
|
|
241
|
+
|
|
242
|
+
| Symptom | Likely cause | Fix |
|
|
243
|
+
| ---------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
244
|
+
| `Missing required env vars` on startup | `ADF_FACTORY_RESOURCE_ID` or `AZURE_TENANT_ID` not set in the MCP client's `env` block | Add them to the client config and restart the client. |
|
|
245
|
+
| Tool call returns `403` from ARM | Your account lacks RBAC on the factory | Ask the resource owner to grant at least **Reader** on the Data Factory resource (Portal → ADF → Access control (IAM)). |
|
|
246
|
+
| Tool call returns `401` / token errors | Conditional Access or MFA blocked the silent token | Sign out of Azure CLI / browser sessions, then re-trigger any tool to force a fresh interactive sign-in. |
|
|
247
|
+
| Browser tab never opens on first call | Running over SSH / inside WSL / on a headless host | Switch to `ADF_AUTH_MODE=device-code` and read the code/URL from the MCP server's stderr log. |
|
|
248
|
+
| `Invalid ADF_AUTH_MODE` | Typo in the mode name | Use one of: `interactive`, `device-code`, `cli`, `service-principal`, `managed-identity`, `default`. |
|
|
249
|
+
| `ADF_AUTH_MODE=service-principal requires ...` | Missing `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, or `AZURE_CLIENT_SECRET` | Set all three in the MCP client's `env` block. |
|
|
250
|
+
| MCP client says "server failed to start" | Wrong path in `args`, or Node not on PATH for the client's user | Verify the path with `node "C:\\path\\to\\index.js"` from a fresh shell. On Windows, the MCP client may inherit a different PATH than your terminal. |
|
|
251
|
+
| Calls hang or time out | ARM is throttling (HTTP 429) and the server is auto-retrying with backoff | Check the MCP server's stderr log — each retry is logged. Up to 3 retries honoring `Retry-After`; on exhaustion, the call fails with the original 429. |
|
|
252
|
+
| Activity output is `{ _truncated: true, ... }` | Default 4 KB truncation kicked in to protect the LLM context window | Pass `full=true` to `query_activity_runs` for the untruncated payload. |
|
|
253
|
+
| LLM says "no tool to start a run / cancel" | Server is in read-only mode (default) | Set `ADF_MCP_MODE=write` in the MCP client's `env` block and restart the client. |
|
|
254
|
+
| Write tool returns `403 Authorization failed` | The identity has Reader but not Contributor on the factory | Grant **Data Factory Contributor** (or a narrower custom role with the relevant pipelineruns/triggers actions) to the user / SP / MI. |
|
|
255
|
+
| Apply call returns "Resource changed since the plan" | Someone (or something) modified the resource between your plan and apply step | Re-run with `dry_run=true` to fetch a fresh plan, then apply the new `confirm_token`. |
|
|
256
|
+
| Apply call returns "Invalid confirm_token" | Token expired (10-min TTL), already used, or server restarted | Re-run with `dry_run=true` to get a new token. |
|
|
257
|
+
| Apply call returns "confirm_token does not match" | The payload changed between plan and apply (e.g. the LLM edited the definition) | Re-run the plan step with the current payload to get a token bound to it. |
|
|
258
|
+
| `404` for a pipeline that exists | Wrong factory in `ADF_FACTORY_RESOURCE_ID` | Confirm the ARM ID matches the factory you expect (subscription, resource group, and name all match). |
|
|
259
|
+
|
|
260
|
+
For everything else, check the project [issues](https://github.com/user-vik/adf-mcp-server/issues).
|
|
261
|
+
|
|
262
|
+
## Security
|
|
263
|
+
|
|
264
|
+
See [SECURITY.md](SECURITY.md) for the disclosure process, in/out-of-scope items, and known design limitations.
|
|
265
|
+
|
|
266
|
+
## Roadmap
|
|
267
|
+
|
|
268
|
+
See [ROADMAP.md](ROADMAP.md) for the staged plan and what's intentionally deferred.
|
|
269
|
+
|
|
270
|
+
## Changelog
|
|
271
|
+
|
|
272
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
MIT — see [LICENSE](LICENSE).
|