<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ozan Dikici</title>
    <description>The latest articles on DEV Community by Ozan Dikici (@ozandikici).</description>
    <link>https://dev.to/ozandikici</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4117207%2F3db7b6b5-0f45-4f09-b0cd-59050bce4a42.jpg</url>
      <title>DEV Community: Ozan Dikici</title>
      <link>https://dev.to/ozandikici</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ozandikici"/>
    <language>en</language>
    <item>
      <title>Website audits as MCP tools: OAuth sign-in and what one finding has to contain</title>
      <dc:creator>Ozan Dikici</dc:creator>
      <pubDate>Wed, 30 Sep 2026 15:24:02 +0000</pubDate>
      <link>https://dev.to/sitelemetry/website-audits-as-mcp-tools-oauth-sign-in-and-what-one-finding-has-to-contain-1nml</link>
      <guid>https://dev.to/sitelemetry/website-audits-as-mcp-tools-oauth-sign-in-and-what-one-finding-has-to-contain-1nml</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;After a coding agent ships a site, the checks that should follow are scattered across separate tools: headers and TLS in one, technical SEO in another, then accessibility, performance and analytics tags. Each has its own report, usually written for a person. The agent that will make the change sees none of it unless I paste a summary in.&lt;/p&gt;

&lt;p&gt;I'm Ozan. I built Sitelemetry to put those checks where the agent works. It audits sites you own or are authorized to test, in six areas: security, technical SEO, AI visibility, accessibility, performance and integrations (analytics and tag setup).&lt;/p&gt;

&lt;h2&gt;
  
  
  The intended loop
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Audit.&lt;/strong&gt; The agent calls an audit tool against the site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix list.&lt;/strong&gt; The tool result carries the evidence and the remediation for every finding, so the agent works from that list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change.&lt;/strong&gt; The agent edits the code or configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-audit.&lt;/strong&gt; The same audit runs again on the same target. A finding that is missing from the new list is fixed; one that is still there is not.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The audit tools are read-only: they report, and the agent that has the repository open makes the change. Step 4 is a new run, not a summary of what was edited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why OAuth for the MCP connection, not an API key in a config file
&lt;/h2&gt;

&lt;p&gt;The audits are seven tools on a remote MCP server over Streamable HTTP: &lt;code&gt;audit_security&lt;/code&gt;, &lt;code&gt;audit_seo&lt;/code&gt;, &lt;code&gt;audit_ai_visibility&lt;/code&gt;, &lt;code&gt;audit_accessibility&lt;/code&gt;, &lt;code&gt;audit_performance&lt;/code&gt;, &lt;code&gt;audit_integrations&lt;/code&gt; and &lt;code&gt;audit_full&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;They are MCP tools because the agent is the one that needs the output. Findings arrive as a tool result, so the evidence and the remediation text are already in context when it starts the change.&lt;/p&gt;

&lt;p&gt;The usual alternative is an API key in a JSON config file. I did not want that. Those files get committed, synced between machines and pasted into bug reports, and the key is a long-lived secret in plain text.&lt;/p&gt;

&lt;p&gt;So authorization is OAuth 2.1 with PKCE (S256) and dynamic client registration. You configure one URL. In Cursor's &lt;code&gt;mcp.json&lt;/code&gt;, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sitelemetry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://sitelemetry.com/mcp"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole entry. The client reads &lt;code&gt;/.well-known/oauth-protected-resource&lt;/code&gt; and &lt;code&gt;/.well-known/oauth-authorization-server&lt;/code&gt;, registers itself, and requests one scope, &lt;code&gt;audit&lt;/code&gt;. The config file holds a URL and nothing secret. The cost is that the client has to support this flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Long audits
&lt;/h2&gt;

&lt;p&gt;A long audit returns &lt;code&gt;status: running&lt;/code&gt; with a &lt;code&gt;jobId&lt;/code&gt;. The client calls the same tool again with that &lt;code&gt;jobId&lt;/code&gt; until the result is final. Polling does not start a second scan.&lt;/p&gt;

&lt;h2&gt;
  
  
  What one finding looks like
&lt;/h2&gt;

