Skip to content

Build an MCP server

Any server that follows the Model Context Protocol works with Fadenstack. This page covers what makes one work well there, and a template to start from.

What works well

  • Serve Streamable HTTP, and protect it with a bearer token that you give to the administrator when they register it.
  • Return text. Fadenstack hands text content to the model as it is; other content types reach it as JSON.
  • Say when a call failed. Set isError on the result and put a message the model can act on in the text.
  • Declare your parameters precisely. The model builds its calls from your input schema, and Fadenstack converts numbers and booleans that arrive as strings to the type you declared.
  • Keep results short. A chat keeps the first 4,000 characters of each tool result in its history. Return the part that answers the question, with a way to ask for more.
  • Keep the tool list short and the descriptions clear. Every tool's name and description is sent to the model with each request that may use it. The model picks tools by their descriptions.
  • Set the hints honestly. Mark tools that only read with readOnlyHint, and tools that delete or send with destructiveHint. An administrator may choose to trust them.

A template: the Brave Search server

mcp-server-brave is an open-source MCP server for web search, built to be copied. It serves Streamable HTTP at /mcp, takes an optional bearer token, and keeps the part that talks to Brave apart from the protocol, so you can swap it for your own system.

To build your own from it, subclass its ToolProvider:

from mcp.types import Tool
from mcp_server.providers.base import ToolProvider


class WikiProvider(ToolProvider):
    @property
    def provider_name(self) -> str:
        return "wiki"

    def list_tools(self) -> list[Tool]:
        return [
            Tool(
                name="search",
                description="Search the company wiki. Returns titles and links.",
                inputSchema={
                    "type": "object",
                    "properties": {"query": {"type": "string"}},
                    "required": ["query"],
                },
            ),
        ]

    async def call_tool(self, name: str, arguments: dict) -> str:
        if name == "search":
            return await self._client.search(arguments["query"])
        raise ValueError(f"Unknown tool: {name}")

Then register the provider in the server's transport module, add its settings, and run it. The repository's provider guide walks through every step.

Register it

Give your administrator the server's address (for example https://wiki-mcp.example.internal/mcp), the transport, and the token. They register it under Extensions → MCP Hub, choose a tool prefix and a privacy setting, and decide who may use it.