mcp-task-server

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.

Category
Visit Server

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

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.

Official
Featured
TypeScript
Magic Component Platform (MCP)

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.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

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.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

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.

Official
Featured
TypeScript
Kagi MCP Server

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.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

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.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured