tooldirectory-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/package.json +35 -0
  4. package/src/index.js +287 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tool Directory (tooldirectory.ai)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # Tool Directory MCP Server
2
+
3
+ MCP server for the [AI Tool Directory](https://tooldirectory.ai) — a live, human-edited catalog of 2,000+ AI tools. Lets any MCP client (Claude Desktop, Claude Code, Cursor, Windsurf, agent frameworks) query the catalog mid-task instead of guessing from a frozen training cut.
4
+
5
+ The headline tool is **`check_tool_status`**: whether an AI tool is still alive. It's the one fact LLMs reliably get wrong — models keep recommending products that shut down months ago. This server answers it from the directory's [AI Graveyard](https://tooldirectory.ai/graveyard), a maintained dataset of 150+ defunct and acquired AI tools, and suggests live alternatives when a tool is dead.
6
+
7
+ ## Tools
8
+
9
+ | Tool | What it does |
10
+ | --- | --- |
11
+ | `search_tools` | Hybrid semantic search across the catalog — by keyword, use case, or category. |
12
+ | `get_tool` | Full profile of one tool: pricing, features, editorial verdict, last human-verified date. |
13
+ | `check_tool_status` | Is this tool active, deceased, or acquired? Date + cause if it shut down, plus live alternatives. |
14
+ | `find_alternatives` | Curated alternatives to a tool; live replacements if it's defunct. |
15
+ | `compare_tools` | Side-by-side comparison of two tools, with the editor's head-to-head verdict when one exists. |
16
+ | `list_tools` | Top-rated active tools for a category or job role, optionally filtered by pricing. |
17
+
18
+ Read-only, no API key, no signup.
19
+
20
+ ## Install
21
+
22
+ ### Hosted endpoint (no install)
23
+
24
+ The server is also available as a remote MCP (Streamable HTTP) — point your client at:
25
+
26
+ ```
27
+ https://tooldirectory.ai/api/mcp
28
+ ```
29
+
30
+ ### Claude Desktop / Claude Code
31
+
32
+ ```json
33
+ {
34
+ "mcpServers": {
35
+ "tooldirectory": {
36
+ "command": "npx",
37
+ "args": ["-y", "tooldirectory-mcp"]
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ Or with Claude Code:
44
+
45
+ ```bash
46
+ claude mcp add tooldirectory -- npx -y tooldirectory-mcp
47
+ ```
48
+
49
+ ### Cursor / Windsurf / other MCP clients
50
+
51
+ Use the same stdio command: `npx -y tooldirectory-mcp`.
52
+
53
+ ### Docker
54
+
55
+ ```bash
56
+ docker build -t tooldirectory-mcp .
57
+ docker run -i tooldirectory-mcp
58
+ ```
59
+
60
+ ## Example prompts
61
+
62
+ - *"Is Jasper still operating? What happened to Inflection Pi?"*
63
+ - *"Find me alternatives to Midjourney with a free tier."*
64
+ - *"Compare Gamma and Beautiful.ai for making decks."*
65
+ - *"What are the best AI tools for sales teams?"*
66
+
67
+ ## How it works
68
+
69
+ This package is a thin stdio front-end over the canonical hosted endpoint at `tooldirectory.ai/api/mcp`, where the search (Meilisearch hybrid semantic) and catalog logic live. Tool schemas are declared locally, so `initialize` and `tools/list` respond instantly with no network call; `tools/call` proxies to the hosted endpoint. Zero runtime dependencies.
70
+
71
+ Set `TOOLDIRECTORY_MCP_ENDPOINT` to override the upstream endpoint (e.g. for testing).
72
+
73
+ ## About the data
74
+
75
+ The catalog is maintained by [Tool Directory](https://tooldirectory.ai) — an AI tool company run by humans that use AI. Every listed tool has a lifecycle status; editorial reviews carry a named editor and a last-verified date. Related machine-readable surfaces:
76
+
77
+ - [Research hub](https://tooldirectory.ai/research) — AI tool mortality statistics
78
+ - [Catalog feed](https://tooldirectory.ai/feed/catalog.json) / [Graveyard feed](https://tooldirectory.ai/feed/graveyard.json)
79
+ - [/mcp](https://tooldirectory.ai/mcp) — server documentation page
80
+
81
+ ## License
82
+
83
+ MIT
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "tooldirectory-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for the AI Tool Directory — search 2,000+ AI tools, compare them, find alternatives, and check whether a tool is still active, defunct, or acquired.",
5
+ "keywords": [
6
+ "mcp",
7
+ "model-context-protocol",
8
+ "mcp-server",
9
+ "ai-tools",
10
+ "ai-tool-directory",
11
+ "ai-graveyard",
12
+ "claude",
13
+ "agents"
14
+ ],
15
+ "homepage": "https://tooldirectory.ai/mcp",
16
+ "bugs": {
17
+ "url": "https://github.com/AI-Directory-Partners/tooldirectory-mcp/issues"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/AI-Directory-Partners/tooldirectory-mcp.git"
22
+ },
23
+ "license": "MIT",
24
+ "author": "Tool Directory (https://tooldirectory.ai)",
25
+ "type": "module",
26
+ "bin": {
27
+ "tooldirectory-mcp": "src/index.js"
28
+ },
29
+ "files": [
30
+ "src/"
31
+ ],
32
+ "engines": {
33
+ "node": ">=18"
34
+ }
35
+ }
package/src/index.js ADDED
@@ -0,0 +1,287 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * tooldirectory-mcp — stdio MCP server for the AI Tool Directory catalog
4
+ * (https://tooldirectory.ai).
5
+ *
6
+ * Lets any MCP client (Claude Desktop, Cursor, Windsurf, agent frameworks)
7
+ * query a live catalog of 2,000+ AI tools mid-task instead of guessing from a
8
+ * frozen training cut. The headline capability is `check_tool_status` —
9
+ * whether a tool is still alive — the one fact LLMs reliably get wrong and
10
+ * the AI Graveyard dataset uniquely answers.
11
+ *
12
+ * Design: thin stdio front-end over the canonical hosted MCP endpoint at
13
+ * https://tooldirectory.ai/api/mcp. Tool schemas are declared locally so
14
+ * `initialize` and `tools/list` answer instantly with no network; `tools/call`
15
+ * proxies to the hosted endpoint where the real search/catalog logic lives.
16
+ * Zero runtime dependencies — the stdio protocol surface is small (newline-
17
+ * delimited JSON-RPC 2.0) and Node 18+ ships fetch.
18
+ *
19
+ * Prefer a remote server? Point your client straight at
20
+ * https://tooldirectory.ai/api/mcp (Streamable HTTP) and skip this package.
21
+ */
22
+
23
+ const ENDPOINT = process.env.TOOLDIRECTORY_MCP_ENDPOINT || 'https://tooldirectory.ai/api/mcp';
24
+ const PROTOCOL_VERSION = '2025-06-18';
25
+ const SERVER_NAME = 'tooldirectory-ai';
26
+ const SERVER_VERSION = '1.0.0';
27
+ const CALL_TIMEOUT_MS = 30_000;
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Tool definitions — kept in lockstep with the hosted endpoint's tools/list.
31
+ // Declared locally so introspection works offline and instantly.
32
+ // ---------------------------------------------------------------------------
33
+
34
+ const TOOLS = [
35
+ {
36
+ name: 'search_tools',
37
+ description:
38
+ 'Search the AI Tool Directory catalog (2,000+ AI tools) by keyword, use case, or category using hybrid semantic search. Returns ranked tools with slug, one-line description, pricing model, and rating. Use this to discover tools, then get_tool for full detail.',
39
+ inputSchema: {
40
+ type: 'object',
41
+ properties: {
42
+ query: {
43
+ type: 'string',
44
+ description:
45
+ 'What the user is looking for, e.g. "AI video editing" or "alternatives to Jasper".'
46
+ },
47
+ limit: {
48
+ type: 'integer',
49
+ description: 'Max results (1-20, default 8).'
50
+ }
51
+ },
52
+ required: ['query']
53
+ }
54
+ },
55
+ {
56
+ name: 'get_tool',
57
+ description:
58
+ 'Get the full profile of one AI tool by its directory slug: description, pricing, key features, editorial verdict and rating, the date it was last human-verified, lifecycle status, and the official site URL.',
59
+ inputSchema: {
60
+ type: 'object',
61
+ properties: {
62
+ slug: {
63
+ type: 'string',
64
+ description:
65
+ 'The directory slug, e.g. "gamma-app-ai-powered-presenting-ideas" (from search_tools).'
66
+ }
67
+ },
68
+ required: ['slug']
69
+ }
70
+ },
71
+ {
72
+ name: 'check_tool_status',
73
+ description:
74
+ 'Check whether an AI tool is still alive. Returns active, deceased, or acquired — with the date and cause if it shut down, and live alternatives if it did. Use this before recommending a tool to avoid suggesting one that no longer exists.',
75
+ inputSchema: {
76
+ type: 'object',
77
+ properties: {
78
+ tool: {
79
+ type: 'string',
80
+ description:
81
+ 'Tool name or directory slug to check, e.g. "Jasper" or "jasper-ai".'
82
+ }
83
+ },
84
+ required: ['tool']
85
+ }
86
+ },
87
+ {
88
+ name: 'find_alternatives',
89
+ description:
90
+ 'Find curated alternatives to a given AI tool by its directory slug. If the tool has shut down, returns live replacements. Good for "what should I use instead of X" questions.',
91
+ inputSchema: {
92
+ type: 'object',
93
+ properties: {
94
+ slug: {
95
+ type: 'string',
96
+ description: 'The directory slug of the tool to find alternatives for.'
97
+ }
98
+ },
99
+ required: ['slug']
100
+ }
101
+ },
102
+ {
103
+ name: 'compare_tools',
104
+ description:
105
+ 'Compare two AI tools side by side by their directory slugs. Returns each tool’s profile (pricing, rating, editorial verdict, lifecycle) plus the editor’s head-to-head verdict and bottom line when one exists for the pair.',
106
+ inputSchema: {
107
+ type: 'object',
108
+ properties: {
109
+ slugA: {
110
+ type: 'string',
111
+ description: 'Directory slug of the first tool.'
112
+ },
113
+ slugB: {
114
+ type: 'string',
115
+ description: 'Directory slug of the second tool.'
116
+ }
117
+ },
118
+ required: ['slugA', 'slugB']
119
+ }
120
+ },
121
+ {
122
+ name: 'list_tools',
123
+ description:
124
+ 'List the top-rated active AI tools in a category or for a job role, optionally filtered by pricing. Good for "what are the best AI tools for sales" or "free tools in <category>".',
125
+ inputSchema: {
126
+ type: 'object',
127
+ properties: {
128
+ category: {
129
+ type: 'string',
130
+ description:
131
+ 'Category slug (from search_tools results), e.g. "productivity".'
132
+ },
133
+ role: {
134
+ type: 'string',
135
+ description: 'Job-role slug, e.g. "sales", "marketing", "customer-support".'
136
+ },
137
+ pricing: {
138
+ type: 'string',
139
+ description: 'Optional pricing filter: Free, Freemium, Free Trial, or Paid.'
140
+ },
141
+ limit: {
142
+ type: 'integer',
143
+ description: 'Max results (1-20, default 8).'
144
+ }
145
+ }
146
+ }
147
+ }
148
+ ];
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // Upstream proxy — tools/call is forwarded to the hosted endpoint.
152
+ // ---------------------------------------------------------------------------
153
+
154
+ async function proxyToolCall(id, params) {
155
+ try {
156
+ const res = await fetch(ENDPOINT, {
157
+ method: 'POST',
158
+ headers: { 'Content-Type': 'application/json' },
159
+ body: JSON.stringify({ jsonrpc: '2.0', id, method: 'tools/call', params }),
160
+ signal: AbortSignal.timeout(CALL_TIMEOUT_MS)
161
+ });
162
+ if (!res.ok) {
163
+ return rpcResult(id, {
164
+ content: [{ type: 'text', text: `Upstream error: HTTP ${res.status} from ${ENDPOINT}` }],
165
+ isError: true
166
+ });
167
+ }
168
+ const json = await res.json();
169
+ if (json && typeof json === 'object' && 'result' in json) {
170
+ return rpcResult(id, json.result);
171
+ }
172
+ if (json && typeof json === 'object' && 'error' in json) {
173
+ return { jsonrpc: '2.0', id, error: json.error };
174
+ }
175
+ return rpcResult(id, {
176
+ content: [{ type: 'text', text: 'Upstream returned an unexpected response shape.' }],
177
+ isError: true
178
+ });
179
+ } catch (err) {
180
+ const message = err && err.name === 'TimeoutError' ? 'Upstream request timed out.' : `Upstream request failed: ${err?.message ?? 'unknown error'}`;
181
+ return rpcResult(id, {
182
+ content: [{ type: 'text', text: message }],
183
+ isError: true
184
+ });
185
+ }
186
+ }
187
+
188
+ // ---------------------------------------------------------------------------
189
+ // JSON-RPC 2.0 over stdio (newline-delimited)
190
+ // ---------------------------------------------------------------------------
191
+
192
+ function rpcResult(id, result) {
193
+ return { jsonrpc: '2.0', id, result };
194
+ }
195
+
196
+ function rpcError(id, code, message) {
197
+ return { jsonrpc: '2.0', id, error: { code, message } };
198
+ }
199
+
200
+ async function handleMessage(msg) {
201
+ if (!msg || typeof msg !== 'object') return rpcError(null, -32600, 'Invalid Request');
202
+ const id = msg.id ?? null;
203
+ const method = msg.method;
204
+
205
+ // Notifications: no response.
206
+ if (typeof method === 'string' && method.startsWith('notifications/')) return null;
207
+
208
+ switch (method) {
209
+ case 'initialize': {
210
+ const requested = msg.params?.protocolVersion;
211
+ return rpcResult(id, {
212
+ protocolVersion: typeof requested === 'string' ? requested : PROTOCOL_VERSION,
213
+ capabilities: { tools: { listChanged: false } },
214
+ serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },
215
+ instructions:
216
+ 'Read-only access to the AI Tool Directory catalog. Use check_tool_status before recommending a tool to confirm it still exists.'
217
+ });
218
+ }
219
+ case 'ping':
220
+ return rpcResult(id, {});
221
+ case 'tools/list':
222
+ return rpcResult(id, { tools: TOOLS });
223
+ case 'tools/call': {
224
+ const params = msg.params ?? {};
225
+ const known = TOOLS.some((t) => t.name === params.name);
226
+ if (!known) {
227
+ return rpcResult(id, {
228
+ content: [{ type: 'text', text: `Unknown tool: ${String(params.name)}` }],
229
+ isError: true
230
+ });
231
+ }
232
+ return proxyToolCall(id, params);
233
+ }
234
+ default:
235
+ return rpcError(id, -32601, `Method not found: ${String(method)}`);
236
+ }
237
+ }
238
+
239
+ function send(response) {
240
+ if (response) process.stdout.write(JSON.stringify(response) + '\n');
241
+ }
242
+
243
+ // Exit only after stdin has closed AND all in-flight requests have answered —
244
+ // otherwise a one-shot pipe (or a clean client shutdown) kills pending
245
+ // tools/call fetches before their responses flush.
246
+ let pending = 0;
247
+ let stdinClosed = false;
248
+
249
+ function maybeExit() {
250
+ if (stdinClosed && pending === 0) process.exit(0);
251
+ }
252
+
253
+ let buffer = '';
254
+ process.stdin.setEncoding('utf8');
255
+ process.stdin.on('data', (chunk) => {
256
+ buffer += chunk;
257
+ let newline;
258
+ while ((newline = buffer.indexOf('\n')) !== -1) {
259
+ const line = buffer.slice(0, newline).trim();
260
+ buffer = buffer.slice(newline + 1);
261
+ if (!line) continue;
262
+ let msg;
263
+ try {
264
+ msg = JSON.parse(line);
265
+ } catch {
266
+ send(rpcError(null, -32700, 'Parse error'));
267
+ continue;
268
+ }
269
+ pending += 1;
270
+ handleMessage(msg)
271
+ .then(send)
272
+ .catch((err) => {
273
+ process.stderr.write(`[tooldirectory-mcp] handler error: ${err?.message}\n`);
274
+ send(rpcError(msg?.id ?? null, -32603, 'Internal error'));
275
+ })
276
+ .finally(() => {
277
+ pending -= 1;
278
+ maybeExit();
279
+ });
280
+ }
281
+ });
282
+ process.stdin.on('end', () => {
283
+ stdinClosed = true;
284
+ maybeExit();
285
+ });
286
+
287
+ process.stderr.write(`[tooldirectory-mcp] v${SERVER_VERSION} ready (stdio) → ${ENDPOINT}\n`);