Mcp general info

, ,

just thought this deserves a topic here. Feel free to ask questions about it, share your experience etc.

Never got how it works or why i would need it

MCP Sucks!!

Disclaimer: I have no clue what I’m talking about. This is my first time playing with it. I’m probably wrong. Please disagree or share you’re experience.

Tried installing mcp basic module in python on my phone and besides it eating up over a full gb in termux and taking forever it also failed when actually importing with it making a bad kernel system call rejected by kernel. then i looked into what mcp is actually and decided it sucks.

to be clear, i’m talking about the hype for every1 to build and use 1. Maybe enterprise is different. I’m talking about personal use cases.

what is mcp?

So basically, mcp is supposed to be a way for ai to get access to you’re local tools. So it used to be you’d tell ai to write a script, you’d copy paste it in you’re terminal, manually copy paste the output, manually upload 3 files, download ai files etc etc. Today its much simpler (officially). you just write a mcp server that lets ai access tools you define such as reading files, writing files, running commands or whatever you decide to setup. You can give it access to all files, some, read, write, specific commands or whatever you decide.

what really happens under the hood.

so they didn’t create a new https. You’re still using regular requests where typical ai’s support special json keys that define the available tool names etc and the ai knows how to respond back asking to use 1 of the tools. All this is nothing to do with mcp itself. mcp is just a very fancy json way that you’re phone interacts locally to split the part that speaks to ai from the part that hosts you’re tools and runs them. The mcp user stdio to communicate between these 2 processes. To be honest i don’t see the point. Yes it may be needed for production lever server management connected to ai but for simple connecting to you’re laptop, private server etc?? Why?! i just find it as another level of unnecessary complexity.

now i’m trying to make it manual. Just send https requests, parse the response myself… without any dependencies. (besides requests obviously)

connect ai to you’re local tools easily and safely.

I couldn’t find a safe easy solution (free of course) for ai in my terminal. I thought mcp and look at my previous post. Therefore, i wrote this script for myself, its specifically tailored for me, just putting it out there as its easy to change for anyone.

what the code does

this safely allows ai to execute any command, read any file and write or overwrite any file! BUT absolutely anything it wants to do, it requires the user to grant the ai permission. For commands the user can edit it or deny. Any command he denies the AI gets a message that the user denied it and the user can send along a brief reason why he denied it (instead of the AI changing and trying again if the user doesn’t want to). The script itself keeps track of state so you can have a continued conversation. and best of all ZERO DEPENDENCIES! so instead of compiling and installing huge dependencies, this needs absolutely nothing. Just raw python. Which males it possible and lightweight to run on Termux!!

it integrates with 2 models (Cuz both are free) Gemini and GitHub. For GitHub it user gpt4o and gemini you can choose between 2.5 flash and 2.5 pro and sometimes 3 pro works. You’ll have to paste in you’re Gemini API key and/or GitHub pat (I think that’s what it’s called.). You’ll probably also wanna change the system prompt from my specific setup one. Both the API and system prompt are at the top of the script easy to modify.

Here’s the code:

#!/data/data/com.termux/files/usr/bin/python

"""
Zero-Dependency AI Client (Gemini & GitHub/OpenAI)
Compatibility: Python 3.8+ (Standard Library Only)
"""

import json
import urllib.request # Fixed: was urllib.error, need request for urlopen
import urllib.error
import subprocess
import readline
import sys
import os
import base64
# import requests # Removed: Violates "Standard Library Only" and isn't used.

# --- 1. CONFIGURATION ---
# Edit these or set via os.environ
API_KEYS = {
  "gemini": os.environ.get("GEMINI_API_KEY", "or paste here"),
  "github": os.environ.get("GITHUB_TOKEN", "or paste here")
}

SYSTEM_PROMPT = """
**System Context & User Profile**

**The User:**
You are assisting a Python developer (GitHub: flipphoneguy) who values efficiency and transparency.

**The Environment:**
* **Primary Mobile:** Sonim XP5800 (XP5s). A rugged, locked-down Android device with a small screen/keypad.
* **Development Environment:** Termux (Android).
* **Laptop:** Lenovo ThinkPad T590 running Manjaro Linux KDE (primary) and Windows 11 Pro.

**Preferences & Constraints:**
1. **Honesty First:** If you do not know the solution, state that clearly. Do not provide false hope or definitive "best" fixes without explaining trade-offs.
2. **Concise Code:** The user prefers extremely concise, efficient code.
3. **No Full Rewrites (Python):**
   - When suggesting changes to Python scripts, provide *only* the specific lines that need changing (small blocks) with context.
   - **CRITICAL:** If you are editing a script, **create a NEW file** (e.g., `script_v2.py`) with the fixed code instead of overwriting the original.
4. **Interface:** The user dislikes frontend/GUI work and prefers CLI/backend development.
"""

