AnnouncementsIntroducing Arkoren

    Menu

    First Server

    Build a local HTTP MCP server and discover and call its first tool with an MCP client.

    What's on this page
    1. Create a typed public tool and explicitly register its HTTP MCP endpoint.
    2. Discover and call the tool with the MCP Inspector over loopback HTTP.
    3. Recognize connection failures and retain application-owned server lifecycle.
    On this page

    Build a small server that returns a fixed service label through MCP. This walkthrough takes you from an empty Go project to client discovery and a tool call over HTTP. It needs no database, model API key, or Arkoren CLI.

    Use Go 1.27 or later. For the client checks, install Node.js 22.19 or later and npm to run the MCP Inspector. The Inspector is a client, not a server dependency.

    Project Setup

    Create a directory and initialize its module:

    mkdir mcp-service
    cd mcp-service
    go mod init example.com/mcp-service

    Install the tool definitions and HTTP adapter:

    go get arkoren.dev/core arkoren.dev/driver/mcp

    The adapter brings in the official Go MCP SDK. The following steps create tools.go and main.go in this directory; append each block to its named file.

    Service Operation

    Create tools.go with its package and imports:

    package main
    
    import (
    	"context"
    
    	"arkoren.dev/core/mcp"
    )

    Add an object result and a handler that returns only a public sample label:

    type ServiceInfo struct {
    	Name string `json:"name"`
    }
    
    func describeService(ctx context.Context, input mcp.NoInput) (ServiceInfo, error) {
    	return ServiceInfo{Name: "parcel-demo"}, nil
    }

    This is static sample data, not a health check or inspection of the running process. mcp.NoInput represents an empty object, {}, not JSON null.

    Tool Definition

    Publish the handler below it in tools.go:

    var describe = mcp.NewTool("service.describe", describeService, func(action *mcp.ToolBuilder[mcp.NoInput, ServiceInfo]) {
    	action.Description("Return the public name of this sample service.")
    	action.ReadOnly(true)
    	action.OpenWorld(false)
    })

    The definition supplies the name, schemas, and hints clients discover. It opens no network connection and does not call the handler. ReadOnly describes this operation; it does not enforce access control.

    Endpoint Registration

    Create main.go with these imports:

    package main
    
    import (
    	"log"
    	"net/http"
    
    	"arkoren.dev/core/http/middleware/csrf"
    	"arkoren.dev/core/http/router"
    	"arkoren.dev/driver/mcp/mcpserver"
    )

    Add a constructor that explicitly registers the tool:

    func newEndpoint() (*mcpserver.Server, error) {
    	return mcpserver.New("parcel-demo", "1.0.0", func(listener *mcpserver.Builder) {
    		listener.Tools(describe)
    	})
    }

    The name and version identify your server. Construction rejects invalid tool definitions and duplicate names. The returned endpoint is an http.Handler, not a listening server.

    HTTP Mount

    This unauthenticated sample is safe only for the fixed public result shown above. Keep it on loopback. Before exposing private tools or remote access, configure TLS, authentication, Host, and Origin policy.

    Append main to main.go:

    func main() {
    	endpoint, err := newEndpoint()
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	routes := router.New()
    	routes.Use(csrf.Middleware())
    	routes.Mount("/mcp", endpoint)
    
    	log.Print("serving MCP at http://127.0.0.1:8080/mcp")
    	log.Fatal(http.ListenAndServe("127.0.0.1:8080", routes))
    }

    The CSRF middleware rejects unsafe cross-origin browser requests. It is not authentication. Your application owns the listener and mount; the MCP endpoint has no Start or Stop method.

    Run the project:

    go run .

    The terminal logs the endpoint URL and stays running. If port 8080 is occupied, choose another port in both the code and the client commands below.

    Client Verification

    Leave the server running and use a second terminal to discover its tools:

    npx @modelcontextprotocol/inspector --cli http://127.0.0.1:8080/mcp \
      --transport http --method tools/list

    The first invocation downloads the Inspector if needed. The response lists service.describe with its description and object schemas. Discovery does not execute the handler.

    Call the listed operation through the same HTTP transport:

    npx @modelcontextprotocol/inspector --cli http://127.0.0.1:8080/mcp \
      --transport http --method tools/call --tool-name service.describe

    The result’s structuredContent contains this object, also returned as text content:

    {
      "name": "parcel-demo"
    }

    A successful list and call confirm protocol discovery and execution, not merely an open HTTP port. To use an AI application’s client instead, configure an MCP server with this URL and Streamable HTTP transport. A stdio-only client cannot connect. The client must run locally to reach this loopback address; a hosted client needs a reachable, secured deployment.

    Connection Failures

    If the client cannot connect, check that go run . is still running and that the host, port, and /mcp path match. Opening /mcp in a browser is not an MCP test: this stateless endpoint uses POST, and GET returns 405 Method Not Allowed.

    For manual protocol requests, send Content-Type: application/json and an Accept header containing both application/json and text/event-stream. Prefer an MCP client, which also handles protocol negotiation.

    Application Integration

    Stop the example with Ctrl+C. This standalone process does not implement graceful shutdown. When adding MCP to an existing application, retain its HTTP lifecycle and timeouts; register the MCP handler without adding a second listener or lifecycle owner.

    Continue with Tools for validated arguments, output contracts, and safe failures. For exact declarations and settings, see the core MCP API and MCP server API.

    Open a bulk documentation archive?

    This file contains every guide in its scope, not API declarations. Loading it all into an agent can exhaust its context window and increase processing time and cost. For coding tasks, start with the index and read only the relevant pages.

    Use the full archive for offline indexing, bulk processing, or deliberate large-context use.

    Browse the index instead
    Open archive