<?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>Forem: Jesse Neumann</title>
    <description>The latest articles on Forem by Jesse Neumann (@jneums).</description>
    <link>https://forem.com/jneums</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3196358%2F0650fda5-5b07-46ff-b507-cffc611ccfac.jpg</url>
      <title>Forem: Jesse Neumann</title>
      <link>https://forem.com/jneums</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jneums"/>
    <language>en</language>
    <item>
      <title>How We're Building a Decentralized App Store for AI Agents on Web3</title>
      <dc:creator>Jesse Neumann</dc:creator>
      <pubDate>Wed, 13 Aug 2025 17:07:47 +0000</pubDate>
      <link>https://forem.com/jneums/how-were-building-a-decentralized-app-store-for-ai-agents-on-web3-2dh2</link>
      <guid>https://forem.com/jneums/how-were-building-a-decentralized-app-store-for-ai-agents-on-web3-2dh2</guid>
      <description>&lt;p&gt;A couple of weeks ago, we dropped an &lt;a href="https://github.com/prometheus-protocol/prometheus-protocol/tree/main/packages/canisters/auth_server" rel="noopener noreferrer"&gt;OAuth 2.1 auth server&lt;/a&gt; for the Internet Computer. That was just the first brick. We've been heads-down since then, and it's time to show you the building it's part of.&lt;/p&gt;

&lt;p&gt;We're building a trustless, on-chain app store for AI agents.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem: Agents Need Tools, Not Walled Gardens
&lt;/h3&gt;

&lt;p&gt;AI agents are getting powerful, but they're often limited to the tools their creators give them. To be truly autonomous, they need a way to discover and securely use a universe of external tools and data sources.&lt;/p&gt;

&lt;p&gt;This is where the &lt;strong&gt;Model-Context-Protocol (MCP)&lt;/strong&gt;, an open standard from Anthropic, comes in. Think of it as the &lt;strong&gt;LSP for AI&lt;/strong&gt;—a common language that lets any agent talk to any tool.&lt;/p&gt;

&lt;p&gt;Our vision is to create the registry for these tools on the IC. No central party controls it. The community verifies the code, and developers publish their work in a transparent, auditable way.&lt;/p&gt;

&lt;p&gt;This project is our entry for the ICP hackathon, and we were honored to take first place in the initial round. Here's a look at our progress and the stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Tools: SDKs and a CLI
&lt;/h3&gt;

&lt;p&gt;You can't have an app store without tools to build the apps. So we built them. We're also thrilled to be working alongside others in the community, like &lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/baolongt"&gt;@baolongt&lt;/a&gt;&lt;/strong&gt;, who has built a fantastic Rust SDK.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Motoko SDK:&lt;/strong&gt; We've released the &lt;code&gt;mcp-motoko-sdk&lt;/code&gt; to let you build MCP servers in Motoko.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mops add mcp-motoko-sdk
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Repo: &lt;a href="https://github.com/prometheus-protocol/motoko-sdk" rel="noopener noreferrer"&gt;github.com/prometheus-protocol/motoko-sdk&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rust SDK (by &lt;a class="mentioned-user" href="https://dev.to/baolongt"&gt;@baolongt&lt;/a&gt;):&lt;/strong&gt; For the Rustaceans out there.&lt;br&gt;
Repo: &lt;a href="https://github.com/ByteSmithLabs/ic-rmcp" rel="noopener noreferrer"&gt;github.com/ByteSmithLabs/ic-rmcp&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Publisher CLI:&lt;/strong&gt; To make publishing easy, we're building &lt;code&gt;npx @prometheus-protocol/cli publish&lt;/code&gt;. It's a simple CLI that manages the entire lifecycle. The developer workflow is declarative:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;code&gt;npx @prometheus-protocol/cli init&lt;/code&gt;: Creates a &lt;code&gt;prometheus.yml&lt;/code&gt; manifest.&lt;/li&gt;
&lt;li&gt; The developer fills in the repo URL and WASM path.&lt;/li&gt;
&lt;li&gt; &lt;code&gt;npx @prometheus-protocol/cli submit&lt;/code&gt;: Reads the manifest, hashes the WASM, and submits it to the registry for verification.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The "Secret Sauce": Composing Open Standards
&lt;/h3&gt;

&lt;p&gt;The only reason we can move this fast is because we're standing on the shoulders of giants. Our entire system is just a composition of incredible, community-driven ICRC standards. No black boxes.&lt;/p&gt;

&lt;p&gt;Here's the stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;ICRC-118&lt;/code&gt; &amp;amp; &lt;code&gt;ICRC-120&lt;/code&gt;:&lt;/strong&gt; These standards, pioneered by &lt;strong&gt;@skilesare&lt;/strong&gt;, give us a robust app store engine for managing versions and a secure upgrade path for canisters.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;ICRC-126&lt;/code&gt;:&lt;/strong&gt; The immutable verification logbook. Our &lt;code&gt;submit&lt;/code&gt; command calls this to create a public record that a new WASM version is ready for auditing.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;ICRC-127&lt;/code&gt; &amp;amp; &lt;code&gt;ICRC-2&lt;/code&gt;:&lt;/strong&gt; The economic engine. This allows anyone to fund bounties on a verification request. When an auditor submits a valid attestation, the bounty is paid out automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We didn't have to invent any of this. We just had to wire it together.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Next? Join Us.
&lt;/h3&gt;

&lt;p&gt;The foundation is laid. The SDKs are live. The core logic is being built. Now, we need more builders.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Build an MCP Server:&lt;/strong&gt; Grab an SDK and build the first on-chain tool for an AI agent.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Talk to Us:&lt;/strong&gt; We've created the &lt;strong&gt;IC MCP Discord&lt;/strong&gt; with &lt;a class="mentioned-user" href="https://dev.to/baolongt"&gt;@baolongt&lt;/a&gt; to coordinate this effort. If you're building, have ideas, or want to help, this is the place to be.
&lt;strong&gt;Join the Discord: &lt;a href="https://discord.gg/SNqu76Ayzk" rel="noopener noreferrer"&gt;https://discord.gg/SNqu76Ayzk&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Check the Code:&lt;/strong&gt; The main repo for the registry and orchestrator is here:
&lt;strong&gt;&lt;a href="https://github.com/prometheus-protocol/prometheus-protocol" rel="noopener noreferrer"&gt;github.com/prometheus-protocol/prometheus-protocol&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's build the open infrastructure for the next generation of autonomous systems.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>web3</category>
      <category>dfinity</category>
    </item>
    <item>
      <title>Portal One Just Got a Major Upgrade for AI Agent Developers! (GPT-4.1 Default, Persistent Memory &amp; More)</title>
      <dc:creator>Jesse Neumann</dc:creator>
      <pubDate>Mon, 23 Jun 2025 13:23:23 +0000</pubDate>
      <link>https://forem.com/jneums/portal-one-just-got-a-major-upgrade-for-ai-agent-developers-gpt-41-default-persistent-memory--3ama</link>
      <guid>https://forem.com/jneums/portal-one-just-got-a-major-upgrade-for-ai-agent-developers-gpt-41-default-persistent-memory--3ama</guid>
      <description>&lt;p&gt;Hey Devs!&lt;/p&gt;

&lt;p&gt;I'm excited to roll out some powerful new features in Portal One, our platform for building and managing AI agents. We've been listening to your feedback and focused on making development faster, agents smarter, and integrations seamless.&lt;/p&gt;

&lt;p&gt;Here's the lowdown:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get Building Instantly (No More API Key Hunt!)&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Default GPT-4.1: New projects now use OpenAI's gpt-4.1 by default. Create and test agents right away!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Starter Kit: Default workspace + 3 pre-configured agents to explore from login.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Simplified Creation: Often, a title + description is all you need.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Agents That Remember: Introducing Persistent Memory&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Our agents now feature a memory system (powered by Mem0 &amp;amp; Qdrant vector store).&lt;br&gt;
Benefits: Reduced redundancy, personalized responses, agents that learn over time.&lt;br&gt;
Options: 'All' (global memory), 'Chat' (scoped to conversation), 'None'.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Discover &amp;amp; Connect Tools Effortlessly with MCP Server Discovery&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;New discovery pane shows available remote MCP servers.&lt;/p&gt;

&lt;p&gt;Powered by remote-mcp-servers.com – our new open-source public server registry! (Yes, you can contribute!)&lt;br&gt;
Agents can even browse the registry for other servers (direct connection by agents coming soon!).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enhanced Tool Integration &amp;amp; Context Management&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sync and view resources from MCP servers directly in Portal One.&lt;br&gt;
Attach resources to messages or 'Pin' them for automatic context injection.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Smoother Developer Experience (UX Wins)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Quick add workspace, agent cards view, instant chat buttons, improved conversation management.&lt;br&gt;
We believe these updates will significantly boost your productivity and the capabilities of the AI agents you build.&lt;/p&gt;

