chore: remove mcp-server directory
Remove MCP (Model Context Protocol) server integration. This was an AI assistant tool that doesn't belong in the main repository.
This commit is contained in:
parent
a17cd53111
commit
c79fd4cb40
3 changed files with 0 additions and 235 deletions
|
|
@ -1,65 +0,0 @@
|
||||||
# Pulse Codex MCP Server
|
|
||||||
|
|
||||||
An MCP (Model Context Protocol) server that provides OpenAI Codex integration for Claude Code.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **codex** - Ask questions to OpenAI Codex AI assistant
|
|
||||||
- **codex_clear_session** - Clear stored session context
|
|
||||||
- Automatic session management across conversation turns
|
|
||||||
- Clean response extraction (no token counts or debug info)
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### 1. Build the Server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /opt/pulse/mcp-server
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Add to Claude Code
|
|
||||||
|
|
||||||
```bash
|
|
||||||
claude mcp add --transport stdio pulse-codex -- node /opt/pulse/mcp-server/dist/index.js
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### In Claude Code Conversations
|
|
||||||
|
|
||||||
**Ask a question:**
|
|
||||||
```
|
|
||||||
Use the codex tool to evaluate whether we should use X or Y for Z scenario
|
|
||||||
```
|
|
||||||
|
|
||||||
**Continue a conversation:**
|
|
||||||
```
|
|
||||||
Use the codex tool with conversationId "my-conversation" to dig deeper into that approach
|
|
||||||
```
|
|
||||||
|
|
||||||
**Start fresh:**
|
|
||||||
```
|
|
||||||
Use the codex_clear_session tool with conversationId "my-conversation"
|
|
||||||
```
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
- Each conversation is identified by a `conversationId`
|
|
||||||
- The server automatically maintains codex session IDs for each conversation
|
|
||||||
- Responses are clean (just the answer, no metadata)
|
|
||||||
- Sessions persist across multiple tool calls with the same conversationId
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
Watch mode for development:
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
- **TypeScript** - Type-safe MCP server implementation
|
|
||||||
- **@modelcontextprotocol/sdk** - Official MCP SDK
|
|
||||||
- **stdio transport** - Communicates with Claude Code via standard input/output
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
||||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { exec } from "child_process";
|
|
||||||
import { promisify } from "util";
|
|
||||||
import { writeFile, readFile } from "fs/promises";
|
|
||||||
import { tmpdir } from "os";
|
|
||||||
import { join } from "path";
|
|
||||||
import { randomBytes } from "crypto";
|
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
|
||||||
|
|
||||||
// Session management
|
|
||||||
const sessions = new Map<string, string>(); // Map conversation ID to codex session ID
|
|
||||||
|
|
||||||
// Create the MCP server
|
|
||||||
const server = new McpServer({
|
|
||||||
name: "pulse-codex",
|
|
||||||
version: "1.0.0",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register the codex tool for both questions and implementation
|
|
||||||
server.registerTool(
|
|
||||||
"codex",
|
|
||||||
{
|
|
||||||
title: "Codex AI Assistant",
|
|
||||||
description: "Ask OpenAI Codex to implement changes or answer questions. Codex has full filesystem access and can create/modify files directly. Use this for architectural decisions, code review, technical consultation, and implementation tasks.",
|
|
||||||
inputSchema: {
|
|
||||||
question: z.string().describe("The question or implementation task for Codex"),
|
|
||||||
conversationId: z.string().optional().describe("Optional conversation ID to maintain context across calls"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
async ({ question, conversationId }) => {
|
|
||||||
const convId = conversationId || `conv-${Date.now()}-${process.pid}`;
|
|
||||||
const uniqueId = `${Date.now()}-${process.pid}-${randomBytes(4).toString('hex')}`;
|
|
||||||
const tempFile = join(tmpdir(), `codex-${uniqueId}.txt`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Check if we have an existing codex session for this conversation
|
|
||||||
const existingSessionId = sessions.get(convId);
|
|
||||||
|
|
||||||
// Write question to a temp file to avoid shell escaping issues
|
|
||||||
const questionFile = join(tmpdir(), `codex-q-${uniqueId}.txt`);
|
|
||||||
await writeFile(questionFile, question, "utf-8");
|
|
||||||
|
|
||||||
let command: string;
|
|
||||||
if (existingSessionId) {
|
|
||||||
// Resume existing session
|
|
||||||
command = `codex exec --yolo -o "${tempFile}" resume "${existingSessionId}" < "${questionFile}"`;
|
|
||||||
} else {
|
|
||||||
// Start new session
|
|
||||||
command = `codex exec --yolo -o "${tempFile}" < "${questionFile}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute codex and capture stderr for session ID only
|
|
||||||
// Note: stderr contains all reasoning output, but we only need the session ID
|
|
||||||
const { stdout, stderr } = await execAsync(command, {
|
|
||||||
timeout: 1800000, // 30 minute timeout (codex can take a very long time for complex queries)
|
|
||||||
maxBuffer: 10 * 1024 * 1024, // 10MB buffer
|
|
||||||
});
|
|
||||||
|
|
||||||
// Extract only the session ID from stderr
|
|
||||||
const sessionIdMatch = stderr.match(/session id:\s*([a-f0-9-]+)/);
|
|
||||||
const sessionId = sessionIdMatch ? sessionIdMatch[1] : "";
|
|
||||||
|
|
||||||
// Store session for this conversation
|
|
||||||
if (sessionId && !existingSessionId) {
|
|
||||||
sessions.set(convId, sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the response from the output file
|
|
||||||
const response = await readFile(tempFile, "utf-8");
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `${response.trim()}\n\n---\nSession ID: ${sessionId}\nConversation ID: ${convId}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
// Log full error to stderr for debugging
|
|
||||||
console.error("Codex execution error:", error);
|
|
||||||
|
|
||||||
// Extract only the relevant error info, not the full stderr with reasoning
|
|
||||||
let errorMessage = "Unknown error";
|
|
||||||
if (error instanceof Error) {
|
|
||||||
// For exec errors, the message contains both stdout and stderr
|
|
||||||
// We want to extract just the actual error, not all the reasoning output
|
|
||||||
const lines = error.message.split('\n');
|
|
||||||
// Look for actual error messages (usually start with "Error:" or contain "failed")
|
|
||||||
const relevantLines = lines.filter(line =>
|
|
||||||
line.includes('Error:') ||
|
|
||||||
line.includes('failed') ||
|
|
||||||
line.includes('not found') ||
|
|
||||||
line.includes('permission denied') ||
|
|
||||||
line.includes('exit code')
|
|
||||||
);
|
|
||||||
errorMessage = relevantLines.length > 0 ? relevantLines.join('\n') : error.message;
|
|
||||||
}
|
|
||||||
throw new Error(`Failed to execute codex: ${errorMessage}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Register a tool to clear session history
|
|
||||||
server.registerTool(
|
|
||||||
"codex_clear_session",
|
|
||||||
{
|
|
||||||
title: "Clear Codex Session",
|
|
||||||
description: "Clear the stored codex session for a conversation, starting fresh",
|
|
||||||
inputSchema: {
|
|
||||||
conversationId: z.string().describe("The conversation ID to clear"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
async ({ conversationId }) => {
|
|
||||||
const existed = sessions.has(conversationId);
|
|
||||||
sessions.delete(conversationId);
|
|
||||||
|
|
||||||
const message = existed
|
|
||||||
? `Session cleared for conversation ${conversationId}`
|
|
||||||
: `No session found for conversation ${conversationId}`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: message,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Connect to stdio transport
|
|
||||||
async function main() {
|
|
||||||
const transport = new StdioServerTransport();
|
|
||||||
await server.connect(transport);
|
|
||||||
|
|
||||||
// Log to stderr so it doesn't interfere with MCP protocol
|
|
||||||
console.error("Pulse Codex MCP Server running on stdio");
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch((error) => {
|
|
||||||
console.error("Fatal error:", error);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2022",
|
|
||||||
"module": "ES2022",
|
|
||||||
"lib": ["ES2022"],
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"rootDir": "./src",
|
|
||||||
"outDir": "./dist",
|
|
||||||
"strict": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"declaration": true,
|
|
||||||
"declarationMap": true,
|
|
||||||
"sourceMap": true
|
|
||||||
},
|
|
||||||
"include": ["src/**/*"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue