跳至主要內容

在終端機輸入指令,看看本地 MCP 的運作

· 3 分鐘閱讀

處理流程

使用 MCP 時,我問了 Copilot 小幫手,LLM 用戶端會以什麼樣的流程進行處理。

以下為序列圖。

這裡重要的是:

  1. ①② 取得工具定義
  2. ④ 傳送工具定義
  3. ⑦ 工具呼叫要求
  4. ⑧⑨⑩⑪ 工具呼叫
  5. ⑫ 傳送執行結果

與 MCP 有關的部分是 1. 和 4.,而與 Function calling(函式呼叫)幾乎相同的部分是 2.、3. 和 5.。

在終端機輸入指令來呼叫看看

來嘗試以標準輸入使用本地 MCP。

我用 Windows PowerShell 執行指令。

取得工具清單

舉例取得 @modelcontextprotocol/server-filesystem 的工具清單。

> @{ jsonrpc = "2.0"; method = "tools/list"; id = 1 } | ConvertTo-Json -Compress | npx @modelcontextprotocol/server-filesystem $HOME | ConvertFrom-Json | ConvertTo-Json -Depth 10
Secure MCP Filesystem Server running on stdio
Allowed directories: [ 'C:\\Users\\hikari' ]
{
"result": {
"tools": [
{
"name": "read_file",
"description": "Read the complete contents of a file from the file system. Handles various text encodings and provides detailed error messages if the file cannot be read. Use this tool when you need to examine the contents of a single file. Only works within allowed directories.",
"inputSchema": {
"type": "object",
"properties": {
"path": {
"type": "string"
}
},
"required": [
"path"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}
},
...
]
}
}

tools/list 透過標準輸入傳入,就能以 JSON 格式取得工具清單。

呼叫工具

根據工具資訊來呼叫它。

> @{ jsonrpc = "2.0"; method = "tools/call"; params = @{name = "read_file"; arguments = @{path = ".gitconfig"}}; id = 2 } | ConvertTo-Json -Compress -Depth 10 | npx @modelcontextprotocol/server-filesystem $HOME | ConvertFrom-Json | ConvertTo-Json -Depth 10
Secure MCP Filesystem Server running on stdio
Allowed directories: [ 'C:\\Users\\hikari' ]
{
"result": {
"content": [
{
"type": "text",
"text": "..."
}
]
},
"jsonrpc": "2.0",
"id": 2
}

在 WSL 安裝 Rocky Linux 8.10

· 2 分鐘閱讀

下載 Rocky Linux 8.10 映像

$dest = Join-Path $env:TEMP "Rocky-8-Container-Base.latest.x86_64.tar.xz"
Invoke-WebRequest -Uri "https://dl.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-Container-Base.latest.x86_64.tar.xz" -OutFile $dest

匯入

wsl --import RockyLinux-8.10 $HOME $dest

安裝 passwd

wsl -d RockyLinux-8.10 -u root dnf update -y `&`& dnf install -y passwd

建立使用者

$username = "hikari" # 設定你想要的使用者名稱
wsl -d RockyLinux-8.10 -u root useradd -mG wheel $username
wsl -d RockyLinux-8.10 -u root passwd -d $username # 清除使用者密碼

安裝 sudo

wsl -d RockyLinux-8.10 -u root dnf update -y `&`& dnf install sudo -y

設定預設使用者

$username = "hikari" # 設定你想要的使用者名稱
$uid = wsl -d RockyLinux-8.10 id $username -u
if (-not $uid) {
Write-Error "取得 UID 失敗。使用者 '$username' 可能不存在。"
exit 1
}

$basePath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss"

$targetKey = Get-ChildItem $basePath | Where-Object {
(Get-ItemProperty $_.PSPath).DistributionName -eq "RockyLinux-8.10"
}
if (-not $targetKey) {
Write-Error "找不到 DistributionName 'RockyLinux-8.10'。"
exit 1
}

Set-ItemProperty -Path $targetKey.PSPath -Name "DefaultUid" -Value ([int]$uid)

啟用 EPEL

wsl -d RockyLinux-8.10 -u root dnf update -y `&`& dnf install -y epel-release

啟動

wsl -d RockyLinux-8.10

設為預設發行版

wsl --set-default RockyLinux-8.10

安裝 FastFetch

wsl -d RockyLinux-8.10 -u root dnf update -y `&`& dnf install fastfetch

執行 FastFetch

> wsl -d RockyLinux-8.10 -u root fastfetch
__wgliliiligw_, root@DESKTOP-MS-7C56-B550
_williiiiiiliilililw, -------------------------
_%iiiiiilililiiiiiiiiiii_ OS: Rocky Linux 8.10 x86_64
.Qliiiililiiiiiiililililiilm. Host: Windows Subsystem for Linux (2.0.14.0)
_iiiiiliiiiiililiiiiiiiiiiliil, Kernel: 5.15.133.1-microsoft-standard-WSL2
.lililiiilililiiiilililililiiiii, Uptime: 8 mins
_liiiiiiliiiiiiiliiiiiF{iiiiiilili, Packages: 285 (rpm)
jliililiiilililiiili@` ~ililiiiiiL Shell: bash 4.4.20
iiiliiiiliiiiiiili>` ~liililii Display 1: 1920x1080 @ 60Hz
liliiiliiilililii` -9liiiil Display 2: 1920x1080 @ 60Hz
iiiiiliiliiiiii~ "4lili WM: WSLg (Wayland)
4ililiiiiilil~| -w, )4lf Terminal: Windows Terminal
-liiiiililiF' _liig, )' CPU: AMD Ryzen 9 3900X (24) @ 3.800018 GHz
)iiiliii@` _QIililig, GPU: Microsoft Corporation Basic Render Driver
)iiii>` .Qliliiiililw Memory: 458.57 MiB / 62.76 GiB (0%)
)<>~ .mliiiiiliiiiiil, Disk (/): 51.72 GiB / 1007 GiB (5%)
_gllilililiililii~ Locale: C.UTF-8
giliiiiiiiiiiiiT`
-^~$ililili@~~' ████████████████████████
████████████████████████

在 RHEL 10.0 啟用 epel-release

· 1 分鐘閱讀
sudo dnf update -y
sudo subscription-manager repos --enable codeready-builder-for-rhel-10-x86_64-rpms
sudo dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm

EC2 Instance Connect 在 Windows 上無法在沒有金鑰時連線

· 1 分鐘閱讀

在 Windows 上無法連線到 Instance Connect

PS C:\> aws ec2-instance-connect ssh --instance-id i-0aa38de21acf2aa1c --region ap-south-1
Bad permissions. Try removing permissions for user: \\OWNER RIGHTS (S-1-3-4) on file C:/Users/hikari/AppData/Local/Temp/tmpm9m1bf7j/private-key.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions for 'C:\\Users\\hikari\\AppData\\Local\\Temp\\tmpm9m1bf7j\\private-key' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "C:\\Users\\hikari\\AppData\\Local\\Temp\\tmpm9m1bf7j\\private-key": bad permissions
[email protected]: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).

截至 2025/06/11 的驗證。

從 WSL 可以登入

PS C:\> wsl -- aws ec2-instance-connect ssh --instance-id i-0aa38de21acf2aa1c --region ap-south-1
, #_
~\_ ####_ Amazon Linux 2023
~~ \_#####\
~~ \###|
~~ \#/ ___ https://aws.amazon.com/linux/amazon-linux-2023
~~ V~' '->
~~~ /
~~._. _/
_/ _/
_/m/'
Last login: Tue Jun 10 22:50:33 2025 from 192.168.0.183
[ec2-user@ip-192-168-0-4 ~]$

為什麼?

補充

降級後就能連線。

希望能修正。

參考: https://github.com/aws/aws-cli/issues/9114

msiexec.exe /i https://awscli.amazonaws.com/AWSCLIV2-2.17.35.msi

EC2 Instance Connect 總結

· 3 分鐘閱讀

EC2 Instance Connect 是什麼

EC2 Instance Connect 是用來簡化對 AWS EC2 執行個體的 SSH 連線的服務。

過去的 SSH 連線方式需要事先在執行個體放置公開金鑰,但使用 EC2 Instance Connect 可以將臨時的 SSH 公開金鑰傳送到執行個體以建立連線。(不過,除部分 AMI 外,需要安裝 Instance Connect 的套件)

