mcpnavi.jp is a Japanese directory of MCP services, the plugins that connect an AI agent such as ChatGPT or Claude to data and tools. We launched it on 25 September 2026. Operators of those services sometimes want to be listed. Until last week, the only way to ask was a mailto: link.
Email works, but it is a poor intake channel. People leave out the information we need, and the requests sit in an inbox next to everything else. I wanted a proper form with fixed fields.
The catch is that mcpnavi.jp is a static site. Django renders every page into files, and GitHub Pages serves them. There is no server to receive a POST. So the form needs a backend that someone else runs.
I chose FiraForm. It is made by Taming Tech in Kuala Lumpur, and it is run by a friend of mine, Iszuddin Ismail. I say more about him at the end. That is a disclosure, not the reason. I would not put a form on a public site if it did not do the job.
What FiraForm Is
FiraForm is a form backend. You build the form in your own HTML and point its action at a FiraForm URL:
<form action="https://a.firaform.com/api/f/<form-id>" method="post">
<input type="text" name="service_name" required>
<input type="email" name="email" required>
<button type="submit">Send</button>
</form>
FiraForm stores the submission, can send an email or call a webhook, and then sends the browser to a success page you choose. You never write server code. The site lists AI spam detection (I explain how that works below), a domain whitelist, CSV export, a submissions API and payments as well. There is a free plan, and paid plans start at $5 a month.
Setting Up the Form and Its Fields
The dashboard gives each field a type. Text, email, URL, number, checkbox and so on are all there, so FiraForm can check the value before it accepts a submission. For a listing request, that matters. A service's official URL should be a URL, and a contact address should be an email address.
Our form has 19 fields in three sections: the service, its provider, and the person sending the request. It asks where a value can be read, such as the pricing page or the company page, never for the value itself. A person at mcpnavi then reads those public pages.
The Honeypot Field
FiraForm has a field type called Honeypot. You add a field to your HTML that people never see, and bots fill in because they fill in everything. When a submission arrives with that field filled, FiraForm drops it.
For example, you can set a common parameter field called fax_number, a name a bot is happy to fill:
<input type="text" name="fax_number" value=""
style="display:none" tabindex="-1" autocomplete="off" aria-hidden="true">
Two details are worth knowing:
- Do not use
type="hidden". Many bots skip hidden inputs. A text input hidden with CSS catches more of them.tabindex="-1"keeps keyboard users out of it, andaria-hiddenkeeps screen readers quiet about it. - The drop is silent. The bot still lands on your success page, so it learns nothing. You can still tell the difference: FiraForm adds
submission_idto the success URL only when it stored the submission.
I tested this in a real browser with Selenium against the live site. A person's submission came back with a submission_id. The same form with fax_number filled in by script came back with none. That is the behaviour you want.
Where the AI Comes In: Spam Filtering
A honeypot only catches bots that fill in every field. It does nothing about a person, or a better script, that fills in the form properly and sends an advertisement. FiraForm uses AI for that.
"AI spam detection" on a features page can mean almost anything, so I asked Iszuddin how it works. His answer was "nothing fancy". The AI has one job, which is spam filtering:
- FiraForm removes the personal information from a submission's content.
- It sends the rest to a model hosted on Cloudflare Workers AI.
- If the model says the submission is spam, FiraForm puts it in a separate Spam tab in the dashboard, next to the Submissions tab.
This check goes through a queue, so it does not hold up the form submission. Iszuddin said they work hard to keep submission fast. The visitor does not wait for a model to answer before they see the success page.
This is different from the honeypot. A honeypot submission is dropped and never stored. A submission the model flags is only set aside, so if the model gets one wrong, you can still find it in the Spam tab.
I like how this is built, for three reasons:
- The AI has a narrow job. It sorts submissions into two tabs. It does not write, change or answer anything.
- Personal information is removed before the model sees the content. A contact form collects names and email addresses, and the site owner is responsible for them. Japan has seen a wave of data breaches this month, and every service that receives your users' data is one more place it can leak. Sending less of that data to a model is the right default.
- The visitor does not pay for it. The check runs on a queue, so the form is as quick to submit as one without AI.
Two Languages, Two Forms
mcpnavi.jp is in Japanese at / and in English at /en/, so the form exists in both languages. My first plan was one FiraForm form for both. That does not work, for two reasons:
- A choice is sent as its label. The category dropdown sends 「開発ツール」 on the Japanese page and "Developer tools" on the English one. Mixed into one form, the data is hard to read.
- Each form has one success redirect. The Japanese thank-you page and the English one are different pages. FiraForm ignores a
_nextfield in the submission and always uses the URL set in the form's settings.
So we needed two forms with the same fields. My first instinct was to build the Japanese form in the dashboard, then copy it. There is no copy button. I asked Iszuddin, and the answer was better than a copy button.
What I Like Best: Your HTML Defines the Form
You do not have to define the fields in FiraForm first. Create an empty form, send one submission from your HTML, and FiraForm creates a field for every input it receives. Our form already lives in a JSON file in our repository, and Django renders it in both languages. One test submission from each page gave me both FiraForm forms, with identical field names.
This impressed me. I had assumed the dashboard was the source of truth and my HTML had to match it. With two languages, keeping two dashboards in step by hand would have been tedious and easy to get wrong. Instead, the HTML is the source of truth, and FiraForm follows it. That fits how a developer works: the form is in version control, and the backend adapts.
Two details to know:
- Fill in every field in the first submission. FiraForm creates a field only for an input that arrives with a value. A field left empty on the first submission is not created.
- Every detected field is a text field. You then set the right types in the dashboard.
Feature Request
That second point is my one request. The HTML already says what each input is. type="email" and type="url" are right there in the markup. If FiraForm read the type attribute from the first submission, or at least recognised email and URL, the form would be ready with no extra step. Today I go through each field by hand and change its type, twice, once per language.
Setup Details That Cost Me Time
None of these is a flaw, but each one cost me some time. They are worth knowing before you start.
- The Referrer-Policy header matters. My first live test failed with "Unauthorized domain", and the request showed
origin: "null". Django's defaultReferrer-Policyissame-origin, which makes the browser sendOrigin: nullon a POST to another site. FiraForm's domain whitelist then has nothing to match. SettingSECURE_REFERRER_POLICY = "strict-origin-when-cross-origin", the browsers' own default, fixed it. GitHub Pages sends no such header, so production was fine. The development server was the problem. - The whitelist takes
localhost, not127.0.0.1. To test from a development server, addlocalhostand open the site athttp://localhost:<port>/. The port does not matter. - Check "Allowed Characters" on text fields. Our first English form rejected submissions because a box on some text fields was ticked. That setting blocks punctuation such as
.,@,/and parentheses, which a service name, a URL-like summary or an install command will contain. Leave it unticked unless you need it.
A Note About the Community
FiraForm's maker is Iszuddin Ismail. His company, Taming Tech, also runs Laravel training for companies and individuals across Malaysia and Southeast Asia. In August 2024, Taming Tech hosted a meetup where I spoke at its training centre in Kuala Lumpur.
Many developers in Malaysia know him first as the person behind JOMLAUNCH. He founded and organised it from 2015 to 2022. It started as a demo day for JOMWEB, a Facebook group for Malaysian programmers, developers and system administrators. Vulcan Post's report on the October 2015 edition counted 17 projects in four categories: commercial, personal, open source and community. The group had 11,000 members by then.
The idea was simple: members build something and show it to each other. Over the years it grew in two directions. JOMLAUNCH ASIA is a conference for bootstrappers, indie hackers and solo developers, people who build a software business on the internet with their own money. In 2022, JomLaunch Youth ran with JomWeb as a corporate partner of Malaysia Techlympics, a programme of the Ministry of Science, Technology and Innovation (MOSTI). It offered free tracks in IoT, mobile apps and web apps. MOSTI's press release (Ministry Of Science and Technology, in Malay) reports more than 1,000 participants aged 18 to 30, from more than 50 universities and colleges. The closing ceremony was at the World Trade Centre Kuala Lumpur on 22 October 2022. JOMLAUNCH 2022 ran at the same venue from 19 to 22 October, and our team at LaLoka Labs had a booth and gave talks there.
On the PHP side, Iszuddin wrote 24 Jam Laravel, which he describes as the first comprehensive Laravel book in Bahasa Melayu. It lets Malaysian developers learn the framework in their own language.
I see that background in how FiraForm is built. It is a small tool that does one job, made by someone who writes PHP every day and spent years among people who build and ship their own products. It starts from the developer's HTML, not from a dashboard, and that is the decision a developer would make.
Wrapping Up
FiraForm took a static site from a mailto: link to a real, validated, spam-filtered form in an afternoon, in two languages, with no server of our own. The honeypot catches simple bots, the AI filter moves the spam that gets past it into its own tab, the success redirect works, and the submissions arrive in a clean table.
The feature that stood out was building the form from my own HTML. It turned the multilingual setup from a chore into one test submission per language. If it learned the input types from that same submission, the setup would be complete in one step.
If you run a static site and want a form without running a server, FiraForm is worth a try. You can see ours here.