&lt;p&gt;Read the full announcement: &lt;a href="https://portal.one/blog/supercharged-agents-seamless-onboarding-more/" rel="noopener noreferrer"&gt;https://portal.one/blog/supercharged-agents-seamless-onboarding-more/&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Try Portal One: &lt;a href="https://ai.portal.one" rel="noopener noreferrer"&gt;https://ai.portal.one&lt;/a&gt; &lt;br&gt;
Learn more: &lt;a href="https://portal.one" rel="noopener noreferrer"&gt;https://portal.one&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What do you think? We're keen to hear your feedback! &lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>My AI Agents Kept Getting Lost, So I Tried Giving Them Blinders (And It Worked!)</title>
      <dc:creator>Jesse Neumann</dc:creator>
      <pubDate>Tue, 03 Jun 2025 17:44:38 +0000</pubDate>
      <link>https://forem.com/jneums/my-ai-agents-kept-getting-lost-so-i-tried-giving-them-blinders-and-it-worked-3fn2</link>
      <guid>https://forem.com/jneums/my-ai-agents-kept-getting-lost-so-i-tried-giving-them-blinders-and-it-worked-3fn2</guid>
      <description>&lt;p&gt;Hey Devs!&lt;/p&gt;

&lt;p&gt;Jesse here, solo-building &lt;a href="https://portal.one" rel="noopener noreferrer"&gt;Portal One&lt;/a&gt; – an AI Agent Command Center to help you build, manage, and actually &lt;em&gt;rely on&lt;/em&gt; your AI agents.&lt;/p&gt;

&lt;p&gt;One of the biggest challenges I've hit? Getting AI agents to reliably navigate multi-step tasks. You know the feeling: you design this brilliant workflow, and the agent wanders off, picks the wrong tool, or just plain freezes, overwhelmed by choices. It’s like giving a toddler the keys to a spaceship.&lt;/p&gt;

&lt;p&gt;My "aha!" moment came when I realized: &lt;strong&gt;maybe I'm giving them &lt;em&gt;too much&lt;/em&gt; freedom, &lt;em&gt;too many&lt;/em&gt; tools, all at once.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So, I dove into making our Model Context Protocol (MCP) server smarter – making it &lt;em&gt;dynamic&lt;/em&gt;. Instead of just serving up a static list of all possible tools, it now adapts what the agent can see and do based on the &lt;em&gt;actual state&lt;/em&gt; of the task. Think of it as giving the agent blinders, so it can only focus on what's relevant &lt;em&gt;right now&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Here are a few key realizations from that journey:&lt;/p&gt;




&lt;h3&gt;
  
  
  1. &lt;strong&gt;The "Kid in a Candy Store" Problem: Too Many Tools Spoil the Agent&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Initially, my MCP server was like an eager waiter listing &lt;em&gt;every single item&lt;/em&gt; on a 50-page menu. For an AI agent, this is overwhelming. When an agent sees a giant list of tools, especially if their names are similar or their purposes overlap, it increases the chance of it picking the wrong one or getting stuck in a loop trying to decide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Realization:&lt;/strong&gt;&lt;br&gt;
