context Swift Library

repository·main·Indexed 21 days ago

https://github.com/indragiek/context

A Swift implementation of the Model Context Protocol (MCP) client. It enables applications to connect to MCP-compliant servers to discover and interact with tools, resources, and prompts. The library provides a Client actor for managing the protocol handshake, lifecycle, and sampling requests via the SamplingHandler protocol.

Tokens
2K
Snippets
5
Records
7
Agent score
24%

What's inside context

  1. Handle Sampling Requests with SamplingHandler

    main

    The Client can act as a host for an LLM by implementing the SamplingHandler protocol. When an MCP server sends a sampling/createMessage request, the client uses the provided samplingHandler to generate a response.

    To implement sampling, conform to SamplingHandler and pass your implementation to the Client initializer.

    public protocol SamplingHandler: Sendable {
      func sample(_ request: CreateMessageRequest) async throws -> CreateMessageResponse.Result
    }
    struct MySamplingHandler: SamplingHandler {
        func sample(_ request: CreateMessageRequest) async throws -> CreateMessageResponse.Result {
            // Logic to call an LLM and return a result
            return .init(role: .assistant, content: [.text("Hello from the client!")], model: "gpt-4")
        }
    }
    
    let client = Client(transport: myTransport, samplingHandler: MySamplingHandler())
  2. Initialize and use the MCP Client

    main

    The Client actor is the primary entrypoint for interacting with Model Context Protocol (MCP) servers. To use it, initialize it with a Transport implementation. You must call connect() to establish a connection and initialize the protocol handshake before making any requests.

    Key lifecycle steps:

    1. Initialize: Client(transport: ...)
    2. Connect: try await client.connect()
    3. Interact: Call methods like listTools(), callTool(), or readResource().
    4. Disconnect: try await client.disconnect()

    You can monitor the connection status via the connectionState stream and handle asynchronous errors or server logs through the errors and logs streams.

    // Example setup
    let transport = MyTransportImplementation()
    let client = Client(transport: transport)
    
    do {
        try await client.connect()
        
        // Monitor logs in a separate task
        Task {
            for await log in client.logs {
                print("Server log: \(log.data)")
            }
        }
    
        // Use the client
        let tools = try await client.listTools()
        print("Available tools: \(tools.tools)")
    
        try await client.disconnect()
    } catch {
        print("Client error: \(error)")
    }
  3. Call MCP Tools

    main

    Tools allow the client to execute specific functions or actions on the server.

    • listTools(cursor:): Lists available tools. Updates toolListChanged to false on success.
    • callTool(name:arguments:): Executes a tool by name with the provided arguments (a dictionary of String to JSONValue). Returns a tuple containing the tool's content and an isError boolean indicating if the tool execution resulted in an error.
    // List tools
    let (tools, _) = try await client.listTools()
    
    // Call a tool
    let (content, isError) = try await client.callTool(name: "calculate_sum", arguments: ["a": .number(10), "b": .number(20)])
    if !isError {
        print("Result: \(content)")
    }
  4. Manage MCP Prompts

    main

    The Client allows you to interact with prompt templates provided by the server.

    • listPrompts(cursor:): Fetches a list of available prompt templates. Supports pagination via the cursor parameter. Setting this updates the promptListChanged property to false upon success.
    • getPrompt(name:arguments:): Retrieves a specific prompt template by its name, optionally providing a dictionary of arguments to customize it. Returns the prompt's description and a list of PromptMessage objects.
    // List prompts
    let (prompts, nextCursor) = try await client.listPrompts()
    
    // Get a specific prompt
    let prompt = try await client.getPrompt(name: "analyze_code", arguments: ["language": "swift"])
    print(prompt.description)
  5. Manage MCP Resources

    main

    The Client provides methods to discover and read data resources from the server.

    • listResources(cursor:): Lists available resources. Updates resourceListChanged to false on success.
    • readResource(uri:): Reads the content of a specific resource identified by its URI. Returns an array of EmbeddedResource.
    • listResourceTemplates(cursor:): Lists available resource templates.
    • subscribeToResource(uri:): Subscribes to updates for a specific resource. Returns an AsyncThrowingChannel<ResourceUpdatedNotification, Error> that yields notifications when the resource changes. This requires the server to support resource subscriptions.
    • unsubscribeFromResource(uri:): Stops receiving updates for a specific resource URI.
    // Read a resource
    let contents = try await client.readResource(uri: "file:///logs/app.log")
    
    // Subscribe to updates
    let updates = try await client.subscribeToResource(uri: "file:///config.json")
    Task {
        for try await update in updates {
            print("Resource updated: \(update)")
        }
    }
  6. Reference: ClientError

    main

    The ClientError enum defines the possible errors encountered during MCP client operations. Common cases include:

    • requestFailed(request:error:data:): The server returned an error for a specific request.
    • requestInvalidResponse(request:error:data:): The response received was not a valid JSON-RPC response.
    • requestTimedOut(request:): A response was not received within the configured requestTimeout.
    • requestCancelled(id:): An in-flight request was cancelled.
    • serverError(error:data:): The server sent an error that was not a response to a specific request.
    • notConnected: Attempted an operation without being connected.
    • capabilityNotSupported(String): The server does not support the requested capability.
    • noPendingRequest(id:data:): Received a response for an ID that the client has no record of.
    • unsupportedNotification(data:): Received a notification the client does not recognize.
    • unexpectedRequestType(method:expectedType:): Received a request of an unexpected type for a given method.
  7. Reference: Client Properties and State

    main

    The Client actor maintains several properties that reflect the server's state and capabilities:

    • currentConnectionState: The current ConnectionState (disconnected, disconnecting, connected, or connecting).
    • serverCapabilities: The capabilities supported by the server (populated after connect()).
    • serverInfo: The server's implementation name and version (populated after connect()).
    • serverProtocolVersion: The protocol version supported by the server.
    • requestTimeout: Maximum duration (seconds) to wait for a response (defaults to 120).
    • roots: The list of filesystem roots exposed to the server.
    • promptListChanged, resourceListChanged, toolListChanged: Boolean flags indicating if the server has notified the client of changes to these lists.