Skip to content

Commit 9be2bd6

Browse files
authored
Add renaming tool and description features (#69)
* Add renaming tool and description features by runtime params or config file Signed-off-by: Sun Ro Lee <[email protected]> * write changelog for tool perperties override feature Signed-off-by: Sun Ro Lee <[email protected]> * add warning if the length of tool desciption is over 1024 char Signed-off-by: Sun Ro Lee <[email protected]> * Add tool field aliases for flexible tool naming Signed-off-by: Sun Ro Lee <[email protected]> * Add display_name field to tool generator Signed-off-by: Sun Ro Lee <[email protected]> * Modify error message on `is_tool_compatible` to show supported version Signed-off-by: Sun Ro Lee <[email protected]> * Add user guide for tool customization Signed-off-by: Sun Ro Lee <[email protected]> * merge example of config files to `example_config.yml` Signed-off-by: Sun Ro Lee <[email protected]> * add tool customization section to user guide table_of_content Signed-off-by: Sun Ro Lee <[email protected]> * Fix test isolation issues - Add OPENSEARCH_SSL_VERIFY to environment variable cleanup in test_client.py - Fix mock data structure issues in test_tool_filters.py by patching TOOL_REGISTRY - Fix unpacking error in serverless compatibility test Signed-off-by: Sun Ro Lee <[email protected]> * Fix serverless compatibility test expectations - Update test to correctly expect is_tool_compatible to be called with None version in serverless mode - Mock should return True for serverless mode compatibility check - Verify both tools are enabled in serverless environment Signed-off-by: Sun Ro Lee <[email protected]> --------- Signed-off-by: Sun Ro Lee <[email protected]>
1 parent 257afd2 commit 9be2bd6

File tree

16 files changed

+1013
-384
lines changed

16 files changed

+1013
-384
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
55
## [Unreleased]
66

77
### Added
8+
- Allow overriding tool properties via configuration ([#69](https://github.com/opensearch-project/opensearch-mcp-server-py/pull/69))
89
- Extend list indices tool ([#68](https://github.com/opensearch-project/opensearch-mcp-server-py/pull/68))
910
- Add `OPENSEARCH_NO_AUTH` environment variable for connecting to clusters without authentication
1011

USER_GUIDE.md

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- [Authentication](#authentication)
1111
- [Running the Server](#running-the-server)
1212
- [Tool Filter](#tool-filter)
13+
- [Tool Customization](#tool-customization)
1314
- [LangChain Integration](#langchain-integration)
1415

1516
## Overview
@@ -99,15 +100,15 @@ See [Authentication](#authentication) section for detailed authentication setup.
99100
"args": [
100101
"opensearch-mcp-server-py",
101102
"--mode", "multi",
102-
"--config", "/path/to/your/clusters.yml"
103+
"--config", "/path/to/your/config.yml"
103104
],
104105
"env": {}
105106
}
106107
}
107108
}
108109
```
109110

110-
**Example YAML Configuration File (`clusters.yml`):**
111+
**Example YAML Configuration File (`config.yml`):**
111112
```yaml
112113
version: "1.0"
113114
description: "OpenSearch cluster configurations"
@@ -137,6 +138,15 @@ clusters:
137138
profile: "your-aws-profile"
138139
is_serverless: true
139140

141+
# Tool customization configurations (supported in both Single and Multi Mode)
142+
tools:
143+
ListIndexTool:
144+
display_name: "Index Manager"
145+
description: "List and manage OpenSearch indices with enhanced functionality"
146+
SearchIndexTool:
147+
display_name: "Super Searcher"
148+
GetShardsTool:
149+
description: "Retrieve detailed information about OpenSearch shards"
140150
```
141151
142152
**Key Points about Multi Mode:**
@@ -346,13 +356,13 @@ python -m mcp_server_opensearch --profile my-aws-profile
346356
### Multi Mode
347357
```bash
348358
# Stdio Server with config file
349-
python -m mcp_server_opensearch --mode multi --config clusters.yml
359+
python -m mcp_server_opensearch --mode multi --config config.yml
350360
351361
# Streaming Server with config file
352-
python -m mcp_server_opensearch --mode multi --config clusters.yml --transport stream
362+
python -m mcp_server_opensearch --mode multi --config config.yml --transport stream
353363
354364
# With AWS Profile (fallback if not in config)
355-
python -m mcp_server_opensearch --mode multi --config clusters.yml --profile my-aws-profile
365+
python -m mcp_server_opensearch --mode multi --config config.yml --profile my-aws-profile
356366
357367
# Fallback to single mode behavior (no config file)
358368
python -m mcp_server_opensearch --mode multi
@@ -436,6 +446,8 @@ When using multi-mode, each cluster in your YAML configuration file accepts the
436446

437447
OpenSearch MCP server supports tool filtering to disable specific tools by name, category, or operation type. You can configure filtering using either a YAML configuration file or environment variables.
438448

449+
**Important Note: Tool filtering is only supported in Single Mode. In Multi Mode, all tools are available without any filtering.**
450+
439451
### Configuration Methods
440452

441453
1. YAML Configuration File
@@ -495,6 +507,46 @@ export OPENSEARCH_SETTINGS_ALLOW_WRITE=true
495507
- When both config file and environment variables are provided, the config file will be prioritized
496508
- Tool filtering is only supported in single mode. In multi mode, tool filtering is not supported
497509

510+
## Tool Customization
511+
512+
OpenSearch MCP server supports tool customization to modify tool display names, descriptions, and other properties. You can customize tools using either a YAML configuration file or runtime parameters.
513+
514+
### Configuration Methods
515+
516+
1. **YAML Configuration File**
517+
518+
Create a YAML file with your tool customization settings:
519+
```yaml
520+
tools:
521+
ListIndexTool:
522+
display_name: "Index Manager"
523+
description: "List and manage OpenSearch indices"
524+
GetShardsTool:
525+
description: "Retrieve detailed information about OpenSearch shards"
526+
```
527+
528+
Use the configuration file when starting the server:
529+
```bash
530+
python -m mcp_server_opensearch --config path/to/config.yml
531+
```
532+
533+
2. **Runtime Parameters**
534+
535+
Customize tools directly via command line arguments:
536+
```bash
537+
python -m mcp_server_opensearch --tool.ListIndexTool.display_name="Index Manager" --tool.SearchIndexTool.description="Custom search tool"
538+
```
539+
540+
### Priority
541+
542+
Runtime parameters have higher priority than configuration file settings. If both are provided, runtime parameters will override the corresponding values in the configuration file.
543+
544+
### Important Notes
545+
- Tool customization is available in both single and multi modes
546+
- Only existing tools can be customized; new tools cannot be created
547+
- Changes take effect immediately when the server starts
548+
- Invalid tool names or properties will be ignored
549+
498550
## LangChain Integration
499551

500552
The OpenSearch MCP server can be easily integrated with LangChain using the SSE server transport.

example_clusters.yml

Lines changed: 0 additions & 34 deletions
This file was deleted.

example_config.yml

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
version: "1.0"
2+
description: "Unified OpenSearch MCP Server Configuration"
3+
4+
# Cluster configurations (used in Multi Mode)
5+
clusters:
6+
local-cluster:
7+
opensearch_url: "http://localhost:9200"
8+
opensearch_username: "admin"
9+
opensearch_password: "your_password_here"
10+
11+
remote-cluster:
12+
opensearch_url: "https://your-opensearch-domain.us-east-2.es.amazonaws.com"
13+
profile: "your-aws-profile"
14+
15+
remote-cluster-with-iam:
16+
opensearch_url: "https://your-opensearch-domain.us-east-2.es.amazonaws.com"
17+
iam_arn: "arn:aws:iam::123456789012:role/YourOpenSearchRole"
18+
aws_region: "us-east-2"
19+
profile: "your-aws-profile"
20+
21+
serverless-cluster:
22+
opensearch_url: "https://collection-id.us-east-1.aoss.amazonaws.com"
23+
aws_region: "us-east-1"
24+
profile: "your-aws-profile"
25+
is_serverless: true
26+
27+
# Tool customization configurations (supported in both Single and Multi Mode)
28+
tools:
29+
ListIndexTool:
30+
display_name: "My Custom Index Lister"
31+
description: "This is a custom description for the tool that lists indices. It's much more descriptive now!"
32+
SearchIndexTool:
33+
display_name: "Super Searcher"
34+
GetShardsTool:
35+
description: "A better description to get information about shards in OpenSearch."
36+
37+
# ==============================================================================
38+
# Tool filtering configurations (ONLY supported in Single Mode)
39+
# ==============================================================================
40+
# Note: These sections are ignored in Multi Mode. Tool filtering only works
41+
# when using Single Mode (--mode single or default mode).
42+
# Uncomment the sections below if you're using Single Mode and want to filter tools.
43+
44+
# tool_category:
45+
# search_tools:
46+
# - "SearchIndexTool"
47+
# - "MsearchTool"
48+
# management_tools:
49+
# - "ListIndexTool"
50+
# - "CreateIndexTool"
51+
# - "DeleteIndexTool"
52+
53+
# tool_filters:
54+
# disabled_tools:
55+
# - "DangerousTool" # Example: disable dangerous tools
56+
# disabled_categories:
57+
# - "experimental_tools" # Example: disable experimental tool category
58+
# disabled_tools_regex:
59+
# - "debug.*" # Example: disable all tools starting with debug
60+
# settings:
61+
# allow_write: true # Enable/disable write operations
62+
63+
64+
# Example configurations for different authentication methods:
65+
#
66+
# 1. IAM Role Authentication (recommended for production):
67+
# - Requires: opensearch_url, iam_arn, aws_region, profile
68+
# - Uses AWS IAM roles for authentication
69+
#
70+
# 2. Basic Authentication:
71+
# - Requires: opensearch_url, opensearch_username, opensearch_password
72+
# - Uses username/password for authentication
73+
#
74+
# 3. AWS Profile Authentication:
75+
# - Requires: opensearch_url, profile
76+
# - Uses AWS credentials from the specified profile
77+
#
78+
# 4. OpenSearch Serverless:
79+
# - Requires: opensearch_url, aws_region
80+
# - Optional: profile, is_serverless: true

src/mcp_server_opensearch/__init__.py

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,36 @@
11
# Copyright OpenSearch Contributors
22
# SPDX-License-Identifier: Apache-2.0
33

4+
import argparse
5+
import asyncio
6+
import logging
7+
from typing import Dict, List
8+
49
from .stdio_server import serve as serve_stdio
510
from .streaming_server import serve as serve_streaming
611

712

13+
def parse_unknown_args_to_dict(unknown_args: List[str]) -> Dict[str, str]:
14+
"""Parses a list of unknown arguments into a dictionary."""
15+
parser = argparse.ArgumentParser()
16+
arg_keys = {arg.split('=')[0] for arg in unknown_args if arg.startswith('--')}
17+
18+
for key in arg_keys:
19+
parser.add_argument(key)
20+
21+
try:
22+
parsed_args, _ = parser.parse_known_args(unknown_args)
23+
return {k: v for k, v in vars(parsed_args).items() if v is not None}
24+
except Exception as e:
25+
logging.error(f"Error parsing unknown arguments: {e}")
26+
return {}
27+
28+
829
def main() -> None:
930
"""
1031
Main entry point for the OpenSearch MCP Server.
1132
Handles command line arguments and starts the appropriate server based on transport type.
1233
"""
13-
import argparse
14-
import asyncio
15-
import logging
16-
1734
# Configure logging
1835
logging.basicConfig(
1936
level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
@@ -43,21 +60,35 @@ def main() -> None:
4360
parser.add_argument(
4461
'--profile', default='', help='AWS profile to use for OpenSearch connection'
4562
)
46-
parser.add_argument('--config', default='', help='Path to a YAML configuration file')
63+
parser.add_argument(
64+
'--config',
65+
dest='config_file_path',
66+
default='',
67+
help='Path to a YAML configuration file',
68+
)
4769

48-
args = parser.parse_args()
70+
args, unknown = parser.parse_known_args()
71+
cli_tool_overrides = parse_unknown_args_to_dict(unknown)
4972

5073
# Start the appropriate server based on transport type
5174
if args.transport == 'stdio':
52-
asyncio.run(serve_stdio(mode=args.mode, profile=args.profile, config=args.config))
75+
asyncio.run(
76+
serve_stdio(
77+
mode=args.mode,
78+
profile=args.profile,
79+
config_file_path=args.config_file_path,
80+
cli_tool_overrides=cli_tool_overrides,
81+
)
82+
)
5383
else:
5484
asyncio.run(
5585
serve_streaming(
5686
host=args.host,
5787
port=args.port,
5888
mode=args.mode,
5989
profile=args.profile,
60-
config=args.config,
90+
config_file_path=args.config_file_path,
91+
cli_tool_overrides=cli_tool_overrides,
6192
)
6293
)
6394

src/mcp_server_opensearch/stdio_server.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,17 @@
88
from mcp_server_opensearch.clusters_information import load_clusters_from_yaml
99
from tools.tool_filter import get_tools
1010
from tools.tool_generator import generate_tools_from_openapi
11+
from tools.tools import TOOL_REGISTRY
12+
from tools.config import apply_custom_tool_config
1113

1214

1315
# --- Server setup ---
14-
async def serve(mode: str = 'single', profile: str = '', config: str = '') -> None:
16+
async def serve(
17+
mode: str = 'single',
18+
profile: str = '',
19+
config_file_path: str = '',
20+
cli_tool_overrides: dict = None,
21+
) -> None:
1522
# Set the global profile if provided
1623
if profile:
1724
from opensearch.client import set_profile
@@ -21,11 +28,16 @@ async def serve(mode: str = 'single', profile: str = '', config: str = '') -> No
2128
server = Server('opensearch-mcp-server')
2229
# Load clusters from YAML file
2330
if mode == 'multi':
24-
load_clusters_from_yaml(config)
31+
load_clusters_from_yaml(config_file_path)
2532

2633
# Call tool generator and tool filter
2734
await generate_tools_from_openapi()
28-
enabled_tools = get_tools(mode, config)
35+
customized_registry = apply_custom_tool_config(
36+
TOOL_REGISTRY, config_file_path, cli_tool_overrides or {}
37+
)
38+
enabled_tools = get_tools(
39+
tool_registry=customized_registry, mode=mode, config_file_path=config_file_path
40+
)
2941
logging.info(f'Enabled tools: {list(enabled_tools.keys())}')
3042

3143
@server.list_tools()
@@ -34,7 +46,7 @@ async def list_tools() -> list[Tool]:
3446
for tool_name, tool_info in enabled_tools.items():
3547
tools.append(
3648
Tool(
37-
name=tool_name,
49+
name=tool_info.get('display_name', tool_name),
3850
description=tool_info['description'],
3951
inputSchema=tool_info['input_schema'],
4052
)
@@ -43,9 +55,17 @@ async def list_tools() -> list[Tool]:
4355

4456
@server.call_tool()
4557
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
46-
tool = enabled_tools.get(name)
47-
if not tool:
58+
# Find the tool by its display name, which is what the client sees
59+
found_tool_key = None
60+
for key, tool_info in enabled_tools.items():
61+
if tool_info.get('display_name', key) == name:
62+
found_tool_key = key
63+
break
64+
65+
if not found_tool_key:
4866
raise ValueError(f'Unknown or disabled tool: {name}')
67+
68+
tool = enabled_tools.get(found_tool_key)
4969
parsed = tool['args_model'](**arguments)
5070
return await tool['function'](parsed)
5171

0 commit comments

Comments
 (0)