# Model Names (Jan 2026)
MODELS = {
  "gemini-flash": "gemini-2.5-flash",
  "gemini-pro": "gemini-2.5-pro",
  "gemini-3": "gemini-3-pro-preview",
  "github": "gpt-4o"
}

# --- 2. TOOL DEFINITIONS ---
# This is the "Schema" sent to the AI.
# Modular: You can append to this list from your main script.
TOOLS_SCHEMA = [
  {
    "name": "execute_command",
    "description": "Run a shell command on the local device. Use this for 'ls', 'cat', 'grep', 'pkg', etc.",
    "parameters": {
      "type": "OBJECT",
      "properties": {
        "command": {
          "type": "STRING",
          "description": "The full bash command to run."
        }
      },
      "required": ["command"]
    }
  },
  {
    "name": "read_file",
    "description": "Read a file in the users system.",
    "parameters": {
      "type": "OBJECT",
      "properties": {
        "path": {
          "type": "STRING",
          "description": "path"
        },
        "raw": {
          "type": "BOOLEAN",
          "description": "change to true to read a picture or any not plaintext style file."
        }
      }, "required": ["path"]
    }
  },
  {
    "name": "write_file",
    "description": "Write a file in users system like code", # Fixed: Missing comma
    "parameters": {
      "type": "OBJECT",
      "properties": {
        "path": {
          "type": "STRING",
          "description": "path"
        },
        "content": {
          "type": "STRING",
          "description": "content to write to file"
        },
        "raw": {
          "type": "BOOLEAN",
          "description": "Normally will write string to file. if raw is true content shall be raw bytes encoded with base64. intended for images etc."
        }
      }, "required": ["path","content"]
    }
  }
]

# --- 3. HELPER CLASSES ---

class ToolExecutor:
  """Handles the 'Firewall' logic: Intercept, Edit, Execute."""

  @staticmethod
  def _prefill_input(text):
    """Injects text into the user input buffer using readline."""
    def hook():
      readline.insert_text(text)
      readline.redisplay()
      readline.set_pre_input_hook(None) # Fixed: empty parens is valid but None is clearer

    readline.set_pre_input_hook(hook)
    try:
      return input(f"\n[EDIT COMMAND] >\n")
    finally:
      readline.set_pre_input_hook(None)

  def execute(self, tool_name, args):
    """
    1. Prints the AI's request.
    2. Prefills the input buffer so you can edit the command.
    3. Runs subprocess on Enter.
    """
    if tool_name == "execute_command":
      cmd = args.get("command", "")
      while True:
        _ = input(f"\n[!] AI wants to run: {cmd}. Allow? (Y/n)\n").lower()
        if _ in ("y",""): break
        elif _ == "n":
          print("[!] Enter brief reply to AI why you denied:")
          return self._prefill_input("User revoked command.")
      final_cmd = self._prefill_input(cmd)
      print(f"    Running...")
      try:
        # Capture output to send back to AI
        result = subprocess.run(
          final_cmd,
          shell=True,
          capture_output=True,
          text=True,
          timeout=90 # Safety timeout
        )
        output = f"EXIT: {result.returncode}\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
      except Exception as e:
        output = f"EXECUTION ERROR: {str(e)}"
      print(output)
      return output
    elif tool_name in ("read_file", "write_file"):
      rw="r" if "read" in tool_name else "w"
      path, content = args.get("path",""), args.get("content","")
      b="b" if args.get("raw",False) else ""

      if tool_name == "write_file" and b=="b" and content:
          try:
              content = base64.b64decode(content)
          except Exception as e:
              return f"Error decoding base64 content: {e}"

      if not os.path.isfile(path) and "read" in tool_name: return f"File not found: {path}"

      granted=input(f"[!] AI wants to {tool_name} {path}. Allow? (y/N)\n")
      if granted.lower() != "y":
        print("[!] AI was blocked. Enter brief reply to AI why you denied:")
        return self._prefill_input("User denied access.")

      try:
        with open(path, rw+b) as f:
          if rw=="r":
            data = f.read()
            if b=="b": return base64.b64encode(data).decode('utf-8') # Return string to AI
            else: return data
          else:
            f.write(content)
            return "Success!"
      except Exception as e:
          return f"File IO Error: {e}"
    else:
      return f"Error: Tool '{tool_name}' not implemented locally."

