Skip to content

Commit 30a4032

Browse files
committed
verify fixes are working
Signed-off-by: Huamin Chen <[email protected]>
1 parent cef10c2 commit 30a4032

File tree

5 files changed

+64
-52
lines changed

5 files changed

+64
-52
lines changed

examples/mcp-classifier-server/README.md

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,41 @@ default_model: openai/gpt-oss-20b
6262
6363
**Tool Auto-Discovery:**
6464
The router automatically discovers classification tools from the MCP server by:
65+
6566
1. Listing available tools on connection
6667
2. Looking for common names: `classify_text`, `classify`, `categorize`, `categorize_text`
6768
3. Pattern matching for tools containing "classif" in name/description
6869
4. Optionally specify `tool_name` to use a specific tool
6970

71+
## Protocol API
72+
73+
This server implements the MCP classification protocol defined in:
74+
75+
```
76+
github.com/vllm-project/semantic-router/src/semantic-router/pkg/connectivity/mcp/api
77+
```
78+
79+
**Required Tools:**
80+
81+
1. **`list_categories`** - Returns `ListCategoriesResponse`:
82+
83+
```json
84+
{"categories": ["math", "science", "technology", ...]}
85+
```
86+
87+
2. **`classify_text`** - Returns `ClassifyResponse`:
88+
89+
```json
90+
{
91+
"class": 1,
92+
"confidence": 0.85,
93+
"model": "openai/gpt-oss-20b",
94+
"use_reasoning": true
95+
}
96+
```
97+
98+
See the `api` package for full type definitions and documentation.
99+
70100
## How It Works
71101

72102
**Intelligent Routing Rules:**
@@ -77,17 +107,6 @@ The router automatically discovers classification tools from the MCP server by:
77107
- Low confidence (<0.6) → `use_reasoning: true`
78108
- Default → `use_reasoning: true`
79109

80-
**Response Format:**
81-
82-
```json
83-
{
84-
"class": 1,
85-
"confidence": 0.85,
86-
"model": "openai/gpt-oss-20b",
87-
"use_reasoning": true
88-
}
89-
```
90-
91110
## Customization
92111

93112
Edit `CATEGORIES` to add categories:

examples/mcp-classifier-server/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ async def health_check(request):
530530
parser.add_argument(
531531
"--http", action="store_true", help="Run in HTTP mode instead of stdio"
532532
)
533-
parser.add_argument("--port", type=int, default=8090, help="HTTP port")
533+
parser.add_argument("--port", type=int, default=8090, help="HTTP port to listen on")
534534
args = parser.parse_args()
535535

536536
if args.http:

src/semantic-router/pkg/services/classification.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ func createLegacyClassifier(config *config.RouterConfig) (*classification.Classi
8585
var categoryMapping *classification.CategoryMapping
8686

8787
// Check if we should load categories from MCP server
88+
// Note: tool_name is optional and will be auto-discovered if not specified
8889
useMCPCategories := config.Classifier.CategoryModel.ModelID == "" &&
89-
config.Classifier.MCPCategoryModel.Enabled &&
90-
config.Classifier.MCPCategoryModel.ToolName != ""
90+
config.Classifier.MCPCategoryModel.Enabled
9191

9292
if useMCPCategories {
9393
// Categories will be loaded from MCP server during initialization

src/semantic-router/pkg/utils/classification/mcp_classifier.go

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
candle_binding "github.com/vllm-project/semantic-router/candle-binding"
1212
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/config"
1313
mcpclient "github.com/vllm-project/semantic-router/src/semantic-router/pkg/connectivity/mcp"
14+
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/connectivity/mcp/api"
1415
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/metrics"
1516
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/observability"
1617
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/utils/entropy"
@@ -45,7 +46,21 @@ type MCPCategoryInference interface {
4546
ListCategories(ctx context.Context) (*CategoryMapping, error)
4647
}
4748

48-
// MCPCategoryClassifier implements both MCPCategoryInitializer and MCPCategoryInference
49+
// MCPCategoryClassifier implements both MCPCategoryInitializer and MCPCategoryInference.
50+
//
51+
// Protocol Contract:
52+
// This client relies on the MCP server to respect the protocol defined in the
53+
// github.com/vllm-project/semantic-router/src/semantic-router/pkg/connectivity/mcp/api package.
54+
//
55+
// The MCP server must implement these tools:
56+
// 1. list_categories - Returns api.ListCategoriesResponse
57+
// 2. classify_text - Returns api.ClassifyResponse or api.ClassifyWithProbabilitiesResponse
58+
//
59+
// The MCP server controls both classification AND routing decisions. When the server returns
60+
// "model" and "use_reasoning" in the classification response, the router will use those values.
61+
// If not provided, the router falls back to the default_model configuration.
62+
//
63+
// For detailed type definitions and examples, see the api package documentation.
4964
type MCPCategoryClassifier struct {
5065
client mcpclient.MCPClient
5166
toolName string
@@ -202,13 +217,8 @@ func (m *MCPCategoryClassifier) Classify(ctx context.Context, text string) (cand
202217
return candle_binding.ClassResult{}, fmt.Errorf("MCP tool returned non-text content")
203218
}
204219

205-
// Parse JSON response: {"class": int, "confidence": float, "model": str, "use_reasoning": bool}
206-
var response struct {
207-
Class int `json:"class"`
208-
Confidence float32 `json:"confidence"`
209-
Model string `json:"model,omitempty"`
210-
UseReasoning *bool `json:"use_reasoning,omitempty"`
211-
}
220+
// Parse JSON response using the API type
221+
var response api.ClassifyResponse
212222
if err := json.Unmarshal([]byte(responseText), &response); err != nil {
213223
return candle_binding.ClassResult{}, fmt.Errorf("failed to parse MCP response: %w", err)
214224
}
@@ -256,14 +266,8 @@ func (m *MCPCategoryClassifier) ClassifyWithProbabilities(ctx context.Context, t
256266
return candle_binding.ClassResultWithProbs{}, fmt.Errorf("MCP tool returned non-text content")
257267
}
258268

259-
// Parse JSON response: {"class": int, "confidence": float, "probabilities": []float, "model": str, "use_reasoning": bool}
260-
var response struct {
261-
Class int `json:"class"`
262-
Confidence float32 `json:"confidence"`
263-
Probabilities []float32 `json:"probabilities"`
264-
Model string `json:"model,omitempty"`
265-
UseReasoning *bool `json:"use_reasoning,omitempty"`
266-
}
269+
// Parse JSON response using the API type
270+
var response api.ClassifyWithProbabilitiesResponse
267271
if err := json.Unmarshal([]byte(responseText), &response); err != nil {
268272
return candle_binding.ClassResultWithProbs{}, fmt.Errorf("failed to parse MCP response: %w", err)
269273
}
@@ -306,10 +310,8 @@ func (m *MCPCategoryClassifier) ListCategories(ctx context.Context) (*CategoryMa
306310
return nil, fmt.Errorf("MCP tool returned non-text content")
307311
}
308312

309-
// Parse JSON response: {"categories": ["cat1", "cat2", ...]}
310-
var response struct {
311-
Categories []string `json:"categories"`
312-
}
313+
// Parse JSON response using the API type
314+
var response api.ListCategoriesResponse
313315
if err := json.Unmarshal([]byte(responseText), &response); err != nil {
314316
return nil, fmt.Errorf("failed to parse MCP categories response: %w", err)
315317
}
@@ -342,10 +344,10 @@ func createMCPCategoryInference(initializer MCPCategoryInitializer) MCPCategoryI
342344
return nil
343345
}
344346

345-
// IsMCPCategoryEnabled checks if MCP-based category classification is properly configured
347+
// IsMCPCategoryEnabled checks if MCP-based category classification is properly configured.
348+
// Note: tool_name is optional and will be auto-discovered during initialization if not specified.
346349
func (c *Classifier) IsMCPCategoryEnabled() bool {
347-
return c.Config.Classifier.MCPCategoryModel.Enabled &&
348-
c.Config.Classifier.MCPCategoryModel.ToolName != ""
350+
return c.Config.Classifier.MCPCategoryModel.Enabled
349351
}
350352

351353
// initializeMCPCategoryClassifier initializes the MCP category classification model
@@ -455,13 +457,8 @@ func (c *Classifier) classifyCategoryMCPWithRouting(text string) (*MCPClassifica
455457
return nil, fmt.Errorf("MCP tool returned non-text content")
456458
}
457459

458-
// Parse JSON response with routing information
459-
var response struct {
460-
Class int `json:"class"`
461-
Confidence float32 `json:"confidence"`
462-
Model string `json:"model,omitempty"`
463-
UseReasoning *bool `json:"use_reasoning,omitempty"`
464-
}
460+
// Parse JSON response with routing information using the API type
461+
var response api.ClassifyResponse
465462
if err := json.Unmarshal([]byte(responseText), &response); err != nil {
466463
return nil, fmt.Errorf("failed to parse MCP response: %w", err)
467464
}

src/semantic-router/pkg/utils/classification/mcp_classifier_test.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -431,14 +431,10 @@ var _ = Describe("Classifier MCP Methods", func() {
431431
Expect(classifier.IsMCPCategoryEnabled()).To(BeFalse())
432432
})
433433

434-
It("should return false when tool name is empty", func() {
435-
classifier.Config.Classifier.MCPCategoryModel.ToolName = ""
436-
Expect(classifier.IsMCPCategoryEnabled()).To(BeFalse())
437-
})
438-
439-
// Note: IsMCPCategoryEnabled now only checks configuration, not runtime state.
440-
// Runtime checks (like initializer != nil) are handled separately in the actual
441-
// classification methods to maintain separation of concerns.
434+
// Note: tool_name is now optional and will be auto-discovered if not specified.
435+
// IsMCPCategoryEnabled only checks if MCP is enabled, not specific configuration details.
436+
// Runtime checks (like initializer != nil or successful connection) are handled
437+
// separately in the actual initialization and classification methods.
442438
})
443439

444440
Describe("classifyCategoryMCP", func() {

0 commit comments

Comments
 (0)