Agents perform &lt;em&gt;way&lt;/em&gt; better with a curated, context-specific list of capabilities. If the task is "start the game," the agent shouldn't even &lt;em&gt;see&lt;/em&gt; the "make a guess" tool yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How Dynamic MCP Helps:&lt;/strong&gt;&lt;br&gt;
The server now only exposes tools relevant to the agent's current state. This drastically reduces the "action space" the agent has to consider, making its decisions simpler and more accurate. It's a core idea in the &lt;a href="https://modelcontextprotocol.io/specification/draft/" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; – the server can control what &lt;code&gt;list_tools&lt;/code&gt; returns based on context.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. &lt;strong&gt;Let the Server Be the "Task Chaperone," Not Just a Vending Machine&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;My early agents felt like they were just blindly hitting an API. They'd call a tool, get a response, and then it was entirely up to their (sometimes flawed) logic to figure out what to do next. This often led to them trying things out of order or missing crucial steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Realization:&lt;/strong&gt;&lt;br&gt;
The server can, and &lt;em&gt;should&lt;/em&gt;, play an active role in guiding the agent through a task by managing the state and updating the agent's context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our Solution: The Number Guessing Game Demo&lt;/strong&gt;&lt;br&gt;
To really nail this down, I built a simple Number Guessing Game where the AI is the player.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Lobby State:&lt;/strong&gt; The agent &lt;em&gt;only&lt;/em&gt; sees a &lt;code&gt;start_game&lt;/code&gt; tool.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Agent calls &lt;code&gt;start_game&lt;/code&gt;:&lt;/strong&gt; The server's internal state changes to "Playing."&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Playing State:&lt;/strong&gt; Now, the &lt;code&gt;start_game&lt;/code&gt; tool is gone. Instead, it sees &lt;code&gt;make_guess&lt;/code&gt; and &lt;code&gt;give_up_game&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Dynamic Tool Schema:&lt;/strong&gt; Even better, after a guess, the schema for &lt;code&gt;make_guess&lt;/code&gt; can change! If the agent guessed too low, the tool's description might update to "Guess a number between 51 and 100."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This way, the server isn't just passively responding; it's actively shaping the agent's understanding of what's possible and sensible at each step. You can see the code for this demo server &lt;a href="https://github.com/portal-labs-infrastructure/number-guessing-game-mcp-server" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;. It really shows how the &lt;a href="https://github.com/modelcontextprotocol/typescript-sdk" rel="noopener noreferrer"&gt;MCP TypeScript SDK&lt;/a&gt; can be used to build these stateful interactions.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. &lt;strong&gt;"Show, Don't Just Tell" Still Wins for Complex Ideas&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Explaining "dynamic, state-aware, context-adaptive capability provisioning for AI agents" is a mouthful and usually gets blank stares.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Realization:&lt;/strong&gt;&lt;br&gt;
A simple, interactive demo makes abstract concepts click. The Number Guessing Game, as basic as it is, perfectly illustrates the power of a dynamic MCP. People &lt;em&gt;get it&lt;/em&gt; when they see the tool list change or the tool's instructions adapt in real-time.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Want the Full Scoop?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This is just a peek at how we're thinking about making AI agents more dependable. The shift to a dynamic MCP server has been a game-changer for us.&lt;/p&gt;

&lt;p&gt;I wrote up a more detailed explanation of the architecture, the "why" behind it, and how it all fits together on the &lt;a href="https://portal.one/blog/dynamic-mcp-servers-tame-complexity/" rel="noopener noreferrer"&gt;Portal One blog&lt;/a&gt;.&lt;br&gt;
And definitely check out the &lt;a href="https://github.com/portal-labs-infrastructure/number-guessing-game-mcp-server" rel="noopener noreferrer"&gt;Number Guessing Game demo server on GitHub&lt;/a&gt; to see it in action!&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>development</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why We Ditched Python for TypeScript (and Survived OAuth) in Our AI Agent MCP Server</title>
      <dc:creator>Jesse Neumann</dc:creator>
      <pubDate>Thu, 22 May 2025 15:51:53 +0000</pubDate>
      <link>https://forem.com/jneums/why-we-ditched-python-for-typescript-and-survived-oauth-in-our-ai-agent-mcp-server-45al</link>
      <guid>https://forem.com/jneums/why-we-ditched-python-for-typescript-and-survived-oauth-in-our-ai-agent-mcp-server-45al</guid>
      <description>&lt;p&gt;Hey Devs!&lt;/p&gt;

&lt;p&gt;I’m Jesse, solo-building &lt;a href="https://portal.one" rel="noopener noreferrer"&gt;Portal One&lt;/a&gt;: an AI Agent Command Center to help you orchestrate, automate, and securely manage your AI agents.&lt;/p&gt;

&lt;p&gt;One of the biggest undertakings so far? Building a production-ready Model Context Protocol (MCP) server—the backbone that connects user agents to a growing ecosystem of tools and services.&lt;/p&gt;

&lt;p&gt;Let’s just say there were some “battle scars” along the way. Here are three of the most valuable (and painful) lessons that shaped our architecture:&lt;/p&gt;




&lt;h3&gt;
  
  
  1. &lt;strong&gt;OAuth 2.0: No Shortcuts in Security&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Early on, I was tempted to use simple API keys or JWTs for agent authorization—until I realized the complexity of permissions, multi-tenancy, and external integrations. Security had to be non-negotiable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why OAuth 2.0?&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Industry standard, widely supported