class GeminiProvider:
  """Manual REST implementation for Google Gemini API."""
  def __init__(self, api_key, model_name):
    self.api_key = api_key
    self.model = model_name
    self.url = f"https://generativelanguage.googleapis.com/v1beta/models/{self.model}:generateContent?key={self.api_key}"
    #  Gemini history format: {"role": "user", "parts": [{"text": "..."}]}
    self.history = []

  def send(self, prompt, tool_result=None):
    """
    Sends a request. Handles both new prompts and tool result replies.
    """
    if tool_result:
      # Append Tool Response to history
      self.history.append({
        "role": "function",
        "parts": [{
          "functionResponse": {
            "name": tool_result["name"],
            "response": {"content": tool_result["content"]} 
          }
        }]
      })
    elif prompt:
      # Append User Prompt to history
      self.history.append({"role": "user", "parts": [{"text": prompt}]})

    # Construct Payload
    payload = {
      "system_instruction": {
        "parts": {"text": SYSTEM_PROMPT}
      },
      "contents": self.history,
      "tools": [{"function_declarations": TOOLS_SCHEMA}]
    }

    # Manual HTTP Request
    req = urllib.request.Request(self.url, method="POST")
    req.add_header('Content-Type', 'application/json')
    data = json.dumps(payload).encode('utf-8')

    try:
      with urllib.request.urlopen(req, data=data) as response:
        result_json = json.loads(response.read().decode('utf-8'))
      return self._parse_response(result_json)
    except urllib.error.HTTPError as e:
      return {"type": "error", "content": f"HTTP {e.code}: {e.read().decode()}"}

  def _parse_response(self, resp):
    """Extracts text or function calls from Gemini's JSON."""
    try:
      candidate = resp["candidates"][0]["content"]
      # Save AI response to history to maintain context
      self.history.append(candidate)

      part = candidate["parts"][0]

      if "functionCall" in part:
        return {
          "type": "tool",
          "name": part["functionCall"]["name"],
          "args": part["functionCall"]["args"]
        }
      else:
        return {
          "type": "text",
          "content": part["text"]
        }
    except (KeyError, IndexError):
      return {"type": "error", "content": "Invalid API Response structure"}

class GitHubProvider:
  """Manual REST implementation for GitHub Models (OpenAI Compatible)."""
  def __init__(self, api_key):
    self.api_key = api_key
    self.url = "https://models.inference.ai.azure.com/chat/completions"
    self.model = "gpt-4o"
    # OpenAI history format: {"role": "user", "content": "..."}
    self.history = [
      {"role": "system", "content": SYSTEM_PROMPT}
    ]

  def send(self, prompt, tool_result=None):
    if tool_result:
      self.history.append({
        "role": "tool",
        "tool_call_id": tool_result["id"],
        "content": tool_result["content"]
      })
    elif prompt:
      self.history.append({"role": "user", "content": prompt})

    # Convert our Schema to OpenAI format (slightly different structure)
    openai_tools = [{
      "type": "function",
      "function": t
    } for t in TOOLS_SCHEMA]

    payload = {
      "model": self.model,
      "messages": self.history,
      "tools": openai_tools
    }

    req = urllib.request.Request(self.url, method="POST")
    req.add_header('Content-Type', 'application/json')
    req.add_header('Authorization', f'Bearer {self.api_key}')
    data = json.dumps(payload).encode('utf-8')

    try:
      with urllib.request.urlopen(req, data=data) as response:
        result_json = json.loads(response.read().decode('utf-8'))
        return self._parse_response(result_json)
    except urllib.error.HTTPError as e:
      return {"type": "error", "content": f"HTTP {e.code}: {e.read().decode()}"}

  def _parse_response(self, resp):
    msg = resp["choices"][0]["message"]
    self.history.append(msg) # Save to history

    if msg.get("tool_calls"):
      call = msg["tool_calls"][0]
      return {
        "type": "tool",
        "name": call["function"]["name"],
        "args": json.loads(call["function"]["arguments"]),
        "id": call["id"] # OpenAI needs this ID for the reply
        }
    else:
      return {
        "type": "text",
        "content": msg["content"]
      }

# --- 4. PUBLIC API ---

def get_provider(name="gemini-flash"):
  """Factory to return the correct provider class."""
  if name == "github":
    return GitHubProvider(API_KEYS["github"])
  # Map friendly name to actual Gemini model ID
  model_id = MODELS.get(name, MODELS["gemini-flash"])
  return GeminiProvider(API_KEYS["gemini"], model_id)

def handle_tool_execution(tool_name, args):
  """
  Wrapper for your main loop to call.
  Handles the 'Edit -> Enter -> Execute' flow.
  """
  executor = ToolExecutor()
  return executor.execute(tool_name, args)

def main():
    # 1. Parse Arguments for Model Selection
    model_choice = "gemini-flash" # Default
    if len(sys.argv) > 1:
        arg = sys.argv[1].lower()
        if arg == "-3":
            model_choice = "gemini-3"
        elif arg == "-pro":
            model_choice = "gemini-pro"
        elif arg == "-flash":
            model_choice = "gemini-flash"
        elif arg in ["-github", "-chatgpt", "-gpt"]:
            model_choice = "github"

    print(f"[*] Initializing {model_choice}...")
    try:
        provider = get_provider(model_choice)
    except Exception as e:
        print(f"[!] Error initializing provider: {e}")
        return

    print("Type 'quit' or 'exit' to stop.")

    # 2. Main Chat Loop
    while True:
        try:
            user_input = input("\n> ")
            if user_input.lower() in ["quit", "exit"]:
                break
            if not user_input.strip():
                continue

            # Send initial request
            print("...")
            response = provider.send(user_input)

            # Handle potentially multiple tool calls in a sequence
            while response.get("type") == "tool":
                tool_name = response["name"]
                tool_args = response["args"]
                tool_id = response.get("id") # Needed for GitHub/OpenAI

                # Execute tool
                tool_output = handle_tool_execution(tool_name, tool_args)

                # Payload for tool result
                tool_result = {
                    "name": tool_name,
                    "content": tool_output,
                    "id": tool_id
                }

                # Send result back to AI and get next response
                print("...")
                response = provider.send(None, tool_result=tool_result)

            # Display final text response
            if response.get("type") == "text":
                print(f"\nAI: {response['content']}")
            elif response.get("type") == "error":
                print(f"\nAPI Error: {response['content']}")

        except KeyboardInterrupt:
            print("\n[!] Exiting...")
            break
        except Exception as e:
            print(f"\n[!] Unexpected Error: {e}")
            break

if __name__ == "__main__":
    main()


Run python filename.py to start the conversation with AI. By default it’ll start with Gemini 2.5 flash. The options are as follows:

  • gemini 2.5 flash: python filename.py (default)
  • gemini 2.5 pro: python filename.py -pro
  • gemini 3 pro experimental: python filename.py -3
  • chatgpt via github: python filename -github or -gpt or -chatgpt

i actually did on termux
chmod +x ai.py cp ai.py $PREFIX/bin/ai

so now i can just run ai from anywhere to get AI console with access to my file.

I’m not understanding. Why not just use the Gemini CLI?

  1. gemini cli (and gh with models extension) is extremely heavyweight. both to install and to run. On termux for dumbphones they may make issues. (like on my phone gemini cli wouldn’t install at all. Gh models extension also wouldn’t but that i could’ve fixed.)
  2. gh can’t execute commands (and probably can’t even read/write files. I’m not sure). and gemini doesn’t ask before it does. not being able to is stupid, and not asking before is catastrophic. If you ever used ai before for fixing problems on your device you probably know this. (just check out the artificial stupidity thread). i know it officially asks before “dangerous” commands but somehow it often fails…
  3. This was fun building :rofl:. It also taught me how stupid mcp’s are.

Claude code works pretty well, I’ve used it in termux on a qin

It costs. no?

Worth every penny if you learn how to work with it

Is it actually better then gemini (especially agent mode like in jules)?

Never used, and I’m specifically referring to opus 4.5.

Yes it does. At least for me, although I haven’t used it extensively

.