Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/translate-to-english.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,5 @@ jobs:
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "chore: add English translations for PR #${{ github.event.pull_request.number }}"
file_pattern: src/content/blog-en/**/*.md
file_pattern: 'src/content/blog-en/*.md'
add_options: '-A'
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
130 changes: 114 additions & 16 deletions scripts/translate_to_en.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,38 @@
POSTS_EN_DIR = REPO_ROOT / "src" / "content" / "blog-en"


PROMPT_SYSTEM = (
"You are an expert technical writer and translator. Translate the given Korean Astro blog post content into natural, concise American English.\n"
"- Preserve front matter keys; translate 'title' and any 'description' or 'summary' fields to English.\n"
"- Keep Markdown structure, headings, code blocks, links, images, and footnotes intact.\n"
"- Do not hallucinate code or change code semantics.\n"
"- Maintain YAML front matter formatting.\n"
"- If there is mixed language, prefer English.\n"
"- Do NOT change the file name or slug. The output will be saved using the exact same filename as the source.\n"
)
PROMPT_SYSTEM = """You are an expert technical writer and translator specializing in MLOps, LLMOps, and AI infrastructure content.

## Task
Translate the given Korean Astro blog post into professional, SEO-optimized American English.

## SEO & Front Matter Guidelines
- **title**: Create a compelling, keyword-rich title (50-60 characters ideal). Include primary keywords like tool names, technologies, or concepts.
- **description**: Write a clear meta description (150-160 characters) that includes target keywords and encourages clicks. Summarize the value proposition.
- Preserve all other front matter keys (pubDate, tags, etc.) exactly as they are.

## Content Translation Guidelines
- Write in a professional yet approachable tone suitable for a senior engineer audience.
- Preserve technical accuracy - do NOT change code, commands, configurations, or technical terms.
- Keep the author's voice and personal experiences intact (e.g., "In my experience...", "I found that...").
- Maintain all Markdown structure: headings, code blocks, links, images, lists, blockquotes.
- Translate Korean technical jargon to commonly accepted English equivalents.
- Keep product names, tool names, and proper nouns unchanged (e.g., mcp-context-forge, Kubernetes, PostgreSQL).
- For headings, use clear and descriptive phrases that work well for SEO and readability.

## Quality Standards
- Use active voice where possible.
- Be concise - remove filler words common in Korean-to-English translation.
- Ensure the translation reads naturally, as if originally written in English.
- Do NOT add or remove content - translate faithfully.
- Do NOT change the filename or slug.

## Output Format
Return the complete translated Markdown file with YAML front matter.
- IMPORTANT: The title and description MUST be translated to English. Do NOT keep them in Korean.
- IMPORTANT: Always wrap title and description values in double quotes to handle special YAML characters like colons. Example: title: "My Title: A Subtitle"
- Do NOT wrap the output in markdown code blocks (no ``` markers).
- Start directly with --- and the YAML front matter."""


def normalize_repo_path(path_like: str | Path) -> Path:
Expand Down Expand Up @@ -86,20 +109,86 @@ def call_openai(model: str, system_prompt: str, user_prompt: str) -> str:
return resp.choices[0].message.content


def strip_markdown_codeblock(text: str) -> str:
"""Strip markdown code block wrappers if present."""
text = text.strip()
if text.startswith("```"):
lines = text.split("\n")
if lines[0].startswith("```"):
lines = lines[1:]
if lines and lines[-1].strip() == "```":
lines = lines[:-1]
text = "\n".join(lines)
return text.strip()


def contains_korean(text: str) -> bool:
"""Check if text contains Korean characters (Hangul)."""
for char in str(text):
if "\uac00" <= char <= "\ud7a3" or "\u1100" <= char <= "\u11ff":
return True
return False


def split_front_matter(translated_markdown: str) -> tuple[dict, str]:
m = re.match(r"^---\n([\s\S]+?)\n---\n?([\s\S]*)$", translated_markdown.strip())
text = strip_markdown_codeblock(translated_markdown)

