[00:00] Create a new folder .cursor in the root of your project and inside this folder a new file githubissuesearch.ts which is where we will place our MCP server implementation. Install the model context protocol SDK and Zod, which are the only two packages we need to make this work. Now, import mcpserver and stdio-server-transport from the model context protocol SDK and also import Zod from the Zod package. To use the GitHub search, you will have to create a personal access token. So let's grab this from the environment and also let the user know if it's missing.
[00:44] Let's create a new instance of the MCP server class. It accepts only one parameter, that's an object where you have to specify the name and the version of your server. To run the server you will have to create a new instance of the stdioServerTransport class and connect that to your server. Now we are ready to register our first tool call using mcpserver.tool. There are a couple of different signatures for this function, I like to use the one where the name of the tool comes first followed by the description of the tool.
[01:16] This is all very important because Cursor or whatever MCP host you will use to run the MCP server will use this information to decide if it should call your tool. As the third parameter, we will pass in an object containing a Zot schema, which describes what parameters we are expecting from Cursor when it calls your tool. Same as for the tool name and description, describe the parameters as accurately as possible to get the best input from Cursor. The fourth and final parameter will be an async function. This function receives the parameters that we just described before using the Zot Schema.
[01:54] I will now ask Cursor to create the implementation of this function by highlighting the function and using it as a context for my prompt. I know it would try to use the HTTPS API by default, but I prefer fetch, so I will be specifying that. It will also add a bunch of console logs by default, which aren't really visible in the MCPServer output logs in cursor, so I will ask it to skip those as well. This almost looks like what I wanted, but the GitHub Issues API returns both pull requests and issues, so I will use here the isIssue flag to get only the issues. The data that I'm interested in is inside data.items now, so I will be using map to just iterate over those entries and grab only the information I want to show to the user.
[02:42] From this function you have to return an object containing a content key with an array of responses. Each response can have a type and a content for that response type. For now, I will just return the stringified results object. To show errors from the MCP, you can do something similar. Instead of throwing an exception when the response was unsuccessful, take the error message that the cursor already generated for us and return it in the same structure like we did it for the response.
[03:14] Adding the isError key will indicate to cursor that the response is an error. To register this MCP server inside cursor, in the .cursor directory, create a new file, mcp.json, and specify one key, mcpservers, which will be the list of the available MCP servers in this project. For each MCP server, you must add the command that Cursor will use to run this server, but keep in mind that if your host application is for example Cloud desktop, you need the full path to the bun executable, which you can get by running which bun in your terminal. Next, you will define the list of arguments, which in our case will have one element, and that is the full path to the githubissuesearch.ts TypeScript file. We also used one environment variable for this MCP server that was the GitHub token.
[04:07] You can create this in your GitHub profile, just go there and create a personal access token, but you also see that Cursor already realized we created an MCP server, and you can enable this simple by clicking the Enable button here. In MCP settings, you will find all the registered MCP servers. As you can see, here's the GitHub issue search, we just defined using the mcp.json. You can also inspect the logs from the MCP server by switching to output MCP logs. So if your script doesn't start up and there is an error in your MCP server, Usually you will have some indication of that here in MCP logs.
[04:51] Let's try our MCP server by opening a new chat and then typing in a prompt that goes like, search some GitHub repository for some kind of error. It will take a couple of seconds for Cursor to realize that it can call your tool to accomplish this task. If you expand this here, you will see the values that Cursor will assign to the MCPServerToolCall when calling your tool. We have the response, but it's a stringified JSON. The links aren't really clickable so let's see if we can do something about this.
[05:22] I'm going to highlight the function that I used to extract the data I wanted to show to the user and ask cursor to format this as markdown. I like the changes so I'm going to accept them and repeat my prompt, but before that I'm going to reload the MCP server because cursor won't automatically pick up the new changes. I will enter a similar prompt like before and now cursor is getting confused by which tool it should call but it corrected itself and actually offered to call the GitHub issue search tool which we wanted to call. But to double-check I'm going to expand the parameters and see what it sends to my toolkit and click run tool. This output looks a lot better compared to the stringify.json.
[06:08] We are actually getting the markdown format right and the links are clickable.