kits / connector-starter
Connector Starter
A minimal, tested Model Context Protocol server in TypeScript and Python, with one example tool and security notes.
- version
- 1.0.0
- files
- 15
- licence
- MIT
- pairs with
- kittybin AI
What’s inside
connector-starter/ ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── python/README.md ├── python/pyproject.toml ├── python/src/connector_starter/__init__.py ├── python/src/connector_starter/__main__.py ├── python/src/connector_starter/server.py ├── python/tests/test_server.py ├── typescript/README.md ├── typescript/package.json ├── typescript/tsconfig.json ├── typescript/src/index.ts ├── typescript/src/server.ts └── typescript/test/server.test.ts
Preview
README.mdpreview
Connector Starter
Version 1.0.0
A small, working Model Context Protocol (MCP) server in two languages: TypeScript and Python. Each starter has one useful example tool, tests that run without a network, and a README. Copy the folder for your language, rename it and start adding your own tools.
MCP is an open protocol that lets an assistant or agent call tools, read resources and use prompts that live outside the app it runs in. You write a server once, and any client that speaks MCP can connect to it.
Who it's for
- Developers who want to expose an API, a database query or an internal script to their assistant or coding agent
- Teams that need a clean, tested starting point instead of copying snippets from several places
- Anyone who prefers to read a small, complete example before reading a specification
You should be comfortable with a terminal and with either Node.js or Python.
How to use it in 5 minutes
- Pick a language. Copy
typescript/orpython/to where your project lives. - Install and test it. TypeScript:
npm install,npm run build,npm test. Python: create a virtual environment,pip install -e ".[dev]",pytest. - Open the server in the MCP Inspector (see "Testing with the MCP Inspector" below) and call
text_statsby hand. - Add the server to your MCP client using the config in "Connecting to a client".
- Read "Adding a second tool" and "Security notes for tools" before you write your own.
File map
| File | What it is |
|---|---|
| `README.md` | This guide: running, connecting, extending, testing and securing the starters |
| `LICENSE.txt` | MIT licence |
| `CHANGELOG.md` | Release history |
| `typescript/README.md` | Setup, scripts, tool reference and an example second tool for the TypeScript starter |
| `typescript/package.json` | Dependencies and the build, start, dev, typecheck and test scripts |
| `typescript/tsconfig.json` | Compiler settings (ES2022, NodeNext modules, strict mode) |
| `typescript/src/server.ts` | `createServer()` with the `text_stats` tool and its zod schemas |
| `typescript/src/index.ts` | Entry point that connects the server to stdio |
| `typescript/test/server.test.ts` | Tests using the SDK client over an in-memory transport |
| `python/README.md` | Setup, tool reference and an example second tool for the Python starter |
| `python/pyproject.toml` | Package metadata, dependencies, dev extras and the console script |
| `python/src/connector_starter/__init__.py` | Package exports |
| `python/src/connector_starter/__main__.py` | Enables `python -m connector_starter` |
| `python/src/connector_starter/server.py` | `create_server()` with the `text_stats` tool and `main()` |
| `python/tests/test_server.py` | Tests using `mcp.Client` connected in-process |
The example tool
Both starters register the same tool, so a client sees identical behaviour whichever language you run.
text_stats counts the words, characters and lines in a piece of text and estimates the reading time.
| Argument | Type | Rules |
|---|---|---|
| `text` | string | Required. Up to 100,000 characters. |
| `words_per_minute` | integer | Optional. 50 to 1000. Defaults to 200. |
Example result for "Hello world\nSecond line here":
{ "words": 5, "characters": 28, "lines": 2, "reading_time_seconds": 2 }
It is deliberately simple: deterministic, no network, no secrets. That makes it a good template for testing and a safe thing to leave in while you build.
Running the starters
TypeScript (Node.js 20 or newer):
cd typescript
npm install
npm run build
npm start
Python (3.10 or newer), macOS and Linux:
cd python
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
connector-starter
On Windows, activate with .\.venv\Scripts\Activate.ps1 instead.
Both servers speak MCP over stdio. Started by hand, they wait silently for input. That is normal: a client normally starts the process for you.
Connecting to a client
Most desktop and editor clients read a JSON config with an mcpServers object. The file name and location differ by client, so check its documentation for where the file lives. The shape is the same. Always use absolute paths, because the client may start the server from a different working directory.
TypeScript (after npm run build):
{
"mcpServers": {
"connector-starter": {
"command": "node",
"args": ["/absolute/path/to/connector-starter/typescript/dist/index.js"]
}
}
}
Python, macOS and Linux (point at the virtual environment so the right packages load):
{
"mcpServers": {
"connector-starter": {
"command": "/absolute/path/to/connector-starter/python/.venv/bin/connector-starter",
"args": []
}
}
}
Python on Windows (backslashes must be doubled in JSON):
{
"mcpServers": {
"connector-starter": {
"command": "C:\\projects\\connector-starter\\python\\.venv\\Scripts\\python.exe",
"args": ["-m", "connector_starter"]
}
}
}
If your server needs settings such as an API key, most clients accept an env object next to args. Keep real values in that local config or in your secret store, never in the repository.
After editing the config, restart the client or reload its MCP servers. If the tool does not appear, check the client's MCP log first. The usual causes are a relative path, a missing build, or a virtual environment that was never installed into.
Adding a second tool
Each language README shows a complete second tool (slugify). The pattern is the same in both:
- Write the logic as a plain function you can unit test.
- Register it inside
createServer()orcreate_server()with a clear name, a one-sentence description and a strict input schema. - Add annotations:
readOnlyHintfor tools that change nothing,destructiveHintfor tools that delete or overwrite,openWorldHintfor tools that reach the internet. - Add tests: one for the plain function, one call through the in-memory client and one invalid-input case.
- Rebuild (TypeScript) and reload the server in your client.
Naming tips: use short snake_case verbs or nouns (search_orders, get_invoice). Write the description for the model that will read it: say what the tool does, what it returns and when not to use it. Keep the number of tools small; many similar tools make selection worse.
Testing with the MCP Inspector
The MCP Inspector is the official tool for poking at a server by hand. It runs with npx, so nothing needs to be installed globally.
Open the browser interface:
cd typescript
npx @modelcontextprotocol/inspector node dist/index.js
cd python
npx @modelcontextprotocol/inspector .venv/bin/connector-starter
On Windows, use .venv\Scripts\connector-starter.exe for the Python command. The Inspector prints a local URL. Open it, press Connect, go to Tools, list the tools and run text_stats with your own text. Try a words_per_minute of 5 to see how a validation error is reported.
For scripts and CI, the Inspector also has a command-line mode:
npx @modelcontextprotocol/inspector --cli node dist/index.js --method tools/list
npx @modelcontextprotocol/inspector --cli node dist/index.js --method tools/call --tool-name text_stats --tool-arg "text=one two three"
The automated tests in each starter cover the same ground without starting a process, so run npm test or pytest on every change and use the Inspector for exploration.
Security notes for tools
A tool is code that an agent can run on your behalf, often with your permissions. Treat every argument as untrusted input, even when you trust the person using the client: the arguments are chosen by a model, and the model can be steered by content it reads.
- Validate input. Give every argument a type, a length or range limit and, where it fits, an enum or pattern. The schema is enforced before your code runs; add checks in code for anything a schema cannot express, such as "this path must stay inside this folder".
- Least privilege. Give the server only the access it needs: a read-only database user, a scoped API token, one directory instead of the whole disk. Split read and write actions into separate tools, and mark write tools with
destructiveHintso clients can ask for confirmation. - Never return secrets. Do not echo tokens, keys, connection strings or full environment variables in results or error messages. Return only the fields the caller needs. Remember that results are shown to the model and may be logged by the client.
- Set timeouts. Put a timeout on every network call, subprocess and query, and cap the size of what you return. A tool that hangs blocks the whole conversation.
- No shell strings. If a tool must run a program, pass arguments as a list and never build a shell command from input. Never pass input to
evalor similar. - Watch paths and URLs. Resolve file paths and reject anything outside an allowed root. For URLs, allow-list hosts and block internal addresses so the tool cannot be used to reach your private network.
- Keep stdout clean. On stdio, stdout is the protocol channel. Log to stderr or to a file.
- Pin and review dependencies. Commit your lockfile in your own project, keep the SDK up to date and read the release notes before major upgrades.
Package versions
This release was built and tested against:
| Starter | Package | Version range |
|---|---|---|
| TypeScript | `@modelcontextprotocol/sdk` | `^1.32.1` |
| TypeScript | `zod` | `^4.6.7` |
| TypeScript | `typescript` (dev) | `^7.0.2` |
| Python | `mcp` | `>=2.3.0,<3` |
No lockfile is included, so npm install and pip install resolve the newest matching versions. Commit the lockfile your install creates so your builds stay repeatable.
A note on limits
These are starters, not frameworks. They use stdio only; remote hosting over Streamable HTTP, authentication, rate limiting and deployment are out of scope. The security notes reduce risk but cannot make a tool safe on their own; review any tool that writes data or touches the network before you connect it to an agent. MCP and its SDKs change quickly, so check the release notes when you upgrade.
Licence
MIT License. See LICENSE.txt.
typescript/src/server.tspreview
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export const SERVER_NAME = "connector-starter";
export const SERVER_VERSION = "1.0.0";
export const MAX_TEXT_LENGTH = 100_000;
export interface TextStats {
words: number;
characters: number;
lines: number;
reading_time_seconds: number;
}
export function computeTextStats(text: string, wordsPerMinute = 200): TextStats {
const trimmed = text.trim();
const words = trimmed === "" ? 0 : trimmed.split(/\s+/).length;
const characters = Array.from(text).length;
let lines = 0;
if (text !== "") {
const parts = text.split(/\r\n|\r|\n/);
lines = parts[parts.length - 1] === "" ? parts.length - 1 : parts.length;
}
const readingTimeSeconds = words === 0 ? 0 : Math.ceil((words / wordsPerMinute) * 60);
return { words, characters, lines, reading_time_seconds: readingTimeSeconds };
}
export function createServer(): McpServer {
const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
server.registerTool(
"text_stats",
{
title: "Text statistics",
description:
"Count the words, characters and lines in a piece of text and estimate how long it takes to read.",
inputSchema: {
text: z
.string()
.max(MAX_TEXT_LENGTH)
.describe("The text to analyse."),
words_per_minute: z
.number()
.int()
.min(50)
.max(1000)
.default(200)
.describe("Reading speed used for the time estimate. Defaults to 200."),
},
outputSchema: {
words: z.number().int(),
characters: z.number().int(),
lines: z.number().int(),
reading_time_seconds: z.number().int(),
},
annotations: {
readOnlyHint: true,
idempotentHint: true,
openWorldHint: false,
},
},
async ({ text, words_per_minute }) => {
const stats = computeTextStats(text, words_per_minute);
return {
content: [{ type: "text", text: JSON.stringify(stats) }],
structuredContent: { ...stats },
};
},
);
return server;
}
python/src/connector_starter/server.pypreview
import math
import re
from typing import Annotated
from mcp.server.mcpserver import MCPServer
from mcp.types import ToolAnnotations
from pydantic import BaseModel, Field
SERVER_NAME = "connector-starter"
SERVER_VERSION = "1.0.0"
MAX_TEXT_LENGTH = 100_000
_LINE_BREAK = re.compile(r"\r\n|\r|\n")
class TextStats(BaseModel):
words: int
characters: int
lines: int
reading_time_seconds: int
def compute_text_stats(text: str, words_per_minute: int = 200) -> TextStats:
words = len(text.split())
characters = len(text)
lines = 0
if text:
parts = _LINE_BREAK.split(text)
lines = len(parts) - 1 if parts[-1] == "" else len(parts)
reading_time_seconds = 0 if words == 0 else math.ceil(words / words_per_minute * 60)
return TextStats(
words=words,
characters=characters,
lines=lines,
reading_time_seconds=reading_time_seconds,
)
def create_server() -> MCPServer:
server = MCPServer(SERVER_NAME, version=SERVER_VERSION)
@server.tool(
name="text_stats",
title="Text statistics",
description=(
"Count the words, characters and lines in a piece of text "
"and estimate how long it takes to read."
),
annotations=ToolAnnotations(
readOnlyHint=True,
idempotentHint=True,
openWorldHint=False,
),
)
def text_stats(
text: Annotated[str, Field(max_length=MAX_TEXT_LENGTH, description="The text to analyse.")],
words_per_minute: Annotated[
int,
Field(
ge=50,
le=1000,
description="Reading speed used for the time estimate. Defaults to 200.",
),
] = 200,
) -> TextStats:
return compute_text_stats(text, words_per_minute)
return server
def main() -> None:
create_server().run("stdio")