patterns = [
r"^---\s*\n([\s\S]+?)\n---\s*\n?([\s\S]*)$",
r"^---\s*\n([\s\S]+?)\n---\s*([\s\S]*)$",
r"^-{3,}\s*\n([\s\S]+?)\n-{3,}\s*\n?([\s\S]*)$",
]

m = None
for pattern in patterns:
m = re.match(pattern, text)
if m:
break

if not m:
return {}, translated_markdown
print(f"[translate] Debug: Could not match frontmatter. First 300 chars:")
print(text[:300])
return {}, text

fm_text, body = m.group(1), m.group(2)
try:
fm = yaml.safe_load(fm_text) or {}
if not isinstance(fm, dict):
print(f"[translate] Debug: Parsed YAML is not a dict: {type(fm)}")
fm = {}
except yaml.YAMLError as e:
print(f"[translate] Debug: YAML parse error, trying to fix: {e}")
fixed_fm_text = fix_yaml_special_chars(fm_text)
try:
fm = yaml.safe_load(fixed_fm_text) or {}
if not isinstance(fm, dict):
fm = {}
else:
print("[translate] Debug: Fixed YAML successfully")
except yaml.YAMLError:
print(f"[translate] Debug: Could not fix YAML:\n{fm_text[:300]}")
fm = {}
except yaml.YAMLError:
fm = {}
return fm, body


def fix_yaml_special_chars(fm_text: str) -> str:
"""Fix YAML values that contain special characters like colons."""
lines = fm_text.split("\n")
fixed_lines = []
for line in lines:
if line.startswith("title:") or line.startswith("description:"):
key, _, value = line.partition(":")
value = value.strip()
if value and not (value.startswith('"') and value.endswith('"')):
if value.startswith("'") and value.endswith("'"):
pass
elif ":" in value or "#" in value:
value = '"' + value.replace('"', '\\"') + '"'
line = f"{key}: {value}"
fixed_lines.append(line)
return "\n".join(fixed_lines)


def ensure_posts_en_dir():
POSTS_EN_DIR.mkdir(parents=True, exist_ok=True)

Expand Down Expand Up @@ -135,11 +224,20 @@ def main() -> int:
prompt = build_prompt(post)
translated = call_openai(model, PROMPT_SYSTEM, prompt)
fm, body = split_front_matter(translated)
original_fm = dict(post)

if not fm:
fm = dict(post)
if "title" not in fm or not fm["title"]:
fm["title"] = dict(post).get("title", "")
print(f"[translate] Warning: Could not parse frontmatter, using original")
fm = original_fm.copy()

for key in original_fm:
if key not in fm:
fm[key] = original_fm[key]

if contains_korean(fm.get("title", "")):
print(f"[translate] Warning: title still contains Korean: {fm.get('title')}")
if contains_korean(fm.get("description", "")):
print(f"[translate] Warning: description still contains Korean")

en_path = to_en_filename(src)
en_path.parent.mkdir(parents=True, exist_ok=True)
Expand Down
165 changes: 165 additions & 0 deletions src/content/blog-en/mcp-context-forge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
---
description: Hands-on experience running IBM’s open-source MCP Gateway, mcp-context-forge,
in production. Learn Virtual Server-based tool authorization, the MCP Catalog, metrics,
and more.
pubDate: '2025-12-19'
tags:
- LLMOps
- MCP
- Agent
title: 'MCP Gateway Recommendation: mcp-context-forge in Production'
---

## TL;DR

> I consider an MCP Gateway one of the essential tools for building an Agentic AI platform. If you’re accumulating more MCP servers than you can manage—or you keep spinning up redundant MCP servers—consider adopting the open-source project mcp-context-forge.



Recently, while working on a project to set up agent infrastructure, I got hands-on experience with several open-source projects, and I wanted to share what I learned.