連線到執行個體的方法

連線到執行個體的方法有好幾種。

① 直接從網際網路連線(與 Instance Connect 無關)

直接從網際網路連線的方式需要經由 Internet Gateway 或 NAT Gateway,且需要公有 IP 位址,因此若限定在私有網路則無法使用。

可以直接使用 ssh 指令,因此是最簡單的方式。

ssh <使用者名稱>@<公有 IP 位址>

② 透過 EC2 Instance Connect 端點的連線

使用 AWS CLI 並透過 EC2 Instance Connect 端點連線時,不需要公有 IP 位址。

另外,還可以省下那部分的費用(每月數百日圓)。

使用 AWS CLI 可用類似下面的指令連線,但需事先匯入金鑰對並為執行個體設定金鑰對。

例如可以使用以下指令:

aws ec2-instance-connect ssh --private-key-file .ssh/id_ed25519 --os-user <使用者名稱> --instance-id <執行個體 ID> --connection-type eice

※ 不過,要事先取得存取金鑰並用 aws configure 設定好。

如果不想讓執行個體連到網際網路,且想使用非官方 AMI,採用此連線方式最合適。

③ 從 AWS 管理主控台用 Instance Connect 連線

若是 Amazon Linux 或 Ubuntu,只要建立 Instance Connect 端點,就可以從管理主控台連到執行個體。

不過,除部分 AMI 外,仍需安裝 Instance Connect 的套件。

詳情請見:https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/ec2-instance-connect-set-up.html

④ 其他連線方式

透過 Session Manager 連線

需為 Session Manager 建立兩個端點,並將允許透過 Session Manager 連線的 IAM 角色附加到執行個體。

此外,除部分 AMI 外,需安裝 Session Manager 的套件。

透過 EC2 序列埠主控台 (Serial Console) 連線

使用序列埠主控台可以直接連到執行個體。請注意,若未設定密碼就無法登入。

安全性設定

網路 ACL(執行個體所在子網)

預設會允許所有流量,因此若使用預設設定通常不需要特別調整。

以下列出最基本需要的設定。

入站規則

入站規則需允許 SSH(22)。

因為執行個體的 SSH 伺服器預設使用 22 埠,故需允許該埠。

出站規則

出站規則需允許自訂 TCP(1024-65535)。

1024-65535 是 SSH 連線時客戶端會使用的埠範圍。

安全性群組(執行個體)

入站規則

入站規則需允許 SSH(22)。

此設定為必要。

出站規則

安全性群組是有狀態(stateful)的,所以通常不需要設定出站規則。

安全性群組(EC2 Instance Connect 端點)

入站規則

因為為有狀態,通常不需要設定入站規則。

出站規則

需允許 SSH(22)。

因為要對執行個體的 22 埠進行通訊,所以必須允許此埠。

在 AWS 使用官方 Rocky Linux 映像

· 5 分鐘閱讀

AMI 的選擇方式

從官方頁面取得 AMI。

https://rockylinux.org/ja-JP/download

選擇要設定給實例的架構 (ARM (aarch64)),並選擇 Cloud Images 裡的 AWS AMI。

alt text

以版本號過濾,找到符合條件的映像。

alt text

AMI ID 無法直接複製,因此點擊 Deploy 按鈕,然後從 AWS 主控台複製。

用 AMI ID 搜尋會出現如下

alt text

用擁有者過濾會比較好。

擁有者 = 792107900819

alt text

事前準備

  • 註冊 Key pair
    • 事先執行 ssh-keygen -t ed25519 指令產生公鑰,將 .ssh/id_ed25519.pub 匯入成 Key pair
  • 安裝 AWS CLI
    • 安裝 CLI
    • 設定存取金鑰 (aws configure)

建立網路

比起 NAT Gateway,使用公開 IP 比較便宜,所以建立 Elastic IP。

架構圖大概長這樣。

建立 EC2 Instance Connect 端點

alt text

建立 EC2 Instance Connect 端點後,可以從 AWS CLI 登入。

建立實例

  • 為了接受 ping 要允許 ICMP(Echo Request)(安全性群組)
  • 允許 SSH 連線(安全性群組)
  • 在孟買區域 (Mumbai) 且 arm64 比較便宜
  • 每 vCPU 需要 1.5 GiB RAM(至少 t4g.medium)

因此我用以下條件建立。

  • 區域:孟買(ap-south-1)
  • 架構:arm64
  • AMI:RHEL 8.10 (LVM, aarch64); ami-0415efd8380284dc4
  • 實例類型:t4g.medium
  • Key pair:在 PC 上建立的公鑰 (.ssh/id_ed25519.pub)
  • 網路:公共子網(關聯了到 Internet Gateway 的路由表)
  • 安全性群組:建立安全性群組(名稱為預設)
    • ssh, 0.0.0.0/0
    • 自訂 ICMP - IPv4(Echo Request), 0.0.0.0/0
  • 儲存:1x 10GiB, gp3

連線

在 PC 上打開終端機,執行以下指令。

aws ec2-instance-connect ssh --private-key-file .ssh/id_ed25519 --os-user rocky --instance-id i-*****************

安裝 Instance Connect 套件

Rocky Linux 的 AMI 映像沒有包含 Instance Connect 套件,無法從管理控制台連線。因此需要安裝套件。

參考 https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/ec2-instance-connect-set-up.html 下載套件。

  • ※選擇 RHEL 的套件
  • ※注意作業系統主版本與架構不同可能無法正常運作

範例

curl https://amazon-ec2-instance-connect-us-west-2.s3.us-west-2.amazonaws.com/latest/linux_arm64/ec2-instance-connect.rhel8.rpm -o /tmp/ec2-instance-connect.rpm
curl https://amazon-ec2-instance-connect-us-west-2.s3.us-west-2.amazonaws.com/latest/linux_amd64/ec2-instance-connect-selinux.noarch.rpm -o /tmp/ec2-instance-connect-selinux.rpm
sudo dnf install -y /tmp/ec2-instance-connect.rpm /tmp/ec2-instance-connect-selinux.rpm

安裝完成後,就可以從 AWS 管理控制台存取。

alt text

CDK (typescript)

我做了 CDK 範例,放上來供參考。

請記得更改 keyName(Key pair)的名稱。

import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';

export interface RockyLinuxStackProps extends cdk.StackProps {
}

export class RockyLinuxStack extends cdk.Stack {
public constructor(scope: cdk.App, id: string, props: RockyLinuxStackProps = {}) {
super(scope, id, props);

// Resources
const ec2dhcpOptions = new ec2.CfnDHCPOptions(this, 'EC2DHCPOptions', {
domainName: 'ap-south-1.compute.internal',
domainNameServers: [
'AmazonProvidedDNS',
],
],
});
ec2dhcpOptions.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2InternetGateway = new ec2.CfnInternetGateway(this, 'EC2InternetGateway', {
{
value: 'igw',
key: 'Name',
},
],
});
ec2InternetGateway.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2vpc = new ec2.CfnVPC(this, 'EC2VPC', {
cidrBlock: '10.0.0.0/16',
enableDnsSupport: true,
instanceTenancy: 'default',
enableDnsHostnames: true,
{
value: 'vpc',
key: 'Name',
},
],
});
ec2vpc.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2VPCGatewayAttachment = new ec2.CfnVPCGatewayAttachment(this, 'EC2VPCGatewayAttachment', {
vpcId: ec2vpc.ref,
internetGatewayId: ec2InternetGateway.ref,
});
ec2VPCGatewayAttachment.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2NetworkAcl = new ec2.CfnNetworkAcl(this, 'EC2NetworkAcl', {
vpcId: ec2vpc.ref,
],
});
ec2NetworkAcl.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2RouteTable = new ec2.CfnRouteTable(this, 'EC2RouteTable', {
vpcId: ec2vpc.ref,
});
ec2RouteTable.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2SecurityGroup = new ec2.CfnSecurityGroup(this, 'EC2SecurityGroup', {
groupDescription: 'launch-wizard-1 created 2025-04-27T00:11:58.641Z',
groupName: 'launch-wizard-1',
vpcId: ec2vpc.ref,
securityGroupIngress: [
{
cidrIp: '0.0.0.0/0',
ipProtocol: 'tcp',
fromPort: 22,
toPort: 22,
},
{
cidrIp: '0.0.0.0/0',
ipProtocol: 'icmp',
fromPort: 8,
toPort: -1,
},
],
securityGroupEgress: [
{
cidrIp: '0.0.0.0/0',
ipProtocol: '-1',
fromPort: -1,
toPort: -1,
},
],
});
ec2SecurityGroup.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2Subnet = new ec2.CfnSubnet(this, 'EC2Subnet', {
vpcId: ec2vpc.ref,
mapPublicIpOnLaunch: false,
enableDns64: false,
availabilityZoneId: 'aps1-az1',
privateDnsNameOptionsOnLaunch: {
EnableResourceNameDnsARecord: false,
HostnameType: 'ip-name',
EnableResourceNameDnsAAAARecord: false,
},
cidrBlock: '10.0.0.0/20',
ipv6Native: false,
{
value: 'subnet-public1-ap-south-1a',
key: 'Name',
},
],
});
ec2Subnet.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2InstanceConnectEndpoint = new ec2.CfnInstanceConnectEndpoint(this, 'EC2InstanceConnectEndpoint', {
preserveClientIp: false,
securityGroupIds: [
ec2SecurityGroup.attrGroupId,
],
subnetId: ec2Subnet.attrSubnetId,
});
ec2InstanceConnectEndpoint.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2vpcdhcpOptionsAssociation = new ec2.CfnVPCDHCPOptionsAssociation(this, 'EC2VPCDHCPOptionsAssociation', {
vpcId: ec2vpc.ref,
dhcpOptionsId: ec2dhcpOptions.ref,
});
ec2vpcdhcpOptionsAssociation.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2RouteHg = new ec2.CfnRoute(this, 'EC2RouteHG', {
routeTableId: ec2RouteTable.ref,
destinationCidrBlock: '0.0.0.0/0',
gatewayId: ec2InternetGateway.ref,
});
ec2RouteHg.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2SubnetNetworkAclAssociation = new ec2.CfnSubnetNetworkAclAssociation(this, 'EC2SubnetNetworkAclAssociation', {
networkAclId: ec2NetworkAcl.ref,
subnetId: ec2Subnet.ref,
});
ec2SubnetNetworkAclAssociation.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2SubnetRouteTableAssociation = new ec2.CfnSubnetRouteTableAssociation(this, 'EC2SubnetRouteTableAssociation', {
routeTableId: ec2RouteTable.ref,
subnetId: ec2Subnet.ref,
});
ec2SubnetRouteTableAssociation.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2Instance = new ec2.CfnInstance(this, 'EC2Instance', {
tenancy: 'default',
instanceInitiatedShutdownBehavior: 'stop',
cpuOptions: {
threadsPerCore: 1,
coreCount: 2,
},
blockDeviceMappings: [
{
ebs: {
volumeType: 'gp3',
iops: 3000,
volumeSize: 10,
encrypted: false,
deleteOnTermination: true,
},
deviceName: '/dev/sda1',
},
],
availabilityZone: 'ap-south-1a',
privateDnsNameOptions: {
enableResourceNameDnsARecord: false,
hostnameType: 'ip-name',
enableResourceNameDnsAaaaRecord: false,
},
ebsOptimized: true,
disableApiTermination: false,
keyName: 'hikari',
sourceDestCheck: true,
placementGroupName: '',
networkInterfaces: [
{
privateIpAddresses: [
{
privateIpAddress: '10.0.3.59',
primary: true,
},
],
secondaryPrivateIpAddressCount: 0,
deviceIndex: '0',
groupSet: [
ec2SecurityGroup.ref,
],
ipv6Addresses: [
],
subnetId: ec2Subnet.ref,
associatePublicIpAddress: true,
deleteOnTermination: true,
},
],
imageId: 'ami-0415efd8380284dc4',
instanceType: 't4g.medium',
monitoring: false,
],
creditSpecification: {
cpuCredits: 'unlimited',
},
});
ec2Instance.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2ElasticIp = new ec2.CfnEIP(this, 'EC2ElasticIp', {
domain: 'vpc',
{
key: 'Name',
value: 'elastic-ip',
},
],
});
ec2ElasticIp.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2EipAssociation = new ec2.CfnEIPAssociation(this, 'EC2EipAssociation', {
eip: ec2ElasticIp.ref,
instanceId: ec2Instance.ref,
});
ec2EipAssociation.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;
}
}

初始化電腦環境時的免費軟體集錦

· 1 分鐘閱讀

※ 此處介紹的免費軟體一般可免費使用,與開源軟體有所區別。

通訊與聊天

影像編輯

音訊編輯

文字轉語音

3D 建模

直播串流

效能測試與系統資訊

開發工具

如何透過序列埠連線到 Hyper-V 上的 Ubuntu

· 2 分鐘閱讀

虛擬機器設定

選擇「具名管道」,並將管道名稱設定為「COM1」。

Hyper-V 序列埠設定

Ubuntu 設定

GRUB 設定

使用 sudo nano /etc/default/grub 開啟 GRUB 設定檔。

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash console=ttyS0,115200n8"

儲存後,執行以下指令套用 GRUB 設定:

sudo update-grub

啟用序列埠

設定服務以允許透過序列埠登入。

sudo systemctl enable [email protected]
sudo systemctl start [email protected]

連線

系統管理員身分啟動。

從 Tera Term 連線

從 Tera Term 連線到 Hyper-V 上 Ubuntu 的序列埠

從 PuTTY 連線

系統管理員身分啟動。

序列線路速率連線類型:
\.\pipe\COM1115200Serial

設定以上項目。

從 PuTTY 連線到 Hyper-V 上 Ubuntu 的序列埠

從 PuTTY 連線到 Hyper-V 上 Ubuntu 的序列埠

系統管理員身分啟動。

從 Windows Terminal 上的 plink.exe 連線到 Hyper-V 上 Ubuntu 的序列埠

[System.Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("utf-8")
[System.Console]::InputEncoding = [System.Text.Encoding]::GetEncoding("utf-8")
plink.exe -serial \\.\pipe\COM1 -sercfg 115200,8,n,1,N

按 Ctrl + C 結束。

觀察 VOICEVOX 的 API

· 3 分鐘閱讀

VOICEVOX 是由編輯器、引擎與核心組成的。

參考: 整體構成

編輯器是應用程式、引擎是 HTTP 伺服器、核心則是執行語音合成處理的模組。

也就是說,編輯器對引擎呼叫 REST API(以下簡稱 API)。

因此這篇文章要觀察這個 API 的內容。

API 的擷取我使用 Wireshark。

啟動時的通訊

我用 http and tcp.port == 50021 做了過濾,結果如下:

啟動時似乎會讀取以下資訊:

  • 版本資訊 /version
  • 引擎的 manifest 資訊 /engine_manifest
  • 聲優資訊 /speakers(像是 ずんだもん 等角色名單)
  • 歌手資訊 /singers(同上)

取得 speakers 和 singers 之後,會更詳細地取得各角色的資訊(/speaker_info?speaker_uuid=xxx, /singer_info?speaker_uuid=xxx)。

合成語音請求時的通訊

接著我用 ずんだもん 發送語音合成請求,觀察 API。

語音的取得流程似乎如下:

  1. 透過 /accent_phrases 取得重音/語調資訊
  2. 透過 /synthesis?speaker=3 合成 ずんだもん 的聲音

在 (2.) 發送的請求主體看起來和 (1.) 的回應類似,像下面這樣。

因此流程是先在 (1.) 取得重音資訊,接著在 (2.) 依據這些資訊合成語音。

實際呼叫 API 試試看

我使用 httpie 這個工具來呼叫 API。

  1. 取得 speakers 資訊

可以看到 ずんだもん(Normal)的 id 是 3。

  1. 取得重音資訊

我用「ずんだもんなのだ」取得重音資訊。(與取得 speaker 不同,這是用 POST)

  1. 合成語音

建立如下的請求主體:

{
"accent_phrases": <從 /accent_phrases 取得的資料>,
"speedScale": 1,
"pitchScale": 0,
"intonationScale": 1,
"volumeScale": 1,
"prePhonemeLength": 0.1,
"postPhonemeLength": 0.1,
"outputSamplingRate": 24000,
"outputStereo": false,
"kana": ""
}

因為 httpie 好像不能處理 wav,所以改用 PowerShell 送請求。

# 定義 URL 與 JSON 資料
$url = 'http://localhost:50021/synthesis?speaker=3'
$jsonBody = @"
{
"accent_phrases": [
{
"moras": [
{
"text": "ズ",
"consonant": "z",
"consonant_length": 0.12722788751125336,
"vowel": "u",
"vowel_length": 0.11318323761224747,
"pitch": 5.773037910461426
},
{
"text": "ン",
"consonant": null,
"consonant_length": null,
"vowel": "N",
"vowel_length": 0.09306197613477707,
"pitch": 6.108947277069092
},
{
"text": "ダ",
"consonant": "d",
"consonant_length": 0.04249810427427292,
"vowel": "a",
"vowel_length": 0.09372275322675705,
"pitch": 6.09743070602417
},
{
"text": "モ",
"consonant": "m",
"consonant_length": 0.07012023776769638,
"vowel": "o",
"vowel_length": 0.1172478124499321,
"pitch": 5.932623386383057
},
{
"text": "ン",
"consonant": null,
"consonant_length": null,
"vowel": "N",
"vowel_length": 0.06496299058198929,
"pitch": 5.745952129364014
},
{
"text": "ナ",
"consonant": "n",
"consonant_length": 0.038462959229946136,
"vowel": "a",
"vowel_length": 0.08576127141714096,
"pitch": 5.5794854164123535
}
],
"accent": 1,
"pause_mora": null,
"is_interrogative": false
},
{
"moras": [
{
"text": "ノ",
"consonant": "n",
"consonant_length": 0.05504273623228073,
"vowel": "o",
"vowel_length": 0.0903041884303093,
"pitch": 5.551316261291504
},
{
"text": "ダ",
"consonant": "d",
"consonant_length": 0.05024997144937515,
"vowel": "a",
"vowel_length": 0.20450790226459503,
"pitch": 5.633930206298828
}
],
"accent": 2,
"pause_mora": null,
"is_interrogative": false
}
],
"speedScale": 1,
"pitchScale": 0,
"intonationScale": 1,
"volumeScale": 1,
"prePhonemeLength": 0.1,
"postPhonemeLength": 0.1,
"outputSamplingRate": 24000,
"outputStereo": false,
"kana": ""
}
"@

# 建立 HTTP 標頭
$headers = @{
'Content-Type' = 'application/json'
}

# 發送 POST 請求並取得回應
$response = Invoke-WebRequest -Uri $url -Method Post -Headers $headers -Body $jsonBody -OutFile "output.wav"

# 開啟並播放
start output.wav

VOICEVOX:ずんだもん

以上!