<?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: Tuğkan</title>
    <description>The latest articles on DEV Community by Tuğkan (@decello).</description>
    <link>https://dev.to/decello</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%2F352653%2Fe6602100-541c-45a2-ba08-f662c24ec008.jpeg</url>
      <title>DEV Community: Tuğkan</title>
      <link>https://dev.to/decello</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/decello"/>
    <language>en</language>
    <item>
      <title>Let your AI agent test your API: two-go's AI layer and MCP server</title>
      <dc:creator>Tuğkan</dc:creator>
      <pubDate>Tue, 02 Jun 2026 18:51:45 +0000</pubDate>
      <link>https://dev.to/decello/let-your-ai-agent-test-your-api-two-gos-ai-layer-and-mcp-server-3ddp</link>
      <guid>https://dev.to/decello/let-your-ai-agent-test-your-api-two-gos-ai-layer-and-mcp-server-3ddp</guid>
      <description>&lt;p&gt;There's a moment in every project where you have a working endpoint, you &lt;em&gt;know&lt;/em&gt;&lt;br&gt;
you should write tests for it, and you also know you're about to spend the next&lt;br&gt;
hour wiring up an HTTP client, an assertion library, and a dozen little helpers&lt;br&gt;
before you write a single meaningful check.&lt;/p&gt;

&lt;p&gt;I got tired of that moment. So I built &lt;a href="https://www.npmjs.com/package/two-go" rel="noopener noreferrer"&gt;&lt;strong&gt;two-go&lt;/strong&gt;&lt;/a&gt;:&lt;br&gt;
a small, zero-dependency library for testing HTTP APIs from Node. You build a&lt;br&gt;
request with a chainable API, attach the checks you care about, and &lt;code&gt;await&lt;/code&gt; it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;go&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;two-go&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;go&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bearer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expectStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expectHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/json/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expectJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data[0].id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one chain sends the request and runs all three checks. If any of them&lt;br&gt;
fails, it throws, so there's no special runner to configure. It works on its&lt;br&gt;
own, and it drops straight into &lt;code&gt;node:test&lt;/code&gt;, Jest, Vitest, or Mocha with no&lt;br&gt;
plugin.&lt;/p&gt;

&lt;p&gt;That's the core. But the part I actually want to talk about in this post is the&lt;br&gt;
part that didn't exist in API testing libraries when I started: &lt;strong&gt;letting an AI&lt;br&gt;
agent do the testing work for you.&lt;/strong&gt; two-go ships two things for that: an&lt;br&gt;
optional AI layer, and an MCP server.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why bother with an AI layer at all?
&lt;/h2&gt;

&lt;p&gt;Most of the friction in API testing isn't writing assertions. It's the &lt;em&gt;staring&lt;br&gt;
at a response and deciding what to assert&lt;/em&gt;. What status should this return?&lt;br&gt;
What's the shape of &lt;code&gt;data[0]&lt;/code&gt;? Which fields could leak? Which weird payloads&lt;br&gt;
break it?&lt;/p&gt;

&lt;p&gt;Those are exactly the questions a language model is good at taking a first pass&lt;br&gt;
at. So two-go has an optional &lt;code&gt;two-go/ai&lt;/code&gt; entry point that can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;draft a test suite from a live endpoint or a sample response,&lt;/li&gt;
&lt;li&gt;explain why a test failed,&lt;/li&gt;
&lt;li&gt;review a response for likely bugs,&lt;/li&gt;
&lt;li&gt;and generate adversarial payloads to fuzz an endpoint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important design choice: &lt;strong&gt;the AI layer never touches the core.&lt;/strong&gt; two-go still has zero runtime dependencies. The AI module talks to the provider over &lt;code&gt;fetch&lt;/code&gt; with your own key, and it works with OpenAI, Anthropic, or any compatible endpoint, including a local model.&lt;/p&gt;
&lt;h3&gt;
  
  
  Drafting a suite from a real endpoint
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-...
two-go ai gen https://api.example.com/users &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt;/users.twogo.mjs

