mcp-task-server
A security-focused Model Context Protocol server for task management over stdio, backed by SQLite. It exposes tools to create, list, complete, delete, and search tasks with strict validation, parameterized SQL, confirmation for deletes, and full audit logging.
README
mcp-task-server
A small, security-focused Model Context Protocol server that exposes a task manager over stdio, backed by SQLite.
MCP is an open protocol that standardizes how applications provide context and tools to large language models. An MCP server exposes capabilities (here: task management tools) that any MCP-compatible client can discover and call. This server uses the stdio transport, so the client launches it as a subprocess and no network port, API key, or cloud service is involved.
This repository doubles as a reference for building MCP servers with security as a first-class design constraint: strict input validation, parameterized SQL, confirmation for destructive operations, and a full audit trail.
Quickstart
Requires Node.js 20 or newer.
git clone https://github.com/antropovanikolitf/mcp-task-server.git
cd mcp-task-server
npm ci
npm run build
npm test
Any MCP client with stdio support can use the server: point it at node dist/index.js. Most clients take a JSON configuration entry along these lines:
{
"mcpServers": {
"tasks": {
"command": "node",
"args": ["/absolute/path/to/mcp-task-server/dist/index.js"],
"env": {
"TASKS_DB_PATH": "/absolute/path/to/tasks.db"
}
}
}
}
TASKS_DB_PATH is optional; it defaults to tasks.db in the working directory.
Tools
| Tool | Arguments | Description |
|---|---|---|
add_task |
title (string, 1 to 200 chars), due? (YYYY-MM-DD), tags? (up to 10 strings, 40 chars each) |
Create a task. |
list_tasks |
filter? with status (open or done), tag, dueAfter, dueBefore |
List tasks, optionally filtered. Due bounds are inclusive. |
complete_task |
id (positive integer) |
Mark a task as done. |
delete_task |
id (positive integer), confirm (must be the boolean literal true) |
Permanently delete a task. Calls without confirm: true are rejected. |
search_tasks |
query (string, 1 to 200 chars) |
Substring search on titles, case-insensitive for ASCII letters (SQLite LIKE semantics). % and _ are matched literally. |
Every tool returns a structured JSON result: { "ok": true, "data": ... } on success, or { "ok": false, "error": { "code", "message" } } on failure. Error codes are invalid_input, not_found, and internal_error. This contract holds over the wire: validation failures come back as this structured shape with isError set, not as opaque protocol errors. Only calling a tool name that does not exist fails at the protocol level.
Each tool also declares MCP tool annotations (readOnlyHint on list_tasks and search_tasks, destructiveHint on delete_task), so clients can gate or confirm calls based on protocol metadata rather than description prose.
Architecture
The code is layered so that each concern is testable in isolation. src/index.ts is wiring only: it builds the store, the server, and the stdio transport. src/server.ts registers the tools/list and tools/call handlers directly on the low-level SDK Server, advertises each tool's JSON Schema (including additionalProperties: false) and annotations for discovery, and converts structured results into MCP tool responses. It deliberately does not use the SDK's high-level tool wrapper: the wrapper validates arguments itself before the handler runs, which would strip unknown properties silently and keep rejected calls out of the audit log. Instead, the raw wire arguments go straight into src/tools.ts, where every handler funnels through a single runTool helper that validates input with zod, appends an audit entry, and converts any unexpected exception into a structured error. src/db.ts is the storage layer, a thin class over better-sqlite3 that uses prepared, parameterized statements exclusively. src/types.ts defines the shared domain types. Tests in test/ cover the storage layer, every tool handler, and the full client-to-server wire path over an in-memory MCP transport, including validation rejections, audit entries for rejected calls, and injection-shaped inputs.
Security considerations
Tool arguments are untrusted input, even when they come from an LLM. A model relays whatever appears in its context, so a prompt-injected document or web page can steer it into calling tools with hostile arguments. This server therefore validates every payload with zod before any logic runs: unknown properties are rejected, strings have length caps, dates must be real calendar dates, and ids must be positive integers. Length limits count UTF-16 code units (JavaScript's string.length), not Unicode code points or grapheme clusters. Nothing reaches the database unvalidated.
Parameterized SQL only. All queries use prepared statements with bound parameters. User-supplied values are never interpolated into SQL text, and LIKE wildcards in search queries are escaped so they match literally.
Least-privilege tool design. The server exposes exactly five narrow tools rather than a generic query or exec surface. Each tool can do one thing, which keeps the blast radius of a misdirected call small and makes every call auditable and gateable by the client.
Confirmation for destructive operations. delete_task requires confirm to be the boolean literal true. A model cannot delete data as a side effect of loose phrasing; the schema forces a deliberate, separate confirmation step, and the tool description instructs clients to obtain explicit user consent first.
Audit logging. Every tool call is appended to an audit_log table with the tool name, a size-capped serialization of the arguments, the outcome (ok, rejected, not_found, or error), and a timestamp. Rejected calls are logged too, so probing attempts against the registered tools are visible after the fact, and the logged arguments are the raw wire payload, unknown keys included; calls to tool names that do not exist fail at the protocol level, as noted above, and never reach this layer. One known gap: the MCP SDK's JSON-RPC layer strips a top-level __proto__ key from the arguments before any application code runs, so a call carrying one is accepted and that key is invisible to the audit log, while nested __proto__ keys do reach the tool layer and are rejected by the strict schemas. Database triggers make the table append-only: UPDATE and DELETE on audit_log are refused at the SQLite level, so history cannot be rewritten through the normal connection (this is tamper resistance, not cryptographic tamper evidence). If the audit write itself fails (for example a read-only database), the failure is reported on stderr and the tool result still reflects what actually happened; the trail is best-effort under storage failure.
Errors are structured, never stack traces. Handlers catch unexpected exceptions and return a generic internal_error result. Internals, file paths, and stack frames are never sent back to the model.
No secrets. The stdio transport needs no API keys, tokens, or network listeners, and the repository contains none.
For a broader treatment of these risks, see the OWASP Top 10 for LLM Applications, in particular LLM01 (Prompt Injection) and LLM06 (Excessive Agency).
Development
npm run build # compile TypeScript to dist/
npm test # run the vitest suite
License
MIT. See LICENSE.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.