fix: fix MCP issue and finalize codex support

This commit is contained in:
Clelia (Astra) Bertelli
2025-12-19 13:47:16 +01:00
parent 9f1e389aa2
commit 96ff108672
6 changed files with 122 additions and 110 deletions
-11
View File
@@ -1,11 +0,0 @@
You are an expert programmer whose task is to assist the user implement their requests within the current working directory.
In order to perform file system operations, you MUST NOT USE the built-in tools you have (Read, Write, Glob, Edit): instead, you MUST USE the `filesystem` MCP server, which provides the following tools:
- `read_file`: read a file, providing its path
- `write_file`: write a file, providing its path and content
- `edit_file`: edit a file, providing the old string and the new string to replace the old one with
- `list_files`: list all the available files
- `file_exists`: check whether or not a file exists, providing its path
Using these tools, you should be able to provide the user with the assistance that they need.
+17 -3
View File
@@ -1,6 +1,6 @@
# Claude + AgentFS + LlamaIndex Workflows
# Coding Agent + AgentFS + LlamaIndex Workflows
A demo where we run Claude Code within a fully-virtualized file system ([AgentFS](https://github.com/tursodatabase/agentfs)), orchestrating it with [LlamaIndex Workflows](https://github.com/run-llama/workflows-ts) and adding the possibility of reading unstructured files (e.g. PDFs or Word/Google docs) with [LlamaCloud](https://cloud.llamaindex.ai).
A demo where we run Claude Code or Codex within a fully-virtualized file system ([AgentFS](https://github.com/tursodatabase/agentfs)), orchestrating it with [LlamaIndex Workflows](https://github.com/run-llama/workflows-ts) and adding the possibility of reading unstructured files (e.g. PDFs or Word/Google docs) with [LlamaCloud](https://cloud.llamaindex.ai).
## Set Up and Run
@@ -18,13 +18,27 @@ pnpm install
# you can use other package managers, but pnpm is preferred
```
If you wish to use the demo with Codex, you need to install the Codex SDK separately (given the size of the library - 140+ MB - its download it disabled by default):
```bash
pnpm add @openai/codex-sdk
```
Moreover, if you wish to run the demo with Codex, you also need to start the MCP server (from a different terminal window, but within the same directory):
```bash
pnpm run mcp-start
```
The MCP will be live on `http://localhost:3000/mcp`, and you will need to add the MCP configuration in [config.toml](./codex/config.toml) to the global Codex configuration in `$HOME/.codex/config.toml`. If you want Codex to use the filesystem MCP by default, you will also need to copy the [AGENTS.md](./codex/AGENTS.md) file, containing the instructions on how to use the server.
Now run the demo with:
```bash
# for the first time
pnpm run start
# for follow-ups
# If you want to add more files to the database
pnpm run clean-start
```
-9
View File
@@ -1,12 +1,3 @@
model = "gpt-4.1"
model_provider = "openai-chat-completions"
[model_providers.openai-chat-completions]
name = "OpenAI using Chat Completions"
base_url = "https://api.openai.com/v1"
env_key = "OPENAI_API_KEY"
wire_api = "responses"
[features]
rmcp_client = true
+6 -6
View File
@@ -67,7 +67,7 @@ async function handleItemStart(event: ThreadEvent) {
),
),
);
if (typeof event.item.error != "undefined") {
if (event.item.error) {
console.log(
red(
bold(
@@ -76,7 +76,7 @@ async function handleItemStart(event: ThreadEvent) {
),
);
} else {
if (typeof event.item.result != "undefined") {
if (event.item.result) {
let finalResult = "";
for (const block of event.item.result.content) {
if (block.type == "text") {
@@ -136,7 +136,7 @@ async function handleItemUpdated(event: ThreadEvent) {
),
),
);
if (typeof event.item.error != "undefined") {
if (event.item.error) {
console.log(
red(
bold(
@@ -145,7 +145,7 @@ async function handleItemUpdated(event: ThreadEvent) {
),
);
} else {
if (typeof event.item.result != "undefined") {
if (event.item.result) {
let finalResult = "";
for (const block of event.item.result.content) {
if (block.type == "text") {
@@ -201,7 +201,7 @@ async function handleItemCompleted(event: ThreadEvent) {
),
),
);
if (typeof event.item.error != "undefined") {
if (event.item.error) {
console.log(
red(
bold(
@@ -210,7 +210,7 @@ async function handleItemCompleted(event: ThreadEvent) {
),
);
} else {
if (typeof event.item.result != "undefined") {
if (event.item.result) {
let finalResult = "";
for (const block of event.item.result.content) {
if (block.type == "text") {
+19 -3
View File
@@ -30,6 +30,10 @@ async function main() {
workflow.handle([startEvent], async (_context, event) => {
await renderLogo();
if (notFromScratch) {
fs.copyFileSync("fs.db", "fsMcp.db");
if (fs.existsSync("fs.db-wal")) {
fs.copyFileSync("fs.db-wal", "fsMcp.db-wal");
}
return filesRegisteredEvent.with();
}
const wd = event.data.workingDirectory;
@@ -45,6 +49,10 @@ async function main() {
"Could not register the files within the AgentFS file system: check writing permissions in the current directory",
});
} else {
fs.copyFileSync("fs.db", "fsMcp.db");
if (fs.existsSync("fs.db-wal")) {
fs.copyFileSync("fs.db-wal", "fsMcp.db-wal");
}
return filesRegisteredEvent.with();
}
});
@@ -53,7 +61,9 @@ async function main() {
workflow.handle([filesRegisteredEvent], async (_context, _event) => {
console.log(
bold(
green("All the files have been uploaded to the AgentFS filesystem, what would you like to do now?"),
green(
"All the files have been uploaded to the AgentFS filesystem, what would you like to do now?",
),
),
);
return requestPromptEvent.with();
@@ -75,7 +85,9 @@ async function main() {
} else {
try {
await runCodex(event.data.prompt, { resumeSession: event.data.resume });
return stopEvent.with({ success: true, error: null });
} catch (error) {
console.error(error);
return stopEvent.with({ success: false, error: JSON.stringify(error) });
}
}
@@ -118,11 +130,15 @@ async function main() {
plan: planMode,
}),
);
const finalEvent = (await resumedContext.stream.until(stopEvent).toArray()).at(-1);
const finalEvent = (
await resumedContext.stream.until(stopEvent).toArray()
).at(-1);
if (typeof finalEvent != "undefined") {
if ("error" in finalEvent.data) {
if (typeof finalEvent.data.error == "string") {
console.log(`${bold(red("An error occurred during the workflow execution:"))} ${finalEvent.data.error}`)
console.log(
`${bold(red("An error occurred during the workflow execution:"))} ${finalEvent.data.error}`,
);
}
}
}
+80 -78
View File
@@ -1,8 +1,11 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { Request, Response } from 'express';
import { Request, Response } from "express";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { type CallToolResult, isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import { randomUUID } from 'node:crypto';
import {
type CallToolResult,
isInitializeRequest,
} from "@modelcontextprotocol/sdk/types.js";
import { randomUUID } from "node:crypto";
import {
readSchemaShape,
fileExistsSchemaShape,
@@ -33,7 +36,7 @@ const getServer = () => {
inputSchema: readSchemaShape,
},
async ({ filePath }) => {
const agentfs = await getAgentFS({});
const agentfs = await getAgentFS({ filePath: "fsMcp.db" });
const content = await readFile(filePath, agentfs);
if (typeof content == "string") {
return { content: [{ type: "text", text: content }] };
@@ -58,7 +61,7 @@ const getServer = () => {
inputSchema: fileExistsSchemaShape,
},
async ({ filePath }) => {
const agentfs = await getAgentFS({});
const agentfs = await getAgentFS({ filePath: "fsMcp.db" });
const exists = await fileExists(filePath, agentfs);
if (exists) {
return {
@@ -79,7 +82,7 @@ const getServer = () => {
inputSchema: writeSchemaShape,
},
async ({ filePath, fileContent }) => {
const agentfs = await getAgentFS({});
const agentfs = await getAgentFS({ filePath: "fsMcp.db" });
const success = await writeFile(filePath, fileContent, agentfs);
if (success) {
return {
@@ -111,7 +114,7 @@ const getServer = () => {
inputSchema: editSchemaShape,
},
async ({ filePath, oldString, newString }) => {
const agentfs = await getAgentFS({});
const agentfs = await getAgentFS({ filePath: "fsMcp.db" });
const editedContent = await editFile(
filePath,
oldString,
@@ -148,9 +151,8 @@ const getServer = () => {
inputSchema: listFilesSchemaShape,
},
async () => {
const agentfs = await getAgentFS({});
const agentfs = await getAgentFS({ filePath: "fsMcp.db" });
const files = await listFiles(agentfs);
console.log("Sending result: ", files)
if (files != "") {
return { content: [{ type: "text", text: files }] };
} else {
@@ -166,90 +168,90 @@ const getServer = () => {
}
},
);
return mcpServer
}
return mcpServer;
};
const app = createMcpExpressApp();
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
app.post('/mcp', async (req: Request, res: Response) => {
console.log('Received MCP request:', req.body);
try {
// Check for existing session ID
const sessionId = req.headers['mcp-session-id'] as string | undefined;
let transport: StreamableHTTPServerTransport;
app.post("/mcp", async (req: Request, res: Response) => {
console.log("Received MCP request:", req.body);
try {
// Check for existing session ID
const sessionId = req.headers["mcp-session-id"] as string | undefined;
let transport: StreamableHTTPServerTransport;
if (sessionId && transports[sessionId]) {
// Reuse existing transport
transport = transports[sessionId];
} else if (!sessionId && isInitializeRequest(req.body)) {
// New initialization request - use JSON response mode
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
enableJsonResponse: true, // Enable JSON response mode
onsessioninitialized: sessionId => {
// Store the transport by session ID when session is initialized
// This avoids race conditions where requests might come in before the session is stored
console.log(`Session initialized with ID: ${sessionId}`);
transports[sessionId] = transport;
}
});
if (sessionId && transports[sessionId]) {
// Reuse existing transport
transport = transports[sessionId];
} else if (!sessionId && isInitializeRequest(req.body)) {
// New initialization request - use JSON response mode
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
enableJsonResponse: true, // Enable JSON response mode
onsessioninitialized: (sessionId) => {
// Store the transport by session ID when session is initialized
// This avoids race conditions where requests might come in before the session is stored
console.log(`Session initialized with ID: ${sessionId}`);
transports[sessionId] = transport;
},
});
// Connect the transport to the MCP server BEFORE handling the request
const server = getServer();
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
return; // Already handled
} else {
// Invalid request - no session ID or not initialization request
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: No valid session ID provided'
},
id: null
});
return;
}
// Handle the request with existing transport - no need to reconnect
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error('Error handling MCP request:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error'
},
id: null
});
}
// Connect the transport to the MCP server BEFORE handling the request
const server = getServer();
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
return; // Already handled
} else {
// Invalid request - no session ID or not initialization request
res.status(400).json({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Bad Request: No valid session ID provided",
},
id: null,
});
return;
}
// Handle the request with existing transport - no need to reconnect
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("Error handling MCP request:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: {
code: -32603,
message: "Internal server error",
},
id: null,
});
}
}
});
// Handle GET requests for SSE streams according to spec
app.get('/mcp', async (req: Request, res: Response) => {
// Since this is a very simple example, we don't support GET requests for this server
// The spec requires returning 405 Method Not Allowed in this case
res.status(405).set('Allow', 'POST').send('Method Not Allowed');
app.get("/mcp", async (req: Request, res: Response) => {
// Since this is a very simple example, we don't support GET requests for this server
// The spec requires returning 405 Method Not Allowed in this case
res.status(405).set("Allow", "POST").send("Method Not Allowed");
});
// Start the server
const PORT = 3000;
app.listen(PORT, error => {
if (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
console.log(`MCP Streamable HTTP Server listening on port ${PORT}`);
app.listen(PORT, (error) => {
if (error) {
console.error("Failed to start server:", error);
process.exit(1);
}
console.log(`MCP Streamable HTTP Server listening on port ${PORT}`);
});
// Handle server shutdown
process.on('SIGINT', async () => {
console.log('Shutting down server...');
process.exit(0);
});
process.on("SIGINT", async () => {
console.log("Shutting down server...");
process.exit(0);
});