
Yet another MCP client for Bedrock
Lo-fi Model Context Protocol client for Bedrock in Typescript
1
2
3
4
5
6
7
mkdir mcp-bedrock-converse
cd mcp-bedrock-converse
npm init -y
npm install @aws-sdk/client-bedrock-runtime @modelcontextprotocol/sdk @inquirer/prompts
npm i --save-dev @types/node
mkdir src
touch src/index.ts
package.json
1
2
3
4
5
6
7
{
"scripts": {
"build": "tsc && chmod 755 build/index.js",
"converse": "node build/index.js",
},
"type": "module",
}
tsconfig.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules"
]
}
src/index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import { BedrockRuntimeClient, ConverseCommand, ConverseCommandInput, Message, Tool, ToolInputSchema } from "@aws-sdk/client-bedrock-runtime";
import { input } from "@inquirer/prompts";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
class ConverseMcpClient {
private mcp: Client // from "@modelcontextprotocol/sdk/client/index.js"
private bedrock: BedrockRuntimeClient
private transport: StdioClientTransport | null = null; // from "@modelcontextprotocol/sdk/client/stdio.js"
private tools: Tool[] = []
constructor(modelId: string) {
this.bedrock = new BedrockRuntimeClient({ region: 'us-east-1' })
this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" })
}
async connectToMcpServer(serverScriptPath: string) {
try {
// Determine script type and appropriate command
const isJs = serverScriptPath.endsWith(".js");
const isPy = serverScriptPath.endsWith(".py");
if (!isJs && !isPy) {
throw new Error("Server script must be a .js or .py file");
}
const command = isPy
? process.platform === "win32"
? "python"
: "python3"
: process.execPath;
// Initialize transport and connect to server
this.transport = new StdioClientTransport({
command,
args: [serverScriptPath],
});
this.mcp.connect(this.transport);
// List available tools
const toolsResult = await this.mcp.listTools();
this.tools = toolsResult.tools.map((tool) => {
const toolInputSchema: ToolInputSchema = {
json: JSON.parse(JSON.stringify(tool.inputSchema))
}
const bedrockTool: Tool = {
toolSpec: {
inputSchema: toolInputSchema,
name: tool.name,
description: tool.description
}
}
return bedrockTool;
});
}
catch (e) {
console.log("Failed to connect to MCP server: ", e);
throw e;
}
}
async converse(conversation: Message[]) {
const input: ConverseCommandInput = {
modelId: modelId,
messages: conversation
}
if (this.tools.length > 0) {
input.toolConfig = {
tools: this.tools
}
}
const response = await this.bedrock.send(
new ConverseCommand(input),
);
if (response.stopReason === 'tool_use') {
if (response.output?.message?.content) {
const message = response.output.message
conversation.push(message)
const content = response.output?.message?.content
for (var contentBlock of content) {
if (contentBlock.toolUse?.name) {
const toolName = contentBlock.toolUse.name
const toolArguments = JSON.parse(JSON.stringify(contentBlock.toolUse.input))
const response = await client.mcp.callTool({
name: toolName,
arguments: toolArguments
})
const message: Message = {
role: "user",
content: [{
toolResult: {
toolUseId: contentBlock.toolUse.toolUseId,
content: [{
text: JSON.stringify(response)
}]
}
}]
}
conversation.push(message)
await this.converse(conversation)
}
}
}
}
else if (response.output?.message) {
const message = response.output.message
console.log(message.content?.[0].text);
conversation.push(message)
}
}
async questionPrompt(message: string, conversation: Message[]): Promise<boolean> {
const answer = await input({ message: message })
if (answer) {
const question: Message = {
role: "user",
content: [{ text: answer }],
}
conversation.push(question)
return true
}
else {
return false;
}
}
async chat() {
const conversation: Message[] = []
try {
if (await this.questionPrompt('Whats up?', conversation)) {
while (true) {
await client.converse(conversation)
if (!await this.questionPrompt('', conversation)) { break }
}
}
} catch (error) {
if (error instanceof Error && error.name === 'ExitPromptError') {
// noop; silence this error
} else {
console.error(error)
throw error;
}
}
}
}
const modelId = 'anthropic.claude-3-haiku-20240307-v1:0'
const client = new ConverseMcpClient(modelId)
//await client.connectToMcpServer('/path/to/mcp/index.js')
await client.chat()
1
2
npm run build
npm run converse
await client.connectToMcpServer('/path/to/mcp/index.js')
where indicated in the code.1
2
3
4
5
6
private mcp: Client // from "@modelcontextprotocol/sdk/client/index.js"
private bedrock: BedrockRuntimeClient
constructor(modelId: string) {
this.bedrock = new BedrockRuntimeClient({ region: 'us-east-1' })
this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
private transport: StdioClientTransport | null = null; // from "@modelcontextprotocol/sdk/client/stdio.js"
private tools: Tool[] = []
async connectToMcpServer(serverScriptPath: string) {
try {
// Determine script type and appropriate command
const isJs = serverScriptPath.endsWith(".js");
const isPy = serverScriptPath.endsWith(".py");
if (!isJs && !isPy) {
throw new Error("Server script must be a .js or .py file");
}
const command = isPy
? process.platform === "win32"
? "python"
: "python3"
: process.execPath;
// Initialize transport and connect to server
this.transport = new StdioClientTransport({
command,
args: [serverScriptPath],
});
this.mcp.connect(this.transport);
// List available tools
const toolsResult = await this.mcp.listTools();
this.tools = toolsResult.tools.map((tool) => {
const toolInputSchema: ToolInputSchema = {
json: JSON.parse(JSON.stringify(tool.inputSchema))
}
const bedrockTool: Tool = {
toolSpec: {
inputSchema: toolInputSchema,
name: tool.name,
description: tool.description
}
}
return bedrockTool;
});
}
catch (e) {
console.log("Failed to connect to MCP server: ", e);
throw e;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
async converse(conversation: Message[]) {
const input: ConverseCommandInput = {
modelId: modelId,
messages: conversation
}
if (this.tools.length > 0) {
input.toolConfig = {
tools: this.tools
}
}
const response = await this.bedrock.send(
new ConverseCommand(input),
);
if (response.stopReason === 'tool_use') {
if (response.output?.message?.content) {
const message = response.output.message
conversation.push(message)
const content = response.output?.message?.content
for (var contentBlock of content) {
if (contentBlock.toolUse?.name) {
const toolName = contentBlock.toolUse.name
const toolArguments = JSON.parse(JSON.stringify(contentBlock.toolUse.input))
const response = await client.mcp.callTool({
name: toolName,
arguments: toolArguments
})
const message: Message = {
role: "user",
content: [{
toolResult: {
toolUseId: contentBlock.toolUse.toolUseId,
content: [{
text: JSON.stringify(response)
}]
}
}]
}
conversation.push(message)
await this.converse(conversation)
}
}
}
}
else if (response.output?.message) {
const message = response.output.message
console.log(message.content?.[0].text);
conversation.push(message)
}
}
ConverseCommand
along with the tools (MCP servers) we have available to use (if any).stopReason
of tool_use
. This is documented here. We then have to call the correct tool using client.mcp.callTool
and return the response to Bedrock along with the toolUseId
which was given by Bedrock in the tool_use request.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
async questionPrompt(message: string, conversation: Message[]): Promise<boolean> {
const answer = await input({ message: message })
if (answer) {
const question: Message = {
role: "user",
content: [{ text: answer }],
}
conversation.push(question)
return true
}
else {
return false;
}
}
async chat() {
const conversation: Message[] = []
try {
if (await this.questionPrompt('Whats up?', conversation)) {
while (true) {
await client.converse(conversation)
if (!await this.questionPrompt('', conversation)) { break }
}
}
} catch (error) {
if (error instanceof Error && error.name === 'ExitPromptError') {
// noop; silence this error
} else {
console.error(error)
throw error;
}
}
}
Any opinions in this post are those of the individual author and may not reflect the opinions of AWS.