<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet href="/feed_style.xsl" type="text/xsl"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="https://www.rssboard.org/media-rss">
  <channel>
    <title>fastmcp on MR70</title>
    <link>https://www.mr70.eu/tags/fastmcp/</link>
    <description>Recent content in fastmcp on MR70</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <copyright>MR70 - [Creative Commons Attribution 4.0 International License](https://creativecommons.org/licenses/by/4.0/).</copyright>
    <lastBuildDate>Sun, 18 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.mr70.eu/tags/fastmcp/index.xml" rel="self" type="application/rss+xml" /><icon>https://www.mr70.eu/logo.svg</icon>
    
    
    <item>
      <title>mcpAD - an MCP server for Microsoft Active Directory</title>
      <link>https://www.mr70.eu/posts/mcpad-mcp-server-active-directory/</link>
      <pubDate>Sun, 18 Oct 2026 00:00:00 +0000</pubDate>
      
      <guid>https://www.mr70.eu/posts/mcpad-mcp-server-active-directory/</guid>
      <description><![CDATA[<h2 id="mcpad">mcpAD</h2>
<p><a href="https://github.com/matteoredaelli/mcpAD">mcpAD</a> is a <a href="https://gofastmcp.com">FastMCP</a> server that exposes the <a href="https://github.com/matteoredaelli/msad"><code>msad</code></a> Active Directory / LDAP library as <strong>MCP tools</strong>.</p>
<p>It is built with <strong>Python 3.14</strong> and <strong>uv</strong>, and reuses the typed, public <code>msad</code> API (search users/groups, memberships, account status). All the tools are <strong>read-only</strong>, which makes it a safe way to let an AI agent explore and audit an Active Directory domain without the risk of changing anything.</p>
<p>If you already know <a href="/posts/msad-cli-active-directory/">msad</a> — the command line tool for working with AD/LDAP — mcpAD is its natural companion: the same library, now reachable from any MCP client (Claude Desktop, IDE agents, custom agents, &hellip;).</p>
<h2 id="tools">Tools</h2>
<p>The server exposes a rich set of read-only tools, grouped by purpose.</p>
<h3 id="search-and-lookup">Search and lookup</h3>
<table>
<thead>
<tr>
<th>Tool</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>find_users</code></td>
<td>Find users by name/surname/mail/sam/department (criteria are ANDed, <code>*</code> wildcards)</td>
</tr>
<tr>
<td><code>get_user</code></td>
<td>Get a single user by sAMAccountName, UPN, mail or cn (exact match)</td>
</tr>
<tr>
<td><code>find_groups</code></td>
<td>Find groups by cn/name/sAMAccountName/displayName (<code>*</code> wildcards)</td>
</tr>
<tr>
<td><code>get_group</code></td>
<td>Get a single group by sAMAccountName or cn (exact match)</td>
</tr>
<tr>
<td><code>find_computers</code></td>
<td>Find computers by name (cn), dNSHostName or operatingSystem</td>
</tr>
<tr>
<td><code>get_computer</code></td>
<td>Get a single computer by sAMAccountName, cn or dNSHostName</td>
</tr>
<tr>
<td><code>find_ous</code></td>
<td>Find organizational units by name (<code>*</code> wildcards)</td>
</tr>
<tr>
<td><code>get_ou</code></td>
<td>Get a single OU by its <code>ou</code> name or full DN (exact match)</td>
</tr>
<tr>
<td><code>get_ou_contents</code></td>
<td>List objects under an OU (optionally filtered by <code>object_class</code>)</td>
</tr>
<tr>
<td><code>get_by_dn</code></td>
<td>Fetch any entry directly by its DN (resolve <code>manager</code> / <code>managedBy</code>)</td>
</tr>
</tbody>
</table>
<p>The <code>find_*</code> tools accept an optional <code>base</code> (a DN, e.g. an OU) to restrict the search to a subtree instead of the whole domain.</p>
<h3 id="memberships-and-account-status">Memberships and account status</h3>
<table>
<thead>
<tr>
<th>Tool</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>group_members</code></td>
<td>List members of a group (direct, or recursive with <code>nested</code>)</td>
</tr>
<tr>
<td><code>user_groups</code></td>
<td>List the groups a user belongs to (nested/recursive by default)</td>
</tr>
<tr>
<td><code>is_member</code></td>
<td>Check whether a user is a (nested) member of a group</td>
</tr>
<tr>
<td><code>is_disabled</code></td>
<td>Check whether an account is disabled</td>
</tr>
<tr>
<td><code>is_locked</code></td>
<td>Check whether an account is locked</td>
</tr>
</tbody>
</table>
<h3 id="audit">Audit</h3>
<table>
<thead>
<tr>
<th>Tool</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>find_inactive_users</code></td>
<td>Users whose last logon is older than <code>days</code> (default 90), optionally including those who never logged on</td>
</tr>
<tr>
<td><code>find_stale_computers</code></td>
<td>Computers whose last logon is older than <code>days</code> (default 90)</td>
</tr>
<tr>
<td><code>get_domain_info</code></td>
<td>The domain object: security-relevant settings and metadata</td>
</tr>
<tr>
<td><code>get_password_policy</code></td>
<td>The default domain password policy (maxPwdAge, minPwdLength, &hellip;)</td>
</tr>
<tr>
<td><code>get_password_policy_violations</code></td>
<td>Enabled users whose password violates the domain policy</td>
</tr>
<tr>
<td><code>get_privileged_groups</code></td>
<td>Well-known privileged groups (Domain Admins, &hellip;) with member counts</td>
</tr>
</tbody>
</table>
<h3 id="diagnostics">Diagnostics</h3>
<table>
<thead>
<tr>
<th>Tool</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>test_connection</code></td>
<td>Test AD connectivity and bind (returns ok / target / auth / error)</td>
</tr>
<tr>
<td><code>health</code></td>
<td>Server health check (server name, msad version, connectivity)</td>
</tr>
</tbody>
</table>
<h2 id="authentication-and-connection">Authentication and connection</h2>
<p>The server has <strong>no connection settings of its own</strong>: it reuses the <code>msad</code> configuration file, <code>~/.msad.toml</code> by default (create it with <code>msad init</code>). Each tool call opens a short-lived connection through <code>msad.connect()</code>.</p>
<p>A minimal <code>~/.msad.toml</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[defaults]
</span></span><span style="display:flex;"><span>domain = <span style="color:#f1fa8c">&#34;group&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[domains.group]
</span></span><span style="display:flex;"><span>host = <span style="color:#f1fa8c">&#34;dc.example.com&#34;</span>
</span></span><span style="display:flex;"><span>base = <span style="color:#f1fa8c">&#34;dc=group,dc=example,dc=com&#34;</span>
</span></span><span style="display:flex;"><span>port = <span style="color:#bd93f9">636</span>
</span></span><span style="display:flex;"><span>use_ssl = <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># user = &#34;svc_account&#34;   # optional</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># password = &#34;...&#34;       # optional</span>
</span></span></code></pre></div><p>You can declare several <code>[domains.*]</code> blocks and pick one per call with the tool&rsquo;s <code>domain</code> argument, or globally via <code>MSAD_DOMAIN</code>.</p>
<p>The authentication mode is chosen automatically from the domain block:</p>
<ul>
<li><strong>Kerberos (SASL)</strong> when <code>user</code>/<code>password</code> are not both set (the default) — obtain a ticket first with <code>kinit</code>.</li>
<li><strong>User / password</strong> when both <code>user</code> and <code>password</code> are set — a simple LDAP bind. Restrict the file permissions (<code>chmod 600 ~/.msad.toml</code>) since the password is stored in clear text.</li>
</ul>
<p>Use LDAPS (<code>port = 636</code>, <code>use_ssl = true</code>) whenever possible.</p>
<h2 id="running">Running</h2>
<p>Using <code>uvx</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>uvx --from git+https://github.com/matteoredaelli/mcpAD mcpAD
</span></span></code></pre></div><p>The transport is configurable (<code>stdio</code>, <code>streamable-http</code>, <code>sse</code>):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>make start-stdio                               <span style="color:#6272a4"># stdio (for local MCP clients)</span>
</span></span><span style="display:flex;"><span>make start-http                                <span style="color:#6272a4"># streamable-http on 127.0.0.1:8000</span>
</span></span><span style="display:flex;"><span>make start-http <span style="color:#8be9fd;font-style:italic">HOST</span><span style="color:#ff79c6">=</span>0.0.0.0 <span style="color:#8be9fd;font-style:italic">PORT</span><span style="color:#ff79c6">=</span><span style="color:#bd93f9">9000</span>         <span style="color:#6272a4"># bind elsewhere</span>
</span></span></code></pre></div><h2 id="registering-the-server-with-an-mcp-client">Registering the server with an MCP client</h2>
<h3 id="local-stdio--the-client-launches-the-server">Local (stdio) — the client launches the server</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;mcpServers&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;mcpAD&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;command&#34;</span>: <span style="color:#f1fa8c">&#34;uv&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;args&#34;</span>: [<span style="color:#f1fa8c">&#34;run&#34;</span>, <span style="color:#f1fa8c">&#34;--directory&#34;</span>, <span style="color:#f1fa8c">&#34;/path/to/mcpAD&#34;</span>, <span style="color:#f1fa8c">&#34;mcpad&#34;</span>],
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;env&#34;</span>: { <span style="color:#ff79c6">&#34;MSAD_DOMAIN&#34;</span>: <span style="color:#f1fa8c">&#34;group&#34;</span> }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The spawned process inherits your environment, so a Kerberos ticket obtained with <code>kinit</code> in the same session is used automatically. For an unattended client, point it at a keytab via <code>KRB5_CLIENT_KTNAME</code>.</p>
<h3 id="remote-streamable-http--the-server-runs-as-a-service">Remote (streamable-http) — the server runs as a service</h3>
<p>Start the service on a host that has AD/Kerberos access:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>make start-http <span style="color:#8be9fd;font-style:italic">HOST</span><span style="color:#ff79c6">=</span>0.0.0.0 <span style="color:#8be9fd;font-style:italic">PORT</span><span style="color:#ff79c6">=</span><span style="color:#bd93f9">9000</span>
</span></span></code></pre></div><p>Then point the client at its URL:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;mcpServers&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;mcpAD&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;transport&#34;</span>: <span style="color:#f1fa8c">&#34;http&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;url&#34;</span>: <span style="color:#f1fa8c">&#34;http://mcp-host.example.com:9000/mcp&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The server itself performs no client authentication: if you expose it beyond localhost, put it behind a reverse proxy / network controls and treat the AD credentials on that host accordingly. Kerberos runs where the <strong>server</strong> runs — that host needs a valid ticket or keytab, not the client.</p>
<h2 id="why-it-is-useful">Why it is useful</h2>
<p>With mcpAD an AI agent can answer questions like <em>&ldquo;which accounts have been inactive for more than 90 days?&rdquo;</em>, <em>&ldquo;who are the members of the Domain Admins group?&rdquo;</em> or <em>&ldquo;show me the password policy violations&rdquo;</em> — directly, in natural language, and safely, because every tool is read-only.</p>
<p>The code is available on <a href="https://github.com/matteoredaelli/mcpAD">GitHub</a>.</p>
]]></description>
      
    </item>
    
    
  </channel>
</rss>