&lt;/li&gt;
&lt;li&gt;Granular scopes for fine-grained permissions
&lt;/li&gt;
&lt;li&gt;Auditable and future-proof&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Oh yeah, and it's also part of the &lt;a href="https://modelcontextprotocol.io/specification/draft/basic/authorization" rel="noopener noreferrer"&gt;MCP Specification&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key lesson:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Implementing OAuth correctly is hard. Expect lots of trial and error with flows, token introspection, and permission logic.&lt;/p&gt;


&lt;h3&gt;
  
  
  2. &lt;strong&gt;The Big Pivot: Python → TypeScript&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I started with Python for the prototype—it’s fast for AI, but when it came to production, I hit walls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type safety issues slowed refactoring&lt;/li&gt;
&lt;li&gt;Async/concurrency model didn’t fit my needs&lt;/li&gt;
&lt;li&gt;Wanted a unified language for backend and integrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Switching to TypeScript/Node.js instantly paid off:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strong typing = fewer runtime bugs&lt;/li&gt;
&lt;li&gt;First-class async/await model&lt;/li&gt;
&lt;li&gt;Huge ecosystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More importantly though, the official &lt;a href="https://github.com/modelcontextprotocol/typescript-sdk" rel="noopener noreferrer"&gt;MCP TypeScript SDK&lt;/a&gt; had just added support for the two things I needed: &lt;code&gt;authInfo&lt;/code&gt; available in the tool context (use the OAuth token to enable authorization), and the Proxy for using the OAuth authorization server (instead of combining the authorization server with the MCP resource server).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Painful truth:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Rewrites are tough, but sometimes necessary for long-term velocity. However, the second time creating something usually results in a better and more thought out implementation.&lt;/p&gt;


&lt;h3&gt;
  
  
  3. &lt;strong&gt;Zod: Data Validation That Won’t Let You Down&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;With so many tools and unpredictable agent payloads, validating data was a nightmare—until I discovered &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Declarative, typesafe schemas&lt;/li&gt;
&lt;li&gt;Automatic TypeScript type inference&lt;/li&gt;
&lt;li&gt;Detailed, developer-friendly error messages&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/typescript-sdk" rel="noopener noreferrer"&gt;Baked into MCP TypeScript SDK&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;z&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="s1"&gt;zod&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;AgentEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;agentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AgentEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;AgentEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Usage:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;AgentEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;incomingEvent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Use Zod schemas everywhere: API endpoints, tool integrations, LLM outputs. It's baked into the &lt;a href="https://github.com/modelcontextprotocol/typescript-sdk" rel="noopener noreferrer"&gt;MCP TypeScript SDK&lt;/a&gt; already so you will be using it whether you like it or not.&lt;/p&gt;



&lt;p&gt;The following code snippet is the function we use to authorize the token to allow access to resources in the users workspaces.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Access to the `authInfo` property injected into the request&lt;/span&gt;
&lt;span class="c1"&gt;// by the MCP SDK middleware enables multi-tenancy and authorization.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;withWorkspaceAccess&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZodTypeAny&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Firestore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RequestHandlerExtra&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ServerRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ServerNotification&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CallToolResult&lt;/span&gt;&lt;span class="o"&gt;&amp;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;return&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RequestHandlerExtra&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ServerRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ServerNotification&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authInfo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;extra&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;user_id&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No user ID found in token.&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;hasAccess&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;checkWorkspaceAccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspace_id&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hasAccess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You do not have access to this workspace.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This short snippet is great at demonstrating each of the "Battle Scars" outlined above. You can check out the &lt;a href="https://github.com/portal-labs-infrastructure/mcp-server-blog" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; as well to see the full example code.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Want More “Battle Scars”?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This is just a taste!&lt;br&gt;&lt;br&gt;
I wrote up a full deep dive—architecture, more code, and the gnarly bits—on the &lt;a href="https://portal.one/blog" rel="noopener noreferrer"&gt;Portal One blog&lt;/a&gt;. &lt;a href="https://portal.one/blog/mcp-server-with-oauth-typescript/" rel="noopener noreferrer"&gt;Check it out!&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What’s the hardest lesson you’ve learned building agent backends, APIs, or scalable integrations?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Drop your “battle scars” in the comments—let’s trade notes and help each other out!&lt;/p&gt;

</description>
      <category>oauth</category>
      <category>mcp</category>
      <category>ai</category>
      <category>portalone</category>
    </item>
  </channel>
</rss>
