Skip to main content
Glama
api.py14.9 kB
# api.py import os import sys import time import httpx import requests from pathlib import Path from datetime import datetime from typing import Iterator, Union from urllib.parse import urlparse from mcp.server.fastmcp import FastMCP from mcp.types import TextContent mcp = FastMCP("Mureka") # setup API key,for calling mureka API,please refer to the method for obtaining:https://platform.mureka.ai/apiKeys # os.environ["MUREKA_API_KEY"] = "<MUREKA_API_KEY>" api_key = os.getenv('MUREKA_API_KEY') global_base_path = os.getenv("MUREKA_MCP_BASE_PATH") api_url = os.getenv('MUREKA_API_URL') if api_url is None: api_url = "https://api.mureka.ai" default_time_out = 60.0 # seconds time_out_env = os.getenv('TIME_OUT_SECONDS') if time_out_env is not None: default_time_out = float(time_out_env) def is_file_writeable(path: Path) -> bool: if path.exists(): return os.access(path, os.W_OK) parent_dir = path.parent return os.access(parent_dir, os.W_OK) def make_output_path( output_directory: str | None, base_path: str | None = None ) -> Path: output_path = None if output_directory is None: output_path = Path.home() / "Desktop" elif not os.path.isabs(output_directory) and base_path: output_path = Path(os.path.expanduser(base_path)) / Path(output_directory) else: output_path = Path(os.path.expanduser(output_directory)) if not is_file_writeable(output_path): raise Exception(f"Directory ({output_path}) is not writeable") output_path.mkdir(parents=True, exist_ok=True) return output_path def extract_filename_from_url(url): # 解析URL parsed_url = urlparse(url) # 获取路径的最后一个部分,即文件名 filename = parsed_url.path.split('/')[-1] return filename def check_audio_file(path: Path) -> bool: audio_extensions = { ".wav", ".mp3", ".m4a", ".aac", ".ogg", ".flac", ".mp4", ".avi", ".mov", ".wmv", } return path.suffix.lower() in audio_extensions def handle_input_file(file_path: str, audio_content_check: bool = True) -> Path: if not os.path.isabs(file_path) and not os.environ.get("ELEVENLABS_MCP_BASE_PATH"): raise Exception( "File path must be an absolute path if ELEVENLABS_MCP_BASE_PATH is not set" ) path = Path(file_path) if not path.exists() and path.parent.exists(): raise Exception(f"File ({path}) does not exist") elif not path.exists(): raise Exception(f"File ({path}) does not exist") elif not path.is_file(): raise Exception(f"File ({path}) is not a file") if audio_content_check and not check_audio_file(path): raise Exception(f"File ({path}) is not an audio or video file") return path @mcp.tool( description="""Generate lyrics with a given prompt then return the title and lyrics text to the client directly. ⚠️ COST WARNING: This tool makes an API call to mureka.ai which may incur costs. Only use when explicitly requested by the user. Args: prompt (str): The prompt to generate lyrics for song Returns: The title and lyrics of song. """ ) async def generate_lyrics(prompt: str) -> dict: try: if not api_key: raise Exception("Can not found API key.") if prompt == "": raise Exception("Prompt text is required.") # call mureka API url = f"{api_url}/v1/lyrics/generate" # set request parameters # for more parameter information, please refer to:https://platform.mureka.cn/docs/api/operations/post-v1-lyrics-generate.html headers = {'Authorization': 'Bearer {}'.format(api_key), 'Content-Type': 'application/json'} params = {'prompt': prompt} async with httpx.AsyncClient(timeout=None) as client: response = await client.post(url, json=params, headers=headers) response.raise_for_status() result = response.json() result_lyrics = result.get("lyrics", "") result_title = result.get("title", "") return {"lyrics": result_lyrics, "title": result_title} except httpx.HTTPError as e: raise Exception(f"HTTP request failed: {str(e)}") from e except KeyError as e: raise Exception(f"Failed to parse response: {str(e)}") from e @mcp.tool( description="""Generate song based on the lyrics text and save the output audio file to a given directory. Directory is optional, if not provided, the output file will be saved to $HOME/Desktop. ⚠️ COST WARNING: This tool makes an API call to mureka.ai which may incur costs. Only use when explicitly requested by the user. Args: lyrics (str): The lyrics to generate song prompt (str, optional): Control song generation by inputting a prompt.For example:r&b, slow, passionate, male vocal. output_directory (str, optional): Directory where files should be saved. Defaults to $HOME/Desktop if not provided. Returns: The output file and name of song generated. """ ) async def generate_song(lyrics: str, prompt: str = "", output_directory: str | None = None) -> \ list[TextContent]: try: if not api_key: raise Exception("Can not found API key.") if lyrics == "": raise Exception("lyrics text is required.") model: str = "auto" output_path = make_output_path(output_directory, global_base_path) # call mureka API url = f"{api_url}/v1/song/generate" # set request parameters # for more parameter information, please refer to:https://platform.mureka.ai/docs/api/operations/post-v1-song-generate.html headers = {'Authorization': 'Bearer {}'.format(api_key), 'Content-Type': 'application/json'} params = {'lyrics': lyrics, 'model': model, 'prompt': prompt} async with httpx.AsyncClient() as client: response = await client.post(url, json=params, headers=headers) response.raise_for_status() result = response.json() # parse result, pick up task id to query task result task_id = result.get("id", "") if len(task_id) == 0: raise Exception(f"generate song failed") current_timestamp = datetime.now().timestamp() while True: if (datetime.now().timestamp() - current_timestamp) > default_time_out: raise Exception(f"generate song time out {default_time_out} seconds") song_urls, status = await query_song_task(task_id) if status == "failed" or status == "cancelled" or status == "timeouted": raise Exception(f"generate song:{status}") elif status == "succeeded": break else: time.sleep(1) # downloads songs output_path.parent.mkdir(parents=True, exist_ok=True) save_path_group = [] for song_url_item in song_urls: filename = extract_filename_from_url(song_url_item) response = requests.get(song_url_item) if response.status_code == 200: song_bytes = response.content else: raise Exception(f"generate song failed! Can't download songs") save_path = output_path / filename with open(save_path, "wb") as f: f.write(song_bytes) save_path_group.append(save_path) return [TextContent( type="text", text=f"Success. File saved as: {save_path}", ) for save_path in save_path_group] except httpx.HTTPError as e: raise Exception(f"HTTP request failed: {str(e)}") from e except KeyError as e: raise Exception(f"Failed to parse response: {str(e)}") from e async def query_song_task(task_id: str) -> ([], str): try: url = f"{api_url}/v1/song/query/{task_id}" headers = {'Authorization': 'Bearer {}'.format(api_key)} async with httpx.AsyncClient() as client: response = await client.get(url, headers=headers) response.raise_for_status() result = response.json() status = result.get("status", "failed") if status == "succeeded": # pick songs url ret_songs_list = [] choices_list = result.get("choices", []) for choice in choices_list: song_url = choice.get("url", "") if len(song_url) > 0: ret_songs_list.append(song_url) if len(ret_songs_list) == 0: return [], "failed" return ret_songs_list, status else: return [], status except httpx.HTTPError as e: raise Exception(f"HTTP request failed: {str(e)}") from e except KeyError as e: raise Exception(f"Failed to parse response: {str(e)}") from e async def query_instrumental_task(task_id: str) -> ([], str): try: url = f"{api_url}/v1/instrumental/query/{task_id}" headers = {'Authorization': 'Bearer {}'.format(api_key)} async with httpx.AsyncClient() as client: response = await client.get(url, headers=headers) response.raise_for_status() result = response.json() status = result.get("status", "failed") if status == "succeeded": # pick songs url ret_songs_list = [] choices_list = result.get("choices", []) for choice in choices_list: song_url = choice.get("url", "") if len(song_url) > 0: ret_songs_list.append(song_url) if len(ret_songs_list) == 0: return [], "failed" return ret_songs_list, status else: return [], status except httpx.HTTPError as e: raise Exception(f"HTTP request failed: {str(e)}") from e except KeyError as e: raise Exception(f"Failed to parse response: {str(e)}") from e @mcp.tool( description="""Generate background music(instrumental) based on the prompt text and save the output audio file to a given directory. Directory is optional, if not provided, the output file will be saved to $HOME/Desktop. ⚠️ COST WARNING: This tool makes an API call to mureka.ai which may incur costs. Only use when explicitly requested by the user. Args: prompt (str, optional): Control music generation by inputting a prompt.For example:r&b, slow, passionate, male vocal. output_directory (str, optional): Directory where files should be saved. Defaults to $HOME/Desktop if not provided. Returns: The output file and name of background music(instrumental) generated. """ ) async def generate_instrumental(prompt: str = "", output_directory: str | None = None) -> list[TextContent]: try: if not api_key: raise Exception("Can not found API key.") if len(prompt) == 0: raise Exception("Prompt is needed.For example:r&b, slow, passionate, male vocal") model: str = "auto" # model: The model to use. Use auto to select the latest model.Valid values:auto, # mureka-5.5 or mureka-6. output_path = make_output_path(output_directory, global_base_path) # call mureka API url = f"{api_url}/v1/instrumental/generate" # set request parameters # for more parameter information, please refer to:https://platform.mureka.ai/docs/api/operations/post-v1-instrumental-generate.html headers = {'Authorization': 'Bearer {}'.format(api_key), 'Content-Type': 'application/json'} params = {'model': model, 'prompt': prompt} async with httpx.AsyncClient() as client: response = await client.post(url, json=params, headers=headers) response.raise_for_status() result = response.json() # parse result, pick up task id to query task result task_id = result.get("id", "") if len(task_id) == 0: raise Exception(f"generate instrumental failed") current_timestamp = datetime.now().timestamp() while True: if (datetime.now().timestamp() - current_timestamp) > default_time_out: raise Exception(f"generate instrumental time out {default_time_out} seconds") song_urls, status = await query_instrumental_task(task_id) if status == "failed" or status == "cancelled" or status == "timeouted": raise Exception(f"generate instrumental:{status}") elif status == "succeeded": break else: time.sleep(1) # downloads songs output_path.parent.mkdir(parents=True, exist_ok=True) save_path_group = [] for song_url_item in song_urls: filename = extract_filename_from_url(song_url_item) response = requests.get(song_url_item) if response.status_code == 200: song_bytes = response.content else: raise Exception(f"generate instrumental failed! Can't download songs") save_path = output_path / filename with open(save_path, "wb") as f: f.write(song_bytes) save_path_group.append(save_path) return [TextContent( type="text", text=f"Success. File saved as: {save_path}", ) for save_path in save_path_group] except httpx.HTTPError as e: raise Exception(f"HTTP request failed: {str(e)}") from e except KeyError as e: raise Exception(f"Failed to parse response: {str(e)}") from e def play( audio: Union[bytes, Iterator[bytes]] ) -> None: if isinstance(audio, Iterator): audio = b"".join(audio) try: import io import sounddevice as sd # type: ignore import soundfile as sf # type: ignore except ModuleNotFoundError: message = ( "`pip install sounddevice soundfile` required when `use_ffmpeg=False` " ) raise ValueError(message) sd.play(*sf.read(io.BytesIO(audio))) sd.wait() @mcp.tool(description="Play an audio file. Supports WAV and MP3 formats.") def play_audio(input_file_path: str) -> TextContent: file_path = handle_input_file(input_file_path) play(open(file_path, "rb").read()) return TextContent(type="text", text=f"Successfully played audio file: {file_path}") def main(): print("Starting MCP server") """Run the MCP server""" mcp.run() if __name__ == "__main__": main()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/SkyworkAI/Mureka-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server