You build one self-contained Tool folder.
Kura is the hosting shell. The Kura owner manages deployment, the public domain, runtime supervision, the directory page, and shared infrastructure. As a Tool author, your job is to deliver one folder that starts correctly and works under its assigned Kura URL.
Application files, dependencies, tool.json, and a working root page.
Public routing, internal ports, process startup, restart handling, listing cards, related Tools, SEO, and counters.
server.js, runtime-manager.js, Render settings, or another Tool's folder.
tools/your-tool/. You should not need to change anything outside that folder.Everything your Tool needs must live together.
Choose a short, URL-safe folder name using lowercase letters, numbers, and hyphens. The folder name becomes the public slug.
tools/
└── token-check/
├── tool.json
├── app.py
├── requirements.txt
├── templates/
│ └── index.html
└── static/
├── style.css
└── app.js
The example above is published at /tools/token-check/. Folder names must be unique. Avoid spaces, uppercase letters, Japanese characters, query symbols, and a trailing dot.
Minimum required files
| Tool type | Minimum files |
|---|---|
| Static | index.html and tool.json |
| Python | app.py or main.py, requirements.txt, and tool.json |
| Node.js | package.json with a start script, application entry file, and tool.json |
| PHP | index.php and tool.json |
| Java | A generated JAR or Maven/Gradle source project, plus tool.json |
tools/_templates/. Copy one, rename the folder, then replace the sample code.Do not rebuild features Kura already supplies.
Once the folder is accepted, Kura automatically adds the Tool to the directory and links the card to its folder page. It also handles:
- English/Japanese directory metadata from
tool.json. - Search, categories, tags, favorites, view counts, and click counts.
- Related Tool cards based on shared categories and tags.
- Page title, meta description, canonical URL, Open Graph tags, JSON-LD, sitemap, and robots metadata.
- Private internal port assignment and proxying for server-based Tools.
- Automatic restart after an unexpected process exit unless
restart: false.
Your Tool should focus on its own function. Do not add a duplicate Kura header, directory navigation, favorite counter, related Tool section, or SEO generator.
Write a complete tool.json.
{
"name": "Token Check",
"runtime": "python",
"entry": "app.py",
"description": "Checks whether a token is valid.",
"descriptionJa": "Tokenが有効か確認します。",
"category": "Utility",
"categoryJa": "ユーティリティ",
"icon": "TC",
"tags": ["Python", "Token"],
"tagsJa": ["Python", "トークン"],
"published": true,
"healthPath": "/",
"startupTimeoutMs": 30000,
"restart": true
}
| Field | Author guidance |
|---|---|
name | Human-readable Tool name. It must be unique after case, spaces, punctuation, and Unicode-width differences are normalized. |
description / descriptionJa | One direct sentence describing what the Tool does. Do not use marketing filler. |
category / categoryJa | Use one broad category. Related Tools use this field. |
tags / tagsJa | Use a few precise tags. Related Tool matching and search use them. |
icon | Short text shown when no image icon is configured. Two characters work best. |
runtime | static, python, node, php, java, or custom. Explicitly setting it is recommended. |
entry | Main Python file, Node file when no package start script is used, or JAR path. |
healthPath | A fast GET endpoint returning a status below 500. Default is /. |
startupTimeoutMs | Increase this only for slow startup, such as a large Java application. Default is 30000. |
restart | Leave true or omit it. Use false only when an automatic restart would be incorrect. |
published | Use false while the folder is unfinished. Omit it or set true for publication. |
allowDuplicateName | Leave false or omit it. Set true on every matching Tool only when duplicate display names are intentional. |
build / start | Advanced overrides. Use only when the standard runtime behavior cannot start the Tool. |
env | Non-secret defaults only. Never commit API keys or passwords here. |
tool.json must be valid JSON. Tool names are checked during the Render build; an accidental duplicate stops the build before deployment.Your Tool is not hosted at the domain root.
A folder named token-check appears at /tools/token-check/. Kura strips that prefix before proxying a dynamic request to your app, but browser-side URLs must still remain inside the Tool path.
| Use | Avoid | Reason |
|---|---|---|
<link href="./style.css"> | <link href="/style.css"> | A leading slash points to the Kura site root. |
fetch("./check") | fetch("/check") | JavaScript URL strings are not automatically rewritten. |
url("./image.png") | url("/image.png") | CSS url() values are not rewritten. |
<form action="./check"> | <form action="/check"> | The absolute action exits the Tool prefix. |
<a href="./history"> | <a href="/history"> | Relative navigation stays within the Tool. |
Kura rewrites common root-relative HTML attributes in server-rendered HTML, redirects beginning with /, and cookie paths. That is a compatibility fallback, not a substitute for prefix-safe code.
Useful environment and proxy values
PORT=12000 # Assigned internal port; value changes
HOST=127.0.0.1 # Recommended bind address
KURA_TOOL_SLUG=token-check
KURA_BASE_PATH=/tools/token-check
X-Forwarded-Prefix: /tools/token-check
For WebSocket clients, build the URL from location and the current Tool path rather than hardcoding a domain.
Give Kura a safe place to append related Tools.
Kura appends its related Tool section to the first available element in this order: main, #app, [role="main"], then body. A normal Tool page should therefore wrap the application in <main id="app">.
<body>
<main id="app">
<section class="tool">
<h1>Token Check</h1>
<!-- Tool UI -->
</section>
</main>
</body>
Layout rules that prevent the related section from appearing beside your app
- Do not place all application elements directly inside a flex or grid
body. - If
bodyuses flex, make it a column:flex-direction: column. - Use
min-height: 100vhinstead of a fixedheight: 100vh. - Do not set
overflow: hiddenon the entire page unless scrolling is handled insidemain. - Avoid full-page fixed positioning. It can cover content injected below the app.
html, body { margin: 0; }
body {
min-height: 100vh;
display: flex;
flex-direction: column;
}
main {
width: 100%;
flex: 1 0 auto;
}
A server-based Tool must start an HTTP server.
Python, Node.js, PHP, Java, and custom Tools run as child processes. Kura assigns a private port and proxies public requests to it.
Never hardcode 3000, 5000, 8000, or another fixed port.
The Tool does not need its own public listener.
Kura waits for healthPath and accepts any response below 500.
A script that performs work and exits is not a web Tool.
HTML / CSS / JavaScript
Use the static runtime when all work can happen in the browser. Kura serves files directly from the Tool folder.
tools/color-picker/
├── index.html
├── style.css
├── app.js
└── tool.json
{
"name": "Color Picker",
"runtime": "static",
"description": "Pick and convert colors in the browser.",
"descriptionJa": "ブラウザ上で色を選択・変換します。",
"category": "Design",
"categoryJa": "デザイン",
"icon": "CP",
"tags": ["Color", "Browser"],
"tagsJa": ["カラー", "ブラウザ"]
}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Color Picker</title>
<link rel="stylesheet" href="./style.css">
<script src="./app.js" defer></script>
</head>
<body>
<main id="app">...</main>
</body>
</html>
Static Tools cannot execute Python, Node, PHP, or Java on demand. Use a dynamic runtime whenever the Tool needs server-side code, private credentials, filesystem processing, or a database.
Python
Each Python Tool with requirements.txt receives its own virtual environment during the build. The default entry is app.py, then main.py.
Flask: recommended simple structure
tools/token-check/
├── app.py
├── requirements.txt
├── templates/
│ └── index.html
├── static/
│ └── style.css
└── tool.json
# requirements.txt
Flask==3.1.1
# app.py
import os
from flask import Flask, jsonify, render_template, request
app = Flask(__name__)
@app.get("/")
def index():
return render_template("index.html")
@app.get("/health")
def health():
return {"ok": True}
@app.post("/check")
def check():
value = request.form.get("value", "").strip()
return jsonify({"valid": bool(value)})
if __name__ == "__main__":
app.run(
host=os.environ.get("HOST", "127.0.0.1"),
port=int(os.environ["PORT"]),
)
{
"name": "Token Check",
"runtime": "python",
"entry": "app.py",
"healthPath": "/health",
"tags": ["Python", "Token"]
}
FastAPI
# requirements.txt
fastapi==0.116.1
uvicorn==0.35.0
# main.py
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.get("/", response_class=HTMLResponse)
def index():
return "<main id='app'><h1>FastAPI Tool</h1></main>"
@app.get("/health")
def health():
return {"ok": True}
{
"name": "FastAPI Tool",
"runtime": "python",
"start": ".venv/bin/uvicorn main:app --host 127.0.0.1 --port $PORT",
"healthPath": "/health"
}
Django
{
"name": "Django Tool",
"runtime": "python",
"start": ".venv/bin/python manage.py runserver 127.0.0.1:$PORT",
"healthPath": "/health/",
"startupTimeoutMs": 60000
}
Include 127.0.0.1 and localhost in ALLOWED_HOSTS. For generated links, static files, and forms, configure the application for the KURA_BASE_PATH prefix or use relative URLs.
Python-specific mistakes
- Every imported third-party package must appear in
requirements.txt. - Do not call an external API secret directly from browser JavaScript; keep that call in Python.
- Do not use a development reloader that starts a second process. Disable reload mode.
- Long setup work should happen before the health endpoint reports ready.
Node.js
When package.json exists, Kura installs dependencies and starts the start script. Commit package-lock.json for reproducible installs.
tools/text-tool/
├── package.json
├── package-lock.json
├── server.js
├── public/
│ └── index.html
└── tool.json
{
"private": true,
"scripts": { "start": "node server.js" },
"dependencies": { "express": "^5.1.0" }
}
const express = require("express");
const path = require("path");
const app = express();
app.set("trust proxy", 1);
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
app.use(express.static(path.join(__dirname, "public")));
app.get("/health", (_req, res) => res.json({ ok: true }));
app.post("/convert", (req, res) => res.json({ result: req.body.value ?? "" }));
app.listen(Number(process.env.PORT), process.env.HOST || "127.0.0.1");
{
"name": "Text Tool",
"runtime": "node",
"healthPath": "/health",
"tags": ["Node.js", "Text"]
}
Set runNpmBuild: true when the repository needs npm run build. After the build, dev dependencies are pruned, so runtime packages must be in dependencies, not only devDependencies.
PHP
Place index.php at the Tool root. Kura starts php -S 127.0.0.1:$PORT -t . automatically.
tools/hash-tool/
├── index.php
├── style.css
└── tool.json
<?php
$result = "";
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$value = $_POST["value"] ?? "";
$result = hash("sha256", $value);
}
?>
<!doctype html>
<html>
<head><link rel="stylesheet" href="./style.css"></head>
<body>
<main id="app">
<form method="post" action="./">
<input name="value">
<button>Hash</button>
</form>
<pre><?= htmlspecialchars($result, ENT_QUOTES, "UTF-8") ?></pre>
</main>
</body>
</html>
{
"name": "Hash Tool",
"runtime": "php",
"healthPath": "/",
"tags": ["PHP", "Hash"]
}
The default image does not install Composer packages automatically. A Composer-based Tool requires coordination with the Kura owner or a custom build setup.
Java
Kura launches the configured JAR with java -jar. Maven and Gradle source projects are built automatically, but your entry must point to the actual generated JAR.
Prebuilt JAR
tools/java-tool/
├── app.jar
└── tool.json
{
"name": "Java Tool",
"runtime": "java",
"entry": "app.jar",
"healthPath": "/health"
}
Spring Boot with Maven
tools/spring-tool/
├── pom.xml
├── src/
└── tool.json
# src/main/resources/application.properties
server.address=127.0.0.1
server.port=${PORT}
server.forward-headers-strategy=framework
{
"name": "Spring Tool",
"runtime": "java",
"entry": "target/spring-tool.jar",
"healthPath": "/actuator/health",
"startupTimeoutMs": 90000
}
For Gradle, set entry to the resulting file under build/libs/. Avoid version-changing JAR names when possible, or update the metadata whenever the version changes.
Custom runtime
Use a custom runtime only when the required executable already exists in the Kura Docker image, or when the Kura owner has agreed to add it. A Tool folder cannot install a new system language by itself at runtime.
{
"name": "Custom Server",
"runtime": "custom",
"build": "your-build-command",
"start": "your-server --host 127.0.0.1 --port $PORT",
"healthPath": "/health",
"startupTimeoutMs": 60000,
"restart": true
}
A string start command replaces $PORT. An array command does not replace it, so the program must read the environment variable itself.
Do not treat the Tool folder as permanent storage.
Files written into the deployed application directory may disappear after a redeploy or service replacement. Use an approved database or an agreed persistent directory for data that must survive.
Secrets
- Never put API keys, passwords, private tokens, or credentials in
tool.json, source code, frontend JavaScript, or committed.envfiles. - Send the required environment variable names to the Kura owner.
- Prefix generic names to prevent collisions, for example
TOKEN_CHECK_API_KEYrather thanAPI_KEY. - Read them at runtime with your language's environment-variable API.
# Python
secret = os.environ["TOKEN_CHECK_API_KEY"]
// Node.js
const secret = process.env.TOKEN_CHECK_API_KEY;
// Java
String secret = System.getenv("TOKEN_CHECK_API_KEY");
Test both the app and the Kura-mounted URL.
1. Test the Tool by itself
Run a dynamic Tool with an arbitrary local port and verify the health route, main page, forms, API calls, static files, errors, and mobile layout.
# macOS / Linux example
PORT=12001 HOST=127.0.0.1 python app.py
# PowerShell example
$env:PORT="12001"
$env:HOST="127.0.0.1"
python app.py
2. Test inside Kura
Do not put it inside _templates.
From the Kura project root, run npm run build.
Run npm start and open the Tool through /tools/your-slug/.
Test forms, JavaScript fetch calls, redirects, cookies, assets, related Tools, and responsive layout.
Opening only http://127.0.0.1:12001/ is not enough. A Tool can work at the root and still break under /tools/your-slug/.
Fix the symptom at the Tool level.
| Symptom | Likely cause | Author fix |
|---|---|---|
| Tool card does not appear | Invalid JSON, published: false, ignored folder name, or missing static index.html. | Validate tool.json, use a normal slug, and include the required entry file. |
| 503 Tool failed to start | The process crashed, did not read PORT, or failed the health check. | Run the same start command locally, inspect imports/dependencies, and verify healthPath. |
| CSS, image, or JavaScript is 404 | An asset URL begins with /. | Use ./asset or framework base-path configuration. |
| Browser API call opens the Kura root | fetch("/route") is absolute. | Use fetch("./route") or construct a URL from the current pathname. |
| Related Tools appear to the right | The page has no main wrapper and body is a row flex/grid container. | Wrap the Tool in <main id="app"> and use a column page layout. |
| Python module missing | The package is not in requirements.txt. | Add an exact compatible dependency and rebuild. |
| Node module missing in production | A runtime package is only in devDependencies. | Move it to dependencies. |
| Java never becomes ready | Wrong JAR path or slow boot. | Correct entry, expose a health route, and increase startupTimeoutMs. |
| Saved data disappears | The Tool writes to ephemeral application files. | Use the approved persistent store or database. |
Send the folder only after every item passes.
tool.json is valid and descriptions are complete.PORT and stays running.<main id="app"> or an equivalent main root./tools/your-slug/, not only at localhost root.制作者が用意するのは、自己完結したToolフォルダー1つです。
KuraはToolを公開するための土台です。Kuraの運営者が、デプロイ、公開ドメイン、プロセス管理、一覧ページ、共通機能を管理します。Tool制作者は、正しく起動し、割り当てられたKura上のURLで動作するフォルダーを提出します。
アプリ本体、依存関係、tool.json、正常に表示できるトップページ。
公開ルーティング、内部ポート、起動・再起動、一覧カード、関連Tool、SEO、閲覧・クリック集計。
server.js、runtime-manager.js、Render設定、他のToolフォルダー。
tools/あなたのTool/ だけです。自分のToolフォルダー外を変更する必要はありません。Toolに必要なファイルは、すべて1つのフォルダーにまとめます。
フォルダー名には、小文字の英数字とハイフンを使います。このフォルダー名が公開URLのslugになります。
tools/
└── token-check/
├── tool.json
├── app.py
├── requirements.txt
├── templates/
│ └── index.html
└── static/
├── style.css
└── app.js
この例は /tools/token-check/ で公開されます。フォルダー名は重複不可です。空白、大文字、日本語、URL記号、末尾のドットは避けてください。
ランタイム別の最低限必要なファイル
| 種類 | 最低限必要なもの |
|---|---|
| 静的Tool | index.html と tool.json |
| Python | app.py または main.py、requirements.txt、tool.json |
| Node.js | start スクリプト付きの package.json、アプリ本体、tool.json |
| PHP | index.php と tool.json |
| Java | 生成済みJAR、またはMaven/Gradleプロジェクトと tool.json |
tools/_templates/ にひな形があります。コピーしてフォルダー名を変更し、サンプルコードを置き換えてください。Kura側にある機能を、Tool内で作り直す必要はありません。
フォルダーが承認されると、Kuraが自動で一覧へ追加し、そのカードからToolページへ移動できるようにします。さらに以下を自動処理します。
tool.jsonを使った日本語・英語の一覧情報。- 検索、カテゴリ、タグ、お気に入り、閲覧数、クリック数。
- カテゴリとタグに基づく関連Tool。
- ページタイトル、説明、canonical、OGP、JSON-LD、sitemap、robots。
- サーバー型Toolの内部ポート割り当てと公開URLへのプロキシ。
restart: falseでない限り、予期しない終了後の自動再起動。
Toolは本来の機能だけに集中してください。Kura共通ヘッダー、一覧ナビゲーション、お気に入り集計、関連Tool欄、SEO生成をTool側で重複実装しないでください。
tool.jsonを丁寧に記述します。
{
"name": "Token Check",
"runtime": "python",
"entry": "app.py",
"description": "Checks whether a token is valid.",
"descriptionJa": "Tokenが有効か確認します。",
"category": "Utility",
"categoryJa": "ユーティリティ",
"icon": "TC",
"tags": ["Python", "Token"],
"tagsJa": ["Python", "トークン"],
"published": true,
"healthPath": "/",
"startupTimeoutMs": 30000,
"restart": true
}
| 項目 | 制作者向け説明 |
|---|---|
name | 一覧カードに表示されるTool名。大文字小文字、空白、記号、全角半角の差を正規化した後も一意である必要があります。 |
description / descriptionJa | 何ができるToolなのかを、誇張なしの1文で書きます。 |
category / categoryJa | 大分類を1つ指定します。関連Toolの判定にも使われます。 |
tags / tagsJa | 少数の正確なタグを指定します。検索と関連Toolに使われます。 |
icon | 画像アイコンがない場合に表示される短い文字。2文字程度を推奨します。 |
runtime | static、python、node、php、java、custom。明示指定を推奨します。 |
entry | Pythonの起動ファイル、package startを使わないNodeのファイル、またはJARのパス。 |
healthPath | 素早く応答し、500未満を返すGETルート。既定値は /。 |
startupTimeoutMs | Javaなど起動が遅い場合だけ増やします。既定値は30000。 |
restart | trueまたは省略を推奨。自動再起動が不適切な特殊ケースのみfalse。 |
published | 未完成時はfalse。公開時はtrueまたは省略。 |
allowDuplicateName | 通常は省略またはfalse。意図的に同名を許可する場合のみ、該当する全Toolでtrueにします。 |
build / start | 標準の起動方法で動かない場合だけ使う上級設定。 |
env | 秘密ではない初期値専用。APIキーやパスワードを入れないでください。 |
tool.json は正しいJSONである必要があります。Tool名はRenderのビルド時に検査され、意図しない重複があるとデプロイ前にビルドが停止します。Toolはドメイン直下ではなく、KuraのURL配下で公開されます。
token-check フォルダーは /tools/token-check/ で表示されます。動的Toolへの転送時にはKuraがこのプレフィックスを取り除きますが、ブラウザ側のURLはToolの配下に残るように書く必要があります。
| 推奨 | 避ける | 理由 |
|---|---|---|
<link href="./style.css"> | <link href="/style.css"> | 先頭のスラッシュはKura本体のルートを指します。 |
fetch("./check") | fetch("/check") | JavaScript内のURL文字列は自動書き換えされません。 |
url("./image.png") | url("/image.png") | CSSの url() は自動書き換えされません。 |
<form action="./check"> | <form action="/check"> | 絶対パスはToolの外へ送信されます。 |
<a href="./history"> | <a href="/history"> | 相対リンクならTool内に留まります。 |
Kuraは、サーバーが返したHTML内の一般的なルート相対属性、/ から始まるリダイレクト、Cookie Pathを互換処理として書き換えます。ただし、最初から相対URLで作るのが最も確実です。
利用できる環境変数・プロキシ情報
PORT=12000 # Kuraが割り当てる内部ポート。固定値ではありません
HOST=127.0.0.1 # 推奨待受アドレス
KURA_TOOL_SLUG=token-check
KURA_BASE_PATH=/tools/token-check
X-Forwarded-Prefix: /tools/token-check
WebSocketクライアントもドメインを固定せず、location と現在のToolパスからURLを組み立ててください。
関連Toolを下に追加できる、正常なページ構造にします。
Kuraは、main、#app、[role="main"]、最後に body の順で関連Toolの追加先を探します。そのため、Tool本体を <main id="app"> で囲むことを推奨します。
<body>
<main id="app">
<section class="tool">
<h1>Token Check</h1>
<!-- ToolのUI -->
</section>
</main>
</body>
関連Toolが右側へ押し込まれないためのルール
- Toolの全要素を、横向きflex/gridの
body直下へ直接置かない。 bodyをflexにする場合はflex-direction: columnにする。- 固定の
height: 100vhではなくmin-height: 100vhを使う。 - ページ全体へ
overflow: hiddenを設定しない。設定する場合はmain内でスクロールを管理する。 - ページ全体を覆うfixed配置を避ける。下に追加された内容を隠す原因になります。
html, body { margin: 0; }
body {
min-height: 100vh;
display: flex;
flex-direction: column;
}
main {
width: 100%;
flex: 1 0 auto;
}
サーバー型Toolは、HTTPサーバーを起動し続ける必要があります。
Python、Node.js、PHP、Java、カスタムToolは子プロセスとして起動します。Kuraが内部ポートを割り当て、公開URLからそのポートへ転送します。
3000、5000、8000などを固定しないでください。
Tool自身が外部公開ポートを持つ必要はありません。
KuraはそのルートへGETし、500未満なら起動完了と判断します。
処理してすぐ終了するスクリプトはWeb Toolとして動きません。
HTML / CSS / JavaScript
すべての処理がブラウザ内で完結する場合はstaticを使います。KuraがToolフォルダー内のファイルをそのまま配信します。
tools/color-picker/
├── index.html
├── style.css
├── app.js
└── tool.json
{
"name": "Color Picker",
"runtime": "static",
"description": "Pick and convert colors in the browser.",
"descriptionJa": "ブラウザ上で色を選択・変換します。",
"category": "Design",
"categoryJa": "デザイン",
"icon": "CP",
"tags": ["Color", "Browser"],
"tagsJa": ["カラー", "ブラウザ"]
}
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Color Picker</title>
<link rel="stylesheet" href="./style.css">
<script src="./app.js" defer></script>
</head>
<body>
<main id="app">...</main>
</body>
</html>
static ToolではPython、Node、PHP、Javaをオンデマンド実行できません。秘密情報、サーバー側処理、ファイル処理、データベースが必要なら動的ランタイムを使います。
Python
requirements.txt があるPython Toolには、ビルド時にTool専用の仮想環境が作られます。標準の起動ファイルは app.py、次に main.py です。
Flaskの推奨構成
tools/token-check/
├── app.py
├── requirements.txt
├── templates/
│ └── index.html
├── static/
│ └── style.css
└── tool.json
# requirements.txt
Flask==3.1.1
# app.py
import os
from flask import Flask, jsonify, render_template, request
app = Flask(__name__)
@app.get("/")
def index():
return render_template("index.html")
@app.get("/health")
def health():
return {"ok": True}
@app.post("/check")
def check():
value = request.form.get("value", "").strip()
return jsonify({"valid": bool(value)})
if __name__ == "__main__":
app.run(
host=os.environ.get("HOST", "127.0.0.1"),
port=int(os.environ["PORT"]),
)
{
"name": "Token Check",
"runtime": "python",
"entry": "app.py",
"healthPath": "/health",
"tags": ["Python", "Token"]
}
FastAPI
# requirements.txt
fastapi==0.116.1
uvicorn==0.35.0
# main.py
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.get("/", response_class=HTMLResponse)
def index():
return "<main id='app'><h1>FastAPI Tool</h1></main>"
@app.get("/health")
def health():
return {"ok": True}
{
"name": "FastAPI Tool",
"runtime": "python",
"start": ".venv/bin/uvicorn main:app --host 127.0.0.1 --port $PORT",
"healthPath": "/health"
}
Django
{
"name": "Django Tool",
"runtime": "python",
"start": ".venv/bin/python manage.py runserver 127.0.0.1:$PORT",
"healthPath": "/health/",
"startupTimeoutMs": 60000
}
ALLOWED_HOSTS に 127.0.0.1 と localhost を含めます。生成リンク、static、フォームは KURA_BASE_PATH に対応させるか、相対URLを使ってください。
Pythonで起きやすいミス
- importする外部パッケージをすべて
requirements.txtに書く。 - 秘密のAPIキーをブラウザJavaScriptから直接使わず、Python側で呼び出す。
- 2つ目のプロセスを起動する開発用リローダーを使わない。reloadを無効にする。
- 初期化処理が終わる前にhealthがreadyを返さないようにする。
Node.js
package.json がある場合、依存関係をインストールして start スクリプトを実行します。再現性のため package-lock.json を含めてください。
tools/text-tool/
├── package.json
├── package-lock.json
├── server.js
├── public/
│ └── index.html
└── tool.json
{
"private": true,
"scripts": { "start": "node server.js" },
"dependencies": { "express": "^5.1.0" }
}
const express = require("express");
const path = require("path");
const app = express();
app.set("trust proxy", 1);
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
app.use(express.static(path.join(__dirname, "public")));
app.get("/health", (_req, res) => res.json({ ok: true }));
app.post("/convert", (req, res) => res.json({ result: req.body.value ?? "" }));
app.listen(Number(process.env.PORT), process.env.HOST || "127.0.0.1");
{
"name": "Text Tool",
"runtime": "node",
"healthPath": "/health",
"tags": ["Node.js", "Text"]
}
本番起動前に npm run build が必要なら runNpmBuild: true を指定します。ビルド後にdev依存は削除されるため、実行時に必要なパッケージは dependencies に入れてください。
PHP
Tool直下へ index.php を置きます。Kuraが php -S 127.0.0.1:$PORT -t . を自動実行します。
tools/hash-tool/
├── index.php
├── style.css
└── tool.json
<?php
$result = "";
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$value = $_POST["value"] ?? "";
$result = hash("sha256", $value);
}
?>
<!doctype html>
<html>
<head><link rel="stylesheet" href="./style.css"></head>
<body>
<main id="app">
<form method="post" action="./">
<input name="value">
<button>Hash</button>
</form>
<pre><?= htmlspecialchars($result, ENT_QUOTES, "UTF-8") ?></pre>
</main>
</body>
</html>
{
"name": "Hash Tool",
"runtime": "php",
"healthPath": "/",
"tags": ["PHP", "Hash"]
}
標準イメージではComposer依存を自動導入しません。Composerを使うToolは、Kura運営者と相談してカスタムビルドを用意する必要があります。
Java
Kuraは指定されたJARを java -jar で起動します。Maven・Gradleのソースプロジェクトは自動ビルドされますが、entry は実際に生成されるJARを指す必要があります。
生成済みJAR
tools/java-tool/
├── app.jar
└── tool.json
{
"name": "Java Tool",
"runtime": "java",
"entry": "app.jar",
"healthPath": "/health"
}
Maven + Spring Boot
tools/spring-tool/
├── pom.xml
├── src/
└── tool.json
# src/main/resources/application.properties
server.address=127.0.0.1
server.port=${PORT}
server.forward-headers-strategy=framework
{
"name": "Spring Tool",
"runtime": "java",
"entry": "target/spring-tool.jar",
"healthPath": "/actuator/health",
"startupTimeoutMs": 90000
}
Gradleでは build/libs/ に生成される実ファイルを entry に指定します。可能なら、バージョンごとにJAR名が変わらない設定にしてください。
カスタムランタイム
必要な実行ファイルがKuraのDockerイメージに存在する場合、または運営者が追加に同意した場合のみ使います。Toolフォルダー単体で、実行時に新しいシステム言語を導入することはできません。
{
"name": "Custom Server",
"runtime": "custom",
"build": "your-build-command",
"start": "your-server --host 127.0.0.1 --port $PORT",
"healthPath": "/health",
"startupTimeoutMs": 60000,
"restart": true
}
文字列形式の start では $PORT が置換されます。配列形式では置換されないため、プログラム側で環境変数を読んでください。
Toolフォルダーを永続ストレージとして使わないでください。
デプロイ済みアプリのフォルダーへ書き込んだファイルは、再デプロイやサービス置換で消える可能性があります。残す必要があるデータは、承認されたデータベースまたは運営者と合意した永続領域へ保存します。
秘密情報
- APIキー、パスワード、非公開Token、認証情報を
tool.json、ソースコード、フロントエンドJavaScript、コミット対象の.envに入れない。 - 必要な環境変数名をKura運営者へ伝える。
- 他Toolとの名前衝突を防ぐため、
API_KEYではなくTOKEN_CHECK_API_KEYのように接頭辞を付ける。 - 各言語の環境変数APIから実行時に読み込む。
# Python
secret = os.environ["TOKEN_CHECK_API_KEY"]
// Node.js
const secret = process.env.TOKEN_CHECK_API_KEY;
// Java
String secret = System.getenv("TOKEN_CHECK_API_KEY");
単体動作と、Kura配下での動作を両方確認します。
1. Tool単体で確認
動的Toolには任意のローカルポートを設定し、health、トップページ、フォーム、API、静的ファイル、エラー処理、スマホ表示を確認します。
# macOS / Linux
PORT=12001 HOST=127.0.0.1 python app.py
# PowerShell
$env:PORT="12001"
$env:HOST="127.0.0.1"
python app.py
2. Kura内で確認
_templates の中には置かないでください。
Kuraプロジェクト直下で npm run build を実行します。
npm start を実行し、/tools/your-slug/ からToolを開きます。
フォーム、fetch、リダイレクト、Cookie、アセット、関連Tool、レスポンシブ表示を確認します。
http://127.0.0.1:12001/ だけで動くことを確認しても不十分です。ルートでは動いても、/tools/your-slug/ 配下では壊れることがあります。
症状に応じてTool側を修正します。
| 症状 | 主な原因 | 制作者が直す内容 |
|---|---|---|
| 一覧にToolが出ない | JSON不正、published: false、無視対象のフォルダー名、staticのindex不足。 | tool.json を検証し、通常のslugと必要な起動ファイルを用意します。 |
| 503 Tool failed to start | クラッシュ、PORT未使用、health失敗。 | 同じ起動コマンドをローカルで試し、依存関係と healthPath を確認します。 |
| CSS・画像・JavaScriptが404 | アセットURLが / から始まっている。 | ./asset またはフレームワークのbase path設定を使います。 |
| ブラウザAPIがKura本体へ飛ぶ | fetch("/route") が絶対パス。 | fetch("./route") または現在のpathnameからURLを作ります。 |
| 関連Toolが右側へ表示される | mainがなく、bodyが横向きflex/grid。 | <main id="app"> で囲み、ページを縦方向レイアウトにします。 |
| Pythonモジュールがない | requirements.txt に未記載。 | 互換性のある依存関係を追加して再ビルドします。 |
| 本番でNodeモジュールがない | 実行時パッケージが devDependencies のみ。 | dependencies へ移動します。 |
| Javaがreadyにならない | JARパス違い、起動が遅い。 | entry、health、startupTimeoutMs を修正します。 |
| 保存データが消える | 一時的なアプリファイルへ保存している。 | 承認済みの永続領域またはデータベースを使います。 |
すべて確認してからフォルダーを提出します。
tool.json が正しく、説明が埋まっている。PORT を読み、終了せず動作する。<main id="app"> または同等のmain領域がある。/tools/your-slug/ で動く。