<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Glacio Blog]]></title><description><![CDATA[Glacio Blog]]></description><link>https://blog.glacio.tech</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1749760630773/9053d8d3-c8c3-44ae-afe8-16e0b5943ee7.png</url><title>Glacio Blog</title><link>https://blog.glacio.tech</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Apr 2026 02:34:46 GMT</lastBuildDate><atom:link href="https://blog.glacio.tech/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building a MCP Client in Swift: A Step-by-Step Guide (Part 2)]]></title><description><![CDATA[This is the second article in our series on the Model Context Protocol (MCP) in Swift. If you haven't read the first part about building an MCP server in Swift, we recommend checking it out first.
In our previous article, we explored how to create a ...]]></description><link>https://blog.glacio.tech/building-a-mcp-client-in-swift-a-step-by-step-guide-part-2</link><guid isPermaLink="true">https://blog.glacio.tech/building-a-mcp-client-in-swift-a-step-by-step-guide-part-2</guid><category><![CDATA[swiftai]]></category><category><![CDATA[mcp-swift]]></category><category><![CDATA[software development]]></category><category><![CDATA[Swift]]></category><category><![CDATA[mcp]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Peter Liddle]]></dc:creator><pubDate>Mon, 23 Jun 2025 02:20:40 GMT</pubDate><content:encoded><![CDATA[<p><em>This is the second article in our series on the Model Context Protocol (MCP) in Swift. If you haven't read the first part about <a target="_blank" href="https://blog.glacio.tech/building-a-mcp-server-in-swift-a-step-by-step-guide">building an MCP server in Swift</a>, we recommend checking it out first.</em></p>
<p>In our previous article, we explored how to create a Model Context Protocol (MCP) server in Swift. Now, let's look at the other side of the equation: building a client application that connects to an MCP server. This guide will walk you through creating a Swift client that can communicate with any MCP-compatible server, including the one we built in our previous tutorial.</p>
<h2 id="heading-what-is-a-mcp-client">What is a MCP Client?</h2>
<p>A Model Context Protocol (MCP) client is an application that connects to an MCP server to access its tools and capabilities. The client sends requests to the server, which processes them and returns responses. This architecture allows AI systems to extend their capabilities through external tools without needing to implement those capabilities directly.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before we begin, make sure you have:</p>
<ul>
<li>macOS 13.3 or later</li>
<li>Swift 6.0 or later</li>
<li>Xcode 15.0 or later (recommended)</li>
<li>A compatible MCP server (like the one from our <a target="_blank" href="https://blog.glacio.tech/building-a-mcp-server-in-swift-a-step-by-step-guide">previous tutorial</a>)</li>
</ul>
<h2 id="heading-step-1-set-up-your-project">Step 1: Set Up Your Project</h2>
<p>First, let's create a new Swift package for our MCP client:</p>
<pre><code class="lang-bash">mkdir SwiftMCPClientExample
<span class="hljs-built_in">cd</span> SwiftMCPClientExample
swift package init --<span class="hljs-built_in">type</span> executable
</code></pre>
<p>Next, update your <code>Package.swift</code> file to include the necessary dependencies:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// swift-tools-version: 6.0</span>

<span class="hljs-keyword">import</span> PackageDescription

<span class="hljs-keyword">let</span> package = <span class="hljs-type">Package</span>(
    name: <span class="hljs-string">"SwiftMCPClientExample"</span>,
    platforms: [
        .macOS(<span class="hljs-string">"13.3"</span>)
    ],
    dependencies: [
        .package(url: <span class="hljs-string">"https://github.com/modelcontextprotocol/swift-sdk.git"</span>, from: <span class="hljs-string">"0.9.0"</span>),
        .package(url: <span class="hljs-string">"https://github.com/apple/swift-log.git"</span>, from: <span class="hljs-string">"1.5.3"</span>),
        .package(url: <span class="hljs-string">"https://github.com/glaciotech/mcp-swift-sdk-helpers.git"</span>, from: <span class="hljs-string">"0.2.0"</span>),
    ],
    targets: [
        .executableTarget(
            name: <span class="hljs-string">"SwiftMCPClientExample"</span>,
            dependencies: [
                .product(name: <span class="hljs-string">"MCP"</span>, package: <span class="hljs-string">"swift-sdk"</span>),
                .product(name: <span class="hljs-string">"MCPHelpers"</span>, package: <span class="hljs-string">"mcp-swift-sdk-helpers"</span>),
            ]),
    ]
)
</code></pre>
<h2 id="heading-step-2-create-a-configuration-structure">Step 2: Create a Configuration Structure</h2>
<p>We'll start by creating a configuration structure to hold information about the MCP server we want to connect to. Create a new file called <code>LocalMCProcess.swift</code> in your Sources directory:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> MCP
<span class="hljs-keyword">import</span> Foundation
<span class="hljs-keyword">import</span> Logging
<span class="hljs-keyword">import</span> System

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">LocalMCPServerConfig</span>: <span class="hljs-title">Codable</span> </span>{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">var</span> name: <span class="hljs-type">String</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">var</span> executablePath: <span class="hljs-type">String</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">var</span> arguments: [<span class="hljs-type">String</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">var</span> environment: [<span class="hljs-type">String</span>: <span class="hljs-type">String</span>]

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">init</span>(name: <span class="hljs-type">String</span>, executablePath: <span class="hljs-type">String</span>, arguments: [<span class="hljs-type">String</span>] = [], environment: [<span class="hljs-type">String</span>: <span class="hljs-type">String</span>] = [:]) {
        <span class="hljs-keyword">self</span>.name = name
        <span class="hljs-keyword">self</span>.executablePath = executablePath
        <span class="hljs-keyword">self</span>.arguments = arguments
        <span class="hljs-keyword">self</span>.environment = environment
    }
}
</code></pre>
<p>This structure defines the configuration needed to start and connect to a local MCP server process.</p>
<h2 id="heading-step-3-implement-the-localmcprocess-class">Step 3: Implement the LocalMCProcess Class</h2>
<p>Next, we'll implement a class that manages the lifecycle of a local MCP server process. Add the following to your <code>LocalMCProcess.swift</code> file:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LocalMCProcess</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> config: <span class="hljs-type">LocalMCPServerConfig</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> process: <span class="hljs-type">Process?</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> stdinPipe: <span class="hljs-type">Pipe?</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> stdoutPipe: <span class="hljs-type">Pipe?</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> stderrPipe: <span class="hljs-type">Pipe?</span>

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">init</span>(config: <span class="hljs-type">LocalMCPServerConfig</span>) {
        <span class="hljs-keyword">self</span>.config = config
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">start</span><span class="hljs-params">()</span></span> async <span class="hljs-keyword">throws</span> -&gt; <span class="hljs-type">Client</span> {
        <span class="hljs-comment">// Create pipes for stdin, stdout, and stderr</span>
        <span class="hljs-keyword">let</span> stdinPipe = <span class="hljs-type">Pipe</span>()
        <span class="hljs-keyword">let</span> stdoutPipe = <span class="hljs-type">Pipe</span>()
        <span class="hljs-keyword">let</span> stderrPipe = <span class="hljs-type">Pipe</span>()

        <span class="hljs-comment">// Create and configure the process</span>
        <span class="hljs-keyword">let</span> process = <span class="hljs-type">Process</span>()
        process.executableURL = <span class="hljs-type">URL</span>(fileURLWithPath: config.executablePath)
        process.arguments = config.arguments

        <span class="hljs-comment">// Set environment variables if provided</span>
        <span class="hljs-keyword">if</span> !config.environment.isEmpty {
            <span class="hljs-keyword">var</span> environment = <span class="hljs-type">ProcessInfo</span>.processInfo.environment
            <span class="hljs-keyword">for</span> (key, value) <span class="hljs-keyword">in</span> config.environment {
                environment[key] = value
            }
            process.environment = environment
        }

        <span class="hljs-comment">// Connect pipes to the process</span>
        process.standardInput = stdinPipe
        process.standardOutput = stdoutPipe
        process.standardError = stderrPipe

        <span class="hljs-comment">// Store references to prevent deallocation</span>
        <span class="hljs-keyword">self</span>.process = process
        <span class="hljs-keyword">self</span>.stdinPipe = stdinPipe
        <span class="hljs-keyword">self</span>.stdoutPipe = stdoutPipe
        <span class="hljs-keyword">self</span>.stderrPipe = stderrPipe

        <span class="hljs-comment">// Launch the process</span>
        <span class="hljs-keyword">try</span> process.run()

        <span class="hljs-comment">// Convert FileHandles to FileDescriptors for StdioTransport</span>
        <span class="hljs-keyword">let</span> inputFD = <span class="hljs-type">FileDescriptor</span>(rawValue: stdoutPipe.fileHandleForReading.fileDescriptor)
        <span class="hljs-keyword">let</span> outputFD = <span class="hljs-type">FileDescriptor</span>(rawValue: stdinPipe.fileHandleForWriting.fileDescriptor)

        <span class="hljs-comment">// Create StdioTransport</span>
        <span class="hljs-keyword">let</span> logger = <span class="hljs-type">Logger</span>(label: <span class="hljs-string">"mcp.transport.process.\(config.name)"</span>)
        <span class="hljs-keyword">let</span> transport = <span class="hljs-type">StdioTransport</span>(
            input: inputFD,
            output: outputFD,
            logger: logger
        )

        <span class="hljs-comment">// Connect the client</span>
        <span class="hljs-keyword">let</span> client = <span class="hljs-type">Client</span>(name: <span class="hljs-keyword">self</span>.config.name, version: <span class="hljs-string">"1.0.0"</span>, configuration: .<span class="hljs-keyword">default</span>)
        <span class="hljs-number">_</span> = <span class="hljs-keyword">try</span> await client.connect(transport: transport)

        <span class="hljs-keyword">return</span> client
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">stop</span><span class="hljs-params">()</span></span> {
        <span class="hljs-comment">// Terminate the process</span>
        process?.terminate()

        <span class="hljs-comment">// Close all file handles</span>
        <span class="hljs-keyword">try</span>? stdinPipe?.fileHandleForWriting.close()
        <span class="hljs-keyword">try</span>? stdoutPipe?.fileHandleForReading.close()
        <span class="hljs-keyword">try</span>? stderrPipe?.fileHandleForReading.close()

        <span class="hljs-comment">// Clear references</span>
        process = <span class="hljs-literal">nil</span>
        stdinPipe = <span class="hljs-literal">nil</span>
        stdoutPipe = <span class="hljs-literal">nil</span>
        stderrPipe = <span class="hljs-literal">nil</span>
    }
}
</code></pre>
<p>This class handles:</p>
<ol>
<li>Starting the MCP server process</li>
<li>Setting up pipes for communication</li>
<li>Creating a transport layer for the MCP client</li>
<li>Connecting the client to the server</li>
<li>Cleaning up resources when done</li>
</ol>
<h2 id="heading-step-4-create-the-main-application">Step 4: Create the Main Application</h2>
<p>Now, let's create our main application that uses the <code>LocalMCProcess</code> class to connect to an MCP server and call its tools. Update your <code>main.swift</code> file:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> Foundation
<span class="hljs-keyword">import</span> MCP
<span class="hljs-keyword">import</span> MCPHelpers
<span class="hljs-keyword">import</span> SwiftyJsonSchema

<span class="hljs-comment">// MARK: - Helpers</span>
<span class="hljs-keyword">let</span> jsonEncoder = <span class="hljs-type">JSONEncoder</span>()
<span class="hljs-keyword">let</span> jsonDecoder = <span class="hljs-type">JSONDecoder</span>()

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">printToolInfo</span><span class="hljs-params">(<span class="hljs-number">_</span> tool: Tool)</span></span> <span class="hljs-keyword">throws</span> {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"--- TOOL INFO ---"</span>)
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Name: \(tool.name)"</span>)
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Annotations: \(tool.annotations)"</span>)

    <span class="hljs-keyword">do</span> {
        <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> rawInputSchema = tool.inputSchema <span class="hljs-keyword">else</span> {
            <span class="hljs-built_in">print</span>(<span class="hljs-string">"ERROR: No input schema in Tool info"</span>)
            <span class="hljs-keyword">return</span>
        }
        <span class="hljs-keyword">let</span> jsonSchema = <span class="hljs-keyword">try</span> <span class="hljs-type">JSONSchema</span>(fromValue: rawInputSchema)
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Schema: \(jsonSchema)"</span>)
    }
    <span class="hljs-keyword">catch</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"!!! Problem reading schema !!!: \(error)"</span>)
    }

    <span class="hljs-built_in">print</span>(<span class="hljs-string">"------------"</span>)
}


<span class="hljs-comment">// MARK: - Test MCP</span>

<span class="hljs-comment">// Get the path to the MCP server executable</span>
<span class="hljs-comment">// You can pass this as a command-line argument or hardcode it</span>
<span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> pathToMCPExampleServerBuildDirectory = <span class="hljs-type">UserDefaults</span>.standard.string(forKey: <span class="hljs-string">"MCP_SERVER_BUILD_PATH"</span>) <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">fatalError</span>(<span class="hljs-string">"!!! YOU NEED TO DEFINE THE PATH TO YOUR MCP SERVER !!!"</span>)
}

<span class="hljs-comment">// Create a configuration for the MCP server</span>
<span class="hljs-keyword">let</span> jsonConfig = <span class="hljs-string">"""
    {
      "name": "swift-mcp-server-example",
        "executablePath": "\(pathToMCPExampleServerBuildDirectory)/Products/Debug/SwiftMCPServerExample",
        "arguments": [],
        "environment": {}
    }
    """</span>

<span class="hljs-comment">// Load the configuration</span>
<span class="hljs-keyword">let</span> config = <span class="hljs-keyword">try</span> jsonDecoder.decode(<span class="hljs-type">LocalMCPServerConfig</span>.<span class="hljs-keyword">self</span>, from: jsonConfig.data(using: .ascii)!)

<span class="hljs-comment">// Start the server process and connect a client</span>
<span class="hljs-keyword">let</span> mcp = <span class="hljs-type">LocalMCProcess</span>(config: config)
<span class="hljs-keyword">let</span> client = <span class="hljs-keyword">try</span> await mcp.start()

<span class="hljs-comment">// List available tools</span>
<span class="hljs-keyword">let</span> toolListResponse = <span class="hljs-keyword">try</span> await client.listTools()
<span class="hljs-keyword">for</span> tool <span class="hljs-keyword">in</span> toolListResponse.tools {
    <span class="hljs-keyword">try</span> printToolInfo(tool)
}

<span class="hljs-comment">// Define a structure for the tool input</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">EchoToolInput</span>: <span class="hljs-title">Codable</span> </span>{
    <span class="hljs-keyword">var</span> echoText: <span class="hljs-type">String</span> = <span class="hljs-string">""</span>
}

<span class="hljs-comment">// Call the "echo" tool</span>
<span class="hljs-keyword">let</span> toolRequest = <span class="hljs-type">EchoToolInput</span>(echoText: <span class="hljs-string">"Hello World from MCP Client"</span>)
<span class="hljs-keyword">let</span> toolArgs = <span class="hljs-keyword">try</span> <span class="hljs-type">Value</span>(toolRequest).objectValue

<span class="hljs-keyword">let</span> result = <span class="hljs-keyword">try</span> await client.callTool(name: <span class="hljs-string">"echo"</span>, arguments: toolArgs)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"RESULT FROM TOOL CALL: \(result.content.first!)"</span>)

<span class="hljs-comment">// Clean up</span>
mcp.stop()
</code></pre>
<h2 id="heading-step-5-running-the-client">Step 5: Running the Client</h2>
<p>To run the client, you need to:</p>
<ol>
<li>Build the MCP server from our previous tutorial (or any compatible MCP server)</li>
<li>Note the path to the built executable</li>
<li>Run the client with the server path as an argument</li>
</ol>
<h3 id="heading-running-from-command-line">Running from Command Line</h3>
<pre><code class="lang-bash">swift run SwiftMCPClientExample -MCP_SERVER_BUILD_PATH=/path/to/server/build/directory
</code></pre>
<p>Replace <code>/path/to/server/build/directory</code> with the actual path to your MCP server's build directory.</p>
<h3 id="heading-running-from-xcode">Running from Xcode</h3>
<p>To run the project in Xcode:</p>
<ol>
<li>Open the project in Xcode by opening the Package.swift file</li>
<li>Select the SwiftMCPClientExample scheme</li>
<li>Go to Product &gt; Scheme &gt; Edit Scheme (or press ⌘&lt;)</li>
<li>Select the 'Run' action on the left panel</li>
<li>Go to the 'Arguments' tab</li>
<li>Under 'Arguments Passed On Launch', click the '+' button</li>
<li>Add <code>-MCP_SERVER_BUILD_PATH=/path/to/server/build/directory</code> (replace with the actual path to your MCP server's build directory)</li>
<li>Click 'Close' and run the project (⌘R)</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750616914177/c1058b79-8598-44a9-8507-500973c38b4c.png" alt="Xcode Scheme Arguments" /></p>
<blockquote>
<p><strong>Tip</strong>: You can find the build directory path in Xcode by building the MCP server project, then selecting Product &gt; Show Build Folder in Finder.</p>
</blockquote>
<h3 id="heading-expected-output">Expected Output</h3>
<p>When you run the client successfully, you should see output similar to the following:</p>
<pre><code class="lang-bash">--- TOOL INFO ---
Name: <span class="hljs-built_in">echo</span>
Annotations: Annotations(title: nil, destructiveHint: nil, idempotentHint: nil, openWorldHint: nil, readOnlyHint: nil)
Schema: {
  <span class="hljs-string">"additionalProperties"</span> : <span class="hljs-literal">false</span>,
  <span class="hljs-string">"properties"</span> : {
    <span class="hljs-string">"echoText"</span> : {
      <span class="hljs-string">"items"</span> : {

      },
      <span class="hljs-string">"description"</span> : <span class="hljs-string">"Text to send to the service that will be echoed back"</span>,
      <span class="hljs-string">"type"</span> : <span class="hljs-string">"string"</span>,
      <span class="hljs-string">"additionalProperties"</span> : <span class="hljs-literal">false</span>
    }
  },
  <span class="hljs-string">"items"</span> : {

  },
  <span class="hljs-string">"type"</span> : <span class="hljs-string">"object"</span>,
  <span class="hljs-string">"required"</span> : [
    <span class="hljs-string">"echoText"</span>
  ],
  <span class="hljs-string">"<span class="hljs-variable">$schema</span>"</span> : <span class="hljs-string">"http:\/\/json-schema.org\/draft-07\/schema#"</span>
}
------------
--- TOOL INFO ---
Name: selectRandom
Annotations: Annotations(title: nil, destructiveHint: nil, idempotentHint: nil, openWorldHint: nil, readOnlyHint: nil)
Schema: {
  <span class="hljs-string">"<span class="hljs-variable">$schema</span>"</span> : <span class="hljs-string">"http:\/\/json-schema.org\/draft-07\/schema#"</span>,
  <span class="hljs-string">"properties"</span> : {
    <span class="hljs-string">"ints"</span> : {
      <span class="hljs-string">"items"</span> : {
        <span class="hljs-string">"items"</span> : {
          <span class="hljs-string">"properties"</span> : {

          },
          <span class="hljs-string">"items"</span> : {

          },
          <span class="hljs-string">"type"</span> : <span class="hljs-string">"object"</span>,
          <span class="hljs-string">"required"</span> : [

          ],
          <span class="hljs-string">"additionalProperties"</span> : <span class="hljs-literal">false</span>
        }
      },
      <span class="hljs-string">"description"</span> : <span class="hljs-string">"An array of integers to select one at random from"</span>,
      <span class="hljs-string">"type"</span> : <span class="hljs-string">"array"</span>,
      <span class="hljs-string">"additionalProperties"</span> : <span class="hljs-literal">false</span>
    }
  },
  <span class="hljs-string">"items"</span> : {

  },
  <span class="hljs-string">"type"</span> : <span class="hljs-string">"object"</span>,
  <span class="hljs-string">"required"</span> : [
    <span class="hljs-string">"ints"</span>
  ],
  <span class="hljs-string">"additionalProperties"</span> : <span class="hljs-literal">false</span>
}
------------
RESULT FROM TOOL CALL: text(<span class="hljs-string">"You sent: Hello World from MCP Client"</span>)
</code></pre>
<p>This output shows:</p>
<ol>
<li>The tool information for the two available tools on the server ("echo" and "selectRandom")</li>
<li>The schema information for each tool</li>
<li>The result of calling the "echo" tool, which returns the text we sent</li>
</ol>
<h2 id="heading-understanding-the-code">Understanding the Code</h2>
<p>Let's break down what's happening in our client application:</p>
<h3 id="heading-1-server-configuration">1. Server Configuration</h3>
<p>We define a <code>LocalMCPServerConfig</code> structure that contains all the information needed to start and connect to a local MCP server:</p>
<ul>
<li><code>name</code>: A unique identifier for the server</li>
<li><code>executablePath</code>: The path to the server executable</li>
<li><code>arguments</code>: Command-line arguments to pass to the server</li>
<li><code>environment</code>: Environment variables to set for the server process</li>
</ul>
<h3 id="heading-2-process-management">2. Process Management</h3>
<p>The <code>LocalMCProcess</code> class handles the lifecycle of the MCP server process:</p>
<ul>
<li>It creates pipes for stdin, stdout, and stderr communication</li>
<li>It launches the server process with the specified configuration</li>
<li>It sets up a transport layer for the MCP client to communicate with the server</li>
<li>It provides a method to stop the server and clean up resources when done</li>
</ul>
<h3 id="heading-3-client-connection">3. Client Connection</h3>
<p>The client connects to the server using the MCP SDK's <code>Client</code> class:</p>
<ul>
<li>It creates a client instance with a name and version</li>
<li>It connects to the server using the transport layer</li>
<li>Once connected, it can list available tools and call them with parameters</li>
</ul>
<h3 id="heading-4-tool-discovery-and-invocation">4. Tool Discovery and Invocation</h3>
<p>The client can discover the tools available on the server:</p>
<ul>
<li>It calls <code>client.listTools()</code> to get a list of available tools</li>
<li>It can inspect each tool's name, annotations, and input schema</li>
<li>It can call a tool by name with the appropriate arguments</li>
</ul>
<h3 id="heading-5-tool-calling">5. Tool Calling</h3>
<p>To call a tool, the client:</p>
<ul>
<li>Creates a structure representing the tool's input parameters</li>
<li>Converts the structure to a dictionary of arguments</li>
<li>Calls the tool with <code>client.callTool(name:, arguments:)</code></li>
<li>Processes the result returned by the tool</li>
</ul>
<h2 id="heading-advanced-usage">Advanced Usage</h2>
<h3 id="heading-handling-different-tool-types">Handling Different Tool Types</h3>
<p>The example above demonstrates calling a simple "echo" tool, but you can call any tool provided by the MCP server. For each tool, you'll need to:</p>
<ol>
<li>Define a structure that matches the tool's input schema</li>
<li>Create an instance of that structure with the appropriate values</li>
<li>Convert the structure to a dictionary of arguments</li>
<li>Call the tool and process the result</li>
</ol>
<h3 id="heading-error-handling">Error Handling</h3>
<p>In a production application, you should add proper error handling:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">do</span> {
    <span class="hljs-keyword">let</span> result = <span class="hljs-keyword">try</span> await client.callTool(name: <span class="hljs-string">"echo"</span>, arguments: toolArgs)
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Success: \(result.content.first!)"</span>)
} <span class="hljs-keyword">catch</span> <span class="hljs-keyword">let</span> error <span class="hljs-keyword">as</span> <span class="hljs-type">MCPError</span> {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"MCP Error: \(error)"</span>)
} <span class="hljs-keyword">catch</span> {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Unexpected error: \(error)"</span>)
}
</code></pre>
<h3 id="heading-asynchronous-tool-calls">Asynchronous Tool Calls</h3>
<p>The MCP SDK uses Swift's async/await pattern for asynchronous operations. Make sure your application is set up to handle asynchronous code:</p>
<pre><code class="lang-swift"><span class="hljs-type">Task</span> {
    <span class="hljs-keyword">do</span> {
        <span class="hljs-keyword">let</span> client = <span class="hljs-keyword">try</span> await mcp.start()
        <span class="hljs-comment">// Use the client...</span>
    } <span class="hljs-keyword">catch</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Error: \(error)"</span>)
    }
}
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, we've built a Swift client that can connect to an MCP server and call its tools. This client can work with any MCP-compatible server, including the one we built in our previous tutorial.</p>
<p>The Model Context Protocol provides a standardized way for AI systems to extend their capabilities through external tools. By implementing both the server and client sides of the protocol, you can create powerful, modular AI applications that can be easily extended with new capabilities.</p>
<h2 id="heading-next-steps">Next Steps</h2>
<p>Now that you understand how to build both MCP servers and clients in Swift, you can:</p>
<ol>
<li>Create your own custom tools to extend AI capabilities</li>
<li>Integrate MCP clients into your Swift applications</li>
<li>Connect your applications to AI systems that support the MCP protocol</li>
</ol>
<h2 id="heading-finding-the-code-on-github">Finding the Code on GitHub</h2>
<p>The complete code for this tutorial is available on GitHub at <a target="_blank" href="https://github.com/glaciotech/SwiftMCPClientExample">github.com/glaciotech/SwiftMCPClientExample</a>. Feel free to clone it, modify it, and use it as a starting point for your own MCP client projects.</p>
<h2 id="heading-resources">Resources</h2>
<ul>
<li><a target="_blank" href="https://modelcontextprotocol.ai/">Model Context Protocol</a></li>
<li><a target="_blank" href="https://github.com/modelcontextprotocol/swift-sdk">Swift MCP SDK</a></li>
<li><a target="_blank" href="https://github.com/glaciotech/SwiftMCPClientExample">Swift MCP Client Example</a></li>
<li><a target="_blank" href="https://github.com/glaciotech/SwiftMCPServerExample">Swift MCP Server Example</a></li>
<li><a target="_blank" href="https://blog.glacio.tech/building-a-mcp-server-in-swift-a-step-by-step-guide">Building a MCP Server in Swift: A Step-by-Step Guide</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Building a MCP Server in Swift: A Step-by-Step Guide]]></title><description><![CDATA[The Model Context Protocol (MCP) is a powerful standard that enables AI systems to connect with external tools and data sources. In this tutorial, we'll walk through creating a simple MCP server in Swift that provides two basic tools: an echo tool an...]]></description><link>https://blog.glacio.tech/building-a-mcp-server-in-swift-a-step-by-step-guide</link><guid isPermaLink="true">https://blog.glacio.tech/building-a-mcp-server-in-swift-a-step-by-step-guide</guid><category><![CDATA[swift mcp]]></category><category><![CDATA[Swift]]></category><category><![CDATA[mcp server]]></category><category><![CDATA[#ai-tools]]></category><category><![CDATA[AI]]></category><category><![CDATA[MCP servers]]></category><category><![CDATA[mcp-swift]]></category><dc:creator><![CDATA[Peter Liddle]]></dc:creator><pubDate>Fri, 13 Jun 2025 19:22:12 GMT</pubDate><content:encoded><![CDATA[<p>The Model Context Protocol (MCP) is a powerful standard that enables AI systems to connect with external tools and data sources. In this tutorial, we'll walk through creating a simple MCP server in Swift that provides two basic tools: an echo tool and a random number selector tool.</p>
<h2 id="heading-what-is-mcp">What is MCP?</h2>
<p>MCP (Model Context Protocol) is a standard that allows AI systems like large language models (LLMs) to interact with external tools and services. By implementing an MCP server, you can extend AI capabilities with custom functionality that can be accessed through a standardized interface.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before we begin, make sure you have:</p>
<ul>
<li>Xcode 15 or later</li>
<li>Swift 6.0 or later</li>
<li>Basic knowledge of Swift and SPM (Swift Package Manager)</li>
</ul>
<h2 id="heading-creating-the-project">Creating the Project</h2>
<p>You can create a new Swift package either using Xcode or the command line. Choose the method you prefer:</p>
<h3 id="heading-option-1-creating-the-package-in-xcode">Option 1: Creating the package in Xcode</h3>
<ol>
<li>Open Xcode and select 'File &gt; New &gt; Package...'</li>
<li>Name your package 'SwiftMCPServerExample'</li>
<li>Select 'Executable' as the package type</li>
<li>Choose a location to save your project and click 'Create'</li>
</ol>
<h3 id="heading-option-2-creating-the-package-via-command-line">Option 2: Creating the package via command line</h3>
<pre><code class="lang-bash">mkdir SwiftMCPServerExample
<span class="hljs-built_in">cd</span> SwiftMCPServerExample
swift package init --<span class="hljs-built_in">type</span> executable
</code></pre>
<p>After creating the package via command line, you can open it in Xcode by double-clicking the <code>Package.swift</code> file or using <code>xed .</code> in the terminal.</p>
<p>Regardless of which method you choose, we'll be using Swift Package Manager (SPM) for dependency management and building/running the project directly in Xcode for the remainder of this tutorial.</p>
<h2 id="heading-setting-up-dependencies">Setting Up Dependencies</h2>
<p>Next, we need to modify the <code>Package.swift</code> file to include the necessary dependencies:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// swift-tools-version: 6.0</span>
<span class="hljs-comment">// The swift-tools-version declares the minimum version of Swift required to build this package.</span>

<span class="hljs-keyword">import</span> PackageDescription

<span class="hljs-keyword">let</span> package = <span class="hljs-type">Package</span>(
    name: <span class="hljs-string">"SwiftMCPServerExample"</span>,
    platforms: [
        .macOS(.v13) <span class="hljs-comment">// This is optional, specify if you have a minimum macOS version requirement</span>
    ],
    dependencies: [
        <span class="hljs-comment">// MCP Swift SDK - the core library for implementing the Model Context Protocol, imported through `mcp-swift-sdk-helpers`</span>
        <span class="hljs-comment">// Uncomment this line if you want to use the core SDK directly without the helper methods</span>
        <span class="hljs-comment">// .package(url: "https://github.com/modelcontextprotocol/swift-sdk.git", from: "0.9.0"),</span>

        <span class="hljs-comment">// Service Lifecycle for managing the server lifecycle</span>
        .package(url: <span class="hljs-string">"https://github.com/swift-server/swift-service-lifecycle.git"</span>, from: <span class="hljs-string">"2.8.0"</span>),

        <span class="hljs-comment">// MCP Helper package - provides utilities for encoding, decoding and producing JSON schemas</span>
        <span class="hljs-comment">// Comment this line if you want to use the core SDK directly without the helper methods</span>
        .package(url: <span class="hljs-string">"https://github.com/glaciotech/mcp-swift-sdk-helpers.git"</span>, from: <span class="hljs-string">"0.1.0"</span>),
    ],
    targets: [
        .executableTarget(
            name: <span class="hljs-string">"SwiftMCPServerExample"</span>,
            dependencies: [
                .product(name: <span class="hljs-string">"ServiceLifecycle"</span>, package: <span class="hljs-string">"swift-service-lifecycle"</span>),
                <span class="hljs-comment">// Comment this line when using the helper package</span>
                <span class="hljs-comment">// .product(name: "MCP", package: "swift-sdk"),</span>

                <span class="hljs-comment">// Use the MCPHelpers product from our helper package</span>
                .product(name: <span class="hljs-string">"MCPHelpers"</span>, package: <span class="hljs-string">"mcp-swift-sdk-helpers"</span>)
            ]
        ),
        .testTarget(
            name: <span class="hljs-string">"SwiftMCPServerExampleTests"</span>,
            dependencies: [
                <span class="hljs-string">"SwiftMCPServerExample"</span>,
                .product(name: <span class="hljs-string">"ServiceLifecycle"</span>, package: <span class="hljs-string">"swift-service-lifecycle"</span>),
                <span class="hljs-comment">// Comment this line when using the helper package</span>
                <span class="hljs-comment">// .product(name: "MCP", package: "swift-sdk"),</span>

                <span class="hljs-comment">// Use the MCPHelpers product from our helper package</span>
                .product(name: <span class="hljs-string">"MCPHelpers"</span>, package: <span class="hljs-string">"mcp-swift-sdk-helpers"</span>)
            ],
            path: <span class="hljs-string">"Tests"</span>
        )
    ]
)
</code></pre>
<p>After updating the Package.swift file, Xcode should automatically resolve the dependencies. If not, you can reset the package cache by going to File &gt; Packages &gt; Reset Package Caches, or run this command in the terminal:</p>
<pre><code class="lang-bash">swift package resolve
</code></pre>
<p>This will download and resolve all the dependencies.</p>
<h3 id="heading-about-the-mcp-helper-package">About the MCP Helper Package</h3>
<p>In this tutorial, we're using an MCP Helper package that wraps the core <code>modelcontextprotocol/swift-sdk</code> and adds some helpful utilities:</p>
<ul>
<li><strong>JSON Schema Generation</strong>: Makes it easier to define the input/output schemas for your tools</li>
<li><strong>Parameter Initialization</strong>: Simplifies parsing incoming parameters from the LLM</li>
<li><strong>Type-Safe Models</strong>: Provides strongly-typed Swift models for your tool inputs and outputs</li>
</ul>
<p>This helper package makes it much easier to create MCP servers by reducing boilerplate code and providing a more Swift-idiomatic API. </p>
<h2 id="heading-creating-the-project-structure">Creating the Project Structure</h2>
<p>Now, let's create the files we need for our MCP server. In Xcode, right-click on the 'Sources' folder and select 'New File...' to create each of the following files:</p>
<ol>
<li><code>MCPServerFactory.swift</code> - Factory for creating and configuring the MCP server</li>
<li><code>MCPService.swift</code> - Service for managing the MCP server lifecycle</li>
<li>We'll also modify the existing <code>main.swift</code> file</li>
</ol>
<h2 id="heading-defining-tool-input-schemas-and-enums">Defining Tool Input Schemas and Enums</h2>
<p>First, let's define the input schemas for our tools and an enum to keep track of our registered tools in <code>MCPServerFactory.swift</code>:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> Foundation
<span class="hljs-keyword">import</span> SwiftyJsonSchema
<span class="hljs-keyword">import</span> ServiceLifecycle
<span class="hljs-keyword">import</span> MCP
<span class="hljs-keyword">import</span> Logging
<span class="hljs-keyword">import</span> MCPHelpers

<span class="hljs-comment">// We make use of SwiftyJsonSchema to generate the JSON schema that informs the LLM of how to structure the tool call</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">EchoToolInput</span>: <span class="hljs-title">ProducesJSONSchema</span>, <span class="hljs-title">ParamInitializable</span> </span>{
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> exampleValue = <span class="hljs-type">EchoToolInput</span>(echoText: <span class="hljs-string">"Echo..."</span>)

    @<span class="hljs-type">SchemaInfo</span>(description: <span class="hljs-string">"Text to send to the service that will be echoed back"</span>)
    <span class="hljs-keyword">var</span> echoText: <span class="hljs-type">String</span> = <span class="hljs-string">""</span>
}

<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">PickRandomToolInput</span>: <span class="hljs-title">ProducesJSONSchema</span>, <span class="hljs-title">ParamInitializable</span> </span>{
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> exampleValue = <span class="hljs-type">PickRandomToolInput</span>(ints: [<span class="hljs-number">5</span>, <span class="hljs-number">4</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>, <span class="hljs-number">123</span>, <span class="hljs-number">8411</span>])

    @<span class="hljs-type">SchemaInfo</span>(description: <span class="hljs-string">"An array of integers to select one at random from"</span>)
    <span class="hljs-keyword">var</span> ints: [<span class="hljs-type">Int</span>]? = <span class="hljs-literal">nil</span>
}

<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">RegisteredTools</span>: <span class="hljs-title">String</span> </span>{
    <span class="hljs-keyword">case</span> echo
    <span class="hljs-keyword">case</span> selectRandom
}
</code></pre>
<h2 id="heading-creating-the-mcp-server-factory">Creating the MCP Server Factory</h2>
<p>Next, let's implement the <code>MCPServerFactory</code> class that will create and configure our MCP server:</p>
<pre><code class="lang-swift"><span class="hljs-comment">/// MCPSeverFactory creates and configures our server with the example Tools and Prompts</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MCPServerFactory</span> </span>{

    <span class="hljs-comment">/// This creates a new instance of a configured MCP server with our Tools ready to go</span>
    <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">makeServer</span><span class="hljs-params">(with logger: Logger)</span></span> async -&gt; <span class="hljs-type">Server</span> {
        <span class="hljs-comment">// Create our server</span>
        <span class="hljs-keyword">let</span> server = <span class="hljs-type">Server</span>(name: <span class="hljs-string">"ExampleSwiftMCPServer"</span>,
                            version: <span class="hljs-string">"1.0"</span>,
                            capabilities: .<span class="hljs-keyword">init</span>(logging: <span class="hljs-type">Server</span>.<span class="hljs-type">Capabilities</span>.<span class="hljs-type">Logging</span>(),
                                                resources: .<span class="hljs-keyword">init</span>(subscribe: <span class="hljs-literal">true</span>, listChanged: <span class="hljs-literal">true</span>),
                                                tools: .<span class="hljs-keyword">init</span>(listChanged: <span class="hljs-literal">true</span>)
        ))

        <span class="hljs-comment">// Register the tools</span>
        await registerTools(on: server)

        <span class="hljs-comment">// Setup the code to handle the tool logic when we receive a request for that tool</span>
        await server.withMethodHandler(<span class="hljs-type">CallTool</span>.<span class="hljs-keyword">self</span>, handler: toolsHandler)

        <span class="hljs-keyword">return</span> server
    }
}
</code></pre>
<h2 id="heading-creating-and-implementing-the-echo-tool">Creating and Implementing the Echo Tool</h2>
<p>Now, let's implement the Echo tool. This tool simply returns whatever text is sent to it. Add the following to the <code>MCPServerFactory</code> class:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// Echo tool handler implementation</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">echoHandler</span><span class="hljs-params">(<span class="hljs-number">_</span> text: String)</span></span> -&gt; <span class="hljs-type">String</span> {
    <span class="hljs-keyword">return</span> text
}
</code></pre>
<h2 id="heading-creating-and-implementing-the-random-selector-tool">Creating and Implementing the Random Selector Tool</h2>
<p>Next, let's implement the Random Selector tool. This tool takes an array of integers and returns a random element from the array:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// Random selector tool handler implementation</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">pickRandomNumberHandler</span><span class="hljs-params">(<span class="hljs-number">_</span> numbers: [Int]?)</span></span> -&gt; <span class="hljs-type">Int</span> {
    <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> numbers = numbers <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> .zero
    }
    <span class="hljs-keyword">return</span> numbers.randomElement() ?? .zero
}
</code></pre>
<h2 id="heading-registering-the-tools-with-the-mcp-server">Registering the Tools with the MCP Server</h2>
<p>Now, let's register our tools with the MCP server. Add this method to the <code>MCPServerFactory</code> class:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">registerTools</span><span class="hljs-params">(on server: Server)</span></span> async {
    <span class="hljs-comment">/// Register a tool list handler</span>
    await server.withMethodHandler(<span class="hljs-type">ListTools</span>.<span class="hljs-keyword">self</span>) { <span class="hljs-number">_</span> <span class="hljs-keyword">in</span>

        <span class="hljs-comment">// Define 2 simple tools: an echo tool that just returns whatever the LLM sends</span>
        <span class="hljs-comment">// and a random selector tool that picks a random number from an array</span>
        <span class="hljs-keyword">let</span> tools = [
            <span class="hljs-type">Tool</span>(
                name: <span class="hljs-type">RegisteredTools</span>.echo.rawValue,
                description: <span class="hljs-string">"Echo back any text that was sent"</span>,
                inputSchema: <span class="hljs-keyword">try</span> .produced(from: <span class="hljs-type">EchoToolInput</span>.<span class="hljs-keyword">self</span>)
            ),
            <span class="hljs-type">Tool</span>(
                name: <span class="hljs-type">RegisteredTools</span>.selectRandom.rawValue,
                description: <span class="hljs-string">"Takes in a collection of numbers or strings and picks one at random"</span>,
                inputSchema: <span class="hljs-keyword">try</span> .produced(from: <span class="hljs-type">PickRandomToolInput</span>.<span class="hljs-keyword">self</span>)
            )
        ]
        <span class="hljs-keyword">return</span> .<span class="hljs-keyword">init</span>(tools: tools)
    }
}
</code></pre>
<h2 id="heading-implementing-the-tool-handler">Implementing the Tool Handler</h2>
<p>Finally, let's implement the tool handler that processes incoming tool requests and routes them to the appropriate handler:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">toolsHandler</span><span class="hljs-params">(params: CallTool.Parameters)</span></span> async <span class="hljs-keyword">throws</span> -&gt; <span class="hljs-type">CallTool</span>.<span class="hljs-type">Result</span> {
    <span class="hljs-keyword">let</span> unknownToolError = <span class="hljs-type">CallTool</span>.<span class="hljs-type">Result</span>(content: [.text(<span class="hljs-string">"Unknown tool"</span>)], isError: <span class="hljs-literal">true</span>)

    <span class="hljs-comment">// Convert tool name to our enum</span>
    <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> tool = <span class="hljs-type">RegisteredTools</span>(rawValue: params.name) <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> unknownToolError
    }

    <span class="hljs-keyword">switch</span> tool {
    <span class="hljs-keyword">case</span> <span class="hljs-type">RegisteredTools</span>.echo:
        <span class="hljs-keyword">let</span> input = <span class="hljs-keyword">try</span> <span class="hljs-type">EchoToolInput</span>(with: params)
        <span class="hljs-keyword">let</span> result = echoHandler(input.echoText)
        <span class="hljs-keyword">return</span> .<span class="hljs-keyword">init</span>(
            content: [.text(<span class="hljs-string">"You sent: \(result)"</span>)],
            isError: <span class="hljs-literal">false</span>
        )

    <span class="hljs-keyword">case</span> <span class="hljs-type">RegisteredTools</span>.selectRandom:
        <span class="hljs-keyword">let</span> input = <span class="hljs-keyword">try</span> <span class="hljs-type">PickRandomToolInput</span>(with: params)
        <span class="hljs-keyword">let</span> result = <span class="hljs-keyword">self</span>.pickRandomNumberHandler(input.ints)
        <span class="hljs-keyword">return</span> .<span class="hljs-keyword">init</span>(
            content: [.text(<span class="hljs-string">"I picked: \(result)"</span>)],
            isError: <span class="hljs-literal">false</span>
        )
    }
}
</code></pre>
<h2 id="heading-creating-the-mcp-service">Creating the MCP Service</h2>
<p>Now, let's create the <code>MCPService.swift</code> file that will handle the lifecycle of our MCP server:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> MCP
<span class="hljs-keyword">import</span> ServiceLifecycle
<span class="hljs-keyword">import</span> Logging

<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">MCPService</span>: <span class="hljs-title">Service</span> </span>{

    <span class="hljs-keyword">let</span> server: <span class="hljs-type">Server</span>
    <span class="hljs-keyword">let</span> transport: <span class="hljs-type">StdioTransport</span>

    <span class="hljs-keyword">init</span>(server: <span class="hljs-type">Server</span>, transport: <span class="hljs-type">StdioTransport</span>) {
        <span class="hljs-keyword">self</span>.server = server
        <span class="hljs-keyword">self</span>.transport = transport
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">run</span><span class="hljs-params">()</span></span> async <span class="hljs-keyword">throws</span> {
        <span class="hljs-comment">// Start the server</span>
        <span class="hljs-keyword">try</span> await server.start(transport: transport)

        <span class="hljs-comment">// Keep running until external cancellation</span>
        <span class="hljs-keyword">try</span> await <span class="hljs-type">Task</span>.sleep(<span class="hljs-keyword">for</span>: .seconds(<span class="hljs-number">10000</span>)) 
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">shutdown</span><span class="hljs-params">()</span></span> async <span class="hljs-keyword">throws</span> {
        <span class="hljs-comment">// Gracefully shutdown the server</span>
        await server.stop()
    }
}
</code></pre>
<h2 id="heading-setting-up-the-main-application">Setting Up the Main Application</h2>
<p>Finally, let's implement the <code>main.swift</code> file to tie everything together:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> MCP
<span class="hljs-keyword">import</span> ServiceLifecycle
<span class="hljs-keyword">import</span> Foundation
<span class="hljs-keyword">import</span> Logging

<span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">App</span>: <span class="hljs-title">Sendable</span> </span>{

    <span class="hljs-comment">// Create a logging object we'll hand to other parts of the project</span>
    <span class="hljs-keyword">let</span> log = {
        <span class="hljs-keyword">var</span> logger = <span class="hljs-type">Logger</span>(label: <span class="hljs-string">"tech.glacio.mcpserverexample"</span>)
        logger.logLevel = .debug
        <span class="hljs-keyword">return</span> logger
    }()

    <span class="hljs-comment">/// Create and start our MCP service, we make use of a ServiceGroup to handle launching and shutting down the server</span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">start</span><span class="hljs-params">()</span></span> async <span class="hljs-keyword">throws</span> {

        log.info(<span class="hljs-string">"App has started"</span>)

        <span class="hljs-comment">// Create the configured server with registered Tools and the MCP service</span>
        <span class="hljs-keyword">let</span> transport = <span class="hljs-type">StdioTransport</span>(logger: log)
        <span class="hljs-keyword">let</span> server = await <span class="hljs-type">MCPServerFactory</span>.makeServer(with: log)
        <span class="hljs-keyword">let</span> mcpService = <span class="hljs-type">MCPService</span>(server: server, transport: transport)

        <span class="hljs-comment">// Create service group with signal handling</span>
        <span class="hljs-keyword">let</span> serviceGroup = <span class="hljs-type">ServiceGroup</span>(services: [mcpService],
                                        gracefulShutdownSignals: [.sigterm, .sigint],
                                        logger: log)

        <span class="hljs-comment">// Run the service group - this blocks until shutdown</span>
        <span class="hljs-keyword">try</span> await serviceGroup.run()
    }
}

<span class="hljs-comment">// Start the app</span>
<span class="hljs-keyword">try</span>! await <span class="hljs-type">App</span>().start()
</code></pre>
<h2 id="heading-example-of-testing-the-tool-call-logic">Example of testing the tool call logic</h2>
<p>To test our MCP server, let's create a simple test in <code>Tests/MCPTest.swift</code>:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> XCTest
<span class="hljs-keyword">import</span> MCP
<span class="hljs-meta">@testable</span> <span class="hljs-keyword">import</span> SwiftMCPServerExample
<span class="hljs-keyword">import</span> Logging

<span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MCPTest</span>: <span class="hljs-title">XCTestCase</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">testToolHandler</span><span class="hljs-params">()</span></span> async <span class="hljs-keyword">throws</span> {
        <span class="hljs-comment">// Test the echo tool</span>
        <span class="hljs-keyword">let</span> echoToolParams = <span class="hljs-type">CallTool</span>.<span class="hljs-type">Parameters</span>(name: <span class="hljs-type">RegisteredTools</span>.echo.rawValue, 
                                                arguments: [<span class="hljs-string">"echoText"</span>: <span class="hljs-string">"Test Text To Echo"</span>])

        <span class="hljs-keyword">let</span> echoResult = <span class="hljs-keyword">try</span> await <span class="hljs-type">MCPServerFactory</span>.toolsHandler(params: echoToolParams)
        <span class="hljs-type">XCTAssertEqual</span>(echoResult.content.first?.text, <span class="hljs-string">"You sent: Test Text To Echo"</span>)
        <span class="hljs-type">XCTAssertFalse</span>(echoResult.isError)

        <span class="hljs-comment">// Test the random selector tool</span>
        <span class="hljs-keyword">let</span> testNumbers = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>]
        <span class="hljs-keyword">let</span> randomToolParams = <span class="hljs-type">CallTool</span>.<span class="hljs-type">Parameters</span>(name: <span class="hljs-type">RegisteredTools</span>.selectRandom.rawValue,
                                                  arguments: [<span class="hljs-string">"ints"</span>: testNumbers])

        <span class="hljs-keyword">let</span> randomResult = <span class="hljs-keyword">try</span> await <span class="hljs-type">MCPServerFactory</span>.toolsHandler(params: randomToolParams)
        <span class="hljs-type">XCTAssertFalse</span>(randomResult.isError)

        <span class="hljs-comment">// Test with an unknown tool</span>
        <span class="hljs-keyword">let</span> unknownToolParams = <span class="hljs-type">CallTool</span>.<span class="hljs-type">Parameters</span>(name: <span class="hljs-string">"unknownTool"</span>, arguments: [:])
        <span class="hljs-keyword">let</span> unknownResult = <span class="hljs-keyword">try</span> await <span class="hljs-type">MCPServerFactory</span>.toolsHandler(params: unknownToolParams)
        <span class="hljs-type">XCTAssertTrue</span>(unknownResult.isError)
    }
}
</code></pre>
<p>Run the tests with:</p>
<pre><code class="lang-bash">swift <span class="hljs-built_in">test</span>
</code></pre>
<h2 id="heading-building-the-mcp-server">Building the MCP Server</h2>
<p>Before using your MCP server with Windsurf, you need to build it:</p>
<p>In Xcode:</p>
<ol>
<li>Select the 'SwiftMCPServerExample' scheme from the scheme selector</li>
<li>Build the project by pressing Cmd+B</li>
</ol>
<p>Alternatively, you can build from the terminal:</p>
<pre><code class="lang-bash">swift build
</code></pre>
<p>Note that running the MCP server directly doesn't do much on its own - it will just wait for input on stdin. The real power comes when Windsurf launches it automatically as an MCP plugin.</p>
<h2 id="heading-testing-and-using-your-mcp-server-in-windsurf">Testing and Using Your MCP Server in Windsurf</h2>
<p>To use your MCP server with Windsurf (an AI development environment), you need to add it to your MCP Server configuration file. Once properly configured, Windsurf will automatically launch your MCP server when needed - you don't need to run it manually.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"mcpServers"</span>: {
    <span class="hljs-attr">"swift-mcp-server-example"</span>: {
      <span class="hljs-attr">"command"</span>: <span class="hljs-string">"{XCODE_BUILD_FOLDER}/Products/Debug/SwiftMCPServerExample"</span>,
      <span class="hljs-attr">"args"</span>: [],
      <span class="hljs-attr">"env"</span>: {}
    }
  }
}
</code></pre>
<p>Replace <code>{XCODE_BUILD_FOLDER}</code> with your actual build folder path. In Xcode, you can find this by going to Product -&gt; Copy Build Folder Path.</p>
<p>After adding this configuration, click the "Refresh" button in Windsurf to discover your new MCP Server. Windsurf will automatically launch your MCP server when needed. You can see the available MCP servers in Windsurf as shown in the screenshot below:</p>
<p><strong>Screenshot</strong>: Windsurf interface showing available MCP servers including swift-mcp-server-example with 2 tools and mcp-server-firecrawl with 9 tools
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749839738279/780e0c82-6e23-4f72-8217-fdd87c45242d.png" alt="Windsurf MCP Server Configuration" class="image--center mx-auto" /></p>
<p>Once your MCP server is running and connected to Windsurf, you can interact with it through Cascade. Here are examples of how to use the tools:</p>
<h3 id="heading-using-the-random-selector-tool">Using the Random Selector Tool</h3>
<p>You can ask Cascade to generate a list of random numbers and use the selectRandom tool to pick one:</p>
<p><strong>Screenshot:</strong> Using the selectRandom tool to pick a random number from a list of integers 1-10. The tool selected 7.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749839678027/7645a096-c68d-4161-a087-f2f432aab6b2.png" alt="MCP Server in Action - Random Selector Tool" class="image--center mx-auto" /></p>
<h3 id="heading-using-the-echo-tool">Using the Echo Tool</h3>
<p>You can also use the echo tool to have text echoed back to you:</p>
<p><strong>Screenshot:</strong> Using the echo tool to have the text "Hello Windsurf" echoed back.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749839714706/c41376bc-35e0-4c94-a26b-a4ef53e235c2.png" alt="MCP Server in Action - Echo Tool" class="image--center mx-auto" /></p>
<p>As you can see from these examples, once your MCP server is properly configured in Windsurf, Cascade can seamlessly access and utilize your custom tools.</p>
<h2 id="heading-extending-your-mcp-server">Extending Your MCP Server</h2>
<p>Now that you have a basic MCP server working, you can extend it with more sophisticated tools. Here are some ideas:</p>
<ol>
<li>Add a weather data tool that fetches real-time weather information</li>
<li>Create a calculator tool that can evaluate mathematical expressions</li>
<li>Implement a translation tool that uses a third-party API</li>
<li>Build a database query tool that can retrieve and manipulate data</li>
</ol>
<h2 id="heading-troubleshooting">Troubleshooting</h2>
<p>If you encounter issues with your MCP server in Windsurf:</p>
<ol>
<li>Make sure you've built the project first</li>
<li>Try closing Windsurf</li>
<li>Kill any running MCP server processes in Activity Monitor</li>
<li>Relaunch Windsurf</li>
</ol>
<p>You can also try these additional troubleshooting steps:</p>
<ul>
<li><strong>Edit the MCP config file</strong>: You can disable problematic MCP servers by commenting them out in your MCP configuration file or adding a <code>"disabled": true</code> property to the server configuration. The MCP config can be accessed by going to Windsurf Settings → Cascade -&gt; Manage Plugins and clicking on the "View raw config" button.</li>
</ul>
<pre><code class="lang-json">{
  <span class="hljs-attr">"mcpServers"</span>: {
    <span class="hljs-attr">"swift-mcp-server-example"</span>: {
      <span class="hljs-attr">"command"</span>: <span class="hljs-string">"{XCODE_BUILD_FOLDER}/Products/Debug/SwiftMCPServerExample"</span>,
      <span class="hljs-attr">"args"</span>: [],
      <span class="hljs-attr">"env"</span>: {},
      <span class="hljs-attr">"disabled"</span>: <span class="hljs-literal">true</span>  <span class="hljs-comment">// Add this line to disable the server</span>
    }
  }
}
</code></pre>
<ul>
<li><strong>View MCP server errors</strong>: You can check for errors in your MCP server by going to Windsurf Settings → Cascade -&gt; Manage Plugins. This section displays the status of each MCP server and any error messages that might help diagnose issues.</li>
</ul>
<h2 id="heading-finding-the-code-on-github">Finding the Code on GitHub</h2>
<p>The complete code for this tutorial is available on GitHub at <a target="_blank" href="https://github.com/glaciotech/SwiftMCPServerExample.git">github.com/glaciotech/SwiftMCPServerExample</a>. Feel free to clone it, modify it, and use it as a starting point for your own MCP server projects.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, we've built a simple MCP server in Swift that provides two tools: an echo tool and a random number selector. We've learned how to:</p>
<ol>
<li>Set up a Swift package with the necessary dependencies</li>
<li>Define tool input schemas using SwiftyJsonSchema</li>
<li>Create and configure an MCP server</li>
<li>Implement tool handlers</li>
<li>Test the MCP server</li>
<li>Integrate the server with Windsurf</li>
</ol>
<p>MCP servers provide a powerful way to extend AI capabilities with custom tools and services. With the knowledge gained from this tutorial, you can now build more sophisticated MCP servers to meet your specific needs.</p>
<p>Happy coding!</p>
]]></content:encoded></item></channel></rss>