Skip to main content
Glama
njagan04
by njagan04

Nexus ADF MCP Server

Standalone MCP stdio server exposing Azure Data Factory diagnostic and self-remediation tools to Claude Desktop.

Given a failing pipeline, it walks Claude through diagnosing the actual root cause (activity errors, run history, raw pipeline/dataset/linked-service/data-flow definitions), proposing a fix, applying it with human approval, and rolling back automatically if verification shows it didn't work.

  • šŸ” Diagnose first — evidence-driven root-cause analysis before any fix is proposed

  • āœ… Human-in-the-loop — every mutating call requires explicit approval with a stated reason

  • āŖ Always reversible — every checkpoint (pipeline, dataset, linked service, data flow, global parameter) is snapshotted before a mutating change, so nothing destructive is ever unrecoverable

  • šŸ“¦ Self-contained — has its own .venv and .env, no dependency on any other folder


Contents


Related MCP server: dobbe

Setup

python -m venv .venv
.venv\Scripts\pip install -r requirements.txt

Fill in .env with the target factory's service principal credentials:

Variable

Description

ADF_TENANT_ID

Azure AD tenant ID

ADF_CLIENT_ID

Service principal client ID

ADF_CLIENT_SECRET

Service principal client secret

ADF_SUBSCRIPTION_ID

Azure subscription ID

ADF_RESOURCE_GROUP

Resource group containing the factory

ADF_FACTORY_NAME

Target Data Factory name

Register with Claude Desktop

