Create a Custom MCP that Calls Cursor Tools

Cursor's AI Agent has the abilitly to invoke MCP tools, but they lack direct access to your project. This lesson shows how to build custom MCP (Model Context Protocol) server tools that bridge this gap. Learn to define tool parameters using Zod schemas and the crucial .describe() method to explicitly request necessary context from the Cursor environment before your tool runs. See how the AI gathers this information (like Git status or version via terminal commands, or web search results) and passes it to your tool, enabling powerful, context-aware automation.

Workflow demonstrated in this lesson:

  • Scaffold a basic MCP server using a Cloudflare template (pnpm create cloudflare).
  • Run the MCP server locally and configure it in Cursor settings.
  • Define MCP tools (e.g., status) within the server code (index.ts).
  • Specify required context for tools using Zod schema parameters and .describe() (e.g., requesting "The status of the git repository").
  • Interact with the Cursor Agent using natural language (e.g., "Please check the status").
  • Observe the Agent's thought process: identifying the need for specific context based on the .describe() text.
  • See the Agent automatically using its own tools (like run_terminal_cmd or web_search) to gather the requested context before calling your MCP tool.
  • Watch the Agent call your MCP tool, passing the gathered local context (Git status, version, etc.) as arguments.
  • Receive the final response from the Agent, incorporating the information provided by your local MCP tool.

Key benefits:

  • Allows Cursor's AI to access and reason about information from your local machine (Git status, versions, file contents, etc.).
  • Enables the creation of AI tools that directly interact with or report on your local development environment.
  • Demonstrates the power of using .describe() in Zod schemas to guide the AI in gathering necessary context automatically.
  • Unlocks advanced automation possibilities by combining AI reasoning with local machine access.
Share with a coworker

Transcript

[00:00] Let's scaffold our MCP using pmpm create. We'll use Cloudflare since they provide an awesome template called remote MCP server. So let this run and we'll call this MCP request tool. Hit enter. It will check out the template and install the dependencies.

[00:16] We'll use git for version control, we don't want to deploy, and then I have cursor alias to C so I can type C mcp request tool, hit enter, and it opens in cursor. So from here we'll zoom in a bit. If we simply start in our terminal with control tilde pnpm dev, This will spin up an mcp and we have a url here which we can copy and go into our configuration and then in our cursor settings under mcp we can add a new global mcp server and we're going to call this status. Then I'll hit tab to auto-complete this. Essentially it's going to find the global npx as the command and then run this mcp remote package, which you can find on npm under mcp remote.

[00:58] And this just helps your local connect to this server here. So we can copy and paste in this, copy, paste right here, and make sure you have the slash SSE. Then once you hit save, you'll see some logs are added here, and then a window will pop open in one of your browsers asking for authorization and right now it's just demo auth. We'll just leave it like this for now. We'll hit approve and just wait for it to return and it says successful and you can tell by the tooling and the design that a lot of this is still early days.

[01:30] So we can close this and you'll see in our settings that our status MCP server is now available. So if we come up to a new chat, I'll just hit slash and say reset context, and I'll say please list the available MCP tools, hit enter, and you'll see it finds this mcp underscore status underscore add. So mpc is just the prefix, status is this name here, and then add is in our index file. If we close that and give us some room to work with, if I scroll down you'll see add is this tool right here. And because this is AI we could say if I have five apples and my wife Mindy has six apples, how many apples do we all have together?

[02:10] And I hit enter, it's going to infer that it needs to use this MCP tool. You can check on the response that it brought in five and six. Five were my apples, six were Mindy's apples, and the result was 11. And then it took the result and gave us a AI generated response. And if you check the logs in your dev tools, you will see posts requests coming through.

[02:30] So I'm going to create another tool called status and then hit tab here to accept these. And then the way that you pull in information from a cursor chat is by adding a describe as part of your definitions here. So if I say describe I can request from the AI to give me something. So I'm going to ask here, git status, then hit tab and let it clear things up, and it'll infer the status of the git repository. And then if we try and run this now, I'll hit save, and I'll say, please check the status.

[03:04] But before we can run this we need to make sure in our cursor settings that we refresh our MCP server so it can do the tool discovery again. But while working on this if we make changes to the tool then we don't need to click refresh. It's only when we add or remove tools where it has to do a discovery process again. So asking it to check the status, so scroll back up we can check on the thought, and if we read the thought you'll see the user wants to check the status. It has a status tool but I need to get the git status first to pass to the tool and that's because the tool definition exposed that we want the status of the git repository.

[03:45] So it knows it needs to do something before it can even run the tool. Then once it has this available it can pass it to the tool. It passed git status of the result of the git call and then we just sent the result back without modifying it at all. The key here is realizing that this text is absolutely critical to getting the information you want from the AI before the tool is called. And if you want to be even more explicit with it, we can check on which tools are available because this is technically a tool called by cursor and its agent.

[04:18] So if I say please list all tools available to cursor, hit enter, and we'll scroll back up and you'll see it has a run terminal command tool. So if I do something like copy this and I'll add this in a comment, and I'm going to grab this, hit command L to bring it into the AI, and tell it to, in this describe text, tell it to explicitly call the terminal tool using git. Let that paste in, hit enter, and we could have typed this out but we'll go ahead and accept this, the status of the git repository obtained by calling the terminal tool with git status porcelain. I would even take this one step further. I would copy this and change terminal to run-terminal-cmd so that it knows for sure which tool it should call.

[05:02] So if we start a new chat and I say please check the status, hit enter. This all happened real fast so we'll scroll back up. You can check the thoughts. This time it knows exactly that it needs to call the run terminal command and then call our status tool. So we can see all of this text.

[05:17] I'll turn on WordWrap and because this is just a function we could have used it to call actual git rather than asking the AI to generate a command that calls git. Or we could even have asked this to generate the specific git command and then run git with that command. You have a lot of options based on how much you're relying on the other tools and how much work you want to do that's predefined inside of your function. But having the run terminal command makes it extremely flexible to call essentially whatever. And then just as another example, if you look at the other available tools, we'll scroll back up and you can see we have web search.

[05:53] So I can copy web search and I can say the git latest version. I typed this out before, it must remember it. I'll hit tab and I'll say the latest version of git obtained by calling the web search tool. And we can say the git current version. And this can be the current version of git obtained by calling the run terminal command.

[06:10] And then in our response, let's just hit tab to generate the rest of this. So this data will come from calling a web search. This will come from calling a tool before MCP is called, and this will also come from the tool before MCP is called. So if I hit save here, start a new chat, check the status, hit enter, you'll see it'll do a lot of work this time before it even gets to call our MCP tool. You'll see the final result here is the latest version is 2.4.9.

[06:36] We have 2.3.9 installed and here's the current status and you can scroll back up and look through everything it found. Here's what was passed and our tool requested git version. Because our tool requested the version, it requested a web search and it found these results and requested the status. That's all because of the initial thoughts of here's everything I need, here are all the tools I need to run, and I need to run all of these before I can even get to the point where I invoke my MCP tool.