&lt;span class="c"&gt;# or from a saved response, with a different provider two-go ai gen ./sample.json --provider anthropic -o test/users.twogo.mjs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Or from code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;aiGenerateTests&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;two-go/ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;aiGenerateTests&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ada&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// or "anthropic", or a custom { baseURL } for a local model&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is a normal &lt;code&gt;*.twogo.mjs&lt;/code&gt; file. It goes into git and runs in CI like anything else. Treat it as a &lt;em&gt;first draft&lt;/em&gt;. It gets the boilerplate and the obvious checks out of the way, and you tighten the assertions from there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Explaining a failure (after the fact, never changing the result)
&lt;/h3&gt;

&lt;p&gt;When a test fails, you can ask the model what probably went wrong. This is advisory: it runs &lt;em&gt;after&lt;/em&gt; the failure and never changes pass or fail.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;explainFailure&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;two-go/ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;expectStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;why&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;explainFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;why&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// likely cause plus a suggested fix&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reviewing and fuzzing
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;aiReview&lt;/code&gt; looks at a response and hands back a list of likely problems: a leaked token, a wrong type, a field that shouldn't be there. &lt;code&gt;aiFuzz&lt;/code&gt; generates adversarial payloads you then send with the normal client.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;aiReview&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;aiFuzz&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;two-go/ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/me&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;aiReview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// [{ severity, field, message }]&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payloads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;aiFuzz&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;payloads&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;possible bug on payload&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are advisory. &lt;code&gt;aiReview&lt;/code&gt; gives you findings, &lt;code&gt;aiFuzz&lt;/code&gt; gives you inputs, and &lt;em&gt;you&lt;/em&gt; decide what to do with them. The model proposes; your assertions dispose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The MCP server: hand the tools to the agent
&lt;/h2&gt;

&lt;p&gt;The AI layer above is two-go calling out to a model. The &lt;strong&gt;MCP server&lt;/strong&gt; flips the direction: it lets an agent like Claude drive two-go directly, as a set of tools.&lt;/p&gt;

&lt;p&gt;If you haven't run into it yet, &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;MCP (Model Context Protocol)&lt;/a&gt; is an open standard for exposing tools to AI agents. two-go ships an MCP server&lt;br&gt;
that runs over stdio with no dependencies, no URL, no account, and no API key. It's all local.&lt;/p&gt;

&lt;p&gt;Install once so the &lt;code&gt;two-go-mcp&lt;/code&gt; command is on your PATH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; two-go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then wire it into your client. For &lt;strong&gt;Claude Code&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add two-go two-go-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;strong&gt;Claude Desktop&lt;/strong&gt;, &lt;strong&gt;Cursor&lt;/strong&gt;, &lt;strong&gt;Windsurf&lt;/strong&gt;, &lt;strong&gt;Copilot CLI&lt;/strong&gt;, or &lt;strong&gt;Kiro&lt;/strong&gt;,&lt;br&gt;
drop this into the client's MCP config:&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;"two-go"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"two-go-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;(VS Code uses a top-level &lt;code&gt;servers&lt;/code&gt; key and a &lt;code&gt;"type": "stdio"&lt;/code&gt; field; Codex and Gemini have their own &lt;code&gt;mcp add&lt;/code&gt; commands. The README has a copy-paste block for each.)&lt;/p&gt;

&lt;p&gt;The tools it exposes to the agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;http_request&lt;/code&gt;: send a request, get back status, headers, timing, and body.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gen_openapi&lt;/code&gt; / &lt;code&gt;gen_postman&lt;/code&gt;: generate a suite from a spec or collection.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;infer_schema&lt;/code&gt;: infer a JSON schema from a value.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;validate_schema&lt;/code&gt;: validate a value against a schema.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once it's connected, you can just &lt;em&gt;talk&lt;/em&gt; to your agent: "call the staging users endpoint and tell me if the shape changed," or "generate a test suite from this OpenAPI file." The agent makes the calls through two-go and reasons over the real responses.&lt;/p&gt;

&lt;p&gt;And because the server logic is importable, you can host it yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;two-go/mcp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;jsonrpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tools/list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The rest of the box (so the AI output has somewhere to land)
&lt;/h2&gt;

