My first Model Context Protocol server extension
In this post, I would like to share my experience implementing a Model Context Protocol (MCP) server extension that I integrated with Claude. My focus is on lessons learned.
Points
3. Lessons learned as to an efficient way to build MCP
4. How to use the stream-HTTP transport with Claude
Model Context Protocol (MCP) servers
There is a lot of discussion about agentic AI. At its core, it allows the GPT models to interact with the external world. Among the first tools was the ability to browse the web. In recent years, there has been a full explosion of services, but it is the Wild West—insecure, with access to your machine in many cases.
At this point, there are thousands of tools and resources available. The way I see the three different abilities of MCP are:
There is no point in my repeating the documentation of the MCP, you can read it here.
MCP components in architectural layers
Let us start with the architectural approach I use — Volatility Based Decomposition (VBD). Below is a taxonomy slide from the Architect Master Class (AMC) by IDesign
When I trigger a tool or a resource on the MCP, I am starting a use case — design it as a Manager — and from the outside system, treat it as a Resource
Lets assume we have these five MCP components — internally designed right.
But when you compose them and they interact, each has its role, same rules of architecture apply
Now we need to find a suitable taxonomy for a symbol for the MCP resource.
Yellow figures are taken, the ones in purple are weird or confusing
That's the proposal I had in mind
I tend to like the fourth from the left bracket style.
Lets talk DDD
As per Fowler's definition
“Aggregate is a pattern in Domain-Driven Design. A DDD aggregate is a cluster of domain objects that can be treated as a single unit”.
We could consider the Entity, Value Object, and Aggregate (with the Aggregate root being the entry point, through Resources).
Recommended by LinkedIn
Classical 3-layer aproach
It is a DAL
Lessons learned
I started with the base example approach. It is very verbose. Structuring the code into independent components could be a potential mess.
I started with the base stdio transport. Pretty rapidly, I could define three tools that authenticate remotely, maintain a session, and query ESRI server mapping and administrative services—a manager to execute the use cases.
But — I wouldn't want to rely on the internal transport — the idea is to distribute rapidly.
There are two more types of transports: SSE, which is intended mainly for server-to-client communication, and HTTP. With SSE becoming a legacy, it made sense to go for the newer streaming protocol.
This is where I found that change to a transport forced me to restructure the whole code flow — not good.
Compare stdio and the streamable-http
Finally, after I made the changes and restarted Claude, it could not register the server or start it and was giving pretty ambiguous responses.
[2025-05-04T22:26:19.231Z] [INFO] HttpStream transport listening on port 3000, endpoint /mcp
[2025-05-04T22:26:19.231Z] [INFO] Started unnamed-mcp-server@0.0.0 successfully on transport http-stream
[2025-05-04T22:26:19.231Z] [INFO] Tools (3): example_tool, generateToken, listServices
[2025-05-04T22:26:19.231Z] [INFO] Server running and ready.
2025-05-04T22:27:19.137Z [mcp-server] [info] Message from client: {"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":0,"reason":"Error: MCP error -32001: Request timed out"}}
2025-05-04T22:27:19.137Z [mcp-server] [info] Client transport closed
2025-05-04T22:27:19.139Z [mcp-server] [info] Server transport closed
2025-05-04T22:27:19.139Z [mcp-server] [info] Client transport closed
2025-05-04T22:27:19.139Z [mcp-server] [info] Server transport closed unexpectedly, this is likely due to the process exiting early. If you are developing this MCP server you can add output to stderr (i.e. `console.error('...')` in JavaScript, `print('...', file=sys.stderr)` in python) and it will appear in this log.
2025-05-04T22:27:19.139Z [mcp-server] [error] Server disconnected. For troubleshooting guidance, please visit our [debugging documentation](https://modelcontextprotocol.io/docs/tools/debugging) {"context":"connection"}
2025-05-04T22:27:19.145Z [mcp-server] [info] Server transport closed
2025-05-04T22:27:19.145Z [mcp-server] [info] Client transport closed
Logging is your friend
Apparently, you could disable Claude starting the server, and starting it manually (which makes sense), same as it would be running remotely. In no vain. Running the server manually and testing MCP client worked without a problem, so it was not the logic.
After few hours I assumed I misconfigured the transport — and found a much better wrapper to build MCP servers FastMCP — available for python and typescript. This one is making it right — a switch of transport is just that a switch of the transport string and the configurations.
server.start({
transportType: "stdio",
});
server.start({
transportType: "httpStream",
httpStream: {
endpoint: "/stream",
port: 8080,
},
});
To my surprise, that did not work either, a few more hours of debugging and log checks ( which are pretty good), seeing that the stdio transport works, it was clear that it is Claude.
Lesson 2: To enable the verbosity of the server that Claude starts, you need to add the environment variable in the config ( which makes sense).
{
"mcpServers": {
"mcp-server": {
"env": {
"MCP_DEBUG_CONSOLE": "true"
}
}
}
}
And then…
Claude can only communicate through a stdio transport.
So this is how you run an MCP that uses a remote transport —
Watch out authentication and authorization flows!
How to use the stream-HTTP transport with Claude
What you need to do is to use a proxy that takes the “stdio” transport and re-transmits the traffic
{
"mcpServers": {
"remote-example": {
"command": "npx",
"args": [
"mcp-remote",
"https://remote-server.example.com/mcp"
]
}
}
}
Since we are leveraging the newer HTTP transport, as per mcp-remote documentation, a couple of additional flags are required
{
"mcpServers": {
"mcp-server": {
"command": "npx",
"args": [
"mcp-remote",
"http://localhost:3000/mcp",
"--transport",
"http-only"
],
"env": {
"MCP_DEBUG_CONSOLE": "true"
}
}
}
}
With this configuration, the goal is achieved.
Summary
MCP provides access to models outside of its box.
Treat and design your first 100 lines of code well; you will not need to feel pain after 5000 lines. MCP is just that proxy tooling; design it modular, scalable, and decomposed the right way. Each MCP should be structured as a mini system, with Managers, Engines, and Resource Accesses.
However, each of these systems comprises a bigger network of MCPs that communicate with each other, and each one has a role: Managers, Engines, and Resource Accesses.
Pay close attention to security and auth—block access based on routes and IPs, watch throttling. Have auth middleware on every MCP and every tool/resource/prompt (except the one used to authenticate).
I would put them behind an API gateway with a central authorization component.