writing my own express-based RESTful API and Model Context Protocol server (part 1)
The Model Context Protocol doesn't seem like it deserves the word, "Protocol", but it sure is promising
I really wanted to learn TypeScript and build off existing node experience, so I figured, why not try my hand at writing an API and MCP server? Originally, I had started writing my own API server to handle OAuth calls from TRMNL (great product btw, use referral code subtype), but got lost in the sauce with the user and authentication flow. A bit ironic considering I work in the identity and access management space. It wasn’t until my friend told me about the Model Context Protocol that I thought would be a nice project to
- Give Morgana (okay, OpenAI) the capability to poll for the weather
- Help Morgana be more natural and conversational in Discord.
I gotta say, at first I thought MCP was a dumb wrapper around REST calls, and to be honest, I somewhat still do. “Model Context Standard” seems a more apt way of describing its current state. If you wanted to simply the concept of it to other people, you could argue that instead of the service just merely responding with the data a client is request, you rather can hold a dialogue with the agent (in this case, the LLM) with the added benefit that it’s stateful! Someone once explained it to me like:
RESTful API: Like a fast food worker, you ask what you want, they give it to you, they don’t care about you afterwards 🍟
MCP: Kinda like a butler - they give you what you want, but have a bit, and just a bit, more smarts, context, and can hold a conversation (how this actually applies and affects LLMs, is still lost on me)
From my experience so far, MCP seems of a more natural way to extend APIs to allow LLMs to consume and use them, by configuring a toolkit and describing to the LLM what each tool provides. The endpoints still do, return raw data and you have the option of either formatting it or not, but ultimately, your LLM agent will parse the received data from the MCP server and summarize it for you. So it can be a bit of additional overhead to configure MCP, but once you’ve set up a single tool, it’s largely boilerplate.
When it comes to authentication, I’m still trying to understand how OAuth works with MCP servers. From what I’ve read so far, it seems MCP implements a weird Frankenstein version of it, but I can’t confirm it. In the meantime, I opted to just set up a simple JWT authentication system and eventually I do plan on extending JWT authentication to leverage the OAuth endpoints provided by my identity management provider, Keycloak.
“Protocol” just seems too strong of a word to describe what really does feel more of a standard to me. It’s not necessarily a new encryption or technology, but rather leveraging existing standards to enable the ease of development and consumption of LLM tools. I understand MCP is not meant to replace RESTful APIs, and I know I’m being a bit harsh on it. However, I do believe the standard is extremely promising and I intend on keeping an eye on it. This can serve as a strong foundation for enhanced machine-human interfacing, as well as LLM to LLM.
I decided to write my RESTful and MCP server in TypeScript, and it’s pretty straight forward on getting set up. While most guides set up an MCP server over STDIO, here’s how I set it up in preparation for SSE:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'
const mcpServer = new McpServer({
name: 'my-mcp-server',
version: '1.0.0',
capabilities: {
resources: {},
tools: {},
},
})
// you dont need to do this but this lets you have stateful sessions
const transports: { [sessionId: string]: SSEServerTransport } = {}
// Discovery endpoint
server.get('/sse', async (req: Request, res: Response) => {
const transport = new SSEServerTransport('/messages', res)
transports[transport.sessionId] = transport
logger.info('New MCP session created:', transport.sessionId)
res.on('close', () => {
logger.info('Closing session', transports[transport.sessionId])
delete transports[transport.sessionId]
})
await mcpServer.connect(transport)
})
// MCP Handler
server.post('/messages', async (req: Request, res: Response) => {
const sessionId = req.query.sessionId as string
if (typeof sessionId != 'string') {
logger.error('Bad sessionId', sessionId)
res.status(400).send({ message: 'Bad sessionId' })
}
const transport = transports[sessionId]
if (transport) {
logger.info(`${transport.sessionId} has an active session`)
await transport.handlePostMessage(req, res, req.body) // don't remove req.body otherwise MCP inspector will panik
} else {
res.status(400).send(`No session was found for ${sessionId}`)
}
})
Of course, in my API server I also provide some authentication middleware. I also opted to move all of my tool registration into a separate file, and exporting that method, passing in the MCP Server instance as an argument.
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { z } from 'zod'
export function registerTools(mcpServer: McpServer) {
mcpServer.tool(
[...]
Luckily, the documentation for setting up your tools is both straightforward and very boilerplate-y. Honestly the hardest thing (and still is for me) is understanding TypeScript syntax enough so I myself, know what’s going on!
This is how the MCP flow works in my mind:
- MCP Client (typically an LLM) makes call to endpoint (
/ssein my server) for authentication and tool discovery - MCP server responds with tools
- MCP Client, based on input from user, can make the decision to use a tool provided by the MCP Server, and if so, sends a request over the
/messagesendpoint (in my server, the client also provides an authorization header for JWT auth) - MCP Server receives the request, goes out and runs the associated code and methods belonging to the tool, and returns back data (optionally formatted, but seems to help the LLM parse it)
- MCP Client looks at the data and can summarize it or perform additional analysis; MCP client can use the same session to make more calls to the MCP Server to use other tools
You can see the current progress of the downstream mirror of the API below:
GitHub - subtype-space/subspace-api An express-based RESTful API and Model Context Protocol (MCP) server for services used by subtype.space. Please note the GitHub repository is a downstream mirror, and updates may be late.