&lt;p&gt;The AI features are only useful because the generated tests run against a real library. The non-AI core is the bulk of two-go:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTP client + inline checks&lt;/strong&gt;: &lt;code&gt;expectStatus&lt;/code&gt;, &lt;code&gt;expectJson&lt;/code&gt;, &lt;code&gt;expectHeader&lt;/code&gt;,
&lt;code&gt;expectJsonSchema&lt;/code&gt;, &lt;code&gt;expectSorted&lt;/code&gt;, and a long list of others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;expect()&lt;/code&gt; for any value&lt;/strong&gt;: Jest-style matchers, with &lt;code&gt;.not&lt;/code&gt;, &lt;code&gt;.resolves&lt;/code&gt;,
and &lt;code&gt;.rejects&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soft assertions&lt;/strong&gt;: collect every failure in a run, throw once at the end.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polling&lt;/strong&gt;: &lt;code&gt;eventually&lt;/code&gt; / &lt;code&gt;pollUntil&lt;/code&gt; for slow or eventually-consistent
endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snapshots&lt;/strong&gt;, &lt;strong&gt;sessions with &lt;code&gt;{{token}}&lt;/code&gt; chaining&lt;/strong&gt;, a &lt;strong&gt;fake-data
generator&lt;/strong&gt;, &lt;strong&gt;async helpers&lt;/strong&gt; (&lt;code&gt;parallelLimit&lt;/code&gt;, &lt;code&gt;mapLimit&lt;/code&gt;, &lt;code&gt;waterfall&lt;/code&gt;), a
~170-function &lt;strong&gt;utility belt&lt;/strong&gt;, and a &lt;strong&gt;JSON schema validator + inference&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Importers&lt;/strong&gt; that turn an OpenAPI doc or Postman collection into a starting
suite, with a CLI: &lt;code&gt;two-go gen openapi ./openapi.json -o test/api.twogo.mjs&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of it ships with hand-written TypeScript types and zero runtime dependencies. Node 18+ (it uses the built-in &lt;code&gt;fetch&lt;/code&gt;), ESM only.&lt;/p&gt;

&lt;h2&gt;
  
  
  A realistic flow, end to end
&lt;/h2&gt;

&lt;p&gt;Here's how the pieces fit together in practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You have an endpoint and an OpenAPI doc. Run &lt;code&gt;two-go gen openapi&lt;/code&gt; to get a
skeleton suite.&lt;/li&gt;
&lt;li&gt;Ask your agent (via the MCP server) to call the live endpoint and flag any
drift from the doc.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;aiReview&lt;/code&gt; on a sample response to surface leaks and type mismatches.&lt;/li&gt;
&lt;li&gt;Tighten the generated checks into real assertions by hand.&lt;/li&gt;
&lt;li&gt;Commit the &lt;code&gt;*.twogo.mjs&lt;/code&gt; files. They run in CI like any other test.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The AI does the tedious first pass: boilerplate, obvious checks, "here's what looks off." You keep the judgment calls. Nothing about the AI layer is load bearing. Pull the key and the core still works exactly the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;two-go &lt;span class="nt"&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/two-go" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/two-go&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/tugkanboz/two-go" rel="noopener noreferrer"&gt;https://github.com/tugkanboz/two-go&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The roadmap (JUnit/JSON reporters, a GraphQL helper, a cookie jar, request-level retry) is open in the issues. If you build something with it, or break it, I'd love to hear about it.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>mcp</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I started awesome-ai-testing because I couldn't find a clean map of AI testing tools</title>
      <dc:creator>Tuğkan</dc:creator>
      <pubDate>Tue, 05 May 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/decello/i-started-awesome-ai-testing-because-i-couldnt-find-a-clean-map-of-ai-testing-tools-5l8</link>
      <guid>https://dev.to/decello/i-started-awesome-ai-testing-because-i-couldnt-find-a-clean-map-of-ai-testing-tools-5l8</guid>
      <description>&lt;p&gt;The AI testing space moves fast. Test generators, self-healing frameworks, MCP servers, LLM evaluators, visual AI platforms, new tools every month. Most of them claim to "use AI" without saying what that actually means.&lt;/p&gt;

