@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 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.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
- 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
+ }