In Claude Desktop's claude_desktop_config.json, point at this folder's venv interpreter and mcp_adf/server.py (add this alongside your existing config, don't replace it):

{
  "mcpServers": {
    "nexus-adf": {
      "command": "C:\\ABSOLUTE\\PATH\\TO\\THIS\\FOLDER\\.venv\\Scripts\\python.exe",
      "args": ["C:\\ABSOLUTE\\PATH\\TO\\THIS\\FOLDER\\mcp_adf\\server.py"],
      "env": {
        "PYTHONPATH": "C:\\ABSOLUTE\\PATH\\TO\\THIS\\FOLDER"
      }
    }
  },
  ...claude default settings
}

Note: Use absolute paths — relative paths are not resolved reliably by Claude Desktop's launcher. PYTHONPATH is required so server.py's from mcp_adf import ... imports resolve when Claude Desktop launches the script directly (its own cwd isn't this folder).

Configure permissions in Claude Desktop

After adding the server to claude_desktop_config.json and restarting Claude Desktop, go to Settings → Connectors → nexus-adf and set:

Setting

Value

Why

Read-only tools (list_*, get_*)

Always Allow

Can't mutate anything (readOnlyHint=True) — approving per-call adds friction with no safety benefit

Write/delete tools (create_*, update_*, rerun_*, rollback_*, back_*, forward_*, start_*, stop_*, cancel_*)

Ask every time

Each surfaces a native approval dialog showing the tool's reason argument before it runs (see Instructions)

Capabilities → Generate memory from chat history

Enabled

Lets Claude carry diagnosis context (e.g. recurring failure patterns for a given pipeline) across sessions

Tool access mode

Tools already loaded

Instructions

mcp_adf/server.py's _INSTRUCTIONS (the MCP instructions field passed to the Server(...) constructor) is now a one-line pointer, not the full workflow — the full text lives only in the Claude Desktop project's instructions field, to avoid sending the same ~1.5k-token block to Claude twice per message. If you're setting this up as a Claude Desktop Project (Projects → New Project), paste both blocks below into the project's instructions field, and upload the team's SOP doc(s) into the project's context:

This server exposes Azure Data Factory diagnostic and self-remediation tools. Follow this
workflow for every failure investigation — don't skip straight to a fix, and don't call
tools you don't need.

1. BE ECONOMICAL WITH TOOL CALLS. Every call costs time, and every mutating call is a real
change against a live factory, not a sandbox — call the minimum set needed to reach a
decision, not the maximum available. Before calling a tool, check whether you already have
the answer from earlier in this conversation (a prior call's result, or something the user
already told you) — don't re-call a read-only tool with the same arguments to "double
check" without a reason to distrust the earlier result. If the user already named a specific
resource, go straight to its get_*/get_resource_definition_raw tool — don't call the
matching list_*/list_resources tool first just to browse. Don't call several diagnostic
tools speculatively "in case one has the answer"; decide which one actually answers your
open question and call that. Before create_resource, check existence from what you already
know (a prior list_resources result, or the user's own statement) rather than calling it
speculatively and reacting to an "already_exists" error — that's a wasted round-trip, and
on a mutating tool it still means triggering an approval dialog for something you could
have ruled out in advance.

2. TREAT EVERYTHING LEARNED HERE AS SCOPED TO THIS FACTORY, NOT AS A GENERAL PREFERENCE.
Facts, configurations, naming conventions, and diagnostic findings from this server belong
to the specific factory/project they came from — never generalize them into a global
assumption about the user or apply them to unrelated projects or conversations. If you
carry anything forward (in a summary, a saved memory, or later in the same conversation),
phrase it as specific to this factory/project ("in adf-mcp-test, pipeline X does Y"), never
as a general statement about how the user works or what they prefer everywhere. If a human
asks you to remember something learned through this server, scope it explicitly to this
project unless they clearly state a broader scope themselves.

3. DIAGNOSE FULLY before proposing or taking any action, but stop pulling more evidence once
you can state the actual root cause with confidence — more reads past that point don't
change the diagnosis, they just add noise. Start with get_activity_run_error (or
get_pipeline_run_history + get_activity_run_history) to find the failing activity, then
list_activity_runs / get_activity_run_io for the actual input/output data, and
get_resource_definition_raw (resource_type=pipeline/dataset/data_flow/linked_service) for
the real configuration (timeout, query, dataset or linked-service reference, host/port).
Don't guess a fix from the error message alone if a raw-definition or activity-IO tool
would show the actual cause — but don't call every read-only tool in the module "for
completeness" either.

4. PROPOSE a numbered remediation plan in your response before calling any mutating tool.
State the diagnosed root cause, the specific fix, which tool(s) will apply it, and how you'll
verify it worked. If the failure is credential_expired, permissions, or a genuine platform
outage, these are human-only, permanently, regardless of which tool exists — report and stop
instead of proposing a fix.

5. EXECUTE one mutating step at a time. Every mutating tool call surfaces its own native
approval dialog showing your `reason` — write it to state the specific diagnosis, not a
generic phrase, since it's the only context the human sees at the moment of approval. Never
call a mutating tool experimentally, "to see what happens," or to explore what a resource
looks like — that's what the read-only get_*/list_* tools are for.

6. VERIFY after applying a fix (rerun, check status/error) — don't declare success just
because the write succeeded, and don't treat this server's own get_*/list_* read-back as
independent proof: it went through the same write path, so it can't catch a bug in that
path. Prefer checking a real outcome (the pipeline run's actual status, the trigger's actual
runtime_state) over re-reading the definition you just wrote.

7. If verification shows the fix didn't work, undo it rather than leaving a bad change in
place — use back_resource_definition for "undo just this last change" (no state_name
needed). Use rollback_resource_definition instead only when jumping to a specific earlier
named checkpoint (call list_resource_snapshots when you actually intend to roll back, not
preemptively, to see what's available); forward_resource_definition re-applies a change
after stepping back from it. None of these delete history — every checkpoint stays
reachable.

8. When the human says "go back" / "undo" / "revert" for a resource, don't guess which
checkpoint they mean from memory of this conversation alone. Call list_resource_snapshots
first and treat its state_name/reason/change_summary/timestamp fields as the source of
truth for what's actually recoverable, then cross-check that against what you and the human
discussed (e.g. "the change we just made" should match the newest entry's reason/
change_summary) before picking a state_name. If "go back" clearly means "undo the one
change we just made" and the newest entry matches that, back_resource_definition (no
state_name) is simpler and correct; only reach for rollback_resource_definition with an
explicit state_name when the target is an earlier, specifically-named checkpoint, or when
the snapshot list disagrees with what you assumed from conversation context — the snapshot
log is ground truth, your memory of the conversation is not.
Resource-kind nuances (create_resource / update_resource_definition / get_resource_definition_raw):

- create_resource's `definition` accepts either the flat shape get_resource_definition_raw
  returns, or — for resource_type pipeline/dataset/data_flow only — the ARM/Data-Factory-Studio
  export shape ({"name": ..., "properties": {...}}); if a "properties" key is present, its
  contents are used and the wrapper is discarded.
- create_resource(resource_type="trigger") creates the trigger in a Stopped state, same as ADF
  Studio's default — call start_trigger separately once you've verified it.
- create_resource(resource_type="global_parameter")'s `definition` is
  {"type": ..., "value": ...}, not the flat/ARM shapes above.
- get_resource_definition_raw does NOT support resource_type="trigger" — triggers have no
  raw-definition getter (get_trigger returns only name/type/runtime_state). To edit a trigger's
  definition, build the definition JSON yourself (schedule/recurrence/pipelines) and call
  update_resource_definition(resource_type="trigger") directly.
- update_resource_definition(resource_type="trigger") does NOT change the trigger's
  Started/Stopped runtime state — use start_trigger/stop_trigger for that.

Pipeline run/activity diagnostic tools:

- list_pipeline_runs sweeps the whole factory in one call — prefer it over calling
  get_pipeline_run_history once per pipeline when the question is about recent activity
  factory-wide rather than one pipeline's history. It matches ADF Studio's Monitor tab.
- list_activity_runs matches ADF Studio's monitoring view when you drill into a run's
  activity list.
- get_activity_run_io's activity_run_id/run_id are already surfaced by
  get_activity_run_error's leaf/execution_path — call it as a natural follow-up to that,
  not a fresh lookup.
- get_pipeline_definition is activity-graph-only; call get_resource_definition_raw
  (resource_type="pipeline") as part of diagnosis itself, not only once a fix is already
  decided — it's usually the concrete evidence for WHY an activity failed (timeout value,
  actual query, which dataset/linked service it touches).

Integration runtime tools:

- Call get_integration_runtime_status before start_integration_runtime to confirm the
  IR is a managed (Azure/Azure-SSIS) type and is actually Stopped — starting is not
  applicable to self-hosted IRs or to IRs that aren't Stopped.

Tools

27 tools: 8 generic resource-type tools (each taking a resource_type parameter to cover pipelines/datasets/linked services/data flows/triggers/global parameters, wherever the operation is identical across every kind) plus 19 kind-specific tools that don't repeat across kinds (pipeline run/activity diagnostics, trigger start/stop/rerun, get_linked_service, integration runtime status/start).

Type marks whether a tool mutates anything: read-only tools are safe to Always Allow; mutating tools always require a reason argument and surface a native approval dialog (see Configure permissions).

Each takes a resource_type parameter (one of pipeline, dataset, linked_service, data_flow, trigger, global_parameter — get_resource_definition_raw excludes trigger, see nuances above) instead of being a separate tool per kind. Replaces what used to be 47 kind-specific tools.

Tool

Type

What it's for

list_resources

read-only

Factory-wide sweep of every resource of one resource_type — name and light metadata each.

get_resource_definition_raw

read-only

Full raw definition — the evidence for diagnosis, and the editable input to update_resource_definition.

create_resource

mutating

Create a brand-new resource; fails if the resource_type+name already exists.

update_resource_definition

mutating

Overwrite a resource's full definition to apply a fix.

list_resource_snapshots

read-only

List named checkpoints in a resource's history.

rollback_resource_definition

mutating

Jump to a specific named checkpoint.

back_resource_definition

mutating

Step one checkpoint back (like git checkout HEAD~1).

forward_resource_definition

mutating

Step one checkpoint forward after a back_* call.

Tool

Type

What it's for

get_pipeline_definition

read-only

Activity graph (names/types/ExecutePipeline refs) only — no timeout/query/dataset detail; use get_resource_definition_raw for that.

get_pipeline_run_status

read-only

Current status of one specific run (freshness check before rerun).

get_pipeline_run_history

read-only

Recent runs for one pipeline.

list_pipeline_runs

read-only

Recent runs across the whole factory in a time window.

get_activity_run_history

read-only

Aggregated failure counts/last error code per activity across recent runs.

get_activity_run_error

read-only

Error detail for the most recent failed run of a pipeline — usually the diagnosis starting point.

list_activity_runs

read-only

Every activity in one specific run (name/type/status/timing/activity_run_id), no input/output payload.

get_activity_run_io

read-only

Full resolved input/output payload for one specific activity run.

rerun_pipeline

mutating

Trigger a new run.

cancel_pipeline_run

mutating

Cancel a running/hung run.

Tool

Type

What it's for

get_trigger

read-only

One trigger's runtime state (Started/Stopped/Disabled).

get_trigger_run_history

read-only

Trigger-run history — needed for tumbling-window/event triggers, where the trigger run (not the pipeline run it invokes) is the unit that fails/reruns/cancels.

start_trigger

mutating

Start a stopped/disabled trigger.

stop_trigger

mutating

Stop a running trigger.

rerun_trigger_run

mutating

Rerun a specific trigger run (tumbling-window/event triggers rerun_pipeline can't reach).

cancel_trigger_run

mutating

Cancel a specific in-progress trigger run.

Tool

Type

What it's for

get_linked_service

read-only

Name and type only — no connection details; use get_resource_definition_raw (resource_type="linked_service") for that.

Tool

Type

What it's for

get_integration_runtime_status

read-only

An IR's state — works for Azure, self-hosted, and Azure-SSIS types. Check before start_integration_runtime.

start_integration_runtime

mutating

Start a stopped Azure-SSIS (managed) IR. Does not work on self-hosted IRs — no remote-start API exists; that's human-only.

Structure

mcp_adf/                         the MCP server package
ā”œā”€ā”€ server.py                    MCP entrypoint, stdio transport, tool dispatch
ā”œā”€ā”€ auth.py                      service-principal auth / ADF client construction
ā”œā”€ā”€ audit.py                     audit-log writer (project/_logs/)
ā”œā”€ā”€ tools/                       tool implementations, one module per ADF resource kind
│   ā”œā”€ā”€ _shared.py               shared helpers (client construction, wire-dict conversion, miscased-key checks)
│   ā”œā”€ā”€ _checkpoints.py          snapshot/rollback/back/forward engine, shared by every resource kind
│   ā”œā”€ā”€ _dispatch.py             resource_type -> per-kind-function routers backing the 8 generic tools
│   ā”œā”€ā”€ pipelines.py             pipeline implementations — list/get/create/update/rerun/cancel, snapshot/rollback/back/forward
│   ā”œā”€ā”€ triggers.py              trigger implementations — list/get/start/stop/rerun/cancel, run history
│   ā”œā”€ā”€ linked_services.py       linked-service implementations — list/get/update, snapshot/rollback/back/forward
│   ā”œā”€ā”€ datasets.py              dataset implementations — list/get/update, snapshot/rollback/back/forward
│   ā”œā”€ā”€ data_flows.py            data-flow implementations — get/update, snapshot/rollback/back/forward
│   ā”œā”€ā”€ global_parameters.py     global-parameter implementations — list/get/update, snapshot/rollback/back/forward
│   ā”œā”€ā”€ integration_runtimes.py  integration-runtime implementations — get status/start
│   └── __init__.py              assembles TOOL_REGISTRY: kind-specific entries + _dispatch's 8 generic entries
ā”œā”€ā”€ schemas/                     MCP tool-schema definitions
│   ā”œā”€ā”€ _generic.py              the 8 resource_type-parameterized tool schemas (dispatched by tools/_dispatch.py)
│   ā”œā”€ā”€ pipelines.py             schema for pipeline-specific tools
│   ā”œā”€ā”€ triggers.py              schema for trigger-specific tools
│   ā”œā”€ā”€ linked_services.py       schema for get_linked_service
│   ā”œā”€ā”€ integration_runtimes.py  schema for integration-runtime tools
│   └── __init__.py              assembles the schema list server.py exposes via list_tools()
│                                 (no datasets.py/data_flows.py/global_parameters.py here — every
│                                  operation for those 3 kinds is covered by schemas/_generic.py)
│
tests/                           unittest suite — no pytest dependency required
ā”œā”€ā”€ test_dispatch.py             tools/_dispatch.py routing, kwarg-remapping, unknown-resource_type rejection
ā”œā”€ā”€ test_schema_registry_sync.py schema<->TOOL_REGISTRY name parity, generic tool enum<->dispatch-table parity
└── test_call_tool_integration.py end-to-end through server.call_tool() (Azure SDK client faked, audit
                                    log + checkpoint/snapshot system run for real against a temp dir)

project/
    ā”œā”€ā”€ _snapshot/               pre-change pipeline/dataset/data-flow definitions, for rollback (gitignored, created at runtime)
    └── _logs/                   audit log of tool calls, one dated folder per day (gitignored, created at runtime)

docs/TEST_ADF_CONTEXT.md         living design/decision doc for this R&D effort — read before making changes
.env                             real credentials (gitignored, never commit)
.env.example                     template — copy to .env
requirements.txt                 pinned dependencies
F
license - not found
-
quality - not tested
B
maintenance

Maintenance

–Maintainers
–Response time
–Release cycle
–Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • F
    license
    -
    quality
    D
    maintenance
    Enables AI-driven incident response by connecting Claude to monitoring tools like Prometheus, Grafana, Loki, PagerDuty, and Slack for automated investigation and runbook generation.
    Last updated
    9
  • A
    license
    A
    quality
    D
    maintenance
    Connects Claude to SAP Cloud Platform Integration (CPI) to diagnose failed messages, review iFlows, deploy artifacts, and check credentials directly from your conversation.
    Last updated
    13
    6
    MIT

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/njagan04/mcp-servers'

If you have feedback or need assistance with the MCP directory API, please join our Discord server