@toolrank/mcp-server 0.1.0 → 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 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,6 @@
1
+ {
2
+ "account": {
3
+ "id": "73621bc6ad8b976554bfe018413061e3",
4
+ "name": "Hiroki Honda"
5
+ }
6
+ }
@@ -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
+ };
@@ -0,0 +1,3 @@
1
+ name = "toolrank-mcp"
2
+ main = "worker.js"
3
+ compatibility_date = "2024-01-01"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toolrank/mcp-server",
3
- "version": "0.1.0",
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,7 +12,7 @@
12
12
  "start": "node dist/index.js",
13
13
  "dev": "tsx src/index.ts"
14
14
  },
15
- "keywords": ["mcp", "ato", "agent-tool-optimization", "toolrank", "ai-agent", "tool-scoring"],
15
+ "keywords": "[\"mcp\",\"ato\",\"agent-tool-optimization\",\"toolrank\",\"ai-agent\",\"tool-scoring\",\"mcp-server\"]",
16
16
  "author": "Hiroki Honda",
17
17
  "license": "MIT",
18
18
  "dependencies": {
@@ -21,5 +21,10 @@
21
21
  "devDependencies": {
22
22
  "tsx": "^4.0.0",
23
23
  "typescript": "^5.5.0"
24
+ },
25
+ "homepage": "https://toolrank.dev",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/imhiroki/toolrank"
24
29
  }
