Flo Writes Code
← Back to Blog

AppIntents meet MCP

While AppIntents is mainly used to let Siri talk to your apps, the SwiftMCP package let's it talk to other LLMs as well.

AppIntents is a framework first released in iOS 16 to let Siri talk to your apps. Over the years, it has expanded its impact on the OS and now also allows your app‘s actions (intents) in Spotlight Search and Shortcuts.

I‘ve talked about the framework‘s relevancy for years, and with the big Siri overhaul hopefully happening later this year, it’s becoming increasingly obvious that apps „have to“ support it if they want to stay relevant.

AppIntents meet MCP

A few weeks ago, I stumbled upon SwiftMCP, a typesafe MCP implementation for Swift based on Swift macros. I explored the package and later talked to Oliver (known as Cocoanetics), about a way to marry SwiftMCP with AppIntents. And boy did he deliver! I actually shipped an update including the new LaunchBuddy MCP server using this package, check out LaunchBuddy here. Let’s dive into how this package works!

dependencies: [
    .package(url: "https://github.com/Cocoanetics/SwiftMCP.git", branch: "main")
]

In essence, we should consider 3 concepts: Server, Transport, and Tool.

Server

The server is similar to the App protocol. It’s what gets instantiated and talks to the clients using your MCP server.

@MCPServer
class MyServer {
    …
}

let server = MyServer()

Transport

This is the kind of connection clients can make to your Server. There are different options: HTTP+SSE, stdio, TCP+Bonjour. I won’t pretend that I’m an expert in these, so for my experiments I just used the “standard” HTTP+SSE transport. You create this once (and store it somewhere) and use it to run your server.

let transport = HTTPSSETransport(server: server, port: 8080)
…
try transport.run()

Tools

Now this is where things get interesting. Tools are the different Swift functions a client can call on your MCPServer. Aside from tools, MCP also supports MCPResource. Tools are functions inside your MCPServer class (this could also be an actor). They can return values or just execute some logic in your app. The sky is the limit!

@MCPServer
class MyServer {
    @MCPTool
    func add(a: Int, b: Int) -> Int {
        return a + b
    }
}

There’s no hard limit on how many tools your server can expose, but you should design your list of tools consciously. As a rule of thumb, prefer making “meta-tools”, that receive configuration parameters - instead of creating multiple similar tools. The @MCPTool macro supports adding a description to make it easier for the connected client LLM to decide which tool to use. It’s highly recommended to add this to all of your tools:

@MCPTool(description: "This tool takes two numbers and returns their sum as an Int.")
func add(a: Int, b: Int) -> Int {
    return a + b
}

Now how does all of this relate to AppIntents?

Great question, because so far it doesn’t. This is where Oliver has been delivering hard, as I hinted in the beginning. In addition to @MCPServer and @MCPTool, he also build out MCPAppIntentTool to merge SwiftMCP with my beloved AppIntents framework.

@MCPServer(name: "MyServer", version: "0.1")
class MyServer: AppShortcutsProvider {

As you can see, you just annotate your AppShortcutsProvider with @MCPServer. Make sure it’s a class though, as structs aren’t supported and actors produce a compiler error (on my machine).

@MCPAppIntentTool(description: "Create a new app idea.")
struct CreateAppIdeaIntent: AppIntent {

The, you add the @MCPAppIntentTool macro annotation to each of the AppIntents used by your AppShortcutsProvider.

That’s it!

Now just create and run your server & transport to let MCP clients talk to your macOS apps. I personally added this configuration to Cursor to talk to my LaunchBuddy MCP server:

{
  "mcpServers": {
    "LaunchBuddyMCPServer": {
      "url": "http://localhost:8080/sse"
    }
  }
}