備忘録

ゆる〜く技術全般。

Claude CodeにPlugin機能が追加されたので早速作ってみた

はじめに

Claude CodeにPlugin機能が新たに追加された。

www.anthropic.com

内容としては、MCP Server/スラッシュコマンド/サブエージェント/フックなどをまとめたパッケージ(Plugin)を公開し、Claude Code上でワンコマンドで導入できるというもの。

これまで私のチームで扱っているリポジトリ内にカスタムスラッシュコマンドやMCP Serverの設定を置いていて、別のチームのリポジトリにも全く同じものを横展開していたのだが、新たに追加したり修正を加えたい場合、他のリポジトリにも同じようにいちいち追加/修正する必要があった(非常に面倒だった)。
このPlugin機能のおかげで、チームや組織で共通で使うPluginをOrg配下のリポジトリで管理/配布することで横展開が非常に容易になった。 また、有志が作成した便利なPluginをワンコマンドで扱えるようになるのも嬉しい。

今後欲しい機能としては、既存のMCP Serverの.claude.jsonファイルのように、チーム内で利用するPluginリストの設定ファイルをリポジトリに含めることで読み込んでくれる機能が欲しい。

実際にPlugin作ってみた

Claude CodeのPlugin機能追加がアナウンスされた当日に、早速自分でPluginを作ってみた。

github.com

Claude Code内で、以下を入力すると利用可能。

  1. /plugin marketplace add hnegishi/cc-prtools
  2. /plugin install cc-prtools@hnegishi

内容としては、以下のスラッシュコマンドを含むPluginとなっている。

  • Pull Requestの作成
  • Pull Requestの更新
  • Pull RequestのSummaryコメント追加

※ 3つ目のPull RequestのSummaryコメント追加は、内容がPR本文と若干被っちゃってる。

ghコマンドかGitHub MCP Serverが導入されている前提だが、Claude Code上で/create-prあるいは/update-pr {PR番号}と入力することで、コード差分を検出して自動でPRを作成/更新してくれる。
Summaryコメントを追加する場合は、/summarize-pr {PR番号}と入力すれば、簡単なまとめ/変更ファイル内容/シーケンス図 付きでPRにコメントを追加してくれる。

もちろんコンテキストにIssueなどの情報を与えることで、その内容を考慮した文章を作成してくれる。

↓ サンプルとして、実際に作成したPR本文Summarizeコメント

PR本文 Summarizeコメント

PULL_REQUEST_TEMPLATE.mdがあれば、それを参照して作成される。(サンプルではテンプレートがない状態で作成)

(おまけ) Docker MCP Toolkitとの連携する時のDebugが楽になった

Docker MCP Toolkitとは、コンテナ化されたMCP Serverをカタログ内から選んでDocker Desktopから管理/実行することができる拡張機能

これまではMCP ClientとしてClaude DesktopやCursorはサポートされていたが、Claude Codeは手動で連携していた。

Dockerが用意したPlugin(/plugin add marketplace docker/claude-plugins)を入れて、gateway-debuggateway-statusでDocker MCP Toolkitとの連携状態を容易に確認できるようになった。

MCP Serverを使う上でのセキュリティ

はじめに

2025/4/4(金)にこれまでInsider版の方では解放されていたMCP ServerとAgent Modeの機能が通常版の方にもリリースされた。

code.visualstudio.com

MCPが一定流行っていることもあり、社内のみんなにも色々試してみて欲しいという思いで週明け早朝に急いで社内のMCP Serverの利用ガイドラインを作成していた。 自分は、VSCode + Roo CodeでAgent + MCP Serverの組み合わせは以前から試験的に触っていてセキュリティ面で思ったことがいくつかあったのでそれをまとめておく。

npxやuvxの危険性

Anthropic社が管理するリポジトリの実装をいくつか眺めると、Reference ServersはDocker環境が用意されているものが多く、コンテナ上で動くものであれば秘匿情報を含むvolumeをマウントしたりわざわざ特権モード(--privileged)をつけたりしない限り必要最小限のcapabilitityで運用するだけで一定の安全を担保できる。

一方で、一部のThird-Party ServersCommunity Serversやその他個人の方々が開発されたものではnpxやuvxで実行させているものも多い。

npxやuvxは言わずもがな、リモートにある実行ファイルを呼び出して実行してしまう。なので、 MCP Serverの配布元を信頼していてもMCP Serverが依存しているライブラリを芋づる式に呼び出す過程で悪意のあるコードが実行されてしまう危険性は考慮しておくべきだろう。 MCP Serverが流行っている現状を鑑みると攻撃対象として既にかなり狙われていると思う。

それらの危険性を考慮した上で、もしどうしても使いたいのであれば配布元の審査や利用しているライブラリに目を通した上でversionを固定した状態で利用するぐらいだろうか。

Agentモードでauto approveにしない

CursorやVSCodeでAgentモードを使ったことがある人ならわかるが、Agentが副作用の起きるアクションを取る前にユーザに対して確認してくれる。これらの承認作業を時々鬱陶しく感じる時があるが、任意コードを実行できてしまったり予測不能なことを偶にするのでAgentからの実行確認は都度チェックしたほうがいい。 特にMCP Serverのtoolsを使うときはlocale環境の外に対して副作用を及ぼすアクションを取ることが多いので注意したい(戒め)。

自分は個人開発しているときに.gitignoreに追加する前のファイルを勝手にcommit pushされてしまって少し面倒くさい思いをした程度でまだ済んでるが、git reset --hardされてしまった人もいる模様。

VSCodeの秘匿情報の扱いが良い

自分の場合は、VSCode + GitHub MCP ServerでPAT(GitHub Personal Access Token)を発行して使っているが、VSCode"inputs": []フィールドのおかげでPATの内容をハードコーディングせずに済んでいる。非常に良い!(でもghコマンドで良い気はしている。)

code.visualstudio.com

最後

自分がMCP Serverのセキュリティ関連で読んだ記事を貼っときます。

zenn.dev

findy-code.io

simonwillison.net

Roo CodeとMCP Server便利!

はじめに

最近、GitHub Cipilot + Roo Code + MCP Serverを使って開発してみたんだけど、本当に便利。 GitHub CopilotはClaude 3.7 SonnetとかThinkingみたいな上位モデルを無限(じゃないと思うけど)に使わせてくれるし、Roo CodeとMCP Serverを組み合わせてGitHubでIssueやPRやCommentをr/wしてくれるので、VSCode上でコード書いてプロンプト入力しているだけでほぼ完結するようになってきた。

(自分はCursorを契約していないので使っていないのだが、Cursorでは同様のことが既にできるっぽい) ※ Cursorは後日契約しました。便利!

MCP Serverに関して

最初に自分のMCP Serverの理解を話すと、Agent(Roo Code)とMCP ServerがJSON-RPCでやり取りしていて、MCP Serverで定義されたtools (例: GitHub)を読み込んで、Agentが必要に応じてMCP Serverに定義されたコマンドのパラメータにプロンプトの内容を付与して実行する。感覚的にはLSP(Language Server Protocol)に近い考え方なのかなという理解。

正直なところ、Anthropicが管理するMCP公式のリポジトリの実装をいくつか眺めた程度なのであまり自信はない。 github.com そのMCP Serverが何をしているか?を詳しく知りたい場合は、リポジトリのindex.tsにtoolsの実装があるのでとりあえずはそこを確認すれば良さそう。

それで自分はどのMCP Serverを使っているかというと、 GitHub MCP serverBrave Search MCP Serverfetchの3つだけ使ってる。

導入はめちゃくちゃ簡単で /settings/mcp_settings.jsonに以下を追加すれば、MCP Server毎にコンテナが立ち上がってくれる。

{
  "mcpServers": {
    "github": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "GITHUB_PERSONAL_ACCESS_TOKEN",
        "mcp/github"
      ],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
      }
    },
    "brave-search": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "BRAVE_API_KEY",
        "mcp/brave-search"
      ],
      "env": {
        "BRAVE_API_KEY": "YOUR_API_KEY_HERE"
      }
    },
    "fetch": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "mcp/fetch"]
    }
  }
}

これだけで、Agentに対してURLを投げれば参照してくれるし「@/.github/PULL_REQUEST_TEMPLATE.mdを参考に現在のブランチのPR作成して」と投げればgit diffして変更内容を把握してPRを作ってくれる。 注意点としては、Roo CodeでGitHub MCP Serverを使っていると、MCP Serverからエラーが返ってきているが実際は正常に処理できている というパターンがあってAgentにその情報を伝えるのがやや面倒だった。

今後出来たらいいなと思っていることとしては、例えばUIを変更した時とかフォームを追加した時に、PRに実際に操作した録画を貼り付けたりすることがあるのだがPuppeteerとかPlaywrightのMCP Serverを使って自動化出来たらめちゃくちゃ便利。

プロダクトチームへの導入について

自分はこの手の便利なツールが登場した時に、チームへ導入できないか?ということを考えるのだが、 プロダクトチームで既にCursor導入しているところはいいとして、VSCode民たちはそのうちGitHub Copilotに実装されるのを待つのが確実だと思う。一応、VSCode InsidersではAgent ModeやMCP Serverを既に使えるのでもうすぐなはず。

今のところ自分はVSCode + Roo Codeを使っているが、もうすぐ実装される件と合わせて、Roo Codeを中央集権的に管理する事はできない(そもそも想定していないと思う)のでプロダクトのチーム開発にRoo Codeを導入する事は若干後ろ向きになっている。具体的には、Roo Codeの実行権限が各自依存なので全てauto approveに設定できてしまったり、GitHub MCP ServerでPATを利用していたり、公式以外のMCP Serverを使ってしまうと脆弱性を孕むので、一部の温度感の高いメンバー間で試用するぐらいがちょうど良いと思った。

Bark with Colab

はじめに

何か業務に活かせないかなぁと生成AI系にアンテナを張っている今日この頃なのですが
最近Barkというtransformerをベースとしたtext-to-audioのモデルが公開されました。 特徴としては、実際の口語に近い雰囲気(棒読みのような単一ではない感じ)をうまく再現できているなと感じました。
日本語も対応していますが、英語圏の人が話す日本語っぽい発音です。「す」が「th」の発音など。

また、プロンプトのテキストにをつけると歌ってくれたり、[laughs]をつけるとテキストの文言に馴染むように笑って発音します。 この辺はREADMEにサンプル音声付きで詳しく書いてあるのでそちらを参照してください。

尚、CC-BY 4.0 NCライセンスですので営利目的での利用はできません。(bark/LICENSE)

この記事でやること。

  • Bark 実行環境 [Google Colab]
  • Bark voice clone [Google Colab]

Bark 実行環境 [Google Colab]

以下をそのままColabに貼り付けて実行すればOKです。UIが立ち上がります。
makawy7/bark-webuiを盛大に参考にしています。

!pip install gradio git+https://github.com/suno-ai/bark.git
import gradio as gr
from bark.generation import SUPPORTED_LANGS
from bark import SAMPLE_RATE, generate_audio
from scipy.io.wavfile import write as write_wav
import os
from datetime import datetime


def generate_text_to_speech(text_prompt, selected_speaker, text_temp, waveform_temp):
    audio_array = generate_audio(text_prompt, selected_speaker, text_temp, waveform_temp)

    now = datetime.now()
    date_str = now.strftime("%m-%d-%Y")
    time_str = now.strftime("%H-%M-%S")

    outputs_folder = os.path.join(os.getcwd(), "outputs")
    if not os.path.exists(outputs_folder):
        os.makedirs(outputs_folder)

    sub_folder = os.path.join(outputs_folder, date_str)
    if not os.path.exists(sub_folder):
        os.makedirs(sub_folder)

    file_name = f"audio_{time_str}.wav"
    file_path = os.path.join(sub_folder, file_name)
    write_wav(file_path, SAMPLE_RATE, audio_array)

    return file_path


speakers_list = []

for lang, code in SUPPORTED_LANGS:
    for n in range(10):
        speakers_list.append(f"{code}_speaker_{n}")

