4 Months within the Making: SwiftMCP 1.0 is Right here


After 4 months of intensive growth, I’m thrilled to announce that SwiftMCP 1.0 is feature-complete and prepared so that you can use.

For these simply becoming a member of, SwiftMCP is a local Swift implementation of the Mannequin Context Protocol (MCP). The aim is to offer a dead-simple method for any developer to make their app, or components of it, obtainable as a robust server for AI brokers and Giant Language Fashions. You’ll be able to learn the official specification at modelcontextprotocol.io.

I did a SwiftMCP 1.0 Function Velocity Run on YouTube, if that’s what you like.

The Core Concept: Your Documentation is the API

Earlier than diving into options, it’s essential to know the philosophy of SwiftMCP. The framework is constructed on the precept that your current documentation ought to be the first supply of fact for an AI. Through the use of customary Swift documentation feedback, you present all of the context an AI wants to know and use your server’s capabilities.

/**
 Provides two numbers and returns their sum.

 - Parameter a: The primary quantity so as to add
 - Parameter b: The second quantity so as to add
 - Returns: The sum of a and b
 */
@MCPTool
func add(a: Int, b: Int) -> Int {
    a + b
}

This code exhibits the best use case. The @MCPTool macro inspects the add perform and its documentation remark. It routinely extracts the primary description (“Provides two numbers…”), the descriptions for parameters a and b, and the outline of the return worth, making all of this data obtainable to an AI consumer with none further work.

Server Options: Exposing Your App’s Logic

These are the capabilities your Swift utility (the server) exposes to a consumer.

Instruments: The Basis of Motion

Instruments are the first technique to expose your app’s performance. By adorning any perform with @MCPTool, you make it a callable motion for an AI. device is well-documented, handles potential errors, and offers clear performance.

// Outline a easy error and enum for the device
enum TaskError: Error { case invalidName }
enum Precedence: String, Codable, CaseIterable { case low, medium, excessive }

/**
 Schedules a process with a given precedence.
 - Parameter identify: The identify of the duty. Can't be empty.
 - Parameter precedence: The execution precedence.
 - Parameter delay: The delay in seconds earlier than the duty runs. Defaults to 0.
 - Returns: A affirmation message.
 - Throws: `TaskError.invalidName` if the identify is empty.
 */
@MCPTool
func scheduleTask(identify: String, precedence: Precedence, delay: Double = 0) async throws -> String {
    guard !identify.isEmpty else {
        throw TaskError.invalidName
    }

    // Simulate async work
    attempt await Process.sleep(for: .seconds(delay))

    return "Process '(identify)' scheduled with (precedence.rawValue) precedence."
}

This instance demonstrates a number of key options directly. The perform is async to carry out work that takes time, and it throws a customized TaskError for invalid enter. It makes use of a CaseIterable enum, Precedence, as a parameter, which SwiftMCP can use to supply auto-completion to purchasers. Lastly, the delay parameter has a default worth, making it optionally available for the caller.

Assets: Publishing Learn-Solely Knowledge

Assets will let you publish information that purchasers can question by URI. SwiftMCP gives a versatile system for this, which could be damaged down into two fundamental classes: function-backed sources and provider-based sources.

Operate-Backed Assets

These sources are outlined by particular person capabilities adorned with the @MCPResource macro. If a perform has no parameters, it acts as a static endpoint. If it has parameters, they have to be represented as placeholders within the URI template.

/// Static Useful resource: Returns a server information string
@MCPResource("server://information")
func getServerInfo() -> String {
    "SwiftMCP Demo Server v1.0"
}

/// Dynamic Useful resource: Returns a greeting for a consumer by ID
/// - Parameter user_id: The consumer's distinctive identifier
@MCPResource("customers://{user_id}/greeting")
func getUserGreeting(user_id: Int) -> String {
    "Howdy, consumer #(user_id)!"
}

The getServerInfo perform is a static useful resource; a consumer can request the URI server://information and can all the time get the identical string again. The getUserGreeting perform is dynamic; the {user_id} placeholder within the URI tells SwiftMCP to count on a price. When a consumer requests customers://123/greeting, the framework routinely extracts “123”, converts it to an Int, and passes it to the user_id parameter.

Supplier-Based mostly Assets (like information)

For exposing a dynamic assortment of sources, like information in a listing, you’ll be able to conform your server to MCPResourceProviding. This requires implementing a property to find the sources and a perform to offer their content material on request.

extension DemoServer: MCPResourceProviding {
    // Announce obtainable file sources
    var mcpResources: [any MCPResource] {
        let docURL = URL(fileURLWithPath: "/Customers/Shared/doc.pdf")
        return [FileResource(uri: docURL, name: "Shared Document")]
    }

    // Present the file's content material when its URI is requested
    func getNonTemplateResource(uri: URL) async throws ->
        [MCPResourceContent] {
        guard FileManager.default.fileExists(atPath: uri.path) else {
            return []
        }

        return attempt [FileResourceContent.from(fileURL: uri)]
    }
}

This code exhibits the two-part mechanism. First, the mcpResources property known as by the framework to find what sources can be found. Right here, we announce a single PDF file. Second, when a consumer truly requests the content material of that file’s URI, the getNonTemplateResource(uri:) perform known as. It verifies the file exists after which returns its contents.

Prompts: Reusable Templates for LLMs

For reusable immediate templates, the @MCPPrompt macro works similar to @MCPTool. It exposes a perform that returns a string or PromptMessage objects, making its parameters obtainable for the AI to fill in.

