Model Context Protocol (MCP) is an open standard that defines how software applications can deliver contextual information to large language models (LLMs). It's intended for integration across a wide range of tools — such as code editors, IDEs, and other applications. By leveraging natural language, MCP allows seamless interaction with multiple tools and data sources.
In this article, we will create a simple MCP server in Swift that returns the current Swift version on your machine.
Creating a server
We'll start with a package:
mkdir swift-version-mcp
cd swift-version-mcp
swift package init --type executableNext, add the official Swift SDK for Model Context Protocol servers and clients. Update the Package.swift file to include the SDK as a dependency:
// swift-tools-version:6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "swift-version-mcp",
platforms: [
.macOS(.v13),
],
products: [
.executable(name: "swift-version-mcp", targets: ["SwiftVersionMCP"]),
],
dependencies: [
.package(url: "https://github.com/modelcontextprotocol/swift-sdk", from: "0.12.1"),
],
targets: [
.executableTarget(name: "SwiftVersionMCP", dependencies: [
.product(name: "MCP", package: "swift-sdk")
]),
]
)Open main.swift and add the following code:
import MCP
import Foundation
let server = Server(
name: "Swift Version Server",
version: "1.0.0",
capabilities: .init(tools: .init(listChanged: false))
)
let transport = StdioTransport()
try await server.start(transport: transport)Here we are creating a server with a name and version. The capabilities parameter is used to define the capabilities of the server. In this case, we are only defining the tools capability. To start the server, we are using the StdioTransport class, which is a transport layer for the server. It allows the server to communicate with the client via standard input and output.
Next, we need to implement the tools. The server will have one tool that returns the current Swift version:
let tool = Tool(name: "swift_version",
description: "Returns the current Swift version",
inputSchema: .object([
"type": .string("object"),
"additionalProperties": .bool(false)
]))
await server.withMethodHandler(ListTools.self) { params in
ListTools.Result(tools: [tool])
}inputSchema is not optional. The specification requires a valid JSON Schema
object, and since SDK 0.10.0 the parameter has no default value, so the code
doesn't compile without it. Our tool takes no parameters, so the schema is an
object that accepts nothing else.
Tool names are case-sensitive and should contain only letters, digits, _, -,
and . — no spaces or other special characters. Both swift_version and
swiftVersion are valid; the snake case here is a habit from other servers.
To make the server respond to the tool, we need to implement the withMethodHandler(CallTool.self) function. It will be called when the client requests the tool. The method should return a Result object with the result of the tool:
await server.withMethodHandler(CallTool.self) { params in
guard params.name == tool.name else {
throw MCPError.invalidParams("Wrong tool name: \(params.name)")
}
let version = swiftVersion() ?? "No version"
return CallTool.Result(content: [.text(text: version, annotations: nil, _meta: nil)])
}
func swiftVersion() -> String? {
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
process.arguments = ["swift", "--version"]
let outputPipe = Pipe()
process.standardOutput = outputPipe
do {
try process.run()
process.waitUntilExit()
let data = outputPipe.fileHandleForReading.readDataToEndOfFile()
return String(data: data, encoding: .utf8)
} catch {
return "Error running swift-version: \(error)"
}
}Because the server may contain several tools, we check the tool name here. The
shorter .text("...") helper still works, but it's deprecated since 0.12.0 and
warns on build.
In the end of the file, add a call to waitUntilCompleted() to keep the server running:
await server.waitUntilCompleted()Build the package with the following command to generate the executable:
swift buildInterfaces in the SDK look a bit strange, because it was highly inspired by TypeScript implementation. It uses the same naming conventions and patterns. Sometimes you need to use dictionaries without strong types, but it is not a big deal. But hopefully it supports structured concurrency, so you can use async/await syntax.
Since this post was written, the SDK covers the
2025-11-25
specification, ships StatelessHTTPServerTransport and
StatefulHTTPServerTransport for servers that aren't local subprocesses,
supports OAuth authentication, and adds tool titles, icons, outputSchema and
structured content. Stdio is still the simplest way to start, though.
Now, we are ready to test the server. Agent mode in Visual Studio Code works with any MCP server, so let's configure it.
Using MCP servers in Visual Studio Code
Here is how to add the server. Make sure you install Github Copilot extension first.
- Open Command Palette... via ⌘+⇧+P shortcut and run MCP: Add Server
- Select a type. For a local executable it's Command (stdio); the other option is HTTP (HTTP or Server-Sent Events)
- Add a path to the executable:
/Users/artemnovichkov/Developer/swift-version-mcp/.build/arm64-apple-macosx/debug/swift-version-mcp- Enter a server ID, for example,
swift-version-server - Select where to save it. Copilot Global writes to
~/.copilot/mcp-config.jsonand is available everywhere; the workspace-root.mcp.jsonis shared with the team. The older destinations, workspace.vscode/mcp.jsonand user-profilemcp.json, still work, but the guided flow marks them as deprecated.
After finishing the setup, you will see the server in the list:
{
"mcpServers": {
"swift-version-server": {
"type": "stdio",
"command": "/Users/artemnovichkov/Developer/swift-version-mcp/.build/arm64-apple-macosx/debug/swift-version-mcp",
"args": []
}
}
}That's the mcpServers format, the same one Cursor and Claude Desktop use. If
you picked one of the VS Code files instead, the top-level key is servers.
Above the server name you will see a Start button. Click on it to start the server:
▷ Start | 1 cached toolsIn the output you will see the following logs:
2025-04-16 23:13:15.438 [info] Starting server swift-version-server
2025-04-16 23:13:15.439 [info] Connection state: Starting
2025-04-16 23:13:15.439 [info] Starting server from LocalProcess extension host
2025-04-16 23:13:15.443 [info] Connection state: Starting
2025-04-16 23:13:15.448 [info] Connection state: Running
2025-04-16 23:13:15.482 [info] Discovered 1 toolsNow open a chat via ^+⌘+I shortcut. In the top left corner you will see Select Tools... button with a count of available tools. Click on it to see a list of available tools.

That's it! Now you can use the tool in the chat. Try to ask a question about Swift version and you will see a response from the server:

Using MCP servers in other clients
You can use the server in other MCP clients like Cursor, Windsurf, Claude Desktop, etc. The configuration may vary, but the idea is the same.
Here is the configuration for Cursor in .cursor/mcp.json :
{
"mcpServers": {
"swift-version-server": {
"type": "stdio",
"command": "/Users/artemnovichkov/Developer/swift-version-mcp/.build/arm64-apple-macosx/debug/swift-version-mcp"
}
}
}And here is the result:

For Claude Desktop, you can use the same configuration as for Cursor, but you need to add the server in claude_desktop_config.json.
And here is the result:

Make sure to restart Claude Desktop after adding the server.
Conclusion
During the development of the server, I learned a lot about Model Context Protocol. I used to joke here about MCP integration in Xcode. It arrived in Xcode 26.3, and Xcode 27 moved external agents behind External Agent Access in Settings → Intelligence, so the tools stay reachable with Xcode closed. I wrote about that in Headless Xcode: From Prompt to Simulator with MCP.
The final code is available on Github.



