Evan X. Merz

Programmer / Master Gardener / Doctor of Music / Curious Person

Your job is (probably) safe from AI

I write a lot about AI on my blog. I've spent my career studying AI. I completed a doctorate using neural networks to write music, and I worked on AI at one of the biggest tech companies in the world. So when I tell you that your job is probably safe from AI, it's not an uninformed opinion.

The natural response to this argument is simply to point to the recent layoffs. Those are due to the economy. AI is just the cover story to protect the stock price.

An AI bot sheepishly telling an office worker that it can't do a complex job

Here are five reasons why your job is (probably) safe from AI.

1. AI is expensive

Have you seen all the stories in the news about exploding AI costs?

Well, without a significant technical advancement, this is the cheapest that AI will ever be. AI is currently being subsidized by venture capital funding. The VC firms are trying to get you addicted to AI at a cheap price so that they can create a market, then raise the prices to where it is profitable.

Your employer is already struggling to justify their spending on AI. They aren't going to be happy paying more for a technology where the benefit is still so poorly defined.

2. The caching problem

AI can never be as cheap as traditional SaaS businesses due to the caching problem. I've written about this at length, so I'll just quote myself here.

The fundamental problem facing everyone in the AI race is that requests to AI can't be cached in the same way. Even if users are making very similar requests, they might not want exactly the same response. In other words, when user A tries to generate a cartoon image of a cat, they probably want one that looks like their cat. When user B makes the same request, they probably don't want the same image as user A.

Everyone knows that AI involves some very complex math that uses Generative Adverserial Networks. These networks, even when not in training mode, can't be cached like traditional web requests. They need to be run on actual servers.

This is what has resulted in the big debate about server resources. AI inherently requires more computing power than traditional web services.

The point here is not that AI can't use caching, it does. The point isn't that AI can't scale, it can.

The point is that it will never be as affordable as a traditional SaaS application. Even when many of the currently known problems are solved, it will still be at least an order of magnitude more expensive than a typical API.

3. AI is unsustainable

Sustaining the internet requires an immense and efficient supply chain. The world needs thousands of new CPUs and GPUs every day, and it can barely make them fast enough.

Sustaining AI requires an order of magnitude more materials that are difficult to engineer and manufacture at scale. Our world can't even keep up with the scale of AI needs today, as can be seen in the exploding cost of computer memory and graphics chips. How is the world going to sustain AI usage when climate change really starts impacting our global supply chain? The broken supply chain during COVID was a sneak preview of what's going to become more and more frequent in the next decades. It will be a miracle if we can keep the worldwide internet up, let alone the additional needs of running LLMs.

4. Large problems require impossibly large context

Doing your job well requires an immense amount of knowledge. It requires knowledge about the business you work for, knowledge about the customer, and knowledge about how to execute your day to day tasks. For LLMs, this information is called the context window, and it limits the amount of information that an LLM can use to reason about a problem. Expanding the context window is difficult, and the models that use larger context windows are more expensive to run.

There might come a day when an AI can hold all the information needed for relatively small jobs, but even that AI will be expensive, and it's never going to be able to do the high level tasks related to your job.

The real job in software engineering is managing the complexity of the codebase, and this can only be done with a high level understanding of the code and architecture that can't be fit into even a large context window. To a greater or lesser degree, this is true of all office jobs.

5. Some of these problems are unsolvable

The final reason why AI probably isn't going to take your job is that some of the known problems are unsolvable. There are many blank spots on the map of AI that we don't think we can fill in. Even when looking at relatively simple parts of the AI infrastructure that we use today, there's lots of stuff that isn't a 100% reliable solution. This applies to AI guardrails, caching, and even data interchange protocols like MCP.

I've already written about the problems with MCP, so I'll just quote myself again.

The MCP spec doesn't specify how to trigger tools/actions based on the prompt or the output of an LLM. In other words, there is nothing in the spec that says what the user or LLM must say in order to tell the client to fetch data from the MCP server.

This leaves each client to do things a little bit differently, and it means that each client is going to be developed based on however their favored model works. A trigger mechanism that works for one client/model/host combination might not work as well for another client/model/host combo.

This is a massive blank spot in the MCP spec, and is, arguably, the most difficult part of this problem. The spec makes it clear that MCP client to MCP server communication is via JSON-RPC, but it leaves out how the LLM to MCP client communication is supposed to occur.

There are dozens of similar problems facing the AI industry today.

AI is still going to change the world

For these reasons, your job is probably safe from being taken over by AI, but that doesn't mean that AI isn't going to change things. The ability to run a machine that can think at a nearly human level is an incredible super power. It's going to change the way we work and plan and think about the future. You should be learning how to use it, and figuring out how it's going to be useful to you in your life.

Your job is probably safe from AI, but it's definitely not safe from the state of the economy.

your-job-is-safe-from-ai

The MCP spec looks like AI slop

For the past few years, I have worked in artificial intelligence at big tech. In that role I have had the good fortune to work on the bleeding edge of AI tech, but I never had the opportunity to dig into the emerging hype around Model Context Protocol (MCP).

MCP is a way for LLMs to interact with data sources. The model is the AI/LLM that is doing the reasoning. The context is the data source that provides information necessary for the problem. The protocol is a way to connect them.

The problem is that there is one crucially bad design decision in the MCP specification, and one blank spot in the MCP spec. Both of these reveal that the MCP spec was probably created by AI.

A programmer sweeping up the AI slop generated by an LLM

Problem 1: MCP clients are tightly coupled to MCP servers in an unscalable way

Here's the example given by Anthropic of how the MCP client works.

Visual Studio Code acts as an MCP host. When Visual Studio Code establishes a connection to an MCP server, such as the Sentry MCP server, the Visual Studio Code runtime instantiates an MCP client object that maintains the connection to the Sentry MCP server. When Visual Studio Code subsequently connects to another MCP server, such as the local filesystem server, the Visual Studio Code runtime instantiates an additional MCP client object to maintain this connection.

If an AI-enabled application wants to use multiple MCP servers (multiple data sources), then it has to have a separate client application for every MCP server. If you need a custom client integration for every MCP server, then what is the value of the specification at all?

MCP claims that it works on a "client server architecture", so to better understand why this is probably AI slop, let's analogize to the client server architecture that everyone understands, the web. When browsing the web, the web browser is the client and the website is served by a web server. They communicate using tightly defined protocols. It's a very clear relationship.

But the MCP spec states that every single MCP server must have its own MCP client that sits like a babel fish in between the MCP server and the AI model. In the case of the web this would be like if you had to install a separate app for every single website you visited. So if you wanted to go to Amazon.com, not only would you need a web browser, but you would also need the Amazon app.

This is bad design because it creates several problems that should be obvious to anyone closely involved with the spec.

  1. This breaks the typical client server pattern because you need numerous clients to use numerous data sources. If a human had designed this, they would have asked, "why not put the work of the client application into the MCP server?"
  2. This is entirely unscalable. If you want your AI to connect with an arbitrary number of data sources, then you will now need an arbitrary number of MCP clients, which each require their own infrastructure, development, and maintenance.

Problem 2: There is no standard way to trigger actions/tools

The MCP spec doesn't specify how to trigger tools/actions based on the prompt or the output of an LLM. In other words, there is nothing in the spec that says what the user or LLM must say in order to tell the client to fetch data from the MCP server.

This leaves each client to do things a little bit differently, and it means that each client is going to be developed based on however their favored model works. A trigger mechanism that works for one client/model/host combination might not work as well for another client/model/host combo.

This is a massive blank spot in the MCP spec, and is, arguably, the most difficult part of this problem. The spec makes it clear that MCP client to MCP server communication is via JSON-RPC, but it leaves out how the LLM to MCP client communication is supposed to occur.

