Skip to content

Commit 7f0ae1c

Browse files
committed
fixup container server and improve descriptions
1 parent 7a68976 commit 7f0ae1c

File tree

9 files changed

+87
-59
lines changed

9 files changed

+87
-59
lines changed

apps/sandbox-container/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "containers-starter",
3-
"version": "0.0.0",
3+
"version": "0.0.1",
44
"private": true,
55
"type": "module",
66
"scripts": {

apps/sandbox-container/server/containerHelpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export const MAX_CONTAINERS = 20
1+
export const MAX_CONTAINERS = 50
22
export async function startAndWaitForPort(
33
environment: 'dev' | 'prod' | 'test',
44
container: Container | undefined,

apps/sandbox-container/server/containerManager.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import { DurableObject } from 'cloudflare:workers'
22

33
import type { Env } from './context'
4+
import { ContainerEvent } from './metrics'
5+
import { MetricsTracker } from '@repo/mcp-observability'
46

57
export class ContainerManager extends DurableObject<Env> {
8+
metrics = new MetricsTracker(this.env.MCP_METRICS, {
9+
name: this.env.MCP_SERVER_NAME,
10+
version: this.env.MCP_SERVER_VERSION
11+
})
12+
613
constructor(
714
public ctx: DurableObjectState,
815
public env: Env
@@ -27,7 +34,8 @@ export class ContainerManager extends DurableObject<Env> {
2734

2835
console.log(id, time, now, now.valueOf() - time.valueOf())
2936

30-
if (now.valueOf() - time.valueOf() > 10 * 60 * 1000) {
37+
// 15m timeout for container lifetime
38+
if (now.valueOf() - time.valueOf() > 15 * 60 * 1000) {
3139
const doId = this.env.USER_CONTAINER.idFromString(id)
3240
const stub = this.env.USER_CONTAINER.get(doId)
3341
await stub.destroyContainer()
@@ -42,6 +50,11 @@ export class ContainerManager extends DurableObject<Env> {
4250
for (const c of activeContainers.keys()) {
4351
activeIds.push(c)
4452
}
53+
54+
this.metrics.logEvent(new ContainerEvent({
55+
active: activeIds.length
56+
}))
57+
4558
return activeIds
4659
}
4760
}

apps/sandbox-container/server/containerMcp.ts

Lines changed: 19 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
44

55
import { ExecParams, FilePathParam, FileWrite } from '../shared/schema'
66
import { BASE_INSTRUCTIONS } from './prompts'
7-
import { fileToBase64, stripProtocolFromFilePath } from './utils'
7+
import { stripProtocolFromFilePath } from './utils'
88

99
import type { Env } from './context'
1010
import type { Props, UserContainer } from '.'
@@ -73,7 +73,9 @@ export class ContainerMcpAgent extends McpAgent<Env, {}, Props> {
7373
})
7474
this.server.tool(
7575
'container_exec',
76-
'Run a command in a container and return the results from stdout. If necessary, set a timeout. To debug, stream back standard error.',
76+
`Run a command in a container and return the results from stdout.
77+
If necessary, set a timeout. To debug, stream back standard error.
78+
If you\'re using python, ALWAYS use python3 alongside pip3`,
7779
{ args: ExecParams },
7880
async ({ args }) => {
7981
return {
@@ -106,56 +108,39 @@ export class ContainerMcpAgent extends McpAgent<Env, {}, Props> {
106108
)
107109
this.server.tool('container_files_list', 'List working directory file tree. This just reads the contents of the current working directory', {}, async ({}) => {
108110
// Begin workaround using container read rather than ls:
109-
const { blob, mimeType } = await this.userContainer.container_file_read('.')
111+
const readFile = await this.userContainer.container_file_read('.')
110112
return {
111113
content: [
112114
{
113115
type: 'resource',
114116
resource: {
115-
text: await blob.text(),
117+
text: readFile.type === "text" ? readFile.textOutput : readFile.base64Output,
116118
uri: `file://`,
117-
mimeType: mimeType,
119+
mimeType: readFile.mimeType,
118120
},
119121
},
120122
],
121123
}
122124
})
123125
this.server.tool(
124126
'container_file_read',
125-
'Read a specific file or directory',
127+
'Read a specific file or directory. Use this tool if you would like to read files or display them to the user. This allow you to get a displayable image for the user if there is an image file.',
126128
{ args: FilePathParam },
127129
async ({ args }) => {
128130
const path = await stripProtocolFromFilePath(args.path)
129-
const { blob, mimeType } = await this.userContainer.container_file_read(path)
131+
const readFile = await this.userContainer.container_file_read(path)
130132

131-
if (mimeType && mimeType.startsWith('text')) {
132-
return {
133-
content: [
134-
{
135-
type: 'resource',
136-
resource: {
137-
text: await blob.text(),
138-
uri: `file://${path}`,
139-
mimeType: mimeType,
140-
},
141-
},
142-
],
143-
}
144-
} else {
145-
return {
146-
content: [
147-
{
148-
type: 'resource',
149-
resource: {
150-
// for some reason the RPC type for Blob is not exactly the same as the regular Blob type
151-
// @ts-ignore
152-
blob: await fileToBase64(blob),
153-
uri: `file://${path}`,
154-
mimeType: mimeType,
155-
},
133+
return {
134+
content: [
135+
{
136+
type: 'resource',
137+
resource: {
138+
text: readFile.type === "text" ? readFile.textOutput : readFile.base64Output,
139+
uri: `file://${path}`,
140+
mimeType: readFile.mimeType,
156141
},
157-
],
158-
}
142+
},
143+
],
159144
}
160145
}
161146
)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { MetricsEvent, MetricsEventIndexIds } from "@repo/mcp-observability"
2+
3+
export class ContainerEvent extends MetricsEvent {
4+
constructor(
5+
private containers: {
6+
active?: number
7+
}
8+
) {
9+
super()
10+
}
11+
12+
toDataPoint(): AnalyticsEngineDataPoint {
13+
return {
14+
indexes: [MetricsEventIndexIds.CONTAINER_MANAGER],
15+
doubles: this.mapDoubles({
16+
double1: this.containers.active,
17+
}),
18+
}
19+
}
20+
}

apps/sandbox-container/server/prompts.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,18 @@ The Container MCP Agent provides access to a sandboxed container environment. Th
66
The container is an Ubuntu 20.04 base image with the following packages installed:
77
- curl
88
- git
9-
- htop
10-
- vim
11-
- wget
129
- net-tools
1310
- build-essential
14-
- nmap
15-
- sudo
16-
- ca-certificates
17-
- lsb-release
1811
- nodejs
1912
- npm
2013
- python3
2114
- python3-pip
2215
23-
You also have the following python packages installed:
24-
- matplotlib
25-
- pandas
26-
- numpy
16+
If necessary, you may install additional packages.
2717
2818
You are given a working directory in which you can create or delete files and execute commands as described below.
2919
30-
If you're using python, ALWAYS use python3 instead of python. ALWAYS make sure to install dependencies, as they won't be installed ahead of time.
20+
If you're using python, ALWAYS use \`python3\` instead of \`python\`. ALWAYS make sure to install dependencies, as they won't be installed ahead of time.
3121
3222
## Resources
3323
@@ -45,7 +35,7 @@ AVOID manually reading or writing files using the \`container_exec\` tool. You s
4535
4636
## Tools
4737
48-
To manage container lifecycle, use the \`container_start\` and \`container_kill\` tools. If you run into errors where you can't connect to the container, attempt to kill and restart the container. If that doesn't work, the system is probably overloaded.
38+
To manage container lifecycle, use the \`container_initialize\` tool. If you run into errors where you can't connect to the container, attempt to restart the container with the same \`container_initialize\` tool. If that doesn't work, the system is probably overloaded.
4939
5040
You can execute actions in the container using the \`container_exec\` tool. By default, stdout is returned back as a string.
5141
To write a file, use the \`container_file_write\` tool. To delete a file, use the \`container_file_delete\` tool.
@@ -54,4 +44,6 @@ The \`container_files_list\` allows you to list file resources. Content is omitt
5444
If you want to get the file contents of a file resource, use \`container_file_read\`, which will return the file contents.
5545
5646
If after calling a tool, you receive an error that a cloudchamber instance cannot be provided, just stop attempting to answer and request that the user attempt to try again later.
47+
48+
If you run into issues, do not attempt to retry after 3 tries unless the user prompts you to. Instead direct the user to report an issue at: https://github.com/cloudflare/mcp-server-cloudflare
5749
`

apps/sandbox-container/server/userContainer.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { getContainerManager } from './containerManager'
77

88
import type { FileList } from '../shared/schema'
99
import type { Env } from './context'
10+
import { fileToBase64 } from "./utils"
1011

1112
export class UserContainer extends DurableObject<Env> {
1213
constructor(
@@ -39,7 +40,8 @@ export class UserContainer extends DurableObject<Env> {
3940
// try to cleanup cleanup old containers
4041
const containerManager = getContainerManager(this.env)
4142

42-
if ((await containerManager.listActive()).length >= MAX_CONTAINERS) {
43+
// if more than half of our containers are being used, let's try reaping
44+
if ((await containerManager.listActive()).length >= (MAX_CONTAINERS / 2)) {
4345
await containerManager.tryKillOldContainers()
4446
if ((await containerManager.listActive()).length >= MAX_CONTAINERS) {
4547
throw new Error(
@@ -127,7 +129,7 @@ export class UserContainer extends DurableObject<Env> {
127129
}
128130
async container_file_read(
129131
filePath: string
130-
): Promise<{ blob: Blob; mimeType: string | undefined }> {
132+
): Promise<{ type: "text", textOutput: string; mimeType: string | undefined } | { type: "base64", base64Output: string; mimeType: string | undefined }> {
131133
const res = await proxyFetch(
132134
this.env.ENVIRONMENT,
133135
this.ctx.container,
@@ -137,9 +139,24 @@ export class UserContainer extends DurableObject<Env> {
137139
if (!res || !res.ok) {
138140
throw new Error(`Request to container failed: ${await res.text()}`)
139141
}
140-
return {
141-
blob: await res.blob(),
142-
mimeType: res.headers.get('Content-Type') ?? undefined,
142+
143+
const mimeType = res.headers.get('Content-Type') ?? undefined
144+
const blob = await res.blob()
145+
146+
console.log(mimeType)
147+
148+
if (mimeType && mimeType.startsWith('text')) {
149+
return {
150+
type: "text",
151+
textOutput: await blob.text(),
152+
mimeType
153+
}
154+
} else {
155+
return {
156+
type: "base64",
157+
base64Output: await fileToBase64(blob),
158+
mimeType
159+
}
143160
}
144161
}
145162

apps/sandbox-container/wrangler.jsonc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@
121121
},
122122
{
123123
"binding": "USER_BLOCKLIST",
124-
"id": "TODO"
124+
"id": "68e95343ded8448db179256c68f175b2"
125125
}
126126
],
127127
},
@@ -135,9 +135,9 @@
135135
// UPDATE WHEN DEPLOYING A NEW IMAGE
136136
"image": "registry.cloudchamber.cfdata.org/sandbox-container:d802004",
137137
"class_name": "UserContainer",
138-
"max_instances": 20,
138+
"max_instances": 50,
139139
"rollout_step_percentage": 100,
140-
"instances": 3,
140+
"instances": 10,
141141
}
142142
],
143143
"durable_objects": {
@@ -174,7 +174,7 @@
174174
},
175175
{
176176
"binding": "USER_BLOCKLIST",
177-
"id": "TODO"
177+
"id": "b874d6ae29ec43e5afad7be8da067676"
178178
}
179179
],
180180
},

packages/mcp-observability/src/analytics-engine.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ export enum MetricsEventIndexIds {
111111
AUTH_USER = 'auth_user',
112112
SESSION_START = 'session_start',
113113
TOOL_CALL = 'tool_call',
114+
CONTAINER_MANAGER = 'container_manager',
114115
}
115116

116117
/**

0 commit comments

Comments
 (0)