input_text = gr.Textbox(label="Input Text", lines=4, placeholder="Enter text here...")
text_temp = gr.Slider(
    0.1,
    1.0,
    value=0.7,
    step=0.1,
    label="Generation Temperature",
    info="1.0 more diverse, 0.1 more conservative",
)
waveform_temp = gr.Slider(
    0.1,
    1.0,
    value=0.7,
    step=0.1,
    label="Waveform temperature", info="1.0 more diverse, 0.1 more conservative"
)
output_audio = gr.Audio(label="Generated Audio", type="filepath")
speaker = gr.Dropdown(speakers_list, value=speakers_list[60], label="Acoustic Prompt")


interface = gr.Interface(
    fn=generate_text_to_speech,
    inputs=[input_text, speaker, text_temp, waveform_temp],
    outputs=output_audio,
    title="Text-to-Speech using Bark",
    description="A simple Bark TTS Web UI.",
)

interface.launch()

Bark voice clone [Google Colab]

serp-ai/bark-with-voice-clone

こちらはBarkをforkしてクローン音声を生成できるようにしたやつです。
7秒未満の音声とテキストのペアを用いて、音声を複製することができます。つまり、任意の声で文章を読み上げることができます。

こちらはipynbが公開されているので、コピーしてそのまま各自任意のファイルを読み込むように変更するだけで動きます。
Colab環境ですと.npzの生成から音声の生成(generate_audio)まで10分ほどかかります☕

私が試した時には、サンプリング対象を変えたりハイパーパラメータを何度か変更し数時間粘ってみたものの、全く似ていなかったりノイズが混じっていたり文章を適切に読み上げない事が多々ありました。
tempを下げすぎる(4以下とかにする)と原型のないノイズ音のみになるので注意してください。

触ってみた所感としては

  • 生成される音声が安定してない。
  • けど7秒未満の短い音声サンプルでここまで出来るのは素晴らしい

今後また更新されるようなので、期待ですね。

追記1

Hugging Faceというサイトで、様々なmodelやdatasetの公開がされています。 眺めているだけで1日終わりそうです。

追記2 (24/10/29)

ふとニュースを観ていたら、以下の様な動画があることを知りました。
意見は様々あると思いますが、こういった声明があるという事に留意して利用できると良いですね。

www.youtube.com

RubyのObjectについて

Ruby で扱える全ての値はオブジェクト

とはよく言われるけど、これが実際には何なのか。
Rubyのオブジェクトチョットワカルって言いたいので調べた。

すごい長くなりそうなので(オブジェクトといっても構造体が複数種あるため)
まずは、自分たちが定義したクラスから生成されるオブジェクトの実体が何なのかから。

結論から言うと
クラス(RClass構造体)から生成されたオブジェクトの実体はRObject構造体である

???と言う事で、実際のコードを見てみる

// ruby/include/ruby/internal/core/robject.h#L48-L58
struct RObject {
    struct RBasic basic;
    union {
        struct {
            uint32_t numiv;
            VALUE *ivptr;
            void *iv_index_tbl; /* shortcut for RCLASS_IV_INDEX_TBL(rb_obj_class(obj)) */
        } heap;
        VALUE ary[ROBJECT_EMBED_LEN_MAX];
    } as;
};

これだけ見ると割とシンプル

  • RBasic・・・次で説明
  • numiv・・・インスタンス変数の数
  • ivptr・・・インスタンス変数の値へのポインタ。あくまで変数名は格納されず値のみ
  • iv_index_tbl・・・インスタンス変数の変数名とivptrの配列内の値を対応させるハッシュテーブル(st_table)へのポインタを格納。変数名はRClass構造体に格納される。

RBasicは

RBasic {
    VALUE flags;                /**< @see enum ::ruby_fl_type. */
    const VALUE klass;

#ifdef __cplusplus
  public:
    RBIMPL_ATTR_CONSTEXPR(CXX11)
    RBIMPL_ATTR_ARTIFICIAL()
    RBIMPL_ATTR_FORCEINLINE()
    RBIMPL_ATTR_NOALIAS()
    /**
     * We need to define this explicit constructor because the field `klass` is
     * const-qualified above,  which effectively  defines the  implicit default
     * constructor as "deleted"  (as of C++11) --  No way but to  define one by
     * ourselves.
     */
    RBasic() :
        flags(RBIMPL_VALUE_NULL),
        klass(RBIMPL_VALUE_NULL)
    {
    }
#endif
};

これまたシンプルでRBasicはflagsklassの2つの値を持っている
flagsは、内部で多目的に使われているんですが、例えば構造体の型(後述のRStringやRArrayとか)を識別するのにも使っています。
klassは、オブジェクトが何のインスタンスであるかを示すクラスポインタ。

以上をふまえて、超ざっくりで言うと
オブジェクト(RObject構造体)は、クラスへのポインタとインスタンス変数の集合体
なので、Class内に定義したmethodsはオブジェクトには含まれていないです。

... 具体的によくわかんねーよって感じになると思うので
実際のRubyコードと対応させて説明してみる。

class Person
    attr_accessor :name, :age
end

上のシンプルなクラスからオブジェクトを生成する。

irb#1(main):001:0> hoge = Person.new
=> #<Person:0x007fe70187f7e0>

irb#1(main):002:0* hoge.name = 'john'
irb#1(main):003:0> hoge.age = 22

irb#1(main):004:0> hoge
=> #<Person:0x007fe70187f7e0 @name="john", @age=22>

この時の、#<Person:0x007fe70187f7e0>部分のPerson はクラスポインタの値で、16進数の0x007fe70187f7e0 はオブジェクトのポインタを示していて、これはオブジェクト毎にユニーク。
この生成されたオブジェクト毎にユニークなポインタがあるので、同じClassから生成されたオブジェクトが複数あってもオブジェクト毎に異なる値を追跡出来ているんですね

で、これらを対応させると...
RObject内のRBasicのklassはPersonを示す、RClass構造体へのポインタ。
RObject内のRBasicのflagsは色々なフラグがあるけど、例えばRObject構造体を示すフラグとか。
numivは2。
ivptrはjohnと22
iv_index_tblは@name, @ageとjohn,22を対応させるハッシュテーブルへのポインタ。
となる。

ここまでで、クラスから生成されたオブジェクトの実体についてざっくり説明したが、文字列や整数とかがRObject構造体と同じなのかと言うとそうではなくてまた別の構造体を使っています。
例えば文字列の値はRString構造体に保存していて、これまたオブジェクトなので文字列にインスタンス変数を持つことも出来ます。
※整数についてはまた話が長くなるので割愛

使う機会はないだろうけど、こんなことも出来ます。

irb#1(main):001:0> str = 'john'
=> "john"
irb#1(main):002:0> str.instance_variable_set('@age', 22)
=> 22
irb#1(main):003:0> str.instance_variables
=> [:@age]

あと厳密に言うと、RObject構造体と同じようにRBasicは共通なんだけど、RStringはivptrやらiv_index_tblは持っていないのでgeneric_iv_tblを使っています。

// ruby/include/ruby/internal/core/rstring.h#L73-L86

struct RString {
    struct RBasic basic;
    union {
        struct {
            long len;
            char *ptr;
            union {
                long capa;
                VALUE shared;
            } aux;
        } heap;
        char ary[RSTRING_EMBED_LEN_MAX + 1];
    } as;
};
// ruby/variable.c#L1217-L1231

static void
generic_ivar_set(VALUE obj, ID id, VALUE val)
{
    struct ivar_update ivup;

    ivup.iv_extended = 0;
    ivup.u.iv_index_tbl = iv_index_tbl_make(obj);
    iv_index_tbl_extend(&ivup, id);
    st_update(generic_iv_tbl, (st_data_t)obj, generic_ivar_update,
          (st_data_t)&ivup);

    ivup.u.ivtbl->ivptr[ivup.index] = val;

    RB_OBJ_WRITTEN(obj, Qundef, val);
}

クラスから生成したオブジェクトの話からちょっと逸れたけど、おわり。





参考

https://github.com/ruby/ruby
http://i.loveruby.net/ja/rhg/book/object.html
Rubyのしくみ -Ruby Under a Microscope-