mcp server
mcp-toolkit
TypeScript suite of Model Context Protocol (MCP) servers on a shared typed mini-framework: read-only SQLite explorer, web-research, and a reminder scheduler.
Set up
Runs on your computer. Pick your agent and operating system.
Before you start
- Node.js 20 or newer
{
"mcpServers": {
"ke-netizen-oops-mcp-toolkit": {
"command": "npx",
"args": [
"-y",
"mcp-toolkit"
]
}
}
}same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12
Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs
README
synced Oct 23, 2026mcp-toolkit
A suite of Model Context Protocol (MCP) servers built on a small, shared,
typed mini-framework. One core defines how a tool is declared (a Zod input
schema + a typed handler) and how tools are registered on an MCP server; three
production-shaped servers are built on top of it:
- sqlite-explorer — open a SQLite database and explore it read-only: list
tables/views, describe a table, and run a single
SELECT(every write/DDL statement is rejected before it reaches the driver). - web-research — fetch a URL and reduce it to readable text, plus a
searchtool. All network access sits behind aFetcher/SearchProviderinterface. - scheduler — create / list / cancel simple in-memory reminders, with time
supplied by an injectable
Clock.
import { z } from 'zod';
import { ToolkitServer, defineTool, jsonResult } from 'mcp-toolkit/core';
const add = defineTool({
name: 'add',
description: 'Add two numbers.',
schema: z.object({ a: z.number(), b: z.number() }), // input is fully typed
execute: ({ a, b }) => jsonResult({ sum: a + b }),
});
const server = new ToolkitServer({ name: 'demo', version: '0.1.0', tools: [add] });
// Drive it directly in a test — no transport, no network:
await server.callTool('add', { a: 2, b: 3 }); // -> { content: [{ type:'text', text:'{ "sum": 5 }' }] }
// ...or serve it over stdio to a real MCP client via server.connect(transport).
Why it exists
MCP servers are easy to start and hard to test, because the interesting logic usually ends up entangled with a transport, a database driver, the network, or the wall clock. mcp-toolkit draws those boundaries explicitly:
- One authoring surface.
defineToolties a Zod schema to a handler whose argument is staticallyz.infer<typeof schema>. The same schema is converted to JSON Schema for the MCPtools/listresponse — one source of truth. - Handlers are callable without a transport.
ToolkitServerimplementstools/listandtools/callin terms of its own publiclistTools()/callTool()methods, so the entire test suite exercises real handler code paths with zero sockets and zero mocking of the SDK. - Every external dependency is injected. The DB connection (
DatabaseOpener), HTTP (Fetcher), search (SearchProvider), and time (Clock) are all interfaces. Production wiring lives only in the threebin.tsentry points; tests pass deterministic fakes. The whole suite runs offline with no keys.
Architecture
flowchart TD
subgraph core["core (shared mini-framework)"]
DT["defineTool(schema, execute)"]
SC["toInputSchema (Zod to JSON Schema)"]
TS["ToolkitServer\nregistry + tools/list + tools/call\nZod validation, error wrapping"]
end
subgraph servers["MCP servers"]
SQL["sqlite-explorer\nopen / list / describe / run_query"]
WEB["web-research\nfetch_url / search"]
SCH["scheduler\ncreate / list / cancel"]
end
subgraph boundaries["injected boundaries (mocked in tests)"]
DB[("DatabaseOpener\n(better-sqlite3, read-only)")]
FET["Fetcher / SearchProvider\n(global fetch, DuckDuckGo)"]
CLK["Clock\n(SystemClock)"]
end
DT --> TS
SC --> TS
TS --> SQL
TS --> WEB
TS --> SCH
SQL --> DB
WEB --> FET
SCH --> CLK
SQL -->|stdio bin| MCP["MCP client\n(e.g. Claude Desktop)"]
WEB -->|stdio bin| MCP
SCH -->|stdio bin| MCP
Quickstart
npm install
npm run typecheck # tsc --noEmit
npm test # vitest run (offline, deterministic)
npm run build # tsc -> dist/
Run a server over stdio
Each server ships a stdio entry point (the way MCP clients launch local tools):
node dist/servers/scheduler/bin.js
node dist/servers/sqlite-explorer/bin.js
node dist/servers/web-research/bin.js
Example MCP client config (e.g. Claude Desktop mcpServers):
{
"mcpServers": {
"scheduler": { "command": "node", "args": ["/abs/path/dist/servers/scheduler/bin.js"] },
"sqlite-explorer": { "command": "node", "args": ["/abs/path/dist/servers/sqlite-explorer/bin.js"] },
"web-research": { "command": "node", "args": ["/abs/path/dist/servers/web-research/bin.js"] }
}
}
Tool reference
| Server | Tool | Input | Notes |
|---|---|---|---|
| sqlite-explorer | open_database | { path } | Opens read-only; replaces any open DB. |
| sqlite-explorer | list_tables | {} | User tables + views. |
| sqlite-explorer | describe_table | { table } | Columns via PRAGMA table_info. |
| sqlite-explorer | run_query | { sql } | Single SELECT/WITH only; capped at 1000 rows. |
| web-research | fetch_url | { url } | HTML reduced to readable text + title. |
| web-research | search | { query, limit? } | Via injected SearchProvider. |
| scheduler | create_reminder | { message, delaySeconds } | Returns the reminder + id. |
| scheduler | list_reminders | { status? } | Status computed against the clock. |
| scheduler | cancel_reminder | { id } | Cannot cancel an already-fired reminder. |
Read-only SQL guard
run_query is the security boundary of sqlite-explorer. The connection is
opened with better-sqlite3's readonly: true, and the SQL is validated
before execution: comments and string/identifier literals are stripped first
(so a column literally named drop or a comment containing DELETE can't fool
the scanner), then the statement must be a single SELECT/WITH, with no
stacked statements and no write/DDL/PRAGMA keywords. See
src/servers/sqlite-explorer/sql-guard.ts and its tests.
Scope — what this is and isn't
Is:
- A working, typed mini-framework for MCP tool servers and three real servers on top of it, with a 45-test offline suite and a Node 18/20/22 CI matrix.
- A demonstration of clean dependency boundaries: DB, HTTP, search, and clock are all injectable, which is what makes the suite deterministic and key-free.
- Wired to the official
@modelcontextprotocol/sdk; the stdio entry points are real and have been smoke-tested with live JSON-RPCinitialize+tools/list.
Isn't:
- A production data gateway. sqlite-explorer's guard is a deliberately strict allow-list for read-only exploration, not a general SQL sandbox; don't point it at untrusted databases as a substitute for OS-level permissions.
- A full Readability/boilerplate-removal engine.
web-research's HTML→text is a dependency-free reducer (drop scripts/styles, collapse tags, decode common entities); it won't match a browser's reader mode on complex pages. - A durable scheduler. Reminders live in memory for the process lifetime and fire by comparison against the clock on query — there is no background timer, persistence, or delivery mechanism.
- A bundled search backend.
web-researchships a DuckDuckGo HTML provider as a realistic default, butsearchis unconfigured unless you inject a provider; the network path is intentionally never exercised by the tests.
Project layout
src/
core/ # defineTool, Zod->JSON Schema, ToolkitServer
servers/
sqlite-explorer/ # db.ts (opener iface), sql-guard.ts, server.ts, bin.ts
web-research/ # fetcher.ts, search.ts, extract.ts, server.ts, bin.ts
scheduler/ # clock.ts, store.ts, server.ts, bin.ts
tests/ # core + one suite per server (offline, deterministic)
License
MIT © 2026 Abed Kamau