&lt;p&gt;This is a real finding from &lt;code&gt;audit_security&lt;/code&gt;, passive profile, run against my own production site on 2026-09-30:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;severity:    medium
title:       CSP permits inline styles
evidence:    Affected location: https://sitelemetry.com/ Content-Security-Policy: style-src: 'unsafe-inline'; hardening points: 13/22 (deficit: 9).
impact:      Inline style permissions weaken restrictions on injected CSS and page appearance.
remediation: Remove 'unsafe-inline' from the reported style directives by moving inline styles into approved stylesheets; test in Report-Only mode first.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is not fixed yet, so I have no before and after to show.&lt;/p&gt;

&lt;p&gt;Give an agent only "medium: CSP" and it has to guess what is wrong and where. Each field has a job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Evidence&lt;/strong&gt; is what was observed: the URL, the header and the directive. The agent does not have to rediscover the problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Impact&lt;/strong&gt; is one sentence on why it matters, so I can decide whether to do it now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remediation&lt;/strong&gt; is the change to make, including the caution to test in Report-Only mode first. A CSP edit can break a page's styling, so that clause belongs in the finding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Severity&lt;/strong&gt; orders the work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Passing checks carry evidence too. In the same run, "HSTS active" came back with &lt;code&gt;max-age=31536000; includeSubDomains&lt;/code&gt; and "Response compression enabled" with &lt;code&gt;content-encoding: gzip; body bytes 109605 → 26480&lt;/code&gt;. A pass without the observed value is only an assertion.&lt;/p&gt;

&lt;h2&gt;
  
  
  A check that did not run is not a pass
&lt;/h2&gt;

&lt;p&gt;That run returned a security score of 96, grade A, with 17 passing checks and 0 critical, 0 high, 1 medium, 3 low and 2 info findings. It also returned coverage status "partial", because the passive profile does not run active checks such as ports or exposure paths. Each of those modules says so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module: ports
status: unavailable
reason: The selected Passive profile does not run this active check.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An agent that sees no port findings is likely to conclude the ports are fine. So the status is reported per module, with the reason written as a sentence. The 96 describes the checks that ran, and "partial" says that was not everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It is not a pentest.&lt;/li&gt;
&lt;li&gt;A score describes what the checks returned on that run, not whether the site is secure.&lt;/li&gt;
&lt;li&gt;AI visibility means readiness for AI crawlers and answers, not tracking of chatbot mentions.&lt;/li&gt;
&lt;li&gt;It is a closed-source hosted service. The public repository holds client manifests only.&lt;/li&gt;
&lt;li&gt;This post covers only the MCP server. Also live: a GitHub Action, a GitLab CI/CD component, an n8n node, a WordPress plugin, a Chrome extension, and a loopback-only Local Agent for localhost targets.&lt;/li&gt;
&lt;li&gt;The snapshot needs no signup. The Free plan is $0 and covers security checks only, 10 scans per month. The other five areas and scheduled audits are on paid plans, from $49/month.&lt;/li&gt;
&lt;li&gt;I build and support it myself.&lt;/li&gt;
&lt;li&gt;My own site still has the medium finding and three low ones open: no CAA record, no MTA-STS policy, and &lt;code&gt;server: nginx&lt;/code&gt; disclosed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://sitelemetry.com/launch-readiness?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=p1-devto&amp;amp;utm_content=demo" rel="noopener noreferrer"&gt;Snapshot, no signup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sitelemetry.com/mcp-guide?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=p1-devto&amp;amp;utm_content=mcp" rel="noopener noreferrer"&gt;MCP setup guide (Codex, Claude Code, Cursor, Claude.ai, ChatGPT and other MCP clients)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Questions for you
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;When coverage is partial, is a per-module &lt;code&gt;unavailable&lt;/code&gt; with a reason enough, or would you withhold or cap the score?&lt;/li&gt;
&lt;li&gt;If you have connected remote MCP servers that use OAuth with dynamic client registration, which clients failed, and at which step?&lt;/li&gt;
&lt;li&gt;Would you rather have the agent work through the whole finding list in one pass, or one finding at a time so each change can be committed and re-audited on its own?&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>mcp</category>
      <category>security</category>
    </item>
    <item>
      <title>2 a.m.: your AI says “fixed.” Your client asks how you know.</title>
      <dc:creator>Ozan Dikici</dc:creator>
      <pubDate>Wed, 09 Sep 2026 10:32:23 +0000</pubDate>
      <link>https://dev.to/sitelemetry/2-am-your-ai-says-fixed-your-client-asks-for-proof-47ih</link>
      <guid>https://dev.to/sitelemetry/2-am-your-ai-says-fixed-your-client-asks-for-proof-47ih</guid>
      <description>&lt;p&gt;Imagine you're finishing a client website at 2 a.m. The AI agent has made its changes. The build passes. You've checked the homepage, and tomorrow's handover is on the calendar.&lt;/p&gt;

&lt;p&gt;Then you picture the client's question: &lt;strong&gt;"How do we know the problems are fixed?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You have an SEO export, a browser screenshot, notes in Excel, a WhatsApp message about a broken form, and an AI conversation that started before the last deployment. Each piece contains something useful. Reconstructing the story takes work.&lt;/p&gt;

&lt;p&gt;The discomfort is understandable. You've put effort into the project, but your evidence is scattered. Saying "the agent fixed it" leaves you accountable for an answer you cannot easily demonstrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the handover question smaller
&lt;/h2&gt;

&lt;p&gt;Start by identifying the claim you need to support.&lt;/p&gt;

&lt;p&gt;"Everything is fine" is too broad. "The contact form has a visible label associated with each input" gives you an observable result. So does "these pages now generate their intended canonical URLs."&lt;/p&gt;

&lt;p&gt;This makes the conversation with a client clearer. You can explain what was checked, what changed, and which questions still need attention. It also gives the next person a starting point if the issue returns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Walk one finding through the whole loop
&lt;/h2&gt;

&lt;p&gt;Take an illustrative example: a product page points its canonical URL at the category page.&lt;/p&gt;

&lt;p&gt;First, confirm the intended behavior. That canonical might be deliberate. Read the page requirements before treating an observation as a defect.&lt;/p&gt;

&lt;p&gt;If it is wrong, record the target, the observed canonical, the expected canonical, and where the evidence came from. Identify the rule or template responsible for generating it.&lt;/p&gt;

&lt;p&gt;Then give the coding agent a focused task:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Inspect canonical generation for this page. Propose the smallest change that produces the intended URL. Check another page using the same template, and explain any shared behavior the change affects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Review the proposed change. Keep unrelated refactoring outside this repair. After applying it, inspect the same page again and record the resulting canonical. Re-test another affected page as well.&lt;/p&gt;

&lt;p&gt;You now have a traceable sequence: observation, decision, change, verification. The result supports a specific handover claim. It leaves room for other checks instead of pretending one repair proves the whole website is ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop paying to rebuild the same context
&lt;/h2&gt;

&lt;p&gt;Fragmentation has a cost even when individual tools are inexpensive.&lt;/p&gt;

&lt;p&gt;Someone must reconcile the spreadsheet with the chat thread, find the latest evidence, and explain the deployment state. If you open another AI conversation, you may repeat that explanation and ask the agent to investigate facts you already collected.&lt;/p&gt;

&lt;p&gt;Structured findings can help reduce token waste by carrying the observation, evidence, scope, and next action together. A focused task gives the agent less reason to repeat broad browsing or guess what "fix the site" means. Keep the context relevant to the finding and preserve the re-test result for the next handover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the work easier to stand behind
&lt;/h2&gt;

&lt;p&gt;For a small web team, a useful commercial question is: can this workflow make delivery and maintenance easier to explain, repeat, and review?&lt;/p&gt;

&lt;p&gt;A buying decision can start there: what repeated work does the tool remove, and is its evidence useful to the person approving the handover?&lt;/p&gt;

&lt;p&gt;That is the workflow we're building with Sitelemetry. It brings security, technical SEO, AI visibility, accessibility, performance, and integration evidence together. Coverage and usage depend on your plan.&lt;/p&gt;

&lt;p&gt;Use ChatGPT, Claude, or a compatible MCP client to review findings. A coding agent can propose changes in your codebase; Sitelemetry's hosted audit tools do not edit the website. Run protected audits on authorized targets with the required domain verification.&lt;/p&gt;

&lt;p&gt;Start with the &lt;a href="https://sitelemetry.com/launch-readiness?utm_source=devto" rel="noopener noreferrer"&gt;free Launch Readiness Snapshot&lt;/a&gt; for limited passive public checks, or read the &lt;a href="https://sitelemetry.com/mcp-guide?utm_source=devto" rel="noopener noreferrer"&gt;MCP setup guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At handover, bring the finding, the fix, and the re-test result into the same conversation.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>testing</category>
      <category>ai</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