Building agent infrastructure isn’t just about creating a system that calls an LLM. It means turning it into a **software platform** that can be operated sustainably. I’d like to introduce a few open-source projects that helped a lot with the architecture design, and this is the first post in that series. I decided to start with the tool I’m most attached to—one I’ve even contributed to and use heavily.

It’s the MCP Gateway open source project: [**mcp-context-forge**](https://github.com/IBM/mcp-context-forge). Let’s walk through it step by step.

As of writing, it’s V1.0.0-BETA-1.



## Why Do You Need an MCP Gateway?

Before explaining mcp-context-forge, it’s worth addressing **“Why do you need an MCP Gateway?”** first. Before adopting an MCP Gateway, I think it’s good to ask yourself:

1. Do I have enough MCP servers and tools to justify needing an MCP Gateway?
2. Will agents need to call the same MCP?

In early-stage or small projects, you may not feel the need for an MCP Gateway. It can even feel like over-engineering since it adds management overhead and can become a single point of failure.

But once you hit the point where **the number of agents and MCPs grows and the system starts scaling**, the story changes. From that moment, the benefits can be so strong that the initial effort to introduce a gateway feels trivial.

The biggest advantage is the MCP Catalog. When everyone is churning out MCP servers because “MCP is great,” understanding what MCP servers and tools exist inside your organization becomes an important problem.

I’ve also run into situations where multiple MCP tools were created that do the exact same thing. In that kind of environment, being able to view a list of what MCP servers and tools exist is a major win.


## What Is mcp-context-forge?

Now let’s look at mcp-context-forge more directly.

`mcp-context-forge` is an MCP Gateway open-sourced by IBM. It efficiently brokers increasingly complex connections between agents and tools.

To be honest, there aren’t many MCP Gateway tools out there. When I was doing research, mcp-context-forge and the kgateway + Agentgateway combination looked appealing. But Agentgateway felt relatively complex to operate, and I felt it wasn’t mature enough for production use at the time. We also already use a different gateway stack internally.

For those reasons, I chose mcp-context-forge. Of course, mcp-context-forge itself says it shouldn’t be used at the production level yet since it’s not a stable release. In my case, I *am* running it in production, and I haven’t felt any major issues.



### Try Running It

My production environment is Kubernetes, but in this post I’ll use Docker.

Given how many GitHub stars it already has, you can test it without much trouble.

```bash
git clone https://github.com/IBM/mcp-context-forge
docker-compose up
```

The setup is mainly composed of the Gateway, a main DB (Postgres or MariaDB), Redis, and `fast_time_server`.
It’s not a complicated architecture, so it’s easy to operate.

There’s a separate time-related server called `fast_time_server`. I’m planning to look into that later (it doesn’t feel very impactful so far).



The start screen looks like this. You get a colorful UI, and it also provides a useful login experience—including optional SSO integration.

The default email address and password are `admin@example.com`, `changeme`.

![image-20251218010740484](/img/mcp-context-forge/image-20251218010740484.png)



After logging in, you’ll see a screen like the following. I’ve been watching this project since v0.8, and as of v1.0.0-BETA-1, you can really feel how much the design has evolved.

![image-20251219001536178](/img/mcp-context-forge/image-20251219001536178.png)

In the sidebar, there are various options:

- MCP Servers: In mcp-context-forge, these are labeled as “Gateways,” but you can think of them as MCP Servers. When you register one here, the tools, prompts, and resources on that MCP server are automatically registered.
- Virtual Servers: I consider this mcp-context-forge’s core feature. You can create a virtual gateway and select the tools, prompts, and resources you need—then use it just like an MCP server. This is a very useful feature for configuring per-agent toolsets and separating tool permissions.
- Tools, Prompts, Resources: Same meaning as in MCP.
- Roots: I’m not sure what this does... I haven’t used it. It looks like a feature for pulling content from storage on the server, but I don’t really know.
- MCP Registry: mcp-context-forge runs a registry you can register from easily. You can add a registry you like with one click.
- Agents: You can register not only MCP servers but also agents.
- Metrics: You can view mcp-context-forge metrics. You can see which tools are called frequently, when they were used most recently, and identify unused tools.
- Teams / Users: You can manage mcp-context-forge user permissions.

![image-20251219001059140](/img/mcp-context-forge/image-20251219001059140.png)

The screen above is the MCP Registry. If you click Add Server for a few entries for testing, you’ll see them registered like in the screenshot below.

Of course, for most tools, adding the server isn’t the end—you still need to use whatever authentication each server requires.

![image-20251219001727809](/img/mcp-context-forge/image-20251219001727809.png)

The screen above shows what gets created when you register GitHub, for example.

![image-20251219002025981](/img/mcp-context-forge/image-20251219002025981.png)

I also registered a few registries for testing besides GitHub. Once configuration is complete, you can browse the tools provided by each MCP server in Tools.

![image-20251219002100211](/img/mcp-context-forge/image-20251219002100211.png)

As shown above, you can test a registered tool.

![image-20251219002139585](/img/mcp-context-forge/image-20251219002139585.png)

This is the Virtual Servers screen—the core feature I emphasized earlier. As you can see, you can select the MCP Servers and the Tools / Resources / Prompts you want.

![image-20251219002209987](/img/mcp-context-forge/image-20251219002209987.png)

After selecting a Virtual Server, you can confirm it gets created like this.

![image-20251219002222560](/img/mcp-context-forge/image-20251219002222560.png)

When you view Config, you can choose as shown above. It supports Stdio, SSE, and HTTP, so there’s no issue using it for MCP.

![image-20251219002234945](/img/mcp-context-forge/image-20251219002234945.png)

This is an example of the SSE method, showing the familiar MCP registration configuration. If you look at the headers, you’ll see it requires a Bearer token—mcp-context-forge uses tokens to control authorization.

![image-20251219002305321](/img/mcp-context-forge/image-20251219002305321.png)

The token configuration screen looks like this. For each token, you can specify which Virtual Servers it can access. There’s also an IP restriction feature, but I haven’t used it. You can also configure permissions, but in my case I typically just define which tools each agent can access, so I haven’t used that either.

Now I’ll wrap up by summarizing the pros and cons of mcp-context-forge.

## Pros

The biggest advantage is that it serves as an MCP Catalog. You can understand at a glance what MCP servers and tools exist in your organization, which helps prevent redundant MCP servers from being created. It also makes cross-team collaboration easier because you can quickly answer questions like, “Do we have a tool for this?”

Permission separation through Virtual Servers is also compelling. You can specify only the tools needed for each agent, which prevents unnecessary tool calls and provides peace of mind from a security standpoint. The metrics feature also makes it easy to see which tools are actually being used versus which are being neglected.

The simple architecture is another plus. You can operate it with just PostgreSQL or MariaDB and Redis, so the operational burden is low. Since it supports Stdio, SSE, and HTTP, there are no compatibility issues with existing MCP clients.

## Cons

I didn’t really feel any major functional downsides. If I had to pick one, it’s that it’s still a beta version, so some features are not implemented yet.

Also, compared to connecting directly from an agent to an MCP, it seems there are a few authentication methods it doesn’t support. In my case, I remember trying to connect using FastMCP Client and failing, so I switched to the mcp library instead. This may have been resolved by now.

Finally, since it’s a gateway, you should consider that it can become a Single Point of Failure. That said, this is a common characteristic of all gateways.

## Conclusion

An MCP Gateway becomes mandatory infrastructure once the number of MCP servers and tools starts growing. mcp-context-forge is still in beta, but based on my experience running it in a real production environment, it worked reliably without major issues.

Most importantly, it’s great that IBM has open-sourced it and is actively developing it. I’m using it while contributing, and issue turnaround is fairly fast.

If you’re considering adopting an MCP Gateway, I recommend taking a look at mcp-context-forge.

## References

- [mcp-context-forge GitHub](https://github.com/IBM/mcp-context-forge)
- [mcp-context-forge official documentation](https://ibm.github.io/mcp-context-forge/)
Loading