unpacking mcp
mcp is having a moment. some say it's the enabling protocol for agents, others claim it's the future of apis. yet despite all of the chatter, i still found it hard to understand what it actually is and how it works.
so i built my own mcp server.
what i learned is that, while mcp is promising, it's currently a pain to work with. when it works, it's incredible. when it doesn't, it's a nightmare. below, i'll walk through how i built it and what i learned in the process.
what is mcp?
mcp is an open protocol that standardizes how applications provide context to large language models. it's like an api layer for agents. an mcp server has three core primitives:
- resources: file-like data that can be read by clients (like api responses or file contents)
- tools: functions that can be called by the llm (with user approval)
- prompts: pre-written templates that help users accomplish specific tasks
why is it exciting?
agents become more useful when they can do more than chat. mcp gives them ways to take action (e.g. fetch some information, write code, etc.).
i've seen a number of interesting applications of mcp over the last month or so. some of my favorites have been:
- supabase's mcp server, which provides your coding environment with access to your database
- exa's twitter mcp server, which allows you to search tweets and user profiles (without needing to use the twitter api)
- stagehand's mcp server, which equips claude code with context from stagehand
at the same time, most of the founders i've talked to about mcp have shared their frustrations with the protocol. now i know why.
how i built it
to start, i spun up a new mastra project with npm create mastra@latest
. from
there, i wrote a stagehand script to scrape the resources from my
personal website and the basecase website.
const { content } = await page.extract({
instruction: `Extract the content in the body of the ${pageName} page.`,
schema: z.object({
content: z.array(
z.object({
content: z.string(),
}),
),
}),
});
next, i wrote a quick function in braintrust to parse the scraped content into structured json.

i ended up with all of my data in a format that could easily be accessed by my tools.
export const aboutMe = [
{
title: "currently",
description: [
"founder & managing partner of basecase capital",
"partner to a number of incredible companies, including ashby, astral, baseten, braintrust, browserbase, default, graphite, orb, resend, supabase, vercel, & others",
"builder & maintainer of a few open-source projects, including briefcase, branded, eventbase, docbase, & others",
"wife & partner in crime to ankur goyal",
],
},
{
title: "previously",
description: [
"joined samsara as the first new grad product manager",
"worked on data platform, reports infra, alerts & automations, & admin tooling with some very incredible engineers",
"interned at greylock and was lucky enough to help out with their incubation of abnormal security",
"launched the home equity platform at blend",
"shipped the first free trial at appdynamics",
"interned on the research & development team at natera",
"studied computer science at columbia university and made some life long friends",
],
},
];
next, i wrote some tools to fetch data from the resources and a system prompt with access to the tools. with mastra, you can write your tools as typescript functions, wrap them in zod schemas, and give them to your agent.
export const getPortfolioCompaniesTool = createTool({
id: "get-portfolio-companies",
description: "get a list of all companies in the basecase portfolio",
outputSchema: z.object({
companies: z.array(z.string()),
}),
inputSchema: z.object({}).optional(),
execute: async () => {
const companies = portfolio.map((company) => company.title);
return { companies };
},
});
it's also super easy to interact with your tools. when you run npm run dev
, you get this chatbot
interface that lets you call your tools directly. this was super helpful for debugging and testing
the various tools.
ready to go, i ran npm run build:mcp
and published my mcp server to npm.
what went wrong?
for context, everything up to this point took me less than an hour. i scraped my site, wrote a few tools, and published my mcp server relatively easily. i figured i’d just drop it into my mcp config and be good to go. sadly that wasn't the case.
when i added my server to windsurf, the connection immediately failed. since the server was running
on my machine with no issues, the mastra crew (shout out shane) helped me
trace it down to a bundling issue (importing from ./tools
instead of ./tools/index.js
).
i updated the imports, rebuilt with npm run build:mcp
, and finally saw the green light next to my
server appear in windsurf (s-tier design by andy and team). i asked a
question and the tool call went through. but then came the next issue.

after finding this github issue from
the supabase mcp server (shout out greg), i discovered that different
clients interpret tools that have no input differently: windsurf sends undefined
, while cursor +
claude send {}
. zod throws an error if you expect {}
but get undefined
, which was causing the
error in windsurf.
i used windsurf (ironic, i know) to help me find a fix (adding .optional()
to the schema). i did that, published version 1.0.9
, and confirmed it worked in windsurf. i made a
demo video, got in bed, and told my husband to try it in cursor. but it didn't work for him.
by fixing windsurf, i had broken cursor and claude desktop. the next morning, the mastra team
shipped a fix to handle these discrepancies internally by treating no input as {}
when no schema
is defined. i removed inputSchema
altogether, republished, and crossed my fingers.
and it finally worked. an hour to build, ~ten hours to debug.
where will it go?
mcp is in an interesting spot. on one hand, it took me less than an hour to build a server. the primitives (resources, tools, and prompts) are simple and intuitive, and with frameworks like mastra, the developer experience is quite good.
on the other hand, it took me ten times as long to handle everything else. the bundling, packaging, and debugging across disparate clients was an absolute mess.
there’s a big gap between where i think mcp could go and where it is today. a few things stood out to me:
-
complexity: mcp still requires significant technical expertise to set up and maintain. small mistakes lead to silent failures that are nearly impossible to debug. in the age of "vibe coding", mcp doesn't yet feel accessible or forgiving enough for the next wave of builders.
-
interoperability: outside of the protocol itself, there's a lack of unified standards, apis, and robust tooling. different implementations and platforms have varying apis, configurations, and compatibility requirements (e.g. one client sends
undefined
for tools with no input, another sends{}
). -
security: mcp runs locally on the user's machine. for a simple server like mine, this is fine. but if you're building something more interesting, you'll probably need to handle authentication.
-
release process: just because your server is working on your machine, doesn't mean it will work when packaged up and run in another client. with mcp, there's no such thing as a preview url or staging environment. any change you make may be a breaking change that could take hours (or longer) to debug.
what's next?
if mcp is going to take off, we need better tools to tackle these challenges. while these issues made my weekend project a little more frustrating than i would have liked, it actually makes me very optimistic. for founders who are looking to build something in an emerging space with a ton of problems and a lot of promise, there's a huge opportunity. if you're building things in this space, i'd love to jam.
if you want to install my mcp server, just run npx opentools i alanagoyal
or add the following to
the mcp config in your preferred client:
"alanagoyal": {
"command": "npx",
"args": ["-y", "@alanagoyal/mcp-server@latest"]
}
the code is fully open-source and available on github and opentools.