WEBINAR Nov 6: End-to-end mobile observability with Embrace and Grafana Cloud. Learn how to connect Embrace mobile telemetry with Grafana Cloud data.

Sign-up

How I added Embrace auto-instrumentation using MCP server (Spoiler: it worked!)

A tale of AI collaboration, debugging detective work, and the magic of good documentation

I’m a senior software engineer at Embrace working across our stacks. While using Anthropic’s Claude Code to help me build my very first iOS app, I learned that our AI assistants often need OUR assistance to be effective. Let me tell you about the issues I ran into, and how I taught Claude to instrument iOS applications with Embrace.

Prologue: When automation dreams meet reality

I’ve been learning Swift to build my first iOS app about finding cool places to eFoil around the world. eFoiling, if you haven’t heard of it, is that wonderfully-freeing sport where you ride what’s essentially a surfboard with a hydrofoil wing underneath and an electric motor. The wing cuts through the water and creates lift, so you’re literally flying above the surface. It’s the closest I’ll ever get to riding Marty McFly’s hoverboard.

The app’s core functionality was coming along nicely, but then my husband asked how I’d know if anyone actually downloaded it, which features they were using most, and how I could know if they ran into any issues. I realized adding Embrace was the best way for me to get proper observability and understand the interaction between technical details and user behavior.

The app itself is pretty straightforward. I have a Firestore database that stores eFoil locations, which I display on a map using MapKit and SwiftUI. Users can tap a location to navigate to see its detailed page. So I knew I wanted to start adding spans to the database calls to see what people were doing, and where they were looking to ride.

Enter Claude, my digital pair programming partner for this adventure.

Act I: The overconfident beginning

“This’ll be easy,” I thought, firing up a conversation with Claude. “Please add some Embrace instrumentation to my code.”

Claude, ever enthusiastic, immediately got to work. Lines of code started appearing with spans and breadcrumbs that looked… almost right. The structure was there, the logic made sense, but something felt off.

// What Claude was generating

let span = Embrace
            .client?
            .span(name: "process-image", severity: "info")

// What I actually needed

let span = Embrace
            .client?
            .buildSpan(name: "process-image")
            .startSpan(); // Much better!

The function signatures were completely wrong. Classic LLM, confidently hallucinating. It guessed at what it thought the instrumentation for my spans would look like, but couldn’t provide spans that would actually work.

Act II: The MCP server solution

“Aha!” I thought. “Claude just needs better examples.” We were running an AI hackathon at the time, so I decided to get creative with an MCP resource that could give Claude hard-coded examples of what a proper Embrace span looked like.

I built the MCP resource, fed it some concrete examples of spans, and asked Claude to integrate Embrace spans into my code. What do you know? Claude hallucinated and completely ignored the resource I’d just created.

When I asked why it didn’t use the resource, Claude said it was “basic” and didn’t think it added value. Translation: Claude told me it already “knew” how to do it.

Narrator voice: Claude did not, in fact, know how to do it.

The generated code still had those stubborn, incorrect function signatures, and worse yet, tried to gaslight me into thinking that it knew best.

Act III: The "Eureka!" moment

So I thought, “hmmm, what if the problem wasn’t Claude’s memory or stubbornness, but simply a lack of concrete examples to learn from?”

First, I tried pointing Claude directly to our Embrace documentation. It performed reasonably well, the spans looked more legitimate and the overall structure improved. But I noticed something: the examples it was generating were eerily similar to the generic ones in our docs. They worked, but they didn’t feel quite right for my specific use case. Plus, the whole process was painfully slow, with Claude taking forever to generate each code block.

Then I had a better idea. I decided to set up GitHub’s MCP server and link it directly to our SDK repository. This would give Claude access to our actual codebase, not just documentation.

Once I connected Claude to the code base through the GitHub MCP server, something clicked for the code assistant. Claude now had access to concrete, real-world examples from our actual SDK implementation. But there was a catch – using Github’s MCP server made Claude incredibly slow. Every query took ages, as the Github MCP server tool processed the entire SDK repository each time I sent a request.

Despite the speed issues, the quality was undeniable. Claude suddenly became a span-adding wizard, generating code that was perfectly tailored to my specific implementation, rather than generic documentation examples.

Act IV: Success at last

Claude suddenly became an instrumentation maven, adding what I needed exactly where I needed it. Database calls got properly wrapped, breadcrumbs appeared in all the right places, and the function signatures were perfect. 

But the process was still painfully slow. So I thought, why not teach Claude directly with the SDK that already existed in its context? I pointed it to the Embrace SDK packaged in the app’s source code and violá, it worked much more quickly.

Claude was now confidently adding spans around API calls, database queries, and async operations. It understood the nuances of when to start spans, when to add breadcrumbs, and how to handle error cases properly. 

Finale: The happy ending

Within a few hours, the eFoil codebase was properly instrumented with Embrace spans and breadcrumbs throughout all the network calls. What could have been a tedious manual process became an efficient collaboration between human insight and AI execution.

The best part? Once Claude had the right examples, it became incredibly consistent. Every new network call got the same high-quality instrumentation treatment.

Takeaways

I came away from this exercise having learned three important rules for educating LLMs: 

  1. The code in the SDK beats documentation every time: Instead of describing what I wanted or specifying version numbers to research, showing Claude actual working code from our iOS SDK was the key to success.
  2. AI needs context, not instructions: Claude didn’t need me to be more specific about what I wanted, it needed concrete examples of what “correct” looked like.
  3. The right tool for the job: While Claude couldn’t magically know our exact SDK patterns out of thin air, once it had access to the Embrace SDK, it became incredibly powerful for repetitive instrumentation tasks.

The overall lesson? When AI starts hallucinating with third-party packages, give it the package (or specific parts of it) directly. Don’t rely on the AI’s training data or generic documentation;, feed it the actual source code and examples from the package itself. In the end, with a little prodding, the Embrace SDK turned Claude from a well-meaning but confused intern into a seasoned developer who actually knew what they were doing.

If you’re interested in learning more about our mobile and web instrumentation, please take a look at the open-source SDKs in our GitHub page. And for any eFoiling enthusiasts, my app is live on the iOS app store!

Embrace Deliver incredible mobile experiences with Embrace.

Get started today with 1 million free user sessions.

Get started free
Related Content
OpenTelemetry panelist headshots with leaf backgrounds

Pumpkin spice and OpenTelemetry for mobile panel recap

In this OpenTelemetry expert panel, we discuss the challenges of collecting telemetry in mobile apps, why mobile developers struggle with observability, and what the current support for OpenTelemetry is on Android and Swift.
AI icon on a smartphone screen

No, AI won’t kill the user interface

In the age of AI, user experience strategy cannot stand still. The interface is not disappearing, but it is shifting into adaptive, intent-driven territory.