ChatGPT PlusからREST APIに書き込む:PHP製MCPでチャットと時刻指定タスクを検証
作成日:2026.11.09
既存のPHP製REST APIにMCPアダプターを追加し、ChatGPT Plusのチャットと1回限りの時刻指定タスクからテストデータを登録した記録です。アダプターの実装、接続手順、書き込みの期限・回数制限、承認や認証に関する注意点を紹介します。
目次
自分で用意したREST APIを、ChatGPTから呼び出してデータを更新できないか試してみました。今回は、Webサーバー上のPHP製REST APIにMCPアダプターを追加し、ChatGPT Plusのチャットと、1回限りの時刻指定タスクからテストレコードを登録しています。
この記事では、その構成と実行結果を紹介します。ChatGPT側の操作は2026年11月8日の検証記録です。対象アカウントでは動きましたが、利用できる画面・ツール・承認の扱いはプランや設定によって変わるため、すべてのPlus環境で同じように動くとは限りません。
MCPをREST APIの入口にする
MCP(Model Context Protocol)は、AIアプリケーションへ外部のデータや操作をツールとして提供するためのプロトコルです。REST APIがすでにある場合は、その前段にMCPのリクエストを受け付ける処理を置き、必要な操作だけを公開できます。
ChatGPT Plus(Web版)
├─ チャットから実行
└─ 時刻指定タスクから実行
↓ MCP / HTTPS(JSON-RPC)
mcp.php:MCPツールをRESTリクエストへ変換
↓ サーバー側でAPIキーを付与
api.php:既存のテスト用REST API
↓
テスト専用のdata.json
今回の検証では、既存のapi.phpや保存形式をMCP向けに変更せず、mcp.phpを追加しました。公開する操作は、疎通確認のrest_pingと、テストレコードを追加するcreate_test_itemの2つです。任意のURLにアクセスしたり、任意のファイルを書き換えたりするツールにはしていません。
以前のPython製MCPサーバーの記事は、CodexからSTDIOで接続する読み取り専用の例でした。今回はChatGPTのWeb版から接続するため、HTTPで受け付ける構成です。MCP専用SDKは使わず、JSON-RPCの処理をPHPで書いています。
前提となるテストAPIと検証範囲
既存のテストAPIには複数の操作がありましたが、MCPから使うのは次の2つだけです。以下のURL・APIキー・ラベルは説明用です。実際のAPIキーをChatGPTの会話へ貼り付ける必要はありません。
| HTTPリクエスト | 認証 | 入出力 |
|---|---|---|
| GET api.php?action=ping | 不要 | 疎通確認用のJSONを返す |
| POST api.php?action=items | X-API-Key | JSONのtitleを受け取り、HTTP 201と作成したレコードをJSONで返す |
たとえば、追加時は{"title":"MCP-PoC: article-check"}を送り、{"id":1,"title":"MCP-PoC: article-check"}のようなJSONが返る想定です。自分のAPIへ接続する場合は、後述のrestRequest()を、そのAPIの認証方式・パス・応答形式に合わせて変更してください。ここではREST API本体の実装は扱いません。
掲載するアダプターは、検証時のコードから実URLや固有名を除き、空オブジェクトの応答、入力検査、カウンターの書き込み失敗時の処理などを整えたものです。
以下は、実データを置かない隔離環境での短時間のPoC用です。 MCP自体の利用者認証を実装していないため、URLを知った第三者もツールを呼び出せます。期限と回数制限は認証の代わりにはなりません。公開サイトのデータ更新へ転用する場合は、先にMCP側の認証・認可を用意してください。
設定と保護するファイルを用意する
まず、テストAPIと同じディレクトリへ、次の設定をmcp-config.phpとして保存します。api_urlはクエリ文字列の付いていないAPIのURL、api_keyはそのAPIが受け付けるキーへ置き換えます。初めは書き込みを無効にしておきます。
<?php
return [
'api_url' => 'https://example.com/APITEST/api.php',
'api_key' => 'REPLACE_WITH_TEST_API_KEY',
'enable_write_test' => false,
'write_test_expires_at' => '',
];
この例は同じディレクトリに設定を置くため、Apacheの既存の.htaccessへ次を追記します。既存ルールを丸ごと置き換えないようにしてください。APIが保存するdata.jsonもブラウザーから直接読めないようにします。バックアップやログは公開ディレクトリへ置かず、可能なら設定やデータもWeb公開範囲の外へ出す構成にします。
<FilesMatch "^(mcp-config\.php|\.mcp-write-count|data\.json)$">
Require all denied
</FilesMatch>
Apache 2.4でこの指定が有効になる設定を前提としています。指定の意味はApache公式のFilesMatchとRequireの説明を参照してください。配置後、各ファイルへ直接アクセスして内容が返らないことを確認します。.htaccessが適用されないサーバーでは、同じ保護をサーバー設定等で行う必要があります。
PHPでMCPアダプターを作る
MCPでは、初期化のinitializeに続き、ツール一覧を返すtools/list、指定されたツールを動かすtools/callを処理します。tools/listで公開する引数の形式に加えて、実行側でも入力を検査します。
| ツール | 引数 | REST APIへ送る内容 |
|---|---|---|
| rest_ping | なし | GET action=ping |
| create_test_item | label | POST action=items。titleの先頭にMCP-PoC:を付ける |
以下をmcp.phpとして保存します。never戻り値型を使用するため、構文上はPHP 8.1以降が必要です。新たに環境を用意する場合は、運用時点でサポートされているPHPを選んでください。今回コードを実行した版はPHP 8.3.36です。
<?php
// Isolated, short-lived PoC only. PHP 8.1+ and ext-curl.
declare(strict_types=1);
header('Cache-Control: no-store');
header('X-Content-Type-Options: nosniff');
// Validate browser-supplied Origin per MCP transport guidance. Server clients
// generally omit Origin; if supplied, reject unrecognized origins.
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if ($origin !== '' && !in_array($origin, ['https://chatgpt.com', 'https://chat.openai.com'], true)) {
http_response_code(403);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['error' => 'Origin not allowed']);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
header('Allow: POST');
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['error' => 'Use MCP JSON-RPC POST (GET/SSE not implemented)']);
exit;
}
$path = __DIR__ . '/mcp-config.php';
$config = is_file($path) ? require $path : [];
if (!is_array($config)) $config = [];
$apiUrl = (string)($config['api_url'] ?? '');
$apiKey = (string)($config['api_key'] ?? '');
$expires = (string)($config['write_test_expires_at'] ?? '');
$expiryTs = $expires !== '' ? strtotime($expires) : false;
$writeEnabled = ($config['enable_write_test'] ?? false) === true
&& $apiKey !== '' && $expiryTs !== false && time() < $expiryTs;
function respond(array $result, int $status = 200): never {
http_response_code($status);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);
exit;
}
function rpcResponse($id, array|object $result): never {
respond(['jsonrpc'=>'2.0', 'id'=>$id, 'result'=>$result]);
}
function rpcError($id, int $code, string $message, int $httpStatus = 200): never {
respond(['jsonrpc'=>'2.0', 'id'=>$id, 'error'=>['code'=>$code, 'message'=>$message]], $httpStatus);
}
function resultText(string $text, bool $error = false): array {
return ['content'=>[['type'=>'text', 'text'=>$text]], 'isError'=>$error];
}
function restRequest(string $apiUrl, string $action, string $method = 'GET', string $key = '', ?array $payload = null): array {
if ($apiUrl === '') throw new RuntimeException('API URL is not configured');
$url = $apiUrl . '?action=' . rawurlencode($action);
$curl = curl_init($url);
if ($curl === false) throw new RuntimeException('cURL initialization failed');
$headers = ['Accept: application/json'];
if ($key !== '') $headers[] = 'X-API-Key: ' . $key;
$opts = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 12,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
CURLOPT_HTTPHEADER => $headers,
];
if ($method === 'POST') {
$headers[] = 'Content-Type: application/json';
$opts[CURLOPT_POST] = true;
$opts[CURLOPT_POSTFIELDS] = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$opts[CURLOPT_HTTPHEADER] = $headers;
}
curl_setopt_array($curl, $opts);
$body = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
$error = curl_error($curl);
curl_close($curl);
if ($body === false) throw new RuntimeException('REST connection failed: ' . $error);
$result = json_decode($body, true, 32, JSON_THROW_ON_ERROR);
if (!is_array($result)) throw new RuntimeException('Expected a JSON object or array');
return ['status'=>$status, 'body'=>$result];
}
function allowOneWrite(): bool {
$fp = @fopen(__DIR__ . '/.mcp-write-count', 'c+');
if ($fp === false) return false;
try {
if (!flock($fp, LOCK_EX)) return false;
if (!rewind($fp)) return false;
$rawCount = stream_get_contents($fp);
if ($rawCount === false) return false;
$rawCount = trim($rawCount);
if ($rawCount !== '' && !preg_match('/\A[0-3]\z/', $rawCount)) return false;
$count = $rawCount === '' ? 0 : (int)$rawCount;
if ($count >= 3) return false;
$next = (string)($count + 1);
if (!rewind($fp)) return false;
if (fwrite($fp, $next) !== strlen($next) || !fflush($fp)) {
throw new RuntimeException('Write counter could not be saved');
}
return true;
} finally {
flock($fp, LOCK_UN);
fclose($fp);
}
}
function tool(string $name, string $description, array $properties, array $required, bool $readOnly): array {
return [
'name'=>$name,
'description'=>$description,
'inputSchema'=>[
'type'=>'object', 'properties'=>(object)$properties,
'required'=>$required, 'additionalProperties'=>false,
],
'annotations'=>[
'readOnlyHint'=>$readOnly,
'destructiveHint'=>false,
'openWorldHint'=>false,
],
'securitySchemes'=>[['type'=>'noauth']],
'_meta'=>['securitySchemes'=>[['type'=>'noauth']]],
];
}
if (!str_contains($_SERVER['HTTP_ACCEPT'] ?? '', 'application/json')) {
respond(['error'=>'Accept application/json is required'], 406);
}
if (strtolower(trim(explode(';', $_SERVER['CONTENT_TYPE'] ?? '')[0])) !== 'application/json') {
respond(['error'=>'Content-Type application/json is required'], 415);
}
$raw = file_get_contents('php://input', false, null, 0, 65537);
if ($raw === false || strlen($raw) > 65536) respond(['error'=>'Bad request'], 400);
try { $message = json_decode($raw, false, 32, JSON_THROW_ON_ERROR); }
catch (JsonException $e) { rpcError(null, -32700, 'Parse error', 400); }
if (!$message instanceof stdClass || ($message->jsonrpc ?? '') !== '2.0'
|| !is_string($message->method ?? null)) {
rpcError(null, -32600, 'Invalid JSON-RPC request', 400);
}
$method = $message->method;
$version = $_SERVER['HTTP_MCP_PROTOCOL_VERSION'] ?? '';
if (($version !== '' && $version !== '2025-06-18')
|| ($method !== 'initialize' && $version === '')) {
respond(['error'=>'Unsupported or missing MCP-Protocol-Version'], 400);
}
if (!property_exists($message, 'id')) {
if (str_starts_with($method, 'notifications/')) {
http_response_code(202);
exit;
}
rpcError(null, -32600, 'Missing request id', 400);
}
$id = $message->id;
if (!is_int($id) && !is_string($id)) rpcError(null, -32600, 'Invalid request id', 400);
$message = (array)$message;
if ($method === 'initialize') {
$params = $message['params'] ?? null;
if (!$params instanceof stdClass || !is_string($params->protocolVersion ?? null)
|| !($params->capabilities ?? null) instanceof stdClass
|| !($params->clientInfo ?? null) instanceof stdClass) {
rpcError($id, -32602, 'Invalid initialize params');
}
rpcResponse($id, [
'protocolVersion'=>'2025-06-18',
'capabilities'=>['tools'=>['listChanged'=>false]],
'serverInfo'=>['name'=>'rest-poc-php', 'version'=>'0.1.0'],
]);
}
if ($method === 'ping') rpcResponse($id, (object)[]);
if ($method === 'tools/list') {
$tools = [tool('rest_ping', 'Check connectivity to the disposable REST API (no authentication required).', [], [], true)];
if ($writeEnabled) {
$tools[] = tool(
'create_test_item',
'TEMPORARY PoC ONLY: creates one harmless, prefixed test item in the isolated APITEST data.json. Max 3 total requests; not for dashboard data.',
['label'=>['type'=>'string', 'description'=>'Short non-sensitive test label (max 120 UTF-8 bytes)']],
['label'], false
);
}
rpcResponse($id, ['tools'=>$tools]);
}
if ($method === 'tools/call') {
$params = $message['params'] ?? null;
if (!$params instanceof stdClass || !is_string($params->name ?? null)) rpcError($id, -32602, 'Invalid tool name');
$name = $params->name;
$args = $params->arguments ?? new stdClass();
if (!$args instanceof stdClass) rpcError($id, -32602, 'Invalid tool arguments');
$args = (array)$args;
try {
if ($name === 'rest_ping') {
if ($args !== []) rpcError($id, -32602, 'Unexpected arguments');
$r = restRequest($apiUrl, 'ping');
rpcResponse($id, resultText(json_encode($r, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), $r['status'] < 200 || $r['status'] >= 300));
}
if ($name === 'create_test_item' && $writeEnabled) {
if (array_diff(array_keys($args), ['label']) !== []) rpcError($id, -32602, 'Unexpected arguments');
$label = $args['label'] ?? null;
if (!is_string($label) || trim($label) === '' || strlen($label) > 120) {
rpcResponse($id, resultText('label must be 1-120 UTF-8 bytes', true));
}
if (!allowOneWrite()) rpcResponse($id, resultText('Temporary test write budget exceeded or unavailable (max 3 attempts).', true));
$r = restRequest($apiUrl, 'items', 'POST', $apiKey, ['title'=>'MCP-PoC: ' . trim($label)]);
rpcResponse($id, resultText(json_encode($r, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), $r['status'] < 200 || $r['status'] >= 300));
}
rpcError($id, -32602, 'Unknown or disabled tool');
} catch (Throwable $e) {
error_log('MCP PoC error: ' . $e->getMessage());
rpcResponse($id, resultText('Upstream REST API call failed. See server logs.', true));
}
}
rpcError($id, -32601, 'Method not found');
rpcResponse()の引数をarray|objectにしているのは、MCPのpingへ{}を返すためです。これはREST APIの疎通を調べるrest_pingとは別の処理です。
書き込みは、有効化フラグ・APIキー・期限が揃った場合だけ受け付けます。さらに、.mcp-write-countをロックして、REST APIへのPOST試行を全体で3回までにしています。カウンターファイルをPHPの実行ユーザーが作成・更新できる必要があり、開けない場合はPOSTしません。通信や上流APIで失敗した場合も試行数を消費し、自動再試行はしません。期限内でも、上限に達すると登録できなくなります。
カウンターはMCPのセッションとは別に保存します。HTTPの接続を保持せずに処理できますが、試行数までメモリだけで管理しているわけではありません。また、このロックはカウンター用です。REST APIが保存するJSONの競合更新を防ぐ処理ではありません。
HTTP応答はJSONのみとし、SSEストリームは提供せずGETには405を返します。この構成はMCP 2025-06-18のStreamable HTTP仕様にある応答方式を使っています。セッション管理やサーバーからの通知などを一通り実装した汎用サーバーではなく、今回の操作に絞った例です。
PowerShellから初期化と疎通を確認する
ChatGPTへ登録する前に、MCP側とREST API側の疎通を確認しておきます。次のURLは、自分のテスト用mcp.phpへ置き換えてください。以降の例は同じPowerShellセッションで実行します。
$mcpUrl = 'https://example.com/APITEST/mcp.php'
$headers = @{ Accept = 'application/json, text/event-stream' }
$request = @{
jsonrpc = '2.0'
id = 1
method = 'initialize'
params = @{
protocolVersion = '2025-06-18'
capabilities = @{}
clientInfo = @{ name = 'manual-test'; version = '1.0' }
}
} | ConvertTo-Json -Depth 6
$init = Invoke-RestMethod -Method Post -Uri $mcpUrl -Headers $headers -ContentType 'application/json' -Body $request
if ($init.result.protocolVersion -ne '2025-06-18') {
throw 'Unsupported MCP protocol version'
}
$headers['MCP-Protocol-Version'] = $init.result.protocolVersion
$body = '{"jsonrpc":"2.0","method":"notifications/initialized"}'
Invoke-RestMethod -Method Post -Uri $mcpUrl -Headers $headers -ContentType 'application/json' -Body $body
$body = '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
$list = Invoke-RestMethod -Method Post -Uri $mcpUrl -Headers $headers -ContentType 'application/json' -Body $body
$list.result.tools | Select-Object name
$body = '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"rest_ping","arguments":{}}}'
$result = Invoke-RestMethod -Method Post -Uri $mcpUrl -Headers $headers -ContentType 'application/json' -Body $body
$result.result.content[0].text
初期状態のツール一覧はrest_pingだけです。最後の応答には、上流REST APIのHTTPステータスを表すstatusと、JSON応答のbodyが入ります。MCPへのHTTPリクエストが200でも、ツールのisErrorがtrueなら操作は失敗しています。errorが返るプロトコル側の失敗も含めて確認します。2種類のエラーはMCP公式のツールのエラー処理でも区別されています。
このコード例ではプロトコル版を2025-06-18に固定しています。初期化後のHTTPリクエストにも、合意した版をMCP-Protocol-Versionで送ります。別のクライアントへ対応する場合は、版の交渉と実装範囲も見直してください。
ChatGPTからテストレコードを登録する
11月8日の検証では、Web版ChatGPTで次の操作を行いました。登録方法はOpenAI公式のカスタムMCPサーバー接続手順も参照してください。
- 「プラグイン」の「+」から「カスタムMCPサーバーを追加」を開く。
- 名前・説明・テスト用mcp.phpのHTTPS URLを指定する。
- 今回の隔離PoCに限り「認証なし」を選び、警告を確認して作成する。
- 作成したプラグインをインストールし、新しいチャットで@から選択する。
- rest_pingを指定して、REST APIまで疎通するか確認する。
ここまで動いたら、隔離されたAPIにテストデータしかないことを確認し、mcp-config.phpのenable_write_testをtrueへ変更します。write_test_expires_atには、試験終了予定に合わせた近い期限を、タイムゾーン付きのYYYY-MM-DDTHH:MM:SS+09:00形式で指定します。無効化フラグや期限を変えた後は、プラグインのツール情報も更新してください。
書き込みの指示は、使用するプラグイン名とツール名を明示しました。以下の名前・ラベルは説明用に置き換えています。
「REST API PoC」の create_test_item を1回だけ実行してください。
labelは article-check にしてください。
失敗しても再試行や別の方法での書き込みはしないでください。
検証ではテストレコードを登録でき、チャット → MCP → REST API → JSONファイルという経路を確認できました。掲載コードでは、REST APIに送る値は{"title":"MCP-PoC: article-check"}となります。
readOnlyHint: falseは、書き込みツールだとクライアントへ伝える注釈です。承認を不要にする設定でも、サーバーのアクセス制御でもありません。OpenAI公式では書き込みに既定で確認が必要と説明されており、画面で求められた確認は内容を見て判断します。
1回限りの時刻指定タスクからも実行してみる
続いて、同じプラグインを使う時刻指定タスクを作成しました。今回確認したのは、繰り返し実行ではなく1回だけの実行です。OpenAI公式のタスクの説明でも、Webのタスクから、そのチャットで利用可能な接続ツールやプラグインを使う仕組みが案内されています。
タスクには使用するツールと引数を指定し、「1回だけ」「他のデータには触れない」「失敗時の再試行や代替手段での書き込みは行わない」と明記しました。指定時刻が書き込み期限内に入るよう設定しておきます。
| 項目 | 11月8日の実行記録 |
|---|---|
| 指定時刻 | 2026年11月8日 23:19頃(日本時間) |
| 実行時刻 | 2026年11月8日 23:23:53(日本時間) |
| 実行ツール | create_test_item |
| REST APIのHTTP応答 | 201 Created |
| 登録されたレコードのID | 3 |
| 実行時の追加承認 | 今回は不要だった |
| 再試行 | なし |
その場でチャットに指示する場合に加えて、後から起動するタスク経由でもPOSTが実行されたことを確認できました。ただし、追加承認なしで動いた理由を特定したわけではありません。プラグインの権限や承認設定の影響もあるため、「Plusなら無承認で自動書き込みできる」という仕様には一般化しません。
また、指定時刻と実行時刻には数分の差がありました。時刻厳守のジョブ実行基盤として検証したものではありません。遅れて起動した場合に期限切れで失敗することも考え、実行結果を確認できるようにしておきます。
公開用コードで確認したことと、残る課題
公開用コードはChatGPTとは切り離し、隔離したDocker環境で、HTTPリクエストとダミーAPIへの転送を確認しました。実URLや認証情報は使っていません。
| 条件 | 確認結果 |
|---|---|
| 初期化・MCPのping | プロトコル版とサーバー情報、pingの空オブジェクトを返す |
| 通知・GET | initialized通知には202・本文なし、GETには405を返す |
| 書き込み無効・期限切れ | 一覧に書き込みツールを出さず、直接指定しても実行しない |
| 空のlabel・余計な引数・不正JSON | 入力を拒否し、REST APIへPOSTしない |
| 日本語のlabel・正常な追加 | 接頭辞とAPIキーヘッダーを付けてPOSTし、HTTP 201をツール結果に含める |
| 試行上限・カウンターの破損や利用不可 | 追加のPOSTを止める |
| REST APIのHTTPエラー・不正な応答 | ツール結果をisError: trueにする |
今回分かったのは、限定した操作をMCP経由で実行できることです。OAuth認証したMCPからのタスク実行、長期間の安定動作、同時更新、掲載版とChatGPTの再接続は未検証です。検証終了後は書き込みを無効にしてツール情報を更新し、不要なタスクとMCPの接続も停止します。カウンターを消す場合も、先に書き込みを無効にしてください。
継続運用するなら、MCP側の認証・認可に加え、同じ指示が再実行された場合の二重登録防止、更新競合の検出、REST API側の排他・原子的な保存、監査ログが必要になります。プロンプトの「再試行しない」は、サーバー側の冪等性を保証しません。
REST APIのキーをMCPサーバー内に隠すことと、MCPの呼び出し元を認証することも別です。前者だけでは、公開したMCP経由で第三者に操作されることを防げません。本番化では、利用者ごとに許可する操作をサーバー側で判定する設計へ進める必要があります。
まとめ
PHP製のアダプターを挟むことで、既存のREST APIをChatGPTから使うためのMCPツールとして公開できました。検証したPlus環境では、チャットと1回限りの時刻指定タスクの両方から登録できています。
まずは疎通確認とテストデータの追加に絞ると、MCP側の通信、上流APIへの接続、書き込みの成否を分けて確認しやすいと思います。実際のデータ更新へ進めるときは、今回の接続テストに認証・認可とデータ整合性の設計を足していきたいところですね。
奈良市を拠点に、27年以上の経験を持つフリーランスWebエンジニア、阿部辰也です。
これまで、ECサイトのバックエンド開発や業務効率化システム、公共施設の予約システムなど、多彩なプロジェクトを手がけ、企業様や制作会社様のパートナーとして信頼を築いてまいりました。
【制作会社・企業様向けサポート】
Webシステムの開発やサイト改善でお困りの際は、どうぞお気軽にご相談ください。小さな疑問から大規模プロジェクトまで、最適なご提案を心を込めてさせていただきます。
ぜひ、プロフィールやWeb制作会社様向け業務案内、一般企業様向け業務案内もご覧くださいね。
ChatGPTのChrome拡張機能はフォームの入力値まで読める? localhostで試してみた
2026.10.19
ChatGPTのChrome拡張機能を使い、ローカル開発画面のフォーム入力値が読み取られるかを検証しました。password・text・emailの入力型ごとの観測結果と、APIキーなどの秘密情報を入力した画面を共有する際の注意点を紹介します。
VS CodeのREST Client入門:.httpファイルでAPIの動作確認をする
2026.10.06
VS Code拡張のREST Clientを使って、APIへリクエストを送信し、レスポンスを確認する方法を紹介します。.httpファイルの書き方、GET・JSON POST、変数や認証情報の扱いから、環境切替、レスポンスの値を次のリクエストで使う方法までを整理します。
MCPサーバーのコードを読むためのPython入門:JSONと標準入出力の基本
2026.08.15
Python初学者を対象に、MCPサーバーのコードを読み解くために必要な変数・関数・辞書・リスト、JSON、標準入力・標準出力、例外処理を、WindowsのPowerShellと短いサンプルコードで解説します。MCPのSTDIO通信で、標準出力へデバッグログを混ぜてはいけない理由も確認します。
PythonでMCPサーバーを作り、Codexから別プロジェクトの文書を検索する
2026.08.11
別プロジェクトのファイルを検索する読み取り専用MCPサーバーを、Pythonの標準ライブラリで実装します。Codexアプリから呼び出すためのconfig.toml設定、MCPのSTDIO通信、標準出力へログを出さない注意点、PYTHONIOENCODING未設定時のトラブル対処まで紹介します。