VSCodeで簡単なところから始めてみるMCP Server

## やること

- VS Codeの設定
- 簡単なMCP Server(stdio)作る
- それをstreamable httpに変える
- self-hostedなGitlabを使うtoolを追加する

### 前提
Windows11 + WSL + VSCode + Github copilot  
pythonとfastmcpを使う  
uvはインストール済みとする  

## 最初に

### VSCode起動まで
initしてVSCode起動する
```
uv init start_mcp
cd start_mcp
code .
```

### VSCodeのAgent周りの設定
VSCodeの設定で`mcp`で検索し  
`chat.mcp.enabled`と`chat.mcp.discovery.enable`を有効にしておく  
![vscode_setting_mcp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266314/a305f782-fd91-4c0e-802e-8cb489ebb0a2.png)


### fastmcpを追加
```
uv add fastmcp
```

### サイコロMCPを作る

Ctrl+Alt+I でチャット窓開く。  
main.pyを選択、Agentに切り替えた状態で  
![dice_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266314/29bb5881-eb84-4aa3-acb8-1ef62a5bd30f.png)


動かない残念なコードが吐かれたので「まだまだやのぅ」と言いながらmain.pyを以下のように

```
from fastmcp import FastMCP
import random

mcp: FastMCP = FastMCP("sample", port=3000)

@mcp.tool()
def roll_dice() -> int:
    """六面ダイスを振って結果を返す"""
    return random.randint(1, 6)

mcp.run(transport="stdio")
```

### MCP Serverの登録
チャット窓の設定ボタンから  
![mcp_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266314/7d13ebf0-dd1f-49f2-8db6-527b5b83e069.png)

ドロップダウン一番下の「＋その他のツールを追加」  
「＋MCPサーバーの追加」  
「コマンド(stdio)」  
「uv --directory /home/ismk/tmp/mcp/start_mcp run main.py」  
MCPサーバーの名前(そのままエンターでok)  
「ワークスペースの設定」

.vscode/mcp.jsonが作られる
```
{
    "servers": {
        "my-mcp-server-c7014913": {
            "type": "stdio",
            "command": "uv",
            "args": [
                "--directory",
                "/home/ismk/tmp/mcp/start_mcp",
                "run",
                "main.py"
            ]
        }
    }
}
```

無事起動できていれば「1個のツール」が表示される  
![mcp_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266314/72eabe1c-e9c6-4677-ba89-60a80ecdc0c8.png)

ダメだったら出力を見直す

### 使ってみる

![dice_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266314/54bb02cb-75d4-4a09-a8f8-65cee98e1b84.png)
 
![dice_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266314/490375ce-5ea4-4da7-91f8-2776e3d459ed.png)
 


## streamable-httpでやる

### なぜstdioなのか、なぜstreamable-httpなのかと注意点

いろいろ見てまわっても、見つかるソースはstdioが多い。  
サクッと試せるからなのかな。  
不便そうだなと思うのは、`使いたい人がみんなローカルで動かせる状態にしないといけないこと` だろうか。  
Pythonだったり、TypeScriptだったりいろいろあったら面倒そう。  

その点streamable-httpなら、どこかにサーバー立てておけば、使いたい人は接続設定するだけで良い。  

注意すべきなのは、秘匿情報を送っても、それが適切に扱われるか？ということ。  

例えばSlackが作ったSlack用のMCPサーバーなら信頼できそう、あるいは社内ユーザー向けに作られた社内システム用のMCPサーバーなら信頼できそう。ぐらい？  

誰が作ったか分からない、どこで稼働してるか分からないMCPサーバーは、「これ、すごい便利だよ！」みたいな情報に釣られてうっかり使わない方が良いでしょうね。


### やってみる

main.pyの
```
mcp.run(transport="stdio")
```
を
```
mcp.run(transport="streamable-http")
```

mcp.jsonを
```
{
    "servers": {
        "my-mcp-server-c7014913": {
            "type": "http",
            "url": "http://localhost:3000/mcp/"
        }
    }
}
```
URLは`/`で終わらせないとうまく繋がらなかった。

WSLでサーバーを起動しておく
```
uv run main.py
```
そしてmcp.jsonから(再)起動

再び「サイコロ振って」で同様に動くはず


## 応用編:Gitlabからissue一覧を取得してみる

Gitlabにpushするとかブランチ作るとか、実装を支援する系は、[公式のデモ用サーバーアーカイブ](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/gitlab)にあるけど、issueの一覧や内容見るような管理向けのものが無いのでやってみる。  


### 変更を加える

Gitlab APIを呼ぶ為にrequestsを追加
```
uv add requests
```

Gitlabはself-hosted(ローカルネットワーク)なので、それ用の証明書が必要。  
requestsにその場所を指定してやる必要がある(めんどくさい)ので環境変数を設定した上でサーバー起動する。  
```
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
```

main.pyに以下追加
```
import requests

@mcp.tool()
def list_gitlab_issues(ctx: Context):
    """Gitlabからissueの一覧を取得して返す."""
    request: Request = get_http_request()
    token = request.headers.get("private-token", "")
    projectId = request.headers.get("project-id", "")

    url = "https://gitlab.hogehoge.local/api/v4/projects/{}/issues?state=opened".format(projectId)
    headers = {
        "Private-Token": token
    }
    response = requests.get(url, headers=headers)
    return response.json()
```

GitlabのPersonal Access Token(scopeはread_api)を取得、ターゲットとなるリポジトリ設定:一般からプロジェクトIDをメモっておく。  
mcp.jsonを次のように
```
{
    "inputs": [
        {
            "id": "gitlab_pat",
            "description": "GitLab personal access token",
            "type": "promptString",
            "password": true
        }
    ],
    "servers": {
        "my-mcp-server-c7014913": {
            "type": "http",
            "url": "http://localhost:3000/mcp/",
            "headers": {
                "Private-Token": "${input:gitlab_pat}",
                "Project-ID": "532"
            }                
        }
    }
}
```

inputを設定して起動すると入力欄が出てくるので上で得たTokenを入力しておくと実行時に渡してくれる。  
![gitlab_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266314/9fbd7b18-a03e-4ee1-bd82-241b8ae70517.png)


### 動かしてみる

![gitlab_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266314/cde2e2d3-3cca-411e-b36a-3f4fcc9edac6.png)
 

作成者や状態も返してくれた（多いとタイトルだけだったりすることもある）  
![gitlab_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266314/01eef553-80f9-4120-96d3-bb7870abe902.png)


「期限やばいのどれ」みたいな聞き方で絞ってくれる
![gitlab_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266314/57c168cd-a820-4e6c-8cfc-5c7b7e251997.png)