chore: import upstream snapshot with attribution
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:40:33 +08:00
commit e071084ebe
982 changed files with 160368 additions and 0 deletions
+453
View File
@@ -0,0 +1,453 @@
# MCP CLI Command Examples
This document provides examples of using the `micro mcp` commands for AI agent integration.
## Table of Contents
- [List Available Tools](#list-available-tools)
- [Test a Tool](#test-a-tool)
- [Generate Documentation](#generate-documentation)
- [Export to Different Formats](#export-to-different-formats)
## Prerequisites
You need at least one microservice running with the go-micro framework. The service will automatically be discovered via the registry (mdns by default).
Example service:
```bash
cd examples/mcp/hello
go run main.go
```
## List Available Tools
### Human-readable list
```bash
micro mcp list
```
Output:
```
Available MCP Tools:
Service: greeter
• greeter.Greeter.SayHello
Total: 1 tools
```
### JSON output
```bash
micro mcp list --json
```
Output:
```json
{
"count": 1,
"tools": [
{
"description": "Call SayHello on greeter service",
"endpoint": "Greeter.SayHello",
"name": "greeter.Greeter.SayHello",
"service": "greeter"
}
]
}
```
## Test a Tool
### Basic test
```bash
micro mcp test greeter.Greeter.SayHello '{"name": "Alice"}'
```
Output:
```
Testing tool: greeter.Greeter.SayHello
Service: greeter
Endpoint: Greeter.SayHello
Input: {"name": "Alice"}
✅ Call successful!
Response:
{
"message": "Hello Alice!"
}
```
### Test with default empty input
```bash
micro mcp test greeter.Greeter.SayHello
```
This will call the tool with an empty JSON object `{}`.
## Generate Documentation
### Markdown documentation (stdout)
```bash
micro mcp docs
```
Output:
```markdown
# MCP Tools Documentation
Generated: 2026-02-13 14:30:00
Total Tools: 1
## Service: greeter
### greeter.Greeter.SayHello
**Description:** Greets a person by name. Returns a friendly greeting message.
**Example Input:**
\`\`\`json
{"name": "Alice"}
\`\`\`
```
### Markdown documentation (save to file)
```bash
micro mcp docs --output mcp-tools.md
```
This creates a `mcp-tools.md` file with the documentation.
### JSON documentation
```bash
micro mcp docs --format json
```
Output:
```json
{
"count": 1,
"tools": [
{
"description": "Greets a person by name. Returns a friendly greeting message.",
"endpoint": "Greeter.SayHello",
"example": "{\"name\": \"Alice\"}",
"metadata": {
"description": "Greets a person by name. Returns a friendly greeting message.",
"example": "{\"name\": \"Alice\"}"
},
"name": "greeter.Greeter.SayHello",
"scopes": null,
"service": "greeter"
}
]
}
```
### JSON documentation (save to file)
```bash
micro mcp docs --format json --output tools.json
```
## Export to Different Formats
### Export to LangChain (Python)
Generate Python code with LangChain tool definitions:
```bash
micro mcp export langchain
```
Output:
```python
# LangChain Tools for Go Micro Services
# Auto-generated from MCP service discovery
from langchain.tools import Tool
import requests
import json
# Configure your MCP gateway endpoint
MCP_GATEWAY_URL = 'http://localhost:3000/mcp'
def call_mcp_tool(tool_name, arguments):
"""Call an MCP tool via HTTP gateway"""
response = requests.post(
f'{MCP_GATEWAY_URL}/call',
json={'name': tool_name, 'arguments': arguments}
)
response.raise_for_status()
return response.json()
# Define tools
tools = []
def greeter_Greeter_SayHello(arguments: str) -> str:
"""Greets a person by name. Returns a friendly greeting message."""
args = json.loads(arguments) if isinstance(arguments, str) else arguments
return json.dumps(call_mcp_tool('greeter.Greeter.SayHello', args))
tools.append(Tool(
name='greeter.Greeter.SayHello',
func=greeter_Greeter_SayHello,
description='Greets a person by name. Returns a friendly greeting message.'
))
# Example usage:
# from langchain.agents import initialize_agent, AgentType
# from langchain.llms import OpenAI
#
# llm = OpenAI(temperature=0)
# agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION)
# agent.run('Your query here')
```
Save to file:
```bash
micro mcp export langchain --output langchain_tools.py
```
### Export to OpenAPI 3.0
Generate an OpenAPI specification:
```bash
micro mcp export openapi
```
Output:
```json
{
"components": {
"securitySchemes": {
"bearerAuth": {
"scheme": "bearer",
"type": "http"
}
}
},
"info": {
"description": "Auto-generated OpenAPI spec from MCP service discovery",
"title": "Go Micro MCP Services",
"version": "1.0.0"
},
"openapi": "3.0.0",
"paths": {
"/mcp/call/greeter/Greeter/SayHello": {
"post": {
"description": "Greets a person by name. Returns a friendly greeting message.",
"operationId": "greeter_Greeter_SayHello",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": "Successful response"
}
},
"summary": "greeter.Greeter.SayHello"
}
}
},
"servers": [
{
"description": "MCP Gateway",
"url": "http://localhost:3000"
}
]
}
```
Save to file:
```bash
micro mcp export openapi --output openapi.json
```
### Export to raw JSON
Export raw tool definitions:
```bash
micro mcp export json
```
This is similar to `micro mcp docs --format json` but specifically for export purposes.
Save to file:
```bash
micro mcp export json --output tools.json
```
## Using with Different Registries
By default, the commands use mdns registry. You can specify a different registry:
```bash
# Using consul
micro mcp list --registry consul --registry_address consul:8500
# Using etcd
micro mcp list --registry etcd --registry_address etcd:2379
```
## Integration Examples
### Using LangChain Export with Claude
1. Export your tools to LangChain format:
```bash
micro mcp export langchain --output my_tools.py
```
2. Use in your Python agent:
```python
from my_tools import tools
from langchain.agents import initialize_agent, AgentType
from langchain.chat_models import ChatAnthropic
llm = ChatAnthropic(model="claude-3-sonnet-20240229")
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION)
result = agent.run("Greet Alice")
print(result)
```
### Using OpenAPI Export with GPT
1. Export to OpenAPI:
```bash
micro mcp export openapi --output openapi.json
```
2. Upload to ChatGPT as a custom GPT action or use with OpenAI Assistants API.
### Documentation for AI Agents
Generate documentation that AI agents can read to understand your services:
```bash
micro mcp docs --format json --output service-catalog.json
```
This JSON file can be fed to AI agents for service discovery and understanding.
## Advanced Usage
### Piping and Processing
You can pipe the output to other tools:
```bash
# Count tools per service
micro mcp list --json | jq '.tools | group_by(.service) | map({service: .[0].service, count: length})'
# Extract all tool names
micro mcp list --json | jq -r '.tools[].name'
# Filter tools by service
micro mcp list --json | jq '.tools[] | select(.service == "greeter")'
```
### Monitoring and CI/CD
Use these commands in your CI/CD pipeline:
```bash
# Validate all services are discoverable
SERVICE_COUNT=$(micro mcp list --json | jq '.count')
if [ "$SERVICE_COUNT" -lt 5 ]; then
echo "Error: Expected at least 5 services, found $SERVICE_COUNT"
exit 1
fi
# Generate documentation on each deployment
micro mcp docs --output docs/mcp-services.md
git add docs/mcp-services.md
git commit -m "Update MCP service documentation"
```
### Testing in Development
Create a script to test all your tools:
```bash
#!/bin/bash
# test-all-tools.sh
TOOLS=$(micro mcp list --json | jq -r '.tools[].name')
for tool in $TOOLS; do
echo "Testing $tool..."
micro mcp test "$tool" "{}" || echo "Failed: $tool"
done
```
## Troubleshooting
### No tools found
If `micro mcp list` shows 0 tools:
1. Verify services are running:
```bash
ps aux | grep "your-service"
```
2. Check registry (mdns might need time to discover):
```bash
# Wait a few seconds and try again
sleep 3
micro mcp list
```
3. Use a different registry if mdns is unreliable:
```bash
# Start services with consul
micro --registry consul server
# List with consul
micro mcp list --registry consul
```
### Service not responding in tests
If `micro mcp test` fails:
1. Verify the tool name is correct:
```bash
micro mcp list
```
2. Check the JSON input format:
```bash
# Invalid
micro mcp test service.Handler.Method '{invalid}'
# Valid
micro mcp test service.Handler.Method '{"key": "value"}'
```
3. Check service logs for errors.
## Next Steps
- Read the [MCP Documentation](../../gateway/mcp/DOCUMENTATION.md)
- Try the [MCP Examples](../../examples/mcp/README.md)
- Learn about [Tool Scopes and Security](../../gateway/mcp/DOCUMENTATION.md#authentication-and-scopes)
- Explore [Agent SDKs](#) (coming soon)
+846
View File
@@ -0,0 +1,846 @@
// Package mcp provides the 'micro mcp' command for MCP server management
package mcp
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/urfave/cli/v2"
"go-micro.dev/v6/client"
"go-micro.dev/v6/cmd"
"go-micro.dev/v6/codec/bytes"
"go-micro.dev/v6/gateway/mcp"
"go-micro.dev/v6/registry"
"go-micro.dev/v6/wrapper/x402"
)
func init() {
cmd.Register(&cli.Command{
Name: "mcp",
Usage: "MCP server management",
Description: `Manage MCP (Model Context Protocol) server for AI agent integration.
Examples:
# Start MCP server (stdio for Claude Code)
micro mcp serve
# Start MCP server with HTTP/SSE
micro mcp serve --address :3000
# List available tools
micro mcp list
# Test a tool
micro mcp test users.Users.Get
The 'micro mcp' command exposes your microservices as AI-accessible tools via the
Model Context Protocol (MCP). This enables Claude Code, ChatGPT, and other AI agents
to discover and call your services automatically.
For Claude Code integration, add to your config:
{
"mcpServers": {
"my-services": {
"command": "micro",
"args": ["mcp", "serve"]
}
}
}`,
Subcommands: []*cli.Command{
{
Name: "serve",
Usage: "Start MCP server",
Description: `Start an MCP server to expose microservices as AI tools.
By default, uses stdio transport (for Claude Code and local AI tools).
Use --address for HTTP/SSE transport (for web-based agents).
Examples:
# Stdio transport (for Claude Code)
micro mcp serve
# HTTP/SSE transport
micro mcp serve --address :3000
# Custom registry
micro mcp serve --registry consul --registry_address consul:8500`,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "address",
Usage: "HTTP address to listen on (e.g., :3000). If not set, uses stdio.",
},
&cli.StringFlag{
Name: "registry",
Usage: "Registry for service discovery (mdns, consul, etcd)",
Value: "mdns",
},
&cli.StringFlag{
Name: "registry_address",
Usage: "Registry address (e.g., consul:8500)",
},
&cli.StringFlag{
Name: "x402_pay_to",
Usage: "Enable x402 payments for tool calls; the address payments are sent to",
},
&cli.StringFlag{
Name: "x402_amount",
Usage: "Default amount required per tool call, in the asset's smallest unit (e.g. 10000 = 0.01 USDC)",
},
&cli.StringFlag{
Name: "x402_network",
Usage: "Payment network: base (default), solana, ...",
Value: "base",
},
&cli.StringFlag{
Name: "x402_facilitator",
Usage: "x402 facilitator URL (Coinbase CDP, Alchemy, or self-hosted)",
},
&cli.StringFlag{
Name: "x402_config",
Usage: "Path to an x402 config file (payTo, network, asset, amount, per-tool amounts); overrides the x402_* flags",
},
},
Action: serveAction,
},
{
Name: "list",
Usage: "List available tools",
Description: `List all tools available via MCP.
Each service endpoint is exposed as a tool that AI agents can call.
Example:
micro mcp list`,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "registry",
Usage: "Registry for service discovery (mdns, consul, etcd)",
Value: "mdns",
},
&cli.StringFlag{
Name: "registry_address",
Usage: "Registry address",
},
&cli.BoolFlag{
Name: "json",
Usage: "Output as JSON",
},
},
Action: listAction,
},
{
Name: "test",
Usage: "Test a tool",
Description: `Test calling a specific tool.
Example:
micro mcp test users.Users.Get '{"id": "123"}'`,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "registry",
Usage: "Registry for service discovery",
Value: "mdns",
},
&cli.StringFlag{
Name: "registry_address",
Usage: "Registry address",
},
},
Action: testAction,
},
{
Name: "docs",
Usage: "Generate MCP documentation",
Description: `Generate documentation for all available MCP tools.
The documentation includes tool names, descriptions, parameters, and examples
extracted from service metadata and Go comments.
Examples:
# Generate markdown documentation
micro mcp docs
# Generate JSON documentation
micro mcp docs --format json
# Save to file
micro mcp docs --output mcp-tools.md`,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "registry",
Usage: "Registry for service discovery",
Value: "mdns",
},
&cli.StringFlag{
Name: "registry_address",
Usage: "Registry address",
},
&cli.StringFlag{
Name: "format",
Usage: "Output format (markdown, json)",
Value: "markdown",
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Output file (default: stdout)",
},
},
Action: docsAction,
},
{
Name: "export",
Usage: "Export tools to different formats",
Description: `Export MCP tools to various agent framework formats.
Supported formats:
- langchain: LangChain tool definitions (Python)
- openapi: OpenAPI 3.0 specification
- json: Raw JSON tool definitions
Examples:
# Export to LangChain format
micro mcp export langchain
# Export to OpenAPI
micro mcp export openapi --output openapi.yaml
# Export raw JSON
micro mcp export json`,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "registry",
Usage: "Registry for service discovery",
Value: "mdns",
},
&cli.StringFlag{
Name: "registry_address",
Usage: "Registry address",
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Output file (default: stdout)",
},
},
Action: exportAction,
},
},
})
}
// serveAction starts the MCP server
func serveAction(ctx *cli.Context) error {
// Get registry
reg := registry.DefaultRegistry
if regName := ctx.String("registry"); regName != "" {
// TODO: Support other registries (consul, etcd)
if regName != "mdns" {
return fmt.Errorf("registry %s not yet supported, use mdns", regName)
}
}
// Create MCP server options
opts := mcp.Options{
Registry: reg,
Address: ctx.String("address"),
Context: context.Background(),
Logger: log.Default(),
}
// Opt-in x402 payments: a config file (per-tool amounts) or flags.
if cfgPath := ctx.String("x402_config"); cfgPath != "" {
cfg, err := x402.LoadConfig(cfgPath)
if err != nil {
return err
}
opts.Payment = cfg
} else if payTo := ctx.String("x402_pay_to"); payTo != "" {
opts.Payment = &x402.Config{
PayTo: payTo,
Amount: ctx.String("x402_amount"),
Network: ctx.String("x402_network"),
FacilitatorURL: ctx.String("x402_facilitator"),
}
}
// Handle shutdown gracefully
ctx2, cancel := context.WithCancel(opts.Context)
opts.Context = ctx2
defer cancel()
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigChan
cancel()
}()
// Start MCP server
return mcp.Serve(opts)
}
// listAction lists available tools
func listAction(ctx *cli.Context) error {
// Get registry
reg := registry.DefaultRegistry
// Create temporary MCP server to discover tools
opts := mcp.Options{
Registry: reg,
Context: context.Background(),
Logger: log.New(os.Stderr, "", 0), // Log to stderr so stdout is clean
}
// Discover services
services, err := opts.Registry.ListServices()
if err != nil {
return fmt.Errorf("failed to list services: %w", err)
}
if ctx.Bool("json") {
// JSON output
var tools []map[string]interface{}
for _, svc := range services {
fullSvcs, err := opts.Registry.GetService(svc.Name)
if err != nil || len(fullSvcs) == 0 {
continue
}
for _, ep := range fullSvcs[0].Endpoints {
tools = append(tools, map[string]interface{}{
"name": fmt.Sprintf("%s.%s", svc.Name, ep.Name),
"service": svc.Name,
"endpoint": ep.Name,
"description": fmt.Sprintf("Call %s on %s service", ep.Name, svc.Name),
})
}
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(map[string]interface{}{
"tools": tools,
"count": len(tools),
})
}
// Human-readable output
fmt.Println()
fmt.Println(" \033[1mmicro mcp tools\033[0m")
fmt.Println()
toolCount := 0
for _, svc := range services {
fullSvcs, err := opts.Registry.GetService(svc.Name)
if err != nil || len(fullSvcs) == 0 {
continue
}
fmt.Printf(" \033[1m%s\033[0m\n", svc.Name)
for _, ep := range fullSvcs[0].Endpoints {
toolName := fmt.Sprintf("%s.%s", svc.Name, ep.Name)
fmt.Printf(" \033[32m●\033[0m %s\n", toolName)
toolCount++
}
fmt.Println()
}
fmt.Printf(" \033[2m%d tools\033[0m\n\n", toolCount)
return nil
}
// testAction tests a specific tool
func testAction(ctx *cli.Context) error {
if ctx.Args().Len() < 1 {
return fmt.Errorf("usage: micro mcp test <tool-name> [input-json]")
}
toolName := ctx.Args().First()
inputJSON := "{}"
if ctx.Args().Len() > 1 {
inputJSON = ctx.Args().Get(1)
}
// Validate input JSON
var inputData map[string]interface{}
if err := json.Unmarshal([]byte(inputJSON), &inputData); err != nil {
return fmt.Errorf("invalid JSON input: %w", err)
}
// Get registry
reg := registry.DefaultRegistry
if regName := ctx.String("registry"); regName != "" {
if regName != "mdns" {
return fmt.Errorf("registry %s not yet supported, use mdns", regName)
}
}
// Create MCP options
opts := mcp.Options{
Registry: reg,
Context: context.Background(),
Logger: log.New(os.Stderr, "", 0),
}
// Parse tool name (format: "service.endpoint" or "service.Handler.Method")
parts := parseTool(toolName)
if len(parts) < 2 {
return fmt.Errorf("invalid tool name format. Expected: service.endpoint or service.Handler.Method")
}
serviceName := parts[0]
endpointName := parts[1]
// If tool name has 3 parts, combine last two for endpoint (e.g., Handler.Method)
if len(parts) == 3 {
endpointName = parts[1] + "." + parts[2]
}
// Discover the tool from registry
services, err := opts.Registry.GetService(serviceName)
if err != nil || len(services) == 0 {
return fmt.Errorf("service %s not found: %w", serviceName, err)
}
// Find the endpoint
var endpoint *registry.Endpoint
for _, ep := range services[0].Endpoints {
if ep.Name == endpointName {
endpoint = ep
break
}
}
if endpoint == nil {
return fmt.Errorf("endpoint %s not found in service %s", endpointName, serviceName)
}
// Display test info
fmt.Printf("Testing tool: %s\n", toolName)
fmt.Printf("Service: %s\n", serviceName)
fmt.Printf("Endpoint: %s\n", endpointName)
fmt.Printf("Input: %s\n\n", inputJSON)
// Convert input to JSON bytes for RPC call
inputBytes, err := json.Marshal(inputData)
if err != nil {
return fmt.Errorf("failed to marshal input: %w", err)
}
// Make RPC call using bytes codec
c := opts.Client
if c == nil {
c = client.DefaultClient
}
// Create request with bytes frame
req := c.NewRequest(serviceName, endpointName, &bytes.Frame{Data: inputBytes})
// Make the call
var rsp bytes.Frame
if err := c.Call(opts.Context, req, &rsp); err != nil {
fmt.Printf("❌ Call failed: %v\n", err)
return err
}
// Parse and display response
fmt.Println("✅ Call successful!")
fmt.Println("\nResponse:")
// Try to pretty-print JSON response
var result interface{}
if err := json.Unmarshal(rsp.Data, &result); err == nil {
prettyJSON, err := json.MarshalIndent(result, "", " ")
if err == nil {
fmt.Println(string(prettyJSON))
} else {
fmt.Println(string(rsp.Data))
}
} else {
// Not JSON, print raw
fmt.Println(string(rsp.Data))
}
return nil
}
// parseTool splits a tool name into service and endpoint parts
func parseTool(toolName string) []string {
return strings.Split(toolName, ".")
}
// docsAction generates documentation for MCP tools
func docsAction(ctx *cli.Context) error {
// Get registry
reg := registry.DefaultRegistry
// Create temporary MCP server to discover tools
opts := mcp.Options{
Registry: reg,
Context: context.Background(),
Logger: log.New(os.Stderr, "", 0),
}
// Discover services
services, err := opts.Registry.ListServices()
if err != nil {
return fmt.Errorf("failed to list services: %w", err)
}
format := ctx.String("format")
outputFile := ctx.String("output")
// Prepare output writer
writer := os.Stdout
if outputFile != "" {
f, err := os.Create(outputFile)
if err != nil {
return fmt.Errorf("failed to create output file: %w", err)
}
defer f.Close()
writer = f
}
// Collect all tools with metadata
type ToolDoc struct {
Name string `json:"name"`
Service string `json:"service"`
Endpoint string `json:"endpoint"`
Description string `json:"description"`
Example string `json:"example,omitempty"`
Scopes []string `json:"scopes,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
var tools []ToolDoc
for _, svc := range services {
fullSvcs, err := opts.Registry.GetService(svc.Name)
if err != nil || len(fullSvcs) == 0 {
continue
}
for _, ep := range fullSvcs[0].Endpoints {
toolDoc := ToolDoc{
Name: fmt.Sprintf("%s.%s", svc.Name, ep.Name),
Service: svc.Name,
Endpoint: ep.Name,
Description: fmt.Sprintf("Call %s on %s service", ep.Name, svc.Name),
Metadata: ep.Metadata,
}
// Extract description from metadata if available
if desc, ok := ep.Metadata["description"]; ok {
toolDoc.Description = desc
}
// Extract example from metadata if available
if example, ok := ep.Metadata["example"]; ok {
toolDoc.Example = example
}
// Extract scopes from metadata if available
if scopesStr, ok := ep.Metadata["scopes"]; ok && scopesStr != "" {
toolDoc.Scopes = strings.Split(scopesStr, ",")
}
tools = append(tools, toolDoc)
}
}
// Generate output based on format
switch format {
case "json":
enc := json.NewEncoder(writer)
enc.SetIndent("", " ")
return enc.Encode(map[string]interface{}{
"tools": tools,
"count": len(tools),
})
case "markdown":
fmt.Fprintf(writer, "# MCP Tools Documentation\n\n")
fmt.Fprintf(writer, "Generated: %s\n\n", time.Now().Format("2006-01-02 15:04:05"))
fmt.Fprintf(writer, "Total Tools: %d\n\n", len(tools))
// Group by service
serviceMap := make(map[string][]ToolDoc)
for _, tool := range tools {
serviceMap[tool.Service] = append(serviceMap[tool.Service], tool)
}
for service, serviceTools := range serviceMap {
fmt.Fprintf(writer, "## Service: %s\n\n", service)
for _, tool := range serviceTools {
fmt.Fprintf(writer, "### %s\n\n", tool.Name)
fmt.Fprintf(writer, "**Description:** %s\n\n", tool.Description)
if len(tool.Scopes) > 0 {
fmt.Fprintf(writer, "**Required Scopes:** %s\n\n", strings.Join(tool.Scopes, ", "))
}
if tool.Example != "" {
fmt.Fprintf(writer, "**Example Input:**\n```json\n%s\n```\n\n", tool.Example)
}
}
}
return nil
default:
return fmt.Errorf("unsupported format: %s (supported: markdown, json)", format)
}
}
// exportAction exports tools to different formats
func exportAction(ctx *cli.Context) error {
if ctx.Args().Len() < 1 {
return fmt.Errorf("usage: micro mcp export <format>\nSupported formats: langchain, openapi, json")
}
exportFormat := ctx.Args().First()
// Get registry
reg := registry.DefaultRegistry
// Create temporary MCP server to discover tools
opts := mcp.Options{
Registry: reg,
Context: context.Background(),
Logger: log.New(os.Stderr, "", 0),
}
// Discover services
services, err := opts.Registry.ListServices()
if err != nil {
return fmt.Errorf("failed to list services: %w", err)
}
outputFile := ctx.String("output")
// Prepare output writer
writer := os.Stdout
if outputFile != "" {
f, err := os.Create(outputFile)
if err != nil {
return fmt.Errorf("failed to create output file: %w", err)
}
defer f.Close()
writer = f
}
switch exportFormat {
case "langchain":
return exportLangChain(writer, services, opts)
case "openapi":
return exportOpenAPI(writer, services, opts)
case "json":
return exportJSON(writer, services, opts)
default:
return fmt.Errorf("unsupported export format: %s\nSupported: langchain, openapi, json", exportFormat)
}
}
// exportLangChain exports tools in LangChain format (Python)
func exportLangChain(writer *os.File, services []*registry.Service, opts mcp.Options) error {
fmt.Fprintf(writer, "# LangChain Tools for Go Micro Services\n")
fmt.Fprintf(writer, "# Auto-generated from MCP service discovery\n\n")
fmt.Fprintf(writer, "from langchain.tools import Tool\n")
fmt.Fprintf(writer, "import requests\nimport json\n\n")
fmt.Fprintf(writer, "# Configure your MCP gateway endpoint\n")
fmt.Fprintf(writer, "MCP_GATEWAY_URL = 'http://localhost:3000/mcp'\n\n")
fmt.Fprintf(writer, "def call_mcp_tool(tool_name, arguments):\n")
fmt.Fprintf(writer, " \"\"\"Call an MCP tool via HTTP gateway\"\"\"\n")
fmt.Fprintf(writer, " response = requests.post(\n")
fmt.Fprintf(writer, " f'{MCP_GATEWAY_URL}/call',\n")
fmt.Fprintf(writer, " json={'name': tool_name, 'arguments': arguments}\n")
fmt.Fprintf(writer, " )\n")
fmt.Fprintf(writer, " response.raise_for_status()\n")
fmt.Fprintf(writer, " return response.json()\n\n")
fmt.Fprintf(writer, "# Define tools\n")
fmt.Fprintf(writer, "tools = []\n\n")
for _, svc := range services {
fullSvcs, err := opts.Registry.GetService(svc.Name)
if err != nil || len(fullSvcs) == 0 {
continue
}
for _, ep := range fullSvcs[0].Endpoints {
toolName := fmt.Sprintf("%s.%s", svc.Name, ep.Name)
description := fmt.Sprintf("Call %s on %s service", ep.Name, svc.Name)
if desc, ok := ep.Metadata["description"]; ok {
description = desc
}
// Generate Python function name (replace dots with underscores)
funcName := strings.ReplaceAll(toolName, ".", "_")
fmt.Fprintf(writer, "def %s(arguments: str) -> str:\n", funcName)
fmt.Fprintf(writer, " \"\"\"% s\"\"\"\n", description)
fmt.Fprintf(writer, " args = json.loads(arguments) if isinstance(arguments, str) else arguments\n")
fmt.Fprintf(writer, " return json.dumps(call_mcp_tool('%s', args))\n\n", toolName)
fmt.Fprintf(writer, "tools.append(Tool(\n")
fmt.Fprintf(writer, " name='%s',\n", toolName)
fmt.Fprintf(writer, " func=%s,\n", funcName)
fmt.Fprintf(writer, " description='%s'\n", strings.ReplaceAll(description, "'", "\\'"))
fmt.Fprintf(writer, "))\n\n")
}
}
fmt.Fprintf(writer, "# Example usage:\n")
fmt.Fprintf(writer, "# from langchain.agents import initialize_agent, AgentType\n")
fmt.Fprintf(writer, "# from langchain.llms import OpenAI\n")
fmt.Fprintf(writer, "#\n")
fmt.Fprintf(writer, "# llm = OpenAI(temperature=0)\n")
fmt.Fprintf(writer, "# agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION)\n")
fmt.Fprintf(writer, "# agent.run('Your query here')\n")
return nil
}
// exportOpenAPI exports tools in OpenAPI 3.0 format
func exportOpenAPI(writer *os.File, services []*registry.Service, opts mcp.Options) error {
spec := map[string]interface{}{
"openapi": "3.0.0",
"info": map[string]interface{}{
"title": "Go Micro MCP Services",
"description": "Auto-generated OpenAPI spec from MCP service discovery",
"version": "1.0.0",
},
"servers": []map[string]interface{}{
{
"url": "http://localhost:3000",
"description": "MCP Gateway",
},
},
"paths": make(map[string]interface{}),
}
paths := spec["paths"].(map[string]interface{})
for _, svc := range services {
fullSvcs, err := opts.Registry.GetService(svc.Name)
if err != nil || len(fullSvcs) == 0 {
continue
}
for _, ep := range fullSvcs[0].Endpoints {
toolName := fmt.Sprintf("%s.%s", svc.Name, ep.Name)
path := fmt.Sprintf("/mcp/call/%s", strings.ReplaceAll(toolName, ".", "/"))
description := fmt.Sprintf("Call %s on %s service", ep.Name, svc.Name)
if desc, ok := ep.Metadata["description"]; ok {
description = desc
}
operation := map[string]interface{}{
"summary": toolName,
"description": description,
"operationId": strings.ReplaceAll(toolName, ".", "_"),
"requestBody": map[string]interface{}{
"required": true,
"content": map[string]interface{}{
"application/json": map[string]interface{}{
"schema": map[string]interface{}{
"type": "object",
},
},
},
},
"responses": map[string]interface{}{
"200": map[string]interface{}{
"description": "Successful response",
"content": map[string]interface{}{
"application/json": map[string]interface{}{
"schema": map[string]interface{}{
"type": "object",
},
},
},
},
},
}
// Add scope security if available
if scopesStr, ok := ep.Metadata["scopes"]; ok && scopesStr != "" {
operation["security"] = []map[string]interface{}{
{
"bearerAuth": strings.Split(scopesStr, ","),
},
}
}
paths[path] = map[string]interface{}{
"post": operation,
}
}
}
// Add security schemes
spec["components"] = map[string]interface{}{
"securitySchemes": map[string]interface{}{
"bearerAuth": map[string]interface{}{
"type": "http",
"scheme": "bearer",
},
},
}
enc := json.NewEncoder(writer)
enc.SetIndent("", " ")
return enc.Encode(spec)
}
// exportJSON exports raw tool definitions as JSON
func exportJSON(writer *os.File, services []*registry.Service, opts mcp.Options) error {
var tools []map[string]interface{}
for _, svc := range services {
fullSvcs, err := opts.Registry.GetService(svc.Name)
if err != nil || len(fullSvcs) == 0 {
continue
}
for _, ep := range fullSvcs[0].Endpoints {
tool := map[string]interface{}{
"name": fmt.Sprintf("%s.%s", svc.Name, ep.Name),
"service": svc.Name,
"endpoint": ep.Name,
"metadata": ep.Metadata,
}
if desc, ok := ep.Metadata["description"]; ok {
tool["description"] = desc
}
if example, ok := ep.Metadata["example"]; ok {
tool["example"] = example
}
if scopesStr, ok := ep.Metadata["scopes"]; ok && scopesStr != "" {
tool["scopes"] = strings.Split(scopesStr, ",")
}
tools = append(tools, tool)
}
}
enc := json.NewEncoder(writer)
enc.SetIndent("", " ")
return enc.Encode(map[string]interface{}{
"tools": tools,
"count": len(tools),
})
}
+79
View File
@@ -0,0 +1,79 @@
package mcp
import (
"reflect"
"testing"
)
func TestParseTool(t *testing.T) {
tests := []struct {
name string
toolName string
want []string
}{
{
name: "simple two-part tool",
toolName: "service.endpoint",
want: []string{"service", "endpoint"},
},
{
name: "three-part tool (service.Handler.Method)",
toolName: "greeter.Greeter.Hello",
want: []string{"greeter", "Greeter", "Hello"},
},
{
name: "single part (invalid)",
toolName: "service",
want: []string{"service"},
},
{
name: "four-part tool",
toolName: "users.Users.Get.All",
want: []string{"users", "Users", "Get", "All"},
},
{
name: "empty string",
toolName: "",
want: []string{""},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := parseTool(tt.toolName)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("parseTool(%q) = %v, want %v", tt.toolName, got, tt.want)
}
})
}
}
func TestExportFormats(t *testing.T) {
// Test that export formats are recognized
formats := []string{"langchain", "openapi", "json"}
for _, format := range formats {
t.Run(format, func(t *testing.T) {
// This is a basic test to ensure the format strings are defined
// The actual export functions are tested through integration tests
if format == "" {
t.Error("export format should not be empty")
}
})
}
}
func TestDocsFormats(t *testing.T) {
// Test that docs formats are recognized
formats := []string{"markdown", "json"}
for _, format := range formats {
t.Run(format, func(t *testing.T) {
// This is a basic test to ensure the format strings are defined
// The actual docs functions are tested through integration tests
if format == "" {
t.Error("docs format should not be empty")
}
})
}
}