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
isErroron 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 withdestructiveHint. 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.