mirror of
https://github.com/run-llama/agentfs-claude.git
synced 2026-06-30 21:27:55 -04:00
fix: fix MCP issue and finalize codex support
This commit is contained in:
@@ -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.
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user