
Go実装のリモートMCPサーバーをAgentCore Runtimeにホスティングする
はじめに
Amazon Bedrock AgentCore Runtimeで Model Context Protocol (MCP) サーバーをデプロイできます。AWS公式からPythonのFastMCPを使った例が紹介されています。
Goで試した経緯として、以下の記事でAWS Marketplace経由でAgentCore RuntimeにTerraform MCP Serverがホスティングできることを確認し、Go(シングルバイナリ)でもホスティング出来るんだなーと思い試してみました。
基本terraform-mcp-serverの関連がありそうなmcp-goの設定を確認し、適用した形です。
以下のリソースを作成します。
- ECR
- Cognito
- IAM
- Bedrock AgentCore
ホスティングする
ソースコードは以下の通りです。
- 8000ポートでListen
- StreamableHTTPServerでステートレスオプションを設定
package main
import (
"context"
"flag"
"fmt"
"log"
"net/http"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
var port = flag.String("port", "8000", "port to listen on")
func corsMiddleware(port string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", fmt.Sprintf("http://127.0.0.1:%s", port))
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization")
w.Header().Set("Access-Control-Allow-Credentials", "true")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
}
func handleAddTool(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
args := request.GetArguments()
a, aOk := args["a"].(float64)
b, bOk := args["b"].(float64)
if !aOk || !bOk {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: "Error: Invalid arguments. Both 'a' and 'b' must be numbers.",
},
},
}, nil
}
result := int(a) + int(b)
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("The sum of %d and %d is %d", int(a), int(b), result),
},
},
}, nil
}
func main() {
flag.Parse()
mcpServer := server.NewMCPServer(
"go-bin-mcp-server",
"v1.0.0",
server.WithToolCapabilities(true),
server.WithLogging(),
)
mcpServer.AddTool(mcp.NewTool("add",
mcp.WithDescription("Add two numbers"),
mcp.WithNumber("a",
mcp.Required(),
mcp.Description("First number to add"),
),
mcp.WithNumber("b",
mcp.Required(),
mcp.Description("Second number to add"),
),
), handleAddTool)
streamableServer := server.NewStreamableHTTPServer(mcpServer, server.WithStateLess(true))
mux := http.NewServeMux()
mux.Handle("/mcp", streamableServer)
log.Printf("Server listening at http://localhost:%s", *port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", *port), corsMiddleware(*port)(mux)))
}
依存関係は以下の通りです。
module github.com/shuntaka9576/agentcore-mcp-sample-go
go 1.25.0
require github.com/mark3labs/mcp-go v0.37.0
require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Dockerfileを書きます。ランタイム特有の特別なことはありません。
FROM golang:1.25-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY main.go ./
RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -o mcp-server main.go
FROM gcr.io/distroless/base-debian11
WORKDIR /app
COPY /app/mcp-server .
ENTRYPOINT ["./mcp-server"]
ECRを作成します
$ export AWS_REGION=us-west-2
# TODO: AWS資格情報を取得
$ export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
$ export APP_NAME="go-bin-mcp-server"
$ aws ecr create-repository --repository-name $APP_NAME --region $AWS_REGION
{
"repository": {
"repositoryArn": "arn:aws:ecr:us-west-2:<AWSアカウントID>:repository/go-bin-mcp-server",
"registryId": "<AWSアカウントID>",
"repositoryName": "go-bin-mcp-server",
"repositoryUri": "<AWSアカウントID>.dkr.ecr.us-west-2.amazonaws.com/go-bin-mcp-server",
"createdAt": "2025-08-18T12:52:05.171000+09:00",
"imageTagMutability": "MUTABLE",
"imageScanningConfiguration": {
"scanOnPush": false
},
"encryptionConfiguration": {
"encryptionType": "AES256"
}
}
}
ビルドしてpushします
$ aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin "$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com"
$ docker buildx build --platform linux/arm64 -t "$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/${APP_NAME}:latest" --push .
# pushされたことの確認
$ aws ecr describe-images --repository-name $APP_NAME --region $AWS_REGION
IAMを作成します。
長いので折りたたみ
cat > bedrock-agentcore-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ECRImageAccess",
"Effect": "Allow",
"Action": [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
],
"Resource": [
"arn:aws:ecr:${AWS_REGION}:${AWS_ACCOUNT_ID}:repository/*"
]
},
{
"Effect": "Allow",
"Action": [
"logs:DescribeLogStreams",
"logs:CreateLogGroup"
],
"Resource": [
"arn:aws:logs:${AWS_REGION}:${AWS_ACCOUNT_ID}:log-group:/aws/bedrock-agentcore/runtimes/*"
]
},
{
"Effect": "Allow",
"Action": [
"logs:DescribeLogGroups"
],
"Resource": [
"arn:aws:logs:${AWS_REGION}:${AWS_ACCOUNT_ID}:log-group:*"
]
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:logs:${AWS_REGION}:${AWS_ACCOUNT_ID}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*"
]
},
{
"Sid": "ECRTokenAccess",
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"xray:PutTraceSegments",
"xray:PutTelemetryRecords",
"xray:GetSamplingRules",
"xray:GetSamplingTargets"
],
"Resource": [
"*"
]
},
{
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {
"cloudwatch:namespace": "bedrock-agentcore"
}
}
},
{
"Sid": "GetAgentAccessToken",
"Effect": "Allow",
"Action": [
"bedrock-agentcore:GetWorkloadAccessToken",
"bedrock-agentcore:GetWorkloadAccessTokenForJWT",
"bedrock-agentcore:GetWorkloadAccessTokenForUserId"
],
"Resource": [
"arn:aws:bedrock-agentcore:${AWS_REGION}:${AWS_ACCOUNT_ID}:workload-identity-directory/default",
"arn:aws:bedrock-agentcore:${AWS_REGION}:${AWS_ACCOUNT_ID}:workload-identity-directory/default/workload-identity/*"
]
},
{
"Sid": "BedrockModelInvocation",
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream",
"bedrock:ApplyGuardrail"
],
"Resource": [
"arn:aws:bedrock:*::foundation-model/*",
"arn:aws:bedrock:${AWS_REGION}:${AWS_ACCOUNT_ID}:*"
]
}
]
}
EOF
aws iam create-policy \
--policy-name ECRBedrockAgentCorePolicy \
--policy-document file://bedrock-agentcore-policy.json
cat > trust-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AssumeRolePolicy",
"Effect": "Allow",
"Principal": {
"Service": "bedrock-agentcore.amazonaws.com"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "${AWS_ACCOUNT_ID}"
},
"ArnLike": {
"aws:SourceArn": "arn:aws:bedrock-agentcore:${AWS_REGION}:${AWS_ACCOUNT_ID}:*"
}
}
}
]
}
EOF
aws iam create-role \
--role-name ECRBedrockAgentCoreRole \
--assume-role-policy-document file://trust-policy.json \
--description "Role for Bedrock Agent Core to access ECR"
aws iam attach-role-policy \
--role-name ECRBedrockAgentCoreRole \
--policy-arn "arn:aws:iam::${AWS_ACCOUNT_ID}:policy/ECRBedrockAgentCorePolicy"
congitoを作成します。
export POOL_ID=$(aws cognito-idp create-user-pool \
--pool-name "MyUserPool" \
--policies '{"PasswordPolicy":{"MinimumLength":8}}' \
--region us-west-2 | jq -r '.UserPool.Id')
export CLIENT_ID=$(aws cognito-idp create-user-pool-client \
--user-pool-id $POOL_ID \
--client-name "MyClient" \
--no-generate-secret \
--explicit-auth-flows "ALLOW_USER_PASSWORD_AUTH" "ALLOW_REFRESH_TOKEN_AUTH" \
--region us-west-2 | jq -r '.UserPoolClient.ClientId')
aws cognito-idp admin-create-user \
--user-pool-id $POOL_ID \
--username "testuser" \
--temporary-password "TEMP_PASSWORD" \
--region us-west-2 \
--message-action SUPPRESS > /dev/null
aws cognito-idp admin-set-user-password \
--user-pool-id $POOL_ID \
--username "testuser" \
--password "PERMANENT_PASSWORD" \
--region us-west-2 \
--permanent > /dev/null
Bedrock Agent coreを作成します。
CONTAINER_URI="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${APP_NAME}:latest"
DISCOVERY_URL="https://cognito-idp.${AWS_REGION}.amazonaws.com/${POOL_ID}/.well-known/openid-configuration"
aws bedrock-agentcore-control create-agent-runtime \
--region $AWS_REGION \
--agent-runtime-name "go_bin_mcp_server" \
--description "go_bin_mcp_server" \
--agent-runtime-artifact "{
\"containerConfiguration\": {
\"containerUri\": \"${CONTAINER_URI}\"
}
}" \
--role-arn "arn:aws:iam::${AWS_ACCOUNT_ID}:role/ECRBedrockAgentCoreRole" \
--network-configuration '{
"networkMode": "PUBLIC"
}' \
--protocol-configuration '{
"serverProtocol": "MCP"
}' \
--authorizer-configuration "{
\"customJWTAuthorizer\": {
\"discoveryUrl\": \"${DISCOVERY_URL}\",
\"allowedClients\": [\"${CLIENT_ID}\"]
}
}"
以下のような出力になるので、agentRuntimeArnをメモります。
{
"agentRuntimeArn": "arn:aws:bedrock-agentcore:us-west-2:<AWSアカウントID>:runtime/go_bin_mcp_server-lTJSgE5ZBg",
"workloadIdentityDetails": {
"workloadIdentityArn": "arn:aws:bedrock-agentcore:us-west-2:<AWSアカウントID>:workload-identity-directory/default/workload-identity/go_bin_mcp_server-lTJSgE5ZBg"
},
"agentRuntimeId": "go_bin_mcp_server-lTJSgE5ZBg",
"agentRuntimeVersion": "1",
"createdAt": "2025-08-18T04:25:53.080291+00:00",
"status": "CREATING"
}
マネコンからデプロイされていることを確認できます。
export AGENT_ARN="arn:aws:bedrock-agentcore:us-west-2:<AWSアカウントID>:workload-identity-directory/default/workload-identity/go_bin_mcp_server-lTJSgE5ZBg"
export BEARER_TOKEN=$(aws cognito-idp initiate-auth \
--client-id "$CLIENT_ID" \
--auth-flow USER_PASSWORD_AUTH \
--auth-parameters USERNAME='testuser',PASSWORD='PERMANENT_PASSWORD' \
--region us-west-2 | jq -r '.AuthenticationResult.AccessToken')
# 確認
echo $BEARER_TOKEN
export ENCODED_ARN=$(echo "$AGENT_ARN" | jq -rR '@uri')
export MCP_URL="https://bedrock-agentcore.us-west-2.amazonaws.com/runtimes/${ENCODED_ARN}/invocations?qualifier=DEFAULT"
$ curl -X POST "$MCP_URL" \
-H "authorization: Bearer $BEARER_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc": "2.0", "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {"tools": {}}}, "id": 1}'
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"logging":{},"tools":{"listChanged":true}},"serverInfo":{"name":"go-bin-mcp-server","version":"v1.0.0"}}}
$ curl -X POST "$MCP_URL" \
-H "authorization: Bearer $BEARER_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc": "2.0", "method": "tools/list", "params": {}, "id": 2}'
{"jsonrpc":"2.0","id":2,"result":{"tools":[{"annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":true},"description":"Add two numbers","inputSchema":{"properties":{"a":{"description":"First number to add","type":"number"},"b":{"description":"Second number to add","type":"number"}},"required":["a","b"],"type":"object"},"name":"add"}]}}
Claude Codeに設定してみます。
jq -n \
--arg token "$BEARER_TOKEN" \
--arg arn "$AGENT_ARN" \
'{
go_bin_mcp_server: {
headers: {
Authorization: "Bearer \($token)"
},
type: "http",
url: "https://bedrock-agentcore.us-west-2.amazonaws.com/runtimes/\($arn | @uri)/invocations?qualifier=DEFAULT"
}
}'
{
"go_bin_mcp_server": {
"headers": {
"Authorization": "Bearer eyJ..."
},
"type": "http",
"url": "https://bedrock-agentcore.us-west-2.amazonaws.com/runtimes/arn%3Aaws%3Abedrock-agentcore%3Aus-west-2%3A<AWSアカウントID>%3Aruntime%2Fgo_bin_mcp_server-lTJSgE5ZBg/invocations?qualifier=DEFAULT"
}
}
出力されたMCP設定を導入します。
{
mcpServers: {
go_bin_mcp_server: {
headers: {
Authorization: 'Bearer eyJ...',
},
type: 'http',
url: 'https://bedrock-agentcore.us-west-2.amazonaws.com/runtimes/arn%3Aaws%3Abedrock-agentcore%3Aus-west-2%3A<AWSアカウントID>%3Aruntime%2Fgo_bin_mcp_server-lTJSgE5ZBg/invocations?qualifier=DEFAULT',
},
(中略)
ツールが確認できました。
$ claude
╭───────────────────────────────────────────────────────────────────────────────╮
│ ✻ Welcome to Claude Code! │
│ │
│ /help for help, /status for your current setup │
│ │
│ cwd: /Users/shuntaka/repos/github.com/shuntaka9576/agentcore-mcp-sample-go │
╰───────────────────────────────────────────────────────────────────────────────╯
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Tools for go_bin_mcp_server (1 tools) │
│ │
│ ❯ 1. add destructive, open-world │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Esc to go back
呼び出しも確認できました。
$ claude
╭───────────────────────────────────────────────────────────────────────────────╮
│ ✻ Welcome to Claude Code! │
│ │
│ /help for help, /status for your current setup │
│ │
│ cwd: /Users/shuntaka/repos/github.com/shuntaka9576/agentcore-mcp-sample-go │
╰───────────────────────────────────────────────────────────────────────────────╯
> mcpを使って3+28をしてください
⏺ MCPを使って計算をします。
go_bin_mcp_server - add (MCP)(a: 3, b: 28)
⎿ The sum of 3 and 28 is 31
⏺ 31
さいごに
最初はgo-sdkで試していたのですが、StreambleHTTPのセッションなし設定がよくわからずterraform-mcp-serverの実装を確認してこちらの実装に落ち着きました。シングルバイナリで動作しているので他の言語(nodejs, deno, Rust...)でも可能そうですね!サーバーレスかつAWS上でサクッとリモートMCPをたてられるのは便利でいいですね!認証部分(Authorizationヘッダーをつける形にはなりますが...)をマネージドで処理してくれる点も良さそうです!