luma-mcp 1.2.2 → 1.2.4

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/build/image-processor.d.ts +1 -2
  3. package/build/image-processor.d.ts.map +1 -1
  4. package/build/image-processor.js +55 -69
  5. package/build/image-processor.js.map +1 -1
  6. package/build/index.d.ts +1 -1
  7. package/build/index.js +52 -60
  8. package/build/index.js.map +1 -1
  9. package/build/vision-client.d.ts.map +1 -1
  10. package/package.json +1 -1
  11. package/minimax_coding_plan_mcp-0.0.2/.env.test +0 -4
  12. package/minimax_coding_plan_mcp-0.0.2/LICENSE +0 -21
  13. package/minimax_coding_plan_mcp-0.0.2/PKG-INFO +0 -200
  14. package/minimax_coding_plan_mcp-0.0.2/README.md +0 -143
  15. package/minimax_coding_plan_mcp-0.0.2/minimax_coding_plan_mcp.egg-info/PKG-INFO +0 -200
  16. package/minimax_coding_plan_mcp-0.0.2/minimax_coding_plan_mcp.egg-info/SOURCES.txt +0 -17
  17. package/minimax_coding_plan_mcp-0.0.2/minimax_coding_plan_mcp.egg-info/dependency_links.txt +0 -1
  18. package/minimax_coding_plan_mcp-0.0.2/minimax_coding_plan_mcp.egg-info/entry_points.txt +0 -2
  19. package/minimax_coding_plan_mcp-0.0.2/minimax_coding_plan_mcp.egg-info/requires.txt +0 -20
  20. package/minimax_coding_plan_mcp-0.0.2/minimax_coding_plan_mcp.egg-info/top_level.txt +0 -1
  21. package/minimax_coding_plan_mcp-0.0.2/minimax_mcp/__init__.py +0 -3
  22. package/minimax_coding_plan_mcp-0.0.2/minimax_mcp/__main__.py +0 -99
  23. package/minimax_coding_plan_mcp-0.0.2/minimax_mcp/__pycache__/__init__.cpython-313.pyc +0 -0
  24. package/minimax_coding_plan_mcp-0.0.2/minimax_mcp/__pycache__/client.cpython-313.pyc +0 -0
  25. package/minimax_coding_plan_mcp-0.0.2/minimax_mcp/__pycache__/const.cpython-313.pyc +0 -0
  26. package/minimax_coding_plan_mcp-0.0.2/minimax_mcp/__pycache__/exceptions.cpython-313.pyc +0 -0
  27. package/minimax_coding_plan_mcp-0.0.2/minimax_mcp/__pycache__/utils.cpython-313.pyc +0 -0
  28. package/minimax_coding_plan_mcp-0.0.2/minimax_mcp/client.py +0 -104
  29. package/minimax_coding_plan_mcp-0.0.2/minimax_mcp/const.py +0 -4
  30. package/minimax_coding_plan_mcp-0.0.2/minimax_mcp/exceptions.py +0 -24
  31. package/minimax_coding_plan_mcp-0.0.2/minimax_mcp/server.py +0 -169
  32. package/minimax_coding_plan_mcp-0.0.2/minimax_mcp/utils.py +0 -101
  33. package/minimax_coding_plan_mcp-0.0.2/pyproject.toml +0 -59
  34. package/minimax_coding_plan_mcp-0.0.2/setup.cfg +0 -4
  35. package/minimax_coding_plan_mcp-0.0.2/setup.py +0 -6
  36. package/minimax_coding_plan_mcp-0.0.2/test_at_prefix.py +0 -134
  37. package/minimax_coding_plan_mcp-0.0.2/test_real_image.py +0 -153