25
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
- main().catch(console.error);
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
+ }
package/dist/index.d.ts DELETED
@@ -1,13 +0,0 @@
1
- /**
2
- * ToolRank MCP Server v0.1
3
- *
4
- * Provides AI agents with tools to score and optimize MCP tool definitions.
5
- * This server's own tool definitions are designed to achieve ToolRank Score 100/100,
6
- * serving as a live demonstration of ATO (Agent Tool Optimization) principles.
7
- *
8
- * Tools:
9
- * toolrank_score — Analyze tool definitions and return quality scores
10
- * toolrank_compare — Compare scores against category averages
11
- * toolrank_suggest — Generate specific improvement suggestions
12
- */
13
- export {};
package/dist/index.js DELETED
@@ -1,397 +0,0 @@
1
- /**
2
- * ToolRank MCP Server v0.1
3
- *
4
- * Provides AI agents with tools to score and optimize MCP tool definitions.
5
- * This server's own tool definitions are designed to achieve ToolRank Score 100/100,
6
- * serving as a live demonstration of ATO (Agent Tool Optimization) principles.
7
- *
8
- * Tools:
9
- * toolrank_score — Analyze tool definitions and return quality scores
10
- * toolrank_compare — Compare scores against category averages
11
- * toolrank_suggest — Generate specific improvement suggestions
12
- */
13
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
14
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
- import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
16
- function scoreTool(tool) {
17
- const issues = [];
18
- const name = tool.name || "unknown";
19
- const desc = (tool.description || "").trim();
20
- const schema = tool.inputSchema || tool.input_schema || {};
21
- const props = schema.properties || {};
22
- const required = schema.required || [];
23
- const propKeys = Object.keys(props);
24
- // --- Clarity (max 35) ---
25
- let clarityScore = 0;
26
- const clarityMax = 6;
27
- let clarityPoints = 0;
28
- if (!desc) {
29
- issues.push({ dimension: "clarity", severity: "critical", message: "No description defined", fix: "Add a description explaining purpose, usage context, and return value", impact: 15 });
30
- }
31
- else {
32
- clarityPoints += 1;
33
- if (desc.length < 20) {
34
- issues.push({ dimension: "clarity", severity: "critical", message: `Description too short (${desc.length} chars)`, fix: "Expand to 80-200 chars with purpose and context", impact: 10 });
35
- clarityPoints += 0.2;
36
- }
37
- else if (desc.length >= 80 && desc.length <= 250) {
38
- clarityPoints += 1;
39
- }
40
- else if (desc.length >= 50) {
41
- clarityPoints += 0.7;
42
- if (desc.length < 80)
43
- issues.push({ dimension: "clarity", severity: "warning", message: `Description short (${desc.length} chars)`, fix: "Expand to 80-200 chars for optimal agent understanding", impact: 5 });
44
- }
45
- else {
46
- clarityPoints += 0.3;
47
- issues.push({ dimension: "clarity", severity: "warning", message: `Description short (${desc.length} chars)`, fix: "Expand to 80-200 chars", impact: 5 });
48
- }
49
- const descLower = desc.toLowerCase();
50
- if (/^(get|set|create|update|delete|search|find|list|fetch|send|this tool|retriev|return|provid|allow|enabl|perform|analyz|generat|calculat|check|validat|convert|extract|scor|compar|suggest|optimiz)/.test(descLower)) {
51
- clarityPoints += 1;
52
- }
53
- else {
54
- clarityPoints += 0.5;
55
- issues.push({ dimension: "clarity", severity: "warning", message: "No clear purpose verb at start", fix: "Start with a verb: 'Analyzes...', 'Returns...', 'Generates...'", impact: 5 });
56
- }
57
- if (/use this|when|useful for|ideal for|designed for|helps|allows|enables|for example/.test(descLower)) {
58
- clarityPoints += 1;
59
- }
60
- else {
61
- clarityPoints += 0.4;
62
- issues.push({ dimension: "clarity", severity: "warning", message: "No usage context", fix: "Add 'Use this when...' to help agents decide when to select this tool", impact: 6 });
63
- }
64
- if (/returns?|outputs?|produces|yields|result|response|provides|generates/.test(descLower)) {
65
- clarityPoints += 1;
66
- }
67
- else {
68
- clarityPoints += 0.5;
69
- issues.push({ dimension: "clarity", severity: "info", message: "No return value described", fix: "Add 'Returns...' to describe the output", impact: 3 });
70
- }
71
- const nameParts = name.toLowerCase().split(/[_\-]/).filter((p) => p.length > 2);
72
- const matchCount = nameParts.filter((p) => descLower.includes(p)).length;
73
- clarityPoints += nameParts.length > 0 && matchCount / nameParts.length >= 0.5 ? 1 : 0.5;
74
- }
75
- clarityScore = Math.round((clarityPoints / clarityMax) * 35 * 10) / 10;
76
- // --- Precision (max 25) ---
77
- let precisionPoints = 0;
78
- const precisionMax = 5;
79
- if (!schema.type) {
80
- issues.push({ dimension: "precision", severity: "critical", message: "No input schema defined", fix: "Add inputSchema with type definitions for all parameters", impact: 12 });
81
- }
82
- else {
83
- precisionPoints += 1;
84
- if (propKeys.length > 0) {
85
- const missingTypes = propKeys.filter(k => !props[k].type);
86
- if (missingTypes.length === 0)
87
- precisionPoints += 1;
88
- else {
89
- precisionPoints += Math.max(0, 1 - missingTypes.length / propKeys.length);
90
- issues.push({ dimension: "precision", severity: "warning", message: `Missing types: ${missingTypes.join(", ")}`, fix: "Add 'type' to each parameter", impact: 4 });
91
- }
92
- const missingDesc = propKeys.filter(k => !props[k].description);
93
- if (missingDesc.length === 0)
94
- precisionPoints += 1;
95
- else {
96
- precisionPoints += Math.max(0, 1 - missingDesc.length / propKeys.length);
97
- issues.push({ dimension: "precision", severity: "warning", message: `Missing param descriptions: ${missingDesc.join(", ")}`, fix: "Add 'description' to each parameter", impact: 5 });
98
- }
99
- precisionPoints += required.length > 0 ? 1 : 0.5;
100
- if (required.length === 0 && propKeys.length > 0) {
101
- issues.push({ dimension: "precision", severity: "info", message: "No required fields specified", fix: "Add 'required' array for mandatory parameters", impact: 3 });
102
- }
103
- precisionPoints += 1;
104
- }
105
- else {
106
- precisionPoints += 2;
107
- }
108
- }
109
- const precisionScore = Math.round((precisionPoints / precisionMax) * 25 * 10) / 10;
110
- // --- Efficiency (max 15) ---
111
- const tokenEst = JSON.stringify(tool).length / 4;
112
- let effRatio = tokenEst > 2000 ? 0.3 : tokenEst > 1000 ? 0.6 : tokenEst > 500 ? 0.8 : 1.0;
113
- if (tokenEst > 1000)
114
- issues.push({ dimension: "efficiency", severity: "warning", message: `~${Math.round(tokenEst)} tokens estimated`, fix: "Create a compact variant", impact: 3 });
115
- if (!/^[a-z][a-zA-Z0-9_]*$/.test(name)) {
116
- effRatio -= 0.15;
117
- issues.push({ dimension: "efficiency", severity: "warning", message: `Name '${name}' not snake_case`, fix: "Use snake_case like 'search_users'", impact: 3 });
118
- }
119
- const genericNames = new Set(["run", "execute", "do", "action", "tool", "function", "process", "handle"]);
120
- if (genericNames.has(name.toLowerCase())) {
121
- effRatio -= 0.15;
122
- issues.push({ dimension: "efficiency", severity: "warning", message: `Name '${name}' too generic`, fix: "Use specific name like 'create_pull_request'", impact: 5 });
123
- }
124
- const efficiencyScore = Math.round(Math.max(0, effRatio) * 15 * 10) / 10;
125
- // --- Findability (max 25, limited without registry) ---
126
- let findRatio = name.length < 4 ? 0.3 : 0.8;
127
- if (name.length < 4)
128
- issues.push({ dimension: "findability", severity: "warning", message: `Name '${name}' too short for discovery`, fix: "Use a longer, descriptive name", impact: 4 });
129
- const findabilityScore = Math.round(findRatio * 25 * 10) / 10;
130
- const total = Math.round((findabilityScore + clarityScore + precisionScore + efficiencyScore) * 10) / 10;
131
- let level, levelName;
132
- if (total >= 85) {
133
- level = 4;
134
- levelName = "Dominant";
135
- }
136
- else if (total >= 70) {
137
- level = 3;
138
- levelName = "Preferred";
139
- }
140
- else if (total >= 50) {
141
- level = 2;
142
- levelName = "Selectable";
143
- }
144
- else if (total >= 25) {
145
- level = 1;
146
- levelName = "Visible";
147
- }
148
- else {
149
- level = 0;
150
- levelName = "Absent";
151
- }
152
- return {
153
- name,
154
- total,
155
- level,
156
- levelName,
157
- dimensions: { findability: findabilityScore, clarity: clarityScore, precision: precisionScore, efficiency: efficiencyScore },
158
- issues: issues.sort((a, b) => b.impact - a.impact),
159
- };
160
- }
161
- // --- MCP Server Setup ---
162
- const server = new Server({ name: "toolrank", version: "0.1.0" }, { capabilities: { tools: {} } });
163
- // Tool definitions — these ARE the product. ATO Score 100/100 target.
164
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
165
- tools: [
166
- {
167
- name: "toolrank_score",
168
- description: "Analyzes MCP tool definitions and returns a ToolRank Score (0-100) measuring how likely AI agents are to discover and select each tool. Scores four dimensions: Findability (can agents find it?), Clarity (can agents understand it?), Precision (is the schema well-defined?), and Efficiency (is it token-efficient?). Use this when you want to evaluate the quality of your MCP server's tool definitions before publishing. Returns per-tool scores, maturity level (Absent/Visible/Selectable/Preferred/Dominant), specific issues found, and prioritized improvement suggestions with predicted score impact.",
169
- inputSchema: {
170
- type: "object",
171
- properties: {
172
- tools: {
173
- type: "array",
174
- description: "Array of MCP tool definition objects. Each object should have 'name' (string), 'description' (string), and optionally 'inputSchema' (JSON Schema object with properties, required, etc.)",
175
- items: {
176
- type: "object",
177
- properties: {
178
- name: { type: "string", description: "Tool name (e.g., 'create_issue')" },
179
- description: { type: "string", description: "Tool description text" },
180
- inputSchema: { type: "object", description: "JSON Schema for tool input parameters" },
181
- },
182
- required: ["name"],
183
- },
184
- },
185
- server_name: {
186
- type: "string",
187
- description: "Optional name of the MCP server being scored. Used in the report header.",
188
- default: "unnamed",
189
- },
190
- },
191
- required: ["tools"],
192
- },
193
- },
194
- {
195
- name: "toolrank_compare",
196
- description: "Compares a tool's ToolRank Score against ecosystem benchmarks. Use this after running toolrank_score to understand how your tools rank relative to the broader MCP ecosystem. Returns percentile ranking, dimension-by-dimension comparison against category averages, and specific areas where your tools underperform. Requires a prior toolrank_score result or raw tool definitions.",
197
- inputSchema: {
198
- type: "object",
199
- properties: {
200
- tools: {
201
- type: "array",
202
- description: "Array of MCP tool definition objects to compare",
203
- items: { type: "object" },
204
- },
205
- category: {
206
- type: "string",
207
- description: "Tool category for comparison (e.g., 'crm', 'database', 'devtools'). If omitted, compares against the full ecosystem average.",
208
- default: "all",
209
- },
210
- },
211
- required: ["tools"],
212
- },
213
- },
214
- {
215
- name: "toolrank_suggest",
216
- description: "Generates specific, actionable improvement suggestions for MCP tool definitions. Use this when you have a low ToolRank Score and want concrete text rewrites. Returns optimized versions of tool names, descriptions, and schema improvements ranked by expected score impact. Does not execute changes — returns suggestions for review.",
217
- inputSchema: {
218
- type: "object",
219
- properties: {
220
- tool: {
221
- type: "object",
222
- description: "Single MCP tool definition to improve. Must include 'name' and 'description'.",
223
- properties: {
224
- name: { type: "string", description: "Current tool name" },
225
- description: { type: "string", description: "Current tool description" },
226
- inputSchema: { type: "object", description: "Current input schema" },
227
- },
228
- required: ["name"],
229
- },
230
- focus: {
231
- type: "string",
232
- description: "Which dimension to prioritize improvements for",
233
- enum: ["findability", "clarity", "precision", "efficiency", "all"],
234
- default: "all",
235
- },
236
- },
237
- required: ["tool"],
238
- },
239
- },
240
- ],
241
- }));
242
- // Tool execution handlers
243
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
244
- const { name, arguments: args } = request.params;
245
- switch (name) {
246
- case "toolrank_score": {
247
- const tools = args.tools || [];
248
- const serverName = args.server_name || "unnamed";
249
- const results = tools.map((t) => scoreTool(t));
250
- const avgScore = results.length > 0
251
- ? Math.round((results.reduce((s, r) => s + r.total, 0) / results.length) * 10) / 10
252
- : 0;
253
- const allIssues = results.flatMap((r) => r.issues);
254
- const topFixes = allIssues
255
- .sort((a, b) => b.impact - a.impact)
256
- .slice(0, 5);
257
- // Server-level check: too many tools
258
- if (tools.length > 20) {
259
- topFixes.unshift({
260
- dimension: "efficiency",
261
- severity: "warning",
262
- message: `Server has ${tools.length} tools. Agent accuracy degrades past 15-20 tools`,
263
- fix: "Consolidate into 5-15 workflow-oriented tools",
264
- impact: 8,
265
- });
266
- }
267
- return {
268
- content: [{
269
- type: "text",
270
- text: JSON.stringify({
271
- server_name: serverName,
272
- average_score: avgScore,
273
- tool_count: tools.length,
274
- total_issues: allIssues.length,
275
- critical_issues: allIssues.filter((i) => i.severity === "critical").length,
276
- top_improvements: topFixes.slice(0, 3).map((i) => ({
277
- message: i.message,
278
- fix: i.fix,
279
- impact: `+${i.impact}pt`,
280
- })),
281
- tools: results.map((r) => ({
282
- name: r.name,
283
- score: r.total,
284
- level: `${r.level}: ${r.levelName}`,
285
- dimensions: r.dimensions,
286
- issues: r.issues.map((i) => ({
287
- severity: i.severity,
288
- message: i.message,
289
- fix: i.fix,
290
- impact: `+${i.impact}pt`,
291
- })),
292
- })),
293
- }, null, 2),
294
- }],
295
- };
296
- }
297
- case "toolrank_compare": {
298
- const tools = args.tools || [];
299
- const category = args.category || "all";
300
- const results = tools.map((t) => scoreTool(t));
301
- const avgScore = results.length > 0
302
- ? Math.round((results.reduce((s, r) => s + r.total, 0) / results.length) * 10) / 10
303
- : 0;
304
- // Ecosystem benchmarks (from research data, will be replaced with live DB data)
305
- const benchmarks = {
306
- all: { avg: 42, median: 38, p75: 62, p90: 78 },
307
- // Categories will be populated from scan DB
308
- };
309
- const bench = benchmarks[category] || benchmarks.all;
310
- let percentile;
311
- if (avgScore >= bench.p90)
312
- percentile = 95;
313
- else if (avgScore >= bench.p75)
314
- percentile = 80;
315
- else if (avgScore >= bench.median)
316
- percentile = 55;
317
- else if (avgScore >= bench.avg)
318
- percentile = 40;
319
- else
320
- percentile = 20;
321
- return {
322
- content: [{
323
- type: "text",
324
- text: JSON.stringify({
325
- your_score: avgScore,
326
- category,
327
- percentile: `Top ${100 - percentile}%`,
328
- benchmark: bench,
329
- comparison: {
330
- vs_average: `${avgScore > bench.avg ? "+" : ""}${Math.round(avgScore - bench.avg)} points`,
331
- vs_median: `${avgScore > bench.median ? "+" : ""}${Math.round(avgScore - bench.median)} points`,
332
- },
333
- note: "Benchmarks based on ecosystem scan data. Live rankings available at toolrank.dev/ranking",
334
- }, null, 2),
335
- }],
336
- };
337
- }
338
- case "toolrank_suggest": {
339
- const tool = args.tool || {};
340
- const focus = args.focus || "all";
341
- const result = scoreTool(tool);
342
- const suggestions = [];
343
- // Generate concrete suggestions based on issues
344
- for (const issue of result.issues) {
345
- if (focus !== "all" && issue.dimension !== focus)
346
- continue;
347
- const suggestion = {
348
- dimension: issue.dimension,
349
- current_problem: issue.message,
350
- suggested_fix: issue.fix,
351
- expected_impact: `+${issue.impact}pt`,
352
- };
353
- // Generate concrete rewrite for description issues
354
- if (issue.dimension === "clarity" && tool.name) {
355
- if (issue.message.includes("No description") || issue.message.includes("too short")) {
356
- suggestion.example_rewrite = `${tool.name.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase())} — [describe what it does]. Use this when [describe use case]. Returns [describe output format].`;
357
- }
358
- if (issue.message.includes("No clear purpose verb")) {
359
- suggestion.example_rewrite = `Retrieves/Creates/Searches for [object]. ${tool.description || ""}`;
360
- }
361
- if (issue.message.includes("No usage context")) {
362
- suggestion.example_rewrite = `${tool.description || ""} Use this when you need to [specific scenario].`;
363
- }
364
- }
365
- suggestions.push(suggestion);
366
- }
367
- return {
368
- content: [{
369
- type: "text",
370
- text: JSON.stringify({
371
- tool_name: tool.name,
372
- current_score: result.total,
373
- current_level: `${result.level}: ${result.levelName}`,
374
- suggestions: suggestions.sort((a, b) => {
375
- const impA = parseInt(a.expected_impact) || 0;
376
- const impB = parseInt(b.expected_impact) || 0;
377
- return impB - impA;
378
- }),
379
- estimated_score_after_fixes: Math.min(100, result.total + suggestions.reduce((s, sg) => s + (parseInt(sg.expected_impact) || 0), 0)),
380
- }, null, 2),
381
- }],
382
- };
383
- }
384
- default:
385
- return {
386
- content: [{ type: "text", text: `Unknown tool: ${name}` }],
387
- isError: true,
388
- };
389
- }
390
- });
391
- // --- Start Server ---
392
- async function main() {
393
- const transport = new StdioServerTransport();
394
- await server.connect(transport);
395
- console.error("ToolRank MCP Server running on stdio");
396
- }
397
- main().catch(console.error);
package/smithery.yaml DELETED
@@ -1,17 +0,0 @@
1
- name: toolrank
2
- displayName: ToolRank
3
- description: Score and optimize MCP tool definitions for AI agent discovery. Analyze your tools across Findability, Clarity, Precision, and Efficiency to maximize agent selection probability.
4
- homepage: https://toolrank.dev
5
- repository: https://github.com/imhiroki/toolrank
6
- tags:
7
- - developer-tools
8
- - optimization
9
- - scoring
10
- - mcp
11
- - ato
12
- startCommand:
13
- type: stdio
14
- configSchema:
15
- type: object
16
- properties: {}
17
- commandFunction: npx -y @toolrank/mcp-server
package/tsconfig.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "resolveJsonModule": true,
12
- "declaration": true
13
- },
14
- "include": ["src/**/*"]
15
- }