This creates several problems.

  1. The MCP client does ALL the heavy lifting. This shifts the burden from the MCP server, which is basically a passive data source, to the MCP client, which has to magically handle all the communication between the MCP server and the LLM.
  2. The heavy MCP client removes any benefits over RAG. One of the supposed benefits of MCP over RAG is that it's more generalizable, but once you dig in and discover the heavy MCP client, you can see that that isn't true.
  3. The MCP client tool trigger can lead to massive inefficiency. Most MCP clients are defaulting to triggering tools based on keywords in the LLM response, which is going to lead to wasteful overuse of MCP servers.

All of these problems mean that the MCP client is going to be very heavy, and you are going to need several of them, each with complex "business logic" relating to both the LLM and the MCP server.

The MCP specification is AI slop because it doesn't deal with the hard problems

To put it bluntly, the MCP spec is worthless as long as this tightly coupled heavy client exists in the spec. If you have to create a separate client for every single MCP server, then it isn't truly a client-server architecture. It just puts the burden on the AI host applications to do the custom coding to integrate each data source. And if each client can trigger tools in a different way, then what is the spec actually specifying? That we should use JSON-RPC for client server communication? Is that it?

The MCP spec looks like something useful, but when you dig into it, it leaves hard problems unsolved in a way that makes it look like AI slop.

I wanted to finish this post by describing what we could do to fix the MCP spec, but I'm not sure that I can solve that problem in a blog post.

I'm sure the MCP spec can, and will, be clarified in future specifications, but as of August 2026, it just isn't very useful.

the-mcp-specification-is-ai-slop

The Android Camera app has usability problems

I recently decided to put up a web store to sell my art. It can be found at https://art.evanxmerz.com/.

Because I make art, I have to take a lot of pictures. I take pictures of my subjects while I am out hiking, then I take pictures of the artwork itself in order to showcase it online.

I use my camera a lot, so the fact that the camera app on Android is awful really bothers me. In this post, I'm going to list some of the usability issues with the Camera app, and present a quick mockup of how I would redesign the app.

A mockup showing some of the bizarre user interface choices in the Android Camera app.

The most important part of the photo is covered by controls

The unforgivable sin of this app is that it's a camera app where the screen is blocked EXACTLY where you frequently want to put the subject of a photograph.

The most common composition guideline used by ALL photographers and visual artists is the rule of thirds. The rule of thirds tells artists to break their viewport into thirds, vertically and horizontally, then put the subject of the image at the breakpoints that divide each third of the image. This rule provides a quick way to lay out an image in a way that the human eye finds pleasing.

And where did Google put the zoom slider? Exactly on the boundary between the middle and rightmost third of the image. So, if you're trying to compose an image with the subject right at that spot, then you aren't going to be able to see the subject of your photo while taking the photo.

Rarely used controls take up valuable screen space

Why is there an angle measurement in the middle of the viewport? Does anyone really care if their photo is at 1 degree or 0 degrees? Does it really matter so much that it must be always visible?

Even worse is the mode selector. 99% of the time I use the camera app I am taking a photo. The other 1% of the time I am taking a video. There is a nice toggle that allows users to switch between photo and video modes. Then there is a slider that allows selection of photo modes such as panorama, portrait, and long exposure. Why is the photo mode selector using screen real estate all the time when it's used so rarely? This makes no sense.

Multiple controls that do similar things

Finally, there are multiple controls that do very similar things. Like, there are two separate buttons for camera settings. One is the familiar gear icon. It controls the Camera App settings. The other is the sliders icon. It controls the settings related to photography, such as brightness. Why do these need two separate buttons? Why can't the gear icon appear in the menu that opens when the slider icon is clicked? Or better yet, why not put the settings under the question mark icon on the left side?

And the other duplicated controls are the mode selector I mentioned above. The toggle for switching between photo and video modes is fine. Maybe add a long press if you want to open the other mode selector, but we don't need both selectors constantly open.

How I would redesign the Android Camera App

Redesigning any project is more challenging than it appears. It's easy for me to make a mockup that satisfies my needs. The reason those changes aren't immediately implemented is that there are many stakeholders who have concerns other than the usability of the product. That's understandable, but when the situation gets this bad, it's worth calling out.

Here's my 30 second mockup of a better user experience.

A mockup showing how we could improve the Android Camera app.

Notice that the camera button is transparent. Notice that there's nothing on the image itself except the button to take a picture. Notice that the zoom slider has been moved to the upper right and off of the actual image.

The result actually looks a lot more like the camera app user interface from 15 years ago! This type of experience decay is something I've written about before, and it plagues all experiences on the smart phone.

android-camera-has-usability-problems

How to build a full stack LLM chat service with Ollama and JavaScript

This post is my attempt to share a full stack "Hello, World" application using JavaScript, NodeJS and Ollama. It's a simple website that allows users to chat with an LLM.

This example doesn't require you to sign up with cloud service providers or run anything outside of your own computer. The model, the api, and the frontend JavaScript all run on your local machine.

In a production situation, these tasks would be split out onto multiple computers with a lot of RAM and VRAM. The API would run on one web server, the model on another, and the frontend on a client computer. So unless you have a pretty beefy machine, you should expect the result to be slow.

A web developer building a full stack llm web application using NodeJS, ExpressJS, and Ollama.

How to build a full stack LLM service with Ollama and JavaScript

There are many ways to do this, but you DO need to have some understanding of web programming in order to make this happen. I'm assuming that you have nodejs and npm installed on your computer. I'm assuming that you know how to run console commands.

1. Get the API running

In a previous post, I showed you how to put up a simple web API to chat with an LLM. We're going to start this full stack project with that API as a base. So go execute that tutorial, then come back here when you have a functioning API. Make sure to validate your API by running it, and sending a web request to it using a client like Yaak.

2. Create the JavaScript frontend

Now we need a script to actually access the API. We will have our same project serve a static html file with a basic chat interface.

This html file will include a text box for entering a prompt, a submit button, a container for the response, and a title. I'm leaving it deliberately simple in order to demonstrate the basic interconnection between the different services involved.

Type this into a file called index.html in the root of your project.

<html>
  <head>
    <script>
      async function handleSubmit() {
        let prompt = document.getElementById("prompt").value;
        if(prompt != null && prompt.length > 0) {
          // hit our local chat API
          let response = await fetch("http://localhost:8181/chat", {
            method: "POST",
            headers: {
              Accept: "*/*",
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              prompt: prompt,
            })
          });
          // interpret the response
          if(response.body != null) {
            const reader = response.body.getReader();
            let text = "";
            let chunk = await reader.read();
            // this should be using the done parameter to chunk out the response, but there is some implementation issue that I'm just going to ignore...
            let newChatChunk = new TextDecoder().decode(chunk.value);
            text += newChatChunk;

            let responseAsObject = JSON.parse(text);
            document.getElementById('response-container').innerText = responseAsObject.response;
          }
        }
      }
    </script>
  </head>
  <body>
    <h1>My amazing LLM chat service</h1>
    <div id="response-container"></div>
    <div id="input-container">
      <input type="text" id="prompt" value="Why is the sky blue?" />
      <button onClick="handleSubmit()">Submit</button>
    </div>
  </body>
</html>

3. Modify the Node/Express server to serve the HTML file

Next we need to modify our node/express server to have a frontend. We will add a route that serves the html file we just created.

Add the following code to app.js below the API endpoint.

app.get('/', (req, res) => {
  res.sendFile('./index.html', { root: __dirname });
});

4. Run the app

Finally, you just need to run the web server. Remember that you need to restart Node/Express every time you make a change. Use the following command to run the server if you followed my instructions in the prior post.

npm run dev

Now the chat interface should be visible and functional at http://localhost:8181/.

Extending your basic chat service

I hope this template provides a good starting point for you. When you extend this, you will need to provide all the basic stuff that is important to a website, https support, CORS, authentication, and more.