@@ -1,99 +0,0 @@
1
- import os
2
- import json
3
- from pathlib import Path
4
- import sys
5
- from dotenv import load_dotenv
6
- import argparse
7
-
8
- load_dotenv()
9
-
10
-
11
- def get_claude_config_path() -> Path | None:
12
- """Get the Claude config directory based on platform."""
13
- if sys.platform == "win32":
14
- path = Path(Path.home(), "AppData", "Roaming", "Claude")
15
- elif sys.platform == "darwin":
16
- path = Path(Path.home(), "Library", "Application Support", "Claude")
17
- elif sys.platform.startswith("linux"):
18
- path = Path(
19
- os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"), "Claude"
20
- )
21
- else:
22
- return None
23
-
24
- if path.exists():
25
- return path
26
- return None
27
-
28
-
29
- def get_python_path():
30
- return sys.executable
31
-
32
-
33
- def generate_config(api_key: str | None = None):
34
- module_dir = Path(__file__).resolve().parent
35
- server_path = module_dir / "server.py"
36
- python_path = get_python_path()
37
-
38
- final_api_key = api_key or os.environ.get("MINIMAX_API_KEY")
39
- if not final_api_key:
40
- print("Error: Minimax API key is required.")
41
- print("Please either:")
42
- print(" 1. Pass the API key using --api-key argument, or")
43
- print(" 2. Set the MINIMAX_API_KEY environment variable, or")
44
- print(" 3. Add MINIMAX_API_KEY to your .env file")
45
- sys.exit(1)
46
-
47
- config = {
48
- "mcpServers": {
49
- "Minimax": {
50
- "command": "uvx",
51
- "args": [
52
- "minimax-coding-plan-mcp",
53
- ],
54
-
55
- "env": {
56
- "MINIMAX_API_KEY": final_api_key,
57
- "MINIMAX_API_HOST": "https://api.minimax.chat"
58
- },
59
- }
60
- }
61
- }
62
-
63
- return config
64
-
65
-
66
- if __name__ == "__main__":
67
- parser = argparse.ArgumentParser()
68
- parser.add_argument(
69
- "--print",
70
- action="store_true",
71
- help="Print config to screen instead of writing to file",
72
- )
73
- parser.add_argument(
74
- "--api-key",
75
- help="Minimax API key (alternatively, set MINIMAX_API_KEY environment variable)",
76
- )
77
- parser.add_argument(
78
- "--config-path",
79
- type=Path,
80
- help="Custom path to Claude config directory",
81
- )
82
- args = parser.parse_args()
83
-
84
- config = generate_config(args.api_key)
85
-
86
- if args.print:
87
- print(json.dumps(config, indent=2))
88
- else:
89
- claude_path = args.config_path if args.config_path else get_claude_config_path()
90
- if claude_path is None:
91
- print(
92
- "Could not find Claude config path automatically. Please specify it using --config-path argument. The argument should be an absolute path of the claude_desktop_config.json file."
93
- )
94
- sys.exit(1)
95
-
96
- claude_path.mkdir(parents=True, exist_ok=True)
97
- print("Writing config to", claude_path / "claude_desktop_config.json")
98
- with open(claude_path / "claude_desktop_config.json", "w") as f:
99
- json.dump(config, f, indent=2)
@@ -1,104 +0,0 @@
1
- """Minimax API client base class."""
2
-
3
- import requests
4
- import urllib3
5
- from typing import Any, Dict
6
- from minimax_mcp.exceptions import MinimaxAuthError, MinimaxRequestError
7
-
8
- # Disable SSL warnings for China region API
9
- urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
10
-
11
- class MinimaxAPIClient:
12
- """Base client for making requests to Minimax API."""
13
-
14
- def __init__(self, api_key: str, api_host: str):
15
- """Initialize the API client.
16
-
17
- Args:
18
- api_key: The API key for authentication
19
- api_host: The API host URL
20
- """
21
- self.api_key = api_key
22
- self.api_host = api_host
23
- self.session = requests.Session()
24
- self.session.headers.update({
25
- 'Authorization': f'Bearer {api_key}',
26
- 'MM-API-Source': 'Minimax-MCP'
27
- })
28
-
29
- def _make_request(
30
- self,
31
- method: str,
32
- endpoint: str,
33
- **kwargs
34
- ) -> Dict[str, Any]:
35
- """Make an HTTP request to the Minimax API.
36
-
37
- Args:
38
- method: HTTP method (GET, POST, etc.)
39
- endpoint: API endpoint path
40
- **kwargs: Additional arguments to pass to requests
41
-
42
- Returns:
43
- API response data as dictionary
44
-
45
- Raises:
46
- MinimaxAuthError: If authentication fails
47
- MinimaxRequestError: If the request fails
48
- """
49
- url = f"{self.api_host}{endpoint}"
50
-
51
- # Set Content-Type based on whether files are being uploaded
52
- files = kwargs.get('files')
53
- if not files:
54
- self.session.headers['Content-Type'] = 'application/json'
55
- else:
56
- # Remove Content-Type header for multipart/form-data
57
- # requests library will set it automatically with the correct boundary
58
- self.session.headers.pop('Content-Type', None)
59
-
60
- try:
61
- # Disable SSL verification for api.minimaxi.com (China region)
62
- # This is a workaround for SSL certificate verification issues
63
- if 'verify' not in kwargs and 'minimaxi.com' in self.api_host:
64
- kwargs['verify'] = False
65
-
66
- response = self.session.request(method, url, **kwargs)
67
-
68
- # Check for other HTTP errors
69
- response.raise_for_status()
70
-
71
- data = response.json()
72
-
73
- # Check API-specific error codes
74
- base_resp = data.get("base_resp", {})
75
- if base_resp.get("status_code") != 0:
76
- match base_resp.get("status_code"):
77
- case 1004:
78
- raise MinimaxAuthError(
79
- f"API Error: {base_resp.get('status_msg')}, please check your API key and API host."
80
- f"Trace-Id: {response.headers.get('Trace-Id')}"
81
- )
82
- case 2038:
83
- raise MinimaxRequestError(
84
- f"API Error: {base_resp.get('status_msg')}, should complete real-name verification on the open-platform(https://platform.minimaxi.com/user-center/basic-information)."
85
- f"Trace-Id: {response.headers.get('Trace-Id')}"
86
- )
87
- case _:
88
- raise MinimaxRequestError(
89
- f"API Error: {base_resp.get('status_code')}-{base_resp.get('status_msg')} "
90
- f"Trace-Id: {response.headers.get('Trace-Id')}"
91
- )
92
-
93
- return data
94
-
95
- except requests.exceptions.RequestException as e:
96
- raise MinimaxRequestError(f"Request failed: {str(e)}")
97
-
98
- def get(self, endpoint: str, **kwargs) -> Dict[str, Any]:
99
- """Make a GET request."""
100
- return self._make_request("GET", endpoint, **kwargs)
101
-
102
- def post(self, endpoint: str, **kwargs) -> Dict[str, Any]:
103
- """Make a POST request."""
104
- return self._make_request("POST", endpoint, **kwargs)
@@ -1,4 +0,0 @@
1
- # ENV variables
2
- ENV_MINIMAX_API_KEY = "MINIMAX_API_KEY"
3
- ENV_MINIMAX_API_HOST = "MINIMAX_API_HOST"
4
- ENV_FASTMCP_LOG_LEVEL = "FASTMCP_LOG_LEVEL"
@@ -1,24 +0,0 @@
1
- """Custom exceptions for Minimax MCP."""
2
-
3
- class MinimaxAPIError(Exception):
4
- """Base exception for Minimax API errors."""
5
- pass
6
-
7
- class MinimaxAuthError(MinimaxAPIError):
8
- """Authentication related errors."""
9
- pass
10
-
11
- class MinimaxRequestError(MinimaxAPIError):
12
- """Request related errors."""
13
- pass
14
-
15
- class MinimaxTimeoutError(MinimaxAPIError):
16
- """Timeout related errors."""
17
- pass
18
-
19
- class MinimaxValidationError(MinimaxAPIError):
20
- """Validation related errors."""
21
- pass
22
-
23
- class MinimaxMcpError(MinimaxAPIError):
24
- pass
@@ -1,169 +0,0 @@
1
- """
2
- MiniMax Coding Plan MCP Server
3
-
4
- ⚠️ IMPORTANT: This server connects to Minimax API endpoints which may involve costs.
5
- Any tool that makes an API call is clearly marked with a cost warning. Please follow these guidelines:
6
-
7
- 1. Only use these tools when users specifically ask for them
8
- 2. For audio generation tools, be mindful that text length affects the cost
9
- 3. Voice cloning features are charged upon first use after cloning
10
-
11
- Note: Tools without cost warnings are free to use as they only read existing data.
12
- """
13
-
14
- import os
15
- import json
16
- from dotenv import load_dotenv
17
- from mcp.server.fastmcp import FastMCP
18
- from mcp.types import TextContent
19
- from minimax_mcp.utils import (
20
- process_image_url,
21
- )
22
-
23
- from minimax_mcp.const import *
24
- from minimax_mcp.exceptions import MinimaxAPIError, MinimaxRequestError
25
- from minimax_mcp.client import MinimaxAPIClient
26
-
27
- load_dotenv()
28
- api_key = os.getenv(ENV_MINIMAX_API_KEY)
29
- api_host = os.getenv(ENV_MINIMAX_API_HOST)
30
- fastmcp_log_level = os.getenv(ENV_FASTMCP_LOG_LEVEL) or "WARNING"
31
-
32
- if not api_key:
33
- raise ValueError("MINIMAX_API_KEY environment variable is required")
34
- if not api_host:
35
- raise ValueError("MINIMAX_API_HOST environment variable is required")
36
-
37
- mcp = FastMCP("Minimax",log_level=fastmcp_log_level)
38
- api_client = MinimaxAPIClient(api_key, api_host)
39
-
40
- @mcp.tool(
41
- description="""
42
-
43
- Web Search API, works like Google Search.
44
-
45
- Args:
46
- query (str): The search query string. Use 3-5 keywords. Add the current date for time-sensitive queries, e.g. `latest iPhone 2025`
47
-
48
- Search Strategy:
49
- - If no valid results found, try changing search keywords
50
-
51
- Returns:
52
- Text content with search results in JSON format. The response structure is:
53
- {
54
- "organic": [
55
- {
56
- "title": "string - The title of the search result",
57
- "link": "string - The URL link to the result",
58
- "snippet": "string - A brief description or excerpt",
59
- "date": "string - The date of the result"
60
- }
61
- ],
62
- "related_searches": [
63
- {
64
- "query": "string - A related search query suggestion"
65
- }
66
- ],
67
- "base_resp": {
68
- "status_code": "int - Response status code",
69
- "status_msg": "string - Response status message"
70
- }
71
- }
72
- """
73
- )
74
- def web_search(
75
- query: str,
76
- ) -> TextContent:
77
- try:
78
- if not query:
79
- raise MinimaxRequestError("Query is required")
80
-
81
- # Build request payload
82
- payload = {
83
- "q": query
84
- }
85
-
86
- # Call search API
87
- response_data = api_client.post("/v1/coding_plan/search", json=payload)
88
-
89
- # Return JSON dump of response data
90
- return TextContent(
91
- type="text",
92
- text=json.dumps(response_data, ensure_ascii=False, indent=2)
93
- )
94
-
95
- except MinimaxAPIError as e:
96
- return TextContent(
97
- type="text",
98
- text=f"Failed to perform search: {str(e)}"
99
- )
100
-
101
-
102
- @mcp.tool(
103
- description="""
104
-
105
- A powerful LLM that can analyze and understand image content from files or URLs, follow your instruction.
106
- Use this tool to understand images by LLM.
107
- Only support jpeg, png, webp formats. Other formats like pdf/gif/psd/svg and so on are not supported.
108
-
109
- Args:
110
- prompt (str): The text prompt describing what you want to analyze or extract from the image.
111
- image_source (str): The source location of the image to analyze.
112
- Accepts:
113
- - HTTP/HTTPS URL: "https://example.com/image.jpg"
114
- - Local file path: "/path/to/image.png"
115
- Supported formats: JPEG, PNG, WebP
116
-
117
- Returns:
118
- Text content with the image analysis result.
119
- """
120
- )
121
- def understand_image(
122
- prompt: str,
123
- image_source: str,
124
- ) -> TextContent:
125
- try:
126
- if not prompt:
127
- raise MinimaxRequestError("Prompt is required")
128
- if not image_source:
129
- raise MinimaxRequestError("Image source is required")
130
-
131
- # Process image_source: convert HTTP URL or local file to base64, or pass through existing base64
132
- processed_image_url = process_image_url(image_source)
133
-
134
- # Build request payload
135
- payload = {
136
- "prompt": prompt,
137
- "image_url": processed_image_url
138
- }
139
-
140
- # Call VLM API
141
- response_data = api_client.post("/v1/coding_plan/vlm", json=payload)
142
-
143
- # Extract content from response
144
- content = response_data.get("content", "")
145
-
146
- if not content:
147
- raise MinimaxRequestError("No content returned from VLM API")
148
-
149
- # Return the content
150
- return TextContent(
151
- type="text",
152
- text=content
153
- )
154
-
155
- except MinimaxAPIError as e:
156
- return TextContent(
157
- type="text",
158
- text=f"Failed to perform VLM analysis: {str(e)}"
159
- )
160
-
161
-
162
- def main():
163
- print("Starting Minimax MCP server")
164
- """Run the Minimax MCP server"""
165
- mcp.run()
166
-
167
-
168
- if __name__ == "__main__":
169
- main()
@@ -1,101 +0,0 @@
1
- import os
2
- import base64
3
- import requests
4
- from minimax_mcp.const import *
5
- from minimax_mcp.exceptions import MinimaxMcpError, MinimaxRequestError
6
-
7
-
8
- def strip_at_prefix(path: str) -> str:
9
- """
10
- Remove @ prefix from Claude Code file references.
11
-
12
- Claude Code uses @ as a syntax sugar for workspace files.
13
- For example: @test-minimax-image/image.png -> test-minimax-image/image.png
14
-
15
- Args:
16
- path (str): The file path that may have @ prefix
17
-
18
- Returns:
19
- str: Path without @ prefix
20
- """
21
- if path.startswith('@'):
22
- return path[1:]
23
- return path
24
-
25
-
26
- def process_image_url(image_url: str) -> str:
27
- """
28
- Process image URL and convert to base64 data URL format.
29
-
30
- This function handles three types of image inputs:
31
- 1. HTTP/HTTPS URLs: Downloads the image and converts to base64
32
- 2. Base64 data URLs: Passes through as-is
33
- 3. Local file paths: Reads the file and converts to base64
34
-
35
- Args:
36
- image_url (str): The image URL, data URL, or local file path
37
-
38
- Returns:
39
- str: Base64 data URL in format "data:image/{format};base64,{data}"
40
-
41
- Raises:
42
- MinimaxRequestError: If image cannot be downloaded, read, or processed
43
- """
44
- # Remove @ prefix if present (Claude Code syntax)
45
- image_url = strip_at_prefix(image_url)
46
-
47
- # If already in base64 data URL format, pass through
48
- if image_url.startswith("data:"):
49
- return image_url
50
-
51
- # Handle HTTP/HTTPS URLs
52
- if image_url.startswith(("http://", "https://")):
53
- try:
54
- image_response = requests.get(image_url)
55
- image_response.raise_for_status()
56
- image_data = image_response.content
57
-
58
- # Detect image format from content-type header
59
- content_type = image_response.headers.get('content-type', '').lower()
60
- if 'jpeg' in content_type or 'jpg' in content_type:
61
- image_format = 'jpeg'
62
- elif 'png' in content_type:
63
- image_format = 'png'
64
- elif 'webp' in content_type:
65
- image_format = 'webp'
66
- else:
67
- # Default to jpeg if cannot detect
68
- image_format = 'jpeg'
69
-
70
- # Convert to base64 data URL
71
- base64_data = base64.b64encode(image_data).decode('utf-8')
72
- return f"data:image/{image_format};base64,{base64_data}"
73
-
74
- except requests.RequestException as e:
75
- raise MinimaxRequestError(f"Failed to download image from URL: {str(e)}")
76
-
77
- # Handle local file paths
78
- else:
79
- if not os.path.exists(image_url):
80
- raise MinimaxRequestError(f"Local image file does not exist: {image_url}")
81
-
82
- try:
83
- with open(image_url, "rb") as f:
84
- image_data = f.read()
85
-
86
- # Detect image format from file extension
87
- image_format = 'jpeg' # Default
88
- if image_url.lower().endswith('.png'):
89
- image_format = 'png'
90
- elif image_url.lower().endswith('.webp'):
91
- image_format = 'webp'
92
- elif image_url.lower().endswith(('.jpg', '.jpeg')):
93
- image_format = 'jpeg'
94
-
95
- base64_data = base64.b64encode(image_data).decode('utf-8')
96
- return f"data:image/{image_format};base64,{base64_data}"
97
-
98
- except IOError as e:
99
- raise MinimaxRequestError(f"Failed to read local image file: {str(e)}")
100
-
101
-
@@ -1,59 +0,0 @@
1
- [project]
2
- name = "minimax-coding-plan-mcp"
3
- version = "0.0.2"
4
- description = "Specialized MiniMax Model Context Protocol (MCP) server designed for coding-plan users"
5
- authors = [
6
- { name = "Roy Wu", email = "zhengyu@minimax.chat" },
7
- ]
8
- readme = "README.md"
9
- license = { file = "LICENSE" }
10
- classifiers = [
11
- "Development Status :: 4 - Beta",
12
- "Intended Audience :: Developers",
13
- "License :: OSI Approved :: MIT License",
14
- "Programming Language :: Python :: 3",
15
- "Programming Language :: Python :: 3.10",
16
- ]
17
- keywords = [
18
- "minimax",
19
- "mcp",
20
- "web_search",
21
- "understand-image",
22
- ]
23
- requires-python = ">=3.10"
24
- dependencies = [
25
- "mcp[cli]>=1.6.0",
26
- "fastapi>=0.109.2",
27
- "uvicorn>=0.27.1",
28
- "python-dotenv>=1.0.1",
29
- "pydantic>=2.6.1",
30
- "httpx>=0.28.1",
31
- "fuzzywuzzy>=0.18.0",
32
- "python-Levenshtein>=0.25.0",
33
- "sounddevice>=0.5.1",
34
- "soundfile>=0.13.1",
35
- "requests>=2.31.0",
36
- ]
37
-
38
- [project.scripts]
39
- minimax-coding-plan-mcp = "minimax_mcp.server:main"
40
-
41
- [project.optional-dependencies]
42
- dev = [
43
- "pre-commit>=3.6.2",
44
- "ruff>=0.3.0",
45
- "fastmcp>=0.4.1",
46
- "pytest>=8.0.0",
47
- "pytest-cov>=4.1.0",
48
- "twine>=6.1.0",
49
- "build>=1.0.3",
50
- ]
51
-
52
- [build-system]
53
- requires = ["setuptools>=45", "wheel"]
54
- build-backend = "setuptools.build_meta"
55
-
56
- [tool.pytest.ini_options]
57
- testpaths = ["tests"]
58
- python_files = ["test_*.py"]
59
- addopts = "-v --cov=minimax_mcp --cov-report=term-missing"
@@ -1,4 +0,0 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
@@ -1,6 +0,0 @@
1
- from setuptools import setup, find_packages
2
-
3
- setup(
4
- packages=find_packages(),
5
- include_package_data=True,
6
- )