MCP and agentsTechnicalTechnical article

How to turn ChatGPT into a semi-agent: your own MCP with a folder, Python, PowerShell, and ngrok

MorenaTechTechnical users and power usersTechnicalabout 12 min
Published:

A full agent sounds dramatic, but it usually ends with too many permissions and too little control. A semi-agent is much more sensible: ChatGPT gets a few precise tools, works in one folder, and performs selected tasks. No full shell, no access to the whole disk, no unnecessary risk.

Before you read

This is a technical article. It is useful if you care about architecture, integrations, or the implementation layer of AI solutions. The guide-style versions are in the “For small business” section.
In this article I show how to build and expose your own MCP server so that ChatGPT can act like a technical sub-agent. Based on our implementation: one workspace, files, Python, PowerShell, a local server at 127.0.0.1:8015/mcp/, smoke tests, and exposing the whole thing through ngrok.
How it works

A flow architecture without fake smoke and lasers

ChatGPT gets a set of MCP tools, the traffic enters through a tunnel, lands on a local endpoint, and from there reaches only one workspace and controlled actions.

ChatGPT
MCP connector
ngrok
localhost: 8015/mcp/
workspace + Python / PowerShell
This is not full autonomy. It is a controlled work flow. That is exactly why this setup helps more often than it later requires emergency rescue of the process.

ChatGPT as a semi-agent, not as a god of infrastructure

If you want ChatGPT to actually help with technical work, you do not need full autonomy from day one. You need a model that can do exactly what you allow it to do.

For us, the best fit was an in-between setup: a semi-agent working on one repo. It does not control the whole system. It sees one working directory, can read and write text files, and can run prepared Python and PowerShell scripts. That is all.

That is already enough for a surprising number of tasks: fixing configuration, generating helper files, running validation, analyzing results, and retrying work. It still is not a full agent. Good.

What we built on our side

Instead of a giant all-in-one system, we built a connector minimum. Operationally, we use only the tool set that actually makes sense in day-to-day technical work.

get_root
list_dir
read_file_text
write_file_text
append_file_text
stat_path
search_text
run_python_file
run_powershell

This is the point where ChatGPT becomes useful without turning into a digital intern with a key to the server room. The server may expose more functions, but in the semi-agent scenario we use only a selected minimum set of tools.

One folder instead of the whole world

The most important security element does not live in prompts. It lives in the server boundaries. The key rule was simple: the semi-agent operates only inside the repo.

  • file operations run inside the project directory
  • Python runs only .py files from the repo
  • PowerShell works only with a workdir that stays inside the repo
  • we test reading first, then writing, and only at the end execution tools

Less flexibility, more predictability. In practice that is a very good deal, because the model has no physical way to wander outside the agreed zone.

What was missing before: installing FastMCP and ngrok

The previous version of this text had an important gap. It described startup and tunneling, but it did not walk the reader from zero through installing FastMCP and installing ngrok.

This matters for another reason too: in our repo the code imports FastMCP, but the current requirements.txt still does not explicitly contain the fastmcp package. That means someone building the environment strictly from that file can easily end up with a missing-module error.

Installing FastMCP step by step

If you start from scratch, first prepare the environment:

cd C:\jagoda-memory-api
python -m venv .venv
.\.venv\Scripts\Activate.ps1

If PowerShell blocks activation:

Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\.venv\Scripts\Activate.ps1

Then install the repo dependencies:

.\.venv\Scripts\python.exe -m pip install -r requirements.txt

And then install FastMCP explicitly:

.\.venv\Scripts\python.exe -m pip install fastmcp

If you want to fix the project and not only your local environment, add fastmcp to requirements.txt so that the next person does not have to guess what is missing.

At the end, verify that FastMCP is really available:

fastmcp version

Installing ngrok step by step

ngrok is not part of the Python repo and should not pretend to be. It is a separate infrastructure component installed system-wide next to the project.

The simplest path is this: install the ngrok Agent CLI system-wide, connect it to your account with an authtoken, verify the configuration, and only then expose the local port.

After installation, check that the tool works:

ngrok version

Then connect the account:

ngrok config add-authtoken <YOUR_TOKEN>

Then verify the configuration:

ngrok config check

And if everything is correct, expose the local server:

ngrok http 8015

How we ran it locally

The project ran locally in C:\\jagoda-memory-api. We started the server in the usual way:

python server.py

After a correct startup, the active local endpoint was:

http://127.0.0.1:8015/mcp/

Before starting the environment, it also helps to check the base setup, activate the virtualenv, and refresh dependencies.

python --version
pip --version
powershell -v

.\.venv\Scripts\Activate.ps1

Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\.venv\Scripts\Activate.ps1

.\.venv\Scripts\python.exe -m pip install -r requirements.txt

A smoke test that actually makes sense

It is very easy after server startup to go in the wrong direction and start testing writing or execution immediately. That is a mistake. For us, this order worked best:

1. get_root()
2. list_dir('.')
3. read_file_text('README.md')
4. stat_path('README.md')

This simple smoke test answers the four most important questions: is the server rooted in the right directory, can it list the structure, does file reading work, and are file metadata visible? Only later does it make sense to move to writing, Python, and PowerShell.

Status 406 does not have to mean failure

This detail can waste half a day. In our implementation, the /mcp/ endpoint responded, but when opened directly it could return 406. That did not mean the server was broken.

On the contrary. It was a sign that the endpoint was alive but expected a valid MCP request. Before you start deep debugging in the code, check the client layer, headers, and the way the protocol is called.

Python and PowerShell, but on a short leash

In a technical semi-agent this is the easiest place to overdo it. The shortcut idea "since it works, let’s give it a full shell" leads straight into trouble.

run_python_file should run only a local .py file from inside the repo. The model does not execute arbitrary code passed as text. It runs a selected file that already exists in a controlled directory.

print("Hello from MAPI connector minimum")

run_powershell should require a non-empty script, have a timeout, and verify that workdir stays inside the repo. At the beginning it is best to use boring but safe commands:

Get-Location
Get-ChildItem
Test-Path .

Where ngrok is actually useful

A local server alone is not enough if ChatGPT should see it from outside your machine. That is where ngrok comes in. For us it was not part of the repo and not a dependency from requirements.txt, but a separate infrastructure layer.

First check whether ngrok is installed and whether the config passes validation:

ngrok version
ngrok config check

In our case the problem was related to configuration around update_channel. This fix helped:

version: "3"
agent.update_channel: stable
agent.update_check: false

How we exposed the local MCP outside

The tunnel must point to the local server running at http://localhost:8015. You do not tunnel "some process." You tunnel a specific port on which the MCP server is already listening.

Only when the local server is up, the port responds, the /mcp/ endpoint is active, and ngrok has a correct configuration does the whole thing start to make sense as an externally visible connector.

How to manage layers instead of debugging everything at once

The biggest mistake in this kind of integration is to treat the whole stack as one thing. It is not one thing. It is several layers that need to be separated.

  1. Local environment: Python works, pip works, PowerShell works, .venv can be activated, fastmcp is installed.
  2. Project layer: critical files are present, dependencies are installed, python server.py starts without a traceback.
  3. Local MCP layer: port 8015 responds, the /mcp/ endpoint responds, and the file smoke test passes.
  4. Execution layer: run_python_file works and run_powershell works.
  5. External exposure layer: ngrok has a correct configuration, the tunnel points to localhost:8015, and the client reaches the correct MCP endpoint.

Prompts for the first tests in ChatGPT

Once the connector is visible from the client side, do not start with complex tasks. First make the model do control tasks.

Use my MCP and call get_root.

Use my MCP and show the root directory contents with list_dir('.').

Use my MCP and read README.md with read_file_text.

Find occurrences of run_powershell in the repo with search_text.

Create a file called raport.md, save a short summary, and append the result of the next step.

Run a test Python file from the scripts directory.

Run a safe PowerShell command in the project directory.

Most common problems

PowerShell blocks .venv activation

Solution: start with Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass.

fastmcp is missing even though the server imports it

Solution: install fastmcp manually and add it to requirements.txt.

The endpoint responds, but you see 406

Do not panic. First check whether the MCP endpoint is alive and whether the client sends a valid request.

ngrok is installed, but the configuration is wrong

Start with ngrok config check and fix the configuration before debugging half the internet.

The server works locally, but the external client cannot see it

Check whether the tunnel points exactly to http://localhost:8015, not to a wrong port or process.

You jump too quickly to execution commands

Go back to the smoke test. Read files first, then write, and only then run processes.

Why this model works better than a full agent

Because it is predictable. In this setup, ChatGPT has no magical powers. It has a handful of tools, a clearly limited work area, and a simple scope of responsibility. That makes it easier to test, debug, secure, and deploy.

A semi-agent is not a weaker version of an agent. In many scenarios it is simply the better tool for the job.

Summary

If you want to turn ChatGPT into something like a technical operator, do not start with full autonomy. Start with a well-bounded MCP setup. Give it one folder instead of the whole disk, files, Python, and PowerShell, but only inside the repo.

First verify reading. Then writing. Execution comes last. And if you want to expose the local server outside, do it consciously through ngrok as a separate infrastructure layer.

For us, that exact arrangement turned out to be the most sensible: a local server at 127.0.0.1:8015/mcp/, a smoke test before heavier actions, a limited tool set, manual addition of fastmcp where requirements.txt still did not include it, and an external tunnel added only after the local foundation was already verified.

And that is probably the best definition of a semi-agent: it does not do everything. It does what is needed, within boundaries that can be understood and maintained.

Want to organize how the model works instead of giving it the keys to the whole server room?

If you want to build a controlled workflow with AI, MCP, files, and a safe scope of action, we can show you how to do it technically without overbuilding the idea.

Read more in Technical