/// A immediate for saying Howdy
@MCPPrompt()
func helloPrompt(identify: String) -> [PromptMessage] {
    let message = PromptMessage(position: .assistant, 
        content material: .init(textual content: "Howdy (identify)!"))
    return [message]
}

This instance defines a easy immediate template. An AI consumer can uncover this immediate and see that it requires a identify parameter. The consumer can then name the immediate with a particular identify, and the server will execute the perform to assemble and return the totally shaped immediate message, able to be despatched to an LLM.

Progress Reporting: Dealing with Lengthy-Working Duties

For duties that take time, you’ll be able to report progress again to the consumer utilizing RequestContext.present, which prevents the consumer from being left in the dead of night.

@MCPTool
func countdown() async -> String {
    for i in (0...30).reversed() {
        let performed = Double(30 - i) / 30
        await RequestContext.present?.reportProgress(performed, 
            complete: 1.0, message: "(i)s left")
        attempt? await Process.sleep(nanoseconds: 1_000_000_000)
    }
    return "Countdown accomplished!"
}

On this perform, the server loops for 30 seconds. Contained in the loop, reportProgress known as on the RequestContext.present. This sends a notification again to the unique consumer that made the request, which may then use the progress worth and message to replace a UI component like a progress bar.

Shopper Options: The Shopper is in Management

Whereas SwiftMCP is a server framework, it totally helps the highly effective capabilities a consumer can provide. The consumer holds a substantial amount of management, and your server can adapt its conduct by checking Session.present?.clientCapabilities.

Roots: Managing File Entry

The consumer is in full management of what native information the server can see. When a consumer provides or removes a root listing, your server is notified and might react by implementing handleRootsListChanged().

func handleRootsListChanged() async {
    guard let session = Session.present else { return }
    do {
        let updatedRoots = attempt await session.listRoots()
        await session.sendLogNotification(LogMessage(
            stage: .information,
            information: [ "message": "Roots list updated", "roots": updatedRoots ]
        ))
    } catch {
        // Deal with error...
    }
}

This perform is a notification handler. When a consumer modifies its record of shared directories (or “roots”), it sends a notification to the server. SwiftMCP routinely calls this perform, which may then use session.listRoots() to get the up to date record and react accordingly, for instance, by refreshing its personal record of accessible information.

Cancellation: Stopping Duties Gracefully

If the consumer is exhibiting a progress bar for that countdown, it also needs to have a cancel button. The consumer can ship a cancellation notification, and your server code have to be a very good citizen and examine for it with attempt Process.checkCancellation().

Elicitation: Asking the Consumer for Enter

Elicitation is a robust interplay the place the server determines it wants particular, structured data. It sends a JSON schema to the consumer, and the consumer is accountable for rendering a kind to “elicit” that information.

@MCPTool
func requestContactInfo() async throws -> String {
    // Outline the information you want with a JSON schema
    let schema = JSONSchema.object(JSONSchema.Object(
        properties: [
            "name": .string(description: "Your full name"),
            "email": .string(description: "Your email address", 
            format: "email")
        ],
        required: ["name", "email"]
    ))

    // Elicit the data from the consumer
    let response = attempt await RequestContext.present?.elicit(
        message: "Please present your contact data",
        schema: schema
    )

    // Deal with the consumer's response
    swap response?.motion {
    case .settle for:
        let identify = response?.content material?["name"]?.worth as? String ?? "Consumer"
        return "Thanks, (identify)!"
    case .decline:
        return "Consumer declined to offer data."
    case .cancel, .none:
        return "Consumer cancelled the request."
    }
}

This device demonstrates the three steps of elicitation. First, it defines a JSONSchema that specifies the required fields (identify and e mail). Second, it calls elicit on the present request context, sending the schema and a message to the consumer. Third, it waits for the consumer’s response and makes use of a swap assertion to deal with the completely different outcomes: the consumer accepting, declining, or canceling the request.

Sampling: Utilizing the Shopper’s LLM

Maybe probably the most fascinating function is Sampling, which flips the script. The server can request that the consumer carry out a generative process utilizing its personal LLM. This enables your server to be light-weight and delegate AI-heavy lifting.

@MCPTool
func sampleFromClient(immediate: String) async throws -> String {
    // Verify if the consumer helps sampling
    guard await Session.present?.clientCapabilities?.sampling != nil else {
        throw MCPServerError.clientHasNoSamplingSupport
    }

    // Request the era
    return attempt await RequestContext.present?.pattern(immediate: immediate) ?? "No response from consumer"
}

This code exhibits how a server can leverage a consumer’s personal generative capabilities. It first checks if the consumer has marketed assist for sampling. If that’s the case, it calls pattern(immediate:), which sends the immediate to the consumer. The consumer is then accountable for operating the immediate by means of its personal LLM and returning the generated textual content, which the server receives as the results of the await name.

What’s Subsequent?

My imaginative and prescient is for builders to combine MCP servers immediately into their Mac apps. My API.me non-public app does precisely this, exposing a consumer’s native emails, contacts, and calendar by means of a neighborhood server that an LLM can securely work together with. I’m pondering if I ought to put this on the app retailer or probably open supply it. What do you suppose?

It has been numerous work, and it’s lastly prepared. SwiftMCP 1.0 is right here.

I’m very a lot wanting ahead to your suggestions. Please give it a attempt, try the examples on GitHub, and let me know what you suppose. I hope to see you construct some wonderful issues with it.

Oh and in case you haven’t watched it but, I actually suggest watching my demonstration of all the brand new options:


Classes: Updates

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles