「昨日と同じ問いを、今日もまたClaudeに投げている」— そんな感覚に思い当たる方は多いのではないでしょうか。受信トレイの未対応メール件数、レビュー待ちのプルリクエスト、進行中のタスクの優先度。これらは毎日変わる情報なので、一度の回答では翌朝にはもう古くなっています。
Cowork mode の Artifacts は、この「同じ問いの繰り返し」を解消するために用意された機能です。チャットの一回答を、再オープン可能な専用ページに昇格させ、開くたびにMCP連携で最新データを取りに行かせる設計ができます。今回はこのArtifactsを「毎朝開く1ページ」として育てるための設計手順を、実例とともに整理してみました。
チャットの一回答と Artifacts の決定的な違い
通常のチャット応答は、その瞬間のスナップショットです。Slackで未読メッセージを集計してもらった3秒後に新しいメッセージが届けば、もうその回答は古いということになります。一方、Artifactsはコードと表示が永続化された独立ページであり、window.cowork.callMcpTool 経由で開くたびに最新データを取得できます。
私が実際に使い分けているのは次のような基準です。
- 一回答で十分なケース: 概念の説明、過去データの分析、一度きりのまとめ
- Artifacts にすべきケース: 翌日も翌々日も同じ問いを投げる予感があるもの、データが時間で変化するもの、複数のMCPの結果を1ページに集約したいもの
「またこれ聞きそうだな」と感じたら、それはArtifacts化のサインです。
設計を始める前にMCPの応答を必ず一度プローブする
Artifacts開発で最初につまずくのは、MCPツールの返り値の形が想像と違っていることです。Slack MCPの「メッセージ取得」が返すJSONの構造、Linear MCPの「タスク一覧」のフィールド名は、各サービスの公式APIとは微妙に異なる場合があります。
なので、Artifactsを書く前にチャットでそのMCPツールを最小ペイロードで一度実行し、生レスポンスを観察するのが鉄則です。
// プローブ後、観察した形に合わせてパーサーを書く
const response = await window.cowork.callMcpTool({
server: "slack",
tool: "search_messages",
args: { query: "in:#alerts", limit: 10 }
});
// MCPラッパーがcontent[0].textにJSON文字列を入れている場合
const data = typeof response.content?.[0]?.text === "string"
? JSON.parse(response.content[0].text)
: response;
// 公式APIなら data.messages だが、ラッパーで data.results に変わっていることもある
const items = data.results ?? data.messages ?? [];ここで「とりあえず公式APIのドキュメント通りに書く」と、本番で空配列が返ってきて沈黙する事故を起こします。実際に私もLinear連携で半日溶かしました。
実装サンプル: 朝イチで開く「未対応の集約ページ」
ここでは Slack の未読アラートと Linear の自分宛タスクを1ページにまとめる例を示します。
<div id="root">
<h2>今朝の未対応</h2>
<section id="slack"><p>読み込み中…</p></section>
<section id="linear"><p>読み込み中…</p></section>
</div>
<script>
async function loadSlack() {
const r = await window.cowork.callMcpTool({
server: "slack",
tool: "search_messages",
args: { query: "in:#alerts after:yesterday", limit: 20 }
});
const data = JSON.parse(r.content?.[0]?.text ?? "{}");
const items = data.results ?? [];
document.getElementById("slack").innerHTML = items.length
? `<h3>Slack #alerts (${items.length}件)</h3><ul>${
items.map(m => `<li>${m.user}: ${m.text}</li>`).join("")
}</ul>`
: "<h3>Slack: クリーン ✅</h3>";
}
async function loadLinear() {
const r = await window.cowork.callMcpTool({
server: "linear",
tool: "list_my_issues",
args: { state: "open" }
});
const data = JSON.parse(r.content?.[0]?.text ?? "{}");
const items = data.issues ?? [];
document.getElementById("linear").innerHTML = `<h3>自分宛タスク</h3>${
items.map(i => `<div>${i.priority} - ${i.title}</div>`).join("")
}`;
}
Promise.all([loadSlack(), loadLinear()]).catch(e => {
document.getElementById("root").insertAdjacentHTML(
"beforeend",
`<p style="color:#c00">読み込み失敗: ${e.message}</p>`
);
});
</script>このページを毎朝1クリックで開けば、その瞬間のSlackとLinearの状態が手元に揃います。
つまずきやすい落とし穴3つ
実装中によく遭遇する問題と、その対処を共有しておきます。
1. MCPがコネクトされていない状態で開いた時の挙動
ユーザーがあとからアカウントを切り替えるとMCPが切断状態になり、callMcpTool がエラーを返します。私は必ずページ全体を try/catch で囲み、エラー時は「再接続が必要です」と表示する設計にしています。空白画面が出ると「壊れた」と感じてしまうので、明示的なフォールバックが大事です。
2. データ取得が遅いと「壊れたページ」に見える
複数のMCPを並列で叩くと、遅い方に引きずられて全体表示が遅くなります。Promise.all ではなく、各セクションが個別に「読み込み中→結果」に切り替わるように Promise.allSettled で並行ロードするほうがユーザー体験が良くなります。
3. ブラウザストレージは絶対に使えない
localStorage や sessionStorage はArtifactsの実行環境でサポートされていません。状態を保持したい場合はメモリ上の変数で十分なケースがほとんどですが、もしどうしても永続化が必要なら、その状態自体をMCP側(連携サービスのカスタムフィールドやNotionページなど)に保存する設計に切り替えます。
Artifactsを「育てる」運用パターン
一度作ったArtifactsは、使いながら少しずつ改良していくのが現実的です。私が運用しているリズムはこうです。
最初の1日は最小機能だけで開きます。Slackの未読件数だけ、Linearのタスク一覧だけ、というように1セクションから始めます。2〜3日使ってみて「ここに優先度フィルタがあれば便利だ」と感じた時点でClaudeに「このArtifactsに優先度フィルタを追加して」と頼み、update_artifact で差分更新します。
完成形を最初から作ろうとすると、MCPの応答形式の確認ができていない箇所で詰まります。動くものを最小で作り、毎日の利用で見えてきた改善点だけを足していくほうが、結果的に早く実用ダッシュボードに育ちます。
Cowork スケジュールタスクの設計ガイド と組み合わせると、夜間にデータをLinearやNotionへ集約しておき、朝はArtifactsを開くだけで全体が見えるという二段構えの運用も可能になります。
全体を振り返って — 今日の小さな一歩
Artifactsを使い始める最も簡単な入り口は、「いま現在チャットで返してもらっている定期的な質問」を1つ選び、それをArtifactsに昇格させることです。完璧な設計を考える前に、最も繰り返し聞いている問いを1つだけ取り上げて、最小機能のArtifactsにしてみてください。
5分でできる小さなページが、明日からの確認業務を確実に短縮してくれます。
Artifactsは「Claudeに問い続ける関係」から「Claudeと作ったページに毎朝向き合う関係」への転換点だと感じています。