The larger issue is that the LLM is running on the same server/computer as the website and API. The first problem you need to solve when building a service like this is how to run the model in a scalable way, and how to connect it to the user facing part of your website.

how-to-build-a-full-stack-llm-service-with-ollama-and-javascript

How to build a simple LLM chat api using NodeJS and Ollama

This post is my attempt to share a simple "Hello, World" application using NodeJS and Ollama. It's a simple web API that allows users to chat with an LLM.

Cover image for blog post about a Hello, World type coding example for NodeJS and Ollama.

How to build a simple LLM chat API using NodeJS and Ollama

I'm going to show you the simplest way to do this, but you DO need to have some understanding of web programming in order to make this happen. I'm assuming that you have nodejs and npm installed on your computer. I'm assuming that you know how to run console commands.

1. Install the Ollama app

If you run your LLM using the Ollama app, then it will automatically run when you boot into Windows. This is convenient because you don't need to remember to start a second process or run a virtual machine.

Here's the link to download and install the Ollama app for Windows.

NOTE: This app requires a restart, which was not indicated in the installation instructions when I installed it.

you will also need to open the Ollama app, go to Settings, and enable "Expose Ollama to the network". This option enables the local Ollama API that will be used by this example.

Finally, you will need to install the model that you intend to use. In the code below, you will see that I'm using llama3.2:1b, the 1 billion parameter open source model released by Meta. I like this model because it runs really fast on my local machine and it gives reasonably good output most of the time for most tasks.

2. Create a new nodejs application and install dependencies

Create a folder for your nodejs application, then run npm init and give the project any details you like. We will use app.js as our entry point, but since we aren't sharing this project, that isn't actually important.

npm init

The only dependencies for this example are express, ollama, and body-parser. Install them with the following command.

npm i express ollama body-parser

Then you need to set up a command to run the project. Modify the scripts section of package.json to tell npm to run the node server.

"scripts": { "dev": "node app.js" },

After writing our server script, we can then use the following command to run the API.

npm run dev

3. Write the code

Type this code into app.js in the root folder.

DON'T COPY PASTE THIS. If you copy paste then you won't learn anything. Type it out line by line.

/**
 * This project is a simple demonstration of how to build a simple
 * node/express API on top of the ollama API.
 */
const express = require('express');
const ollamaLibrary = require('ollama');
const bodyParser = require('body-parser');
const port = 8181;
const DEFAULT_MODEL = "llama3.2:1b";

// instantiate the express server
const app = express();

// tell express to use the body-parser package to extract json from a post
app.use(bodyParser.json());

const ollama = new ollamaLibrary.Ollama({
  url: "http://localhost:11434"
});

/**
 * Set up a single endpoint for chatting with an llm.
 */ 
app.post('/chat', async (req, res) => {
  let params = req.body;
  let prompt = params?.prompt;
  if(prompt != null && prompt.length > 0) {
    let model = params?.model ?? DEFAULT_MODEL;
    const response = await ollama.chat({
      model: model,
      messages: [{ role: 'user', content: prompt }],
      stream: false
    });
    console.log(response.message.content);
    res.status(200);
    res.json({ response: response.message.content });
  } else {
    res.status(400).send("Invalid prompt.");
  }
});

/**
 * Start the server, and listen for requests.
 */ 
app.listen(port, () => {
  console.log(`Server started and listening on port ${port}...`);
});

4. Test using Yaak or Postman

To test that the system is working, you can use an API testing tool like Yaak or Postman. Just send a post request to http://localhost:8181/chat with the following body json.

{
  "prompt": "Why is the sky blue?"
}

It should look something like this in Yaak.

Example request to test that the nodejs ollama api example is working properly.

Continuing to build a real LLM API

If you were going to extend this into being a real API, then you would want to add authentication and authorization, CORS support, and more. The hardest part about building an API like this is not necessarily the code, but the infrastructure to support it in production.

how-to-build-an-llm-chat-api-using-nodejs-and-ollama