&lt;p&gt;Existing awesome lists either don't cover AI testing or mix it into broader test automation directories. The result: hard to compare options, hard to find open source alternatives, hard to see what's mature versus experimental.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tugkanboz/awesome-ai-testing" rel="noopener noreferrer"&gt;awesome-ai-testing&lt;/a&gt; is a new curated list focused on this gap. It currently has around 100 tools across 20 categories, with each entry tagged as open source, commercial, or open core. I'm sharing it early, not polished, to get feedback from people who actually run tests for a living.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in it
&lt;/h2&gt;

&lt;p&gt;Categories are organized by what people actually search for, not academic taxonomies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test Generation&lt;/strong&gt;: Qodo-Cover, EvoMaster, EvoSuite, Diffblue, plus AI editors like Cursor and Claude Code that can write Playwright, Cypress, and Selenium tests with the right prompting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP-Based Testing&lt;/strong&gt;: Playwright MCP, Chrome DevTools MCP, and community variants. This is the area moving fastest right now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-Healing Test Frameworks&lt;/strong&gt;: Healenium for open source, Testim and Functionize on the commercial side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Natural Language Test Authoring&lt;/strong&gt;: Magnitude, Passmark, Shortest, Auto Playwright, Midscene.js, all open source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM-as-Judge Evaluation&lt;/strong&gt;: Promptfoo, DeepEval, Ragas, Inspect AI, TruLens, Arize Phoenix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mock and Service Virtualization&lt;/strong&gt;: Mountebank, WireMock, Mockoon, MSW, Pact, Hoverfly. Useful when mocking LLM APIs in tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual AI, Mobile AI, Performance, Accessibility, API testing&lt;/strong&gt;: Each with its own section.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each entry has a badge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🆓 Open source&lt;/li&gt;
&lt;li&gt;💰 Commercial
&lt;/li&gt;
&lt;li&gt;🆓💰 Open core (free tier or open source with paid features)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open source options are listed first in each category. The commercial space is well-promoted already; open source needs visibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's excluded
&lt;/h2&gt;

&lt;p&gt;To keep the list useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tools abandoned for 2+ years&lt;/li&gt;
&lt;li&gt;Marketing pages without a real product&lt;/li&gt;
&lt;li&gt;Generic test runners with weak AI claims (no Selenium with bolted-on plugins)&lt;/li&gt;
&lt;li&gt;Closed source tools without a free trial or transparent pricing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Honest disclaimers
&lt;/h2&gt;

&lt;p&gt;The list is new. A few caveats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some categories are denser than others. AI Code Review didn't make the cut yet because it doesn't clearly belong in a testing-focused list.&lt;/li&gt;
&lt;li&gt;This is a curated list, not a benchmark. Tools are included based on quality signals (active maintenance, real users, transparent pricing), not personal testing of every entry.&lt;/li&gt;
&lt;li&gt;Categorization will shift as the list grows. Promptfoo sits in two categories because it genuinely does both.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Three workflows the list supports
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;"I need to test my AI app"&lt;/strong&gt; → LLM and AI System Testing + Mock and Service Virtualization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"I want AI to write my tests"&lt;/strong&gt; → Test Generation + MCP-Based Testing + Natural Language Test Authoring&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"My existing test suite is brittle"&lt;/strong&gt; → Self-Healing Test Frameworks + Test Analytics and Triage&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What would help right now
&lt;/h2&gt;

&lt;p&gt;If you maintain or use an AI testing tool that's missing, open a PR. The contribution guide is two screens long.&lt;/p&gt;

&lt;p&gt;If you spot something miscategorized or inaccurate, open an issue.&lt;/p&gt;

&lt;p&gt;If you tried a tool from the list and it was great or terrible, drop it in the comments here. That kind of signal is exactly what turns a list into a useful list.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/tugkanboz/awesome-ai-testing" rel="noopener noreferrer"&gt;https://github.com/tugkanboz/awesome-ai-testing&lt;/a&gt;&lt;/p&gt;

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