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.
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/package.json +35 -0
- 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`);
|