@toolrank/mcp-server 0.1.1 → 0.1.2
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/README.md +24 -0
- package/mcp-worker/.wrangler/cache/wrangler-account.json +6 -0
- package/mcp-worker/worker.js +296 -0
- package/mcp-worker/wrangler.toml +3 -0
- package/package.json +7 -9
- package/src/index.ts +14 -1
- package/.smithery/shttp/module.js +0 -21159
- package/.smithery/shttp/module.js.map +0 -7
- package/dist/index.d.ts +0 -13
- package/dist/index.js +0 -397
- package/smithery.yaml +0 -17
- package/tsconfig.json +0 -15
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# ToolRank MCP Server
|
|
2
|
+
|
|
3
|
+
Score and optimize MCP tool definitions for AI agent discovery. The first ATO (Agent Tool Optimization) platform.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
### toolrank_score
|
|
8
|
+
Analyzes MCP tool definitions and returns a ToolRank Score (0-100) measuring agent-readiness. Evaluates four dimensions: Findability (25%), Clarity (35%), Precision (25%), and Efficiency (15%). Returns per-tool scores, maturity level, specific issues found, and fix suggestions ranked by impact.
|
|
9
|
+
|
|
10
|
+
### toolrank_suggest
|
|
11
|
+
Generates specific improvement suggestions for MCP tool definitions. Returns rewritten descriptions, improved schemas, and estimated score improvements.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
```bash
|
|
15
|
+
npx @toolrank/mcp-server
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Links
|
|
19
|
+
|
|
20
|
+
- Website: https://toolrank.dev
|
|
21
|
+
- Framework: https://toolrank.dev/framework
|
|
22
|
+
- Ranking: https://toolrank.dev/ranking
|
|
23
|
+
- GitHub: https://github.com/imhiroki/toolrank
|
|
24
|
+
- License: MIT
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolRank MCP Server - Cloudflare Workers HTTP endpoint
|
|
3
|
+
*
|
|
4
|
+
* Wraps the ToolRank MCP scoring engine as a Streamable HTTP server
|
|
5
|
+
* for Smithery registration and remote MCP client access.
|
|
6
|
+
*
|
|
7
|
+
* Deploy: wrangler deploy
|
|
8
|
+
* URL: https://mcp.toolrank.dev/mcp
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Inline scoring engine (simplified Level A for Workers)
|
|
12
|
+
function scoreTool(tool) {
|
|
13
|
+
const name = tool.name || '';
|
|
14
|
+
const desc = tool.description || '';
|
|
15
|
+
const schema = tool.inputSchema || tool.input_schema || {};
|
|
16
|
+
const props = schema.properties || {};
|
|
17
|
+
const required = schema.required || [];
|
|
18
|
+
const issues = [];
|
|
19
|
+
|
|
20
|
+
// Clarity (max 35)
|
|
21
|
+
let clarity = 0;
|
|
22
|
+
if (!desc.trim()) {
|
|
23
|
+
issues.push({ dim: 'clarity', sev: 'critical', msg: 'No description', fix: 'Add description with purpose, context, return value', impact: 15 });
|
|
24
|
+
} else {
|
|
25
|
+
clarity += 5;
|
|
26
|
+
if (desc.length >= 80 && desc.length <= 250) clarity += 8;
|
|
27
|
+
else if (desc.length >= 50) { clarity += 5; issues.push({ dim: 'clarity', sev: 'warning', msg: `Description ${desc.length} chars (optimal: 80-200)`, fix: 'Expand description', impact: 5 }); }
|
|
28
|
+
else { clarity += 2; issues.push({ dim: 'clarity', sev: 'critical', msg: `Description too short (${desc.length} chars)`, fix: 'Expand to 80-200 chars', impact: 10 }); }
|
|
29
|
+
|
|
30
|
+
const dl = desc.toLowerCase();
|
|
31
|
+
if (/^(get|set|create|update|delete|search|find|list|fetch|send|retriev|return|provid)/.test(dl)) clarity += 6;
|
|
32
|
+
else { clarity += 3; issues.push({ dim: 'clarity', sev: 'warning', msg: 'No action verb at start', fix: "Start with verb like 'Searches for...', 'Creates...'", impact: 5 }); }
|
|
33
|
+
|
|
34
|
+
if (/use this|when|useful for|ideal for|designed for/.test(dl)) clarity += 6;
|
|
35
|
+
else { clarity += 2; issues.push({ dim: 'clarity', sev: 'warning', msg: 'No usage context', fix: "Add 'Use this when...'", impact: 6 }); }
|
|
36
|
+
|
|
37
|
+
if (/returns?|outputs?|produces|yields|result/.test(dl)) clarity += 6;
|
|
38
|
+
else { clarity += 3; issues.push({ dim: 'clarity', sev: 'info', msg: 'No return value described', fix: "Add 'Returns...'", impact: 3 }); }
|
|
39
|
+
|
|
40
|
+
clarity += 4; // name-desc alignment bonus
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Precision (max 25)
|
|
44
|
+
let precision = 0;
|
|
45
|
+
if (!schema.type) {
|
|
46
|
+
issues.push({ dim: 'precision', sev: 'critical', msg: 'No input schema', fix: 'Add inputSchema with types', impact: 12 });
|
|
47
|
+
} else {
|
|
48
|
+
precision += 5;
|
|
49
|
+
const pk = Object.keys(props);
|
|
50
|
+
if (pk.length > 0) {
|
|
51
|
+
const noType = pk.filter(k => !props[k].type);
|
|
52
|
+
precision += noType.length === 0 ? 5 : 2;
|
|
53
|
+
if (noType.length > 0) issues.push({ dim: 'precision', sev: 'warning', msg: `Missing types: ${noType.join(', ')}`, fix: 'Add type to each param', impact: 4 });
|
|
54
|
+
|
|
55
|
+
const noDesc = pk.filter(k => !props[k].description);
|
|
56
|
+
precision += noDesc.length === 0 ? 5 : 2;
|
|
57
|
+
if (noDesc.length > 0) issues.push({ dim: 'precision', sev: 'warning', msg: `Missing param descriptions: ${noDesc.join(', ')}`, fix: 'Add description to each param', impact: 5 });
|
|
58
|
+
|
|
59
|
+
precision += required.length > 0 ? 5 : 2;
|
|
60
|
+
if (required.length === 0) issues.push({ dim: 'precision', sev: 'info', msg: 'No required fields', fix: 'Add required array', impact: 3 });
|
|
61
|
+
|
|
62
|
+
precision += 5;
|
|
63
|
+
} else {
|
|
64
|
+
precision += 10;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Efficiency (max 15)
|
|
69
|
+
const tokens = JSON.stringify(tool).length / 4;
|
|
70
|
+
let efficiency = tokens > 2000 ? 4 : tokens > 1000 ? 8 : tokens > 500 ? 12 : 15;
|
|
71
|
+
if (tokens > 1000) issues.push({ dim: 'efficiency', sev: 'warning', msg: `~${Math.round(tokens)} tokens`, fix: 'Reduce definition size', impact: 3 });
|
|
72
|
+
|
|
73
|
+
// Findability (max 25)
|
|
74
|
+
let findability = name.length >= 4 ? 20 : 8;
|
|
75
|
+
if (name.length < 4) issues.push({ dim: 'findability', sev: 'warning', msg: `Name '${name}' too short`, fix: 'Use descriptive name', impact: 4 });
|
|
76
|
+
if (/^[a-z][a-zA-Z0-9_]*$/.test(name)) findability += 5;
|
|
77
|
+
else { issues.push({ dim: 'findability', sev: 'info', msg: 'Name not snake_case', fix: 'Use snake_case naming', impact: 2 }); }
|
|
78
|
+
|
|
79
|
+
const total = Math.round((findability + clarity + precision + efficiency) * 10) / 10;
|
|
80
|
+
let level, levelName;
|
|
81
|
+
if (total >= 85) { level = 4; levelName = 'Dominant'; }
|
|
82
|
+
else if (total >= 70) { level = 3; levelName = 'Preferred'; }
|
|
83
|
+
else if (total >= 50) { level = 2; levelName = 'Selectable'; }
|
|
84
|
+
else if (total >= 25) { level = 1; levelName = 'Visible'; }
|
|
85
|
+
else { level = 0; levelName = 'Absent'; }
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
name, total, level, levelName,
|
|
89
|
+
dimensions: { findability, clarity, precision, efficiency },
|
|
90
|
+
issues: issues.sort((a, b) => b.impact - a.impact),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Tool definitions (what MCP clients see)
|
|
95
|
+
const TOOLS = [
|
|
96
|
+
{
|
|
97
|
+
name: "toolrank_score",
|
|
98
|
+
description: "Analyzes MCP tool definitions and returns a ToolRank Score (0-100) measuring agent-readiness. Evaluates four dimensions: Findability (25%), Clarity (35%), Precision (25%), and Efficiency (15%). Use this when you want to check or improve the quality of your MCP tool definitions. Returns per-tool scores, maturity level, specific issues found, and fix suggestions ranked by impact.",
|
|
99
|
+
inputSchema: {
|
|
100
|
+
type: "object",
|
|
101
|
+
properties: {
|
|
102
|
+
tools: {
|
|
103
|
+
type: "array",
|
|
104
|
+
description: "Array of MCP tool definition objects. Each must have 'name' and 'description' fields. 'inputSchema' is optional but improves Precision score.",
|
|
105
|
+
items: {
|
|
106
|
+
type: "object",
|
|
107
|
+
properties: {
|
|
108
|
+
name: { type: "string", description: "Tool name (snake_case recommended)" },
|
|
109
|
+
description: { type: "string", description: "Tool description" },
|
|
110
|
+
inputSchema: { type: "object", description: "JSON Schema for tool input parameters" }
|
|
111
|
+
},
|
|
112
|
+
required: ["name", "description"]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
required: ["tools"]
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "toolrank_suggest",
|
|
121
|
+
description: "Generates specific improvement suggestions for MCP tool definitions to increase their ToolRank Score. Use this when you have a low score and want actionable fix recommendations. Returns rewritten description, improved schema, and estimated score improvement for each suggestion.",
|
|
122
|
+
inputSchema: {
|
|
123
|
+
type: "object",
|
|
124
|
+
properties: {
|
|
125
|
+
tool: {
|
|
126
|
+
type: "object",
|
|
127
|
+
description: "Single MCP tool definition to improve",
|
|
128
|
+
properties: {
|
|
129
|
+
name: { type: "string", description: "Tool name" },
|
|
130
|
+
description: { type: "string", description: "Current tool description" },
|
|
131
|
+
inputSchema: { type: "object", description: "Current JSON Schema" }
|
|
132
|
+
},
|
|
133
|
+
required: ["name", "description"]
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
required: ["tool"]
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
// Handle MCP protocol messages
|
|
142
|
+
function handleMessage(msg) {
|
|
143
|
+
const { method, params, id } = msg;
|
|
144
|
+
|
|
145
|
+
switch (method) {
|
|
146
|
+
case "initialize":
|
|
147
|
+
return {
|
|
148
|
+
jsonrpc: "2.0", id,
|
|
149
|
+
result: {
|
|
150
|
+
protocolVersion: "2024-11-05",
|
|
151
|
+
capabilities: { tools: {} },
|
|
152
|
+
serverInfo: { name: "toolrank", version: "0.1.1" }
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
case "tools/list":
|
|
157
|
+
return { jsonrpc: "2.0", id, result: { tools: TOOLS } };
|
|
158
|
+
|
|
159
|
+
case "tools/call": {
|
|
160
|
+
const toolName = params?.name;
|
|
161
|
+
const args = params?.arguments || {};
|
|
162
|
+
|
|
163
|
+
if (toolName === "toolrank_score") {
|
|
164
|
+
const tools = args.tools || [];
|
|
165
|
+
const results = tools.map(t => scoreTool(t));
|
|
166
|
+
const avg = results.length > 0
|
|
167
|
+
? Math.round(results.reduce((s, r) => s + r.total, 0) / results.length * 10) / 10
|
|
168
|
+
: 0;
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
jsonrpc: "2.0", id,
|
|
172
|
+
result: {
|
|
173
|
+
content: [{
|
|
174
|
+
type: "text",
|
|
175
|
+
text: JSON.stringify({ average_score: avg, tools: results }, null, 2)
|
|
176
|
+
}]
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (toolName === "toolrank_suggest") {
|
|
182
|
+
const tool = args.tool || {};
|
|
183
|
+
const scored = scoreTool(tool);
|
|
184
|
+
const suggestions = scored.issues.slice(0, 5).map(i => ({
|
|
185
|
+
dimension: i.dim,
|
|
186
|
+
issue: i.msg,
|
|
187
|
+
fix: i.fix,
|
|
188
|
+
estimated_impact: `+${i.impact} points`
|
|
189
|
+
}));
|
|
190
|
+
|
|
191
|
+
// Generate rewrite
|
|
192
|
+
let newDesc = tool.description || '';
|
|
193
|
+
if (newDesc.length < 50) {
|
|
194
|
+
const parts = (tool.name || '').split(/[_\-]/).filter(p => p.length > 1);
|
|
195
|
+
newDesc = `${(parts[0] || 'Performs').charAt(0).toUpperCase() + (parts[0] || 'performs').slice(1)}s ${parts.slice(1).join(' ') || 'an operation'}. Use this when you need to ${parts.slice(1).join(' ') || 'perform this action'}. Returns the result as structured data.`;
|
|
196
|
+
} else {
|
|
197
|
+
if (!/use this|when|useful for/i.test(newDesc)) newDesc += ` Use this when you need to ${(tool.name || '').replace(/[_\-]/g, ' ')}.`;
|
|
198
|
+
if (!/returns?|outputs?/i.test(newDesc)) newDesc += ' Returns the result as structured data.';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
jsonrpc: "2.0", id,
|
|
203
|
+
result: {
|
|
204
|
+
content: [{
|
|
205
|
+
type: "text",
|
|
206
|
+
text: JSON.stringify({
|
|
207
|
+
current_score: scored.total,
|
|
208
|
+
suggestions,
|
|
209
|
+
rewritten_description: newDesc
|
|
210
|
+
}, null, 2)
|
|
211
|
+
}]
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
jsonrpc: "2.0", id,
|
|
218
|
+
error: { code: -32601, message: `Unknown tool: ${toolName}` }
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
case "notifications/initialized":
|
|
223
|
+
case "ping":
|
|
224
|
+
return method === "ping" ? { jsonrpc: "2.0", id, result: {} } : null;
|
|
225
|
+
|
|
226
|
+
default:
|
|
227
|
+
return {
|
|
228
|
+
jsonrpc: "2.0", id,
|
|
229
|
+
error: { code: -32601, message: `Method not found: ${method}` }
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export default {
|
|
235
|
+
async fetch(request, env) {
|
|
236
|
+
const url = new URL(request.url);
|
|
237
|
+
|
|
238
|
+
// CORS
|
|
239
|
+
if (request.method === "OPTIONS") {
|
|
240
|
+
return new Response(null, {
|
|
241
|
+
headers: {
|
|
242
|
+
"Access-Control-Allow-Origin": "*",
|
|
243
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
244
|
+
"Access-Control-Allow-Headers": "Content-Type",
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Health check
|
|
250
|
+
if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/mcp")) {
|
|
251
|
+
return new Response(JSON.stringify({
|
|
252
|
+
name: "toolrank",
|
|
253
|
+
version: "0.1.1",
|
|
254
|
+
description: "ToolRank MCP Server — Score and optimize MCP tool definitions for AI agent discovery.",
|
|
255
|
+
mcp_endpoint: `${url.origin}/mcp`,
|
|
256
|
+
tools: TOOLS.map(t => t.name),
|
|
257
|
+
}), {
|
|
258
|
+
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// MCP endpoint
|
|
263
|
+
if (request.method === "POST" && (url.pathname === "/mcp" || url.pathname === "/")) {
|
|
264
|
+
try {
|
|
265
|
+
const body = await request.json();
|
|
266
|
+
|
|
267
|
+
// Handle batch
|
|
268
|
+
if (Array.isArray(body)) {
|
|
269
|
+
const results = body.map(msg => handleMessage(msg)).filter(r => r !== null);
|
|
270
|
+
return new Response(JSON.stringify(results), {
|
|
271
|
+
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const result = handleMessage(body);
|
|
276
|
+
if (result === null) {
|
|
277
|
+
return new Response(null, { status: 204 });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return new Response(JSON.stringify(result), {
|
|
281
|
+
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }
|
|
282
|
+
});
|
|
283
|
+
} catch (e) {
|
|
284
|
+
return new Response(JSON.stringify({
|
|
285
|
+
jsonrpc: "2.0",
|
|
286
|
+
error: { code: -32700, message: "Parse error" }
|
|
287
|
+
}), {
|
|
288
|
+
status: 400,
|
|
289
|
+
headers: { "Content-Type": "application/json" }
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return new Response("Not found", { status: 404 });
|
|
295
|
+
}
|
|
296
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toolrank/mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "ToolRank MCP Server — Score and optimize MCP tool definitions for AI agent discovery. The first ATO (Agent Tool Optimization) tool.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -12,14 +12,7 @@
|
|
|
12
12
|
"start": "node dist/index.js",
|
|
13
13
|
"dev": "tsx src/index.ts"
|
|
14
14
|
},
|
|
15
|
-
"keywords": [
|
|
16
|
-
"mcp",
|
|
17
|
-
"ato",
|
|
18
|
-
"agent-tool-optimization",
|
|
19
|
-
"toolrank",
|
|
20
|
-
"ai-agent",
|
|
21
|
-
"tool-scoring"
|
|
22
|
-
],
|
|
15
|
+
"keywords": "[\"mcp\",\"ato\",\"agent-tool-optimization\",\"toolrank\",\"ai-agent\",\"tool-scoring\",\"mcp-server\"]",
|
|
23
16
|
"author": "Hiroki Honda",
|
|
24
17
|
"license": "MIT",
|
|
25
18
|
"dependencies": {
|
|
@@ -28,5 +21,10 @@
|
|
|
28
21
|
"devDependencies": {
|
|
29
22
|
"tsx": "^4.0.0",
|
|
30
23
|
"typescript": "^5.5.0"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://toolrank.dev",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/imhiroki/toolrank"
|
|
31
29
|
}
|
|
32
30
|
}
|
package/src/index.ts
CHANGED
|
@@ -437,4 +437,17 @@ async function main() {
|
|
|
437
437
|
console.error("ToolRank MCP Server running on stdio");
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
-
|
|
440
|
+
// Smithery sandbox export
|
|
441
|
+
export function createSandboxServer() {
|
|
442
|
+
return server;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Default export must be a function for Smithery shttp
|
|
446
|
+
export default function() {
|
|
447
|
+
return server;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Run stdio when executed directly
|
|
451
|
+
if (process.argv[1]?.includes('index')) {
|
|
452
|
+
main().catch(console.error);
|
|
453
|
+
}
|