Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions packages/bash-mcp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,25 @@ function getSession(sessionId: string): Bash {
return sessions.get(sessionId)!;
}

const server = new McpServer({
name: '@capsule-run/bash-mcp',
version: '0.1.3',
});
const server = new McpServer(
{
name: '@capsule-run/bash-mcp',
version: '0.1.4',
},
{
instructions: `
You are operating inside a sandboxed Bash environment (WebAssembly).
To discover available commands, run: cat /workspace/manual.md
Sessions are isolated: each session_id has its own cwd, env vars, and filesystem state.
Use the same session_id across calls to maintain state within a workflow, or use an existing session_id to resume.
`,
},
);

server.registerTool(
'run',
{
description:
'Run a bash command inside the sandboxed Capsule environment. Returns stdout, stderr, exit code, filesystem diff, and current shell state (cwd + env).',
description: 'Execute a Bash command in the sandboxed environment',
inputSchema: {
command: z.string().describe('The bash command to execute.'),
session_id: z
Expand Down Expand Up @@ -68,7 +77,7 @@ server.registerTool(
'reset',
{
description:
'Reset the sandboxed filesystem and shell state (cwd, env vars) for a session back to their initial values.',
"Reset a session's filesystem and shell state (cwd, env vars) to their initial values. Useful to start fresh without creating a new session.",
inputSchema: {
session_id: z
.string()
Expand Down Expand Up @@ -96,7 +105,7 @@ server.registerTool(
server.registerTool(
'sessions',
{
description: 'List all active shell sessions.',
description: 'List all active session IDs and their current state.',
},
async () => {
return {
Expand Down
36 changes: 31 additions & 5 deletions packages/bash/src/core/filesystem.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { CommandManual } from '@capsule-run/bash-types';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { createRequire } from 'module';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const require = createRequire(import.meta.url);

export class Filesystem {
constructor(private readonly workspace: string) {}
Expand All @@ -15,17 +18,40 @@ export class Filesystem {
fs.mkdirSync(path.join(this.workspace, dir), { recursive: true });
}

const availableCommandsList = fs
.readdirSync(path.resolve(__dirname, '../commands'))
.join('\n')
.trim();
let commandManuals = '';
const commandsDir = path.resolve(__dirname, '../commands');

for (const command of fs.readdirSync(commandsDir)) {
const commandDir = path.join(commandsDir, command);

if (!fs.statSync(commandDir).isDirectory()) {
continue;
}

const commandPath = path.join(commandDir, `${command}.handler`);

const mod = require(commandPath);
const man = mod.manual as CommandManual;

commandManuals += `-

Command: ${man.name}
Usage: ${man.usage}
Description: ${man.description}${
man.options
? `\nOptions:\n${Object.entries(man.options)
.map(([key, value]) => `${key} : ${value}`)
.join('\n')}`
: ''
}\n\n`;
}

const files: Record<string, string> = {
'etc/resolv.conf': 'nameserver 8.8.8.8\nnameserver 1.1.1.1\n',
'etc/os-release': 'NAME="Capsule OS"\nVERSION="1.0"\nID=capsule\n',
'etc/passwd': 'root:x:0:0:root:/root:/bin/bash\n',
'proc/cpuinfo': 'processor\t: 0\nvendor_id\t: CapsuleVirtualCPU\n',
'workspace/README.md': `You are operating inside a sandboxed bash. Here the list of available commands:\n${availableCommandsList}`,
'workspace/manual.md': `# Capsule Bash Manual\n\n## Available commands:\n${commandManuals}`,
};

for (const [relativePath, content] of Object.entries(files)) {
Expand Down
Loading