This commit adds a complete deployment solution for running Claude OS's MCP (Model Context Protocol) server as a standalone service, separate from the full coding environment. This allows other applications to access the knowledge bases without requiring the CLI or coding tools. Features: - Docker-based deployment with Ollama integration - Automated setup script with model pulling - Read-only database mounting by default - CORS and authentication configuration - Complete API documentation and examples - Python and JavaScript client examples Also includes alternative Cloudflare Workers implementation: - TypeScript starter template - D1 and Vectorize integration examples - Migration scripts and documentation - Deployment comparison guide Files added: - deployment/mcp-only/Dockerfile - deployment/mcp-only/docker-compose.yml - deployment/mcp-only/requirements.txt - deployment/mcp-only/setup.sh - deployment/mcp-only/.env.example - deployment/mcp-only/.dockerignore - deployment/mcp-only/README.md - deployment/mcp-only/DEPLOYMENT_COMPARISON.md - deployment/mcp-only/examples/chat-integration.py - deployment/mcp-only/examples/chat-integration.js - deployment/mcp-only/cloudflare-workers/* This addresses the need to expose MCP data to other applications (knowledge base access, live chat) without deploying the full Claude OS environment to production servers.
290 lines
7.3 KiB
JavaScript
290 lines
7.3 KiB
JavaScript
/**
|
|
* Example: Integrating Claude OS MCP with a Node.js/Express Chat Application
|
|
*
|
|
* This example shows how to use the MCP server as a knowledge base
|
|
* backend for a chat API.
|
|
*/
|
|
|
|
const express = require('express');
|
|
const axios = require('axios');
|
|
|
|
class ClaudeOSKnowledgeBase {
|
|
constructor(baseUrl = 'http://localhost:8051', apiToken = null) {
|
|
this.baseUrl = baseUrl;
|
|
this.headers = { 'Content-Type': 'application/json' };
|
|
|
|
if (apiToken) {
|
|
this.headers['Authorization'] = `Bearer ${apiToken}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Make a JSON-RPC call to the MCP server
|
|
*/
|
|
async callTool(toolName, arguments, kbSlug = null) {
|
|
// Use KB-specific endpoint if slug provided
|
|
let url = `${this.baseUrl}/mcp`;
|
|
if (kbSlug) {
|
|
url = `${this.baseUrl}/mcp/kb/${kbSlug}`;
|
|
}
|
|
|
|
const payload = {
|
|
jsonrpc: '2.0',
|
|
method: 'tools/call',
|
|
params: {
|
|
name: toolName,
|
|
arguments: arguments
|
|
},
|
|
id: 1
|
|
};
|
|
|
|
try {
|
|
const response = await axios.post(url, payload, {
|
|
headers: this.headers
|
|
});
|
|
|
|
if (response.data.error) {
|
|
throw new Error(`MCP Error: ${JSON.stringify(response.data.error)}`);
|
|
}
|
|
|
|
return response.data.result || {};
|
|
} catch (error) {
|
|
throw new Error(`Failed to call MCP: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Search a knowledge base for relevant information
|
|
*/
|
|
async search(kbName, query, options = {}) {
|
|
const {
|
|
topK = 10,
|
|
useHybrid = false,
|
|
useRerank = false
|
|
} = options;
|
|
|
|
const result = await this.callTool('search_knowledge_base', {
|
|
kb_name: kbName,
|
|
query: query,
|
|
top_k: topK,
|
|
use_hybrid: useHybrid,
|
|
use_rerank: useRerank
|
|
});
|
|
|
|
return result.results || [];
|
|
}
|
|
|
|
/**
|
|
* List all available knowledge bases
|
|
*/
|
|
async listKnowledgeBases() {
|
|
const result = await this.callTool('list_knowledge_bases', {});
|
|
return result.knowledge_bases || [];
|
|
}
|
|
|
|
/**
|
|
* Get statistics for a knowledge base
|
|
*/
|
|
async getKBStats(kbName) {
|
|
return await this.callTool('get_kb_stats', { kb_name: kbName });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create an Express.js API for the chat application
|
|
*/
|
|
function createChatAPI() {
|
|
const app = express();
|
|
app.use(express.json());
|
|
|
|
// Initialize MCP client
|
|
const mcp = new ClaudeOSKnowledgeBase(
|
|
process.env.MCP_BASE_URL || 'http://localhost:8051',
|
|
process.env.MCP_API_TOKEN || null
|
|
);
|
|
|
|
/**
|
|
* POST /api/chat
|
|
* Ask a question and get a knowledge-based response
|
|
*/
|
|
app.post('/api/chat', async (req, res) => {
|
|
try {
|
|
const { question, kb_name, options = {} } = req.body;
|
|
|
|
if (!question || !kb_name) {
|
|
return res.status(400).json({
|
|
error: 'Missing required fields: question and kb_name'
|
|
});
|
|
}
|
|
|
|
// Search the knowledge base
|
|
const results = await mcp.search(kb_name, question, {
|
|
topK: options.top_k || 5,
|
|
useHybrid: options.use_hybrid !== false, // default true
|
|
useRerank: options.use_rerank !== false // default true
|
|
});
|
|
|
|
if (results.length === 0) {
|
|
return res.json({
|
|
answer: "I couldn't find any relevant information in the knowledge base.",
|
|
sources: []
|
|
});
|
|
}
|
|
|
|
// Format the context
|
|
const sources = results.map((result, idx) => ({
|
|
id: idx + 1,
|
|
text: result.text,
|
|
filename: result.metadata?.filename || 'Unknown',
|
|
score: result.score || 0
|
|
}));
|
|
|
|
// In a real app, you'd call your LLM here with the context
|
|
// For this example, just return the sources
|
|
res.json({
|
|
answer: `Found ${results.length} relevant results. (In production, this would be processed by an LLM to generate a natural language answer)`,
|
|
sources: sources,
|
|
question: question
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Chat error:', error);
|
|
res.status(500).json({
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/knowledge-bases
|
|
* List all available knowledge bases
|
|
*/
|
|
app.get('/api/knowledge-bases', async (req, res) => {
|
|
try {
|
|
const kbs = await mcp.listKnowledgeBases();
|
|
|
|
res.json({
|
|
knowledge_bases: kbs.map(kb => ({
|
|
name: kb.name,
|
|
slug: kb.slug,
|
|
type: kb.kb_type,
|
|
description: kb.description
|
|
}))
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error listing KBs:', error);
|
|
res.status(500).json({
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/knowledge-bases/:name/stats
|
|
* Get statistics for a specific knowledge base
|
|
*/
|
|
app.get('/api/knowledge-bases/:name/stats', async (req, res) => {
|
|
try {
|
|
const stats = await mcp.getKBStats(req.params.name);
|
|
res.json(stats);
|
|
} catch (error) {
|
|
console.error('Error getting stats:', error);
|
|
res.status(500).json({
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /health
|
|
* Health check endpoint
|
|
*/
|
|
app.get('/health', async (req, res) => {
|
|
try {
|
|
// Try to list KBs to verify MCP connection
|
|
await mcp.listKnowledgeBases();
|
|
res.json({ status: 'ok', mcp_connected: true });
|
|
} catch (error) {
|
|
res.status(503).json({
|
|
status: 'degraded',
|
|
mcp_connected: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
return app;
|
|
}
|
|
|
|
/**
|
|
* Example usage with streaming responses (for real-time chat)
|
|
*/
|
|
async function streamingChatExample(kbName, question) {
|
|
const mcp = new ClaudeOSKnowledgeBase('http://localhost:8051');
|
|
|
|
// 1. Search the knowledge base
|
|
const results = await mcp.search(kbName, question, {
|
|
topK: 5,
|
|
useHybrid: true,
|
|
useRerank: true
|
|
});
|
|
|
|
// 2. Build context for LLM
|
|
const context = results
|
|
.map((r, idx) => `[${idx + 1}] ${r.text}`)
|
|
.join('\n\n---\n\n');
|
|
|
|
console.log('Context for LLM:');
|
|
console.log(context);
|
|
|
|
// 3. In a real app, stream the LLM response
|
|
// using OpenAI SDK, Anthropic SDK, etc.
|
|
/*
|
|
const stream = await openai.chat.completions.create({
|
|
model: 'gpt-4',
|
|
messages: [
|
|
{
|
|
role: 'system',
|
|
content: `You are a helpful assistant. Answer the user's question based on the following context:\n\n${context}`
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: question
|
|
}
|
|
],
|
|
stream: true
|
|
});
|
|
|
|
for await (const chunk of stream) {
|
|
process.stdout.write(chunk.choices[0]?.delta?.content || '');
|
|
}
|
|
*/
|
|
}
|
|
|
|
// Run the server if this file is executed directly
|
|
if (require.main === module) {
|
|
const app = createChatAPI();
|
|
const PORT = process.env.PORT || 3000;
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`Chat API server running on port ${PORT}`);
|
|
console.log('');
|
|
console.log('Endpoints:');
|
|
console.log(` POST http://localhost:${PORT}/api/chat`);
|
|
console.log(` GET http://localhost:${PORT}/api/knowledge-bases`);
|
|
console.log(` GET http://localhost:${PORT}/api/knowledge-bases/:name/stats`);
|
|
console.log(` GET http://localhost:${PORT}/health`);
|
|
console.log('');
|
|
console.log('Example request:');
|
|
console.log(` curl -X POST http://localhost:${PORT}/api/chat \\`);
|
|
console.log(` -H "Content-Type: application/json" \\`);
|
|
console.log(` -d '{"question": "How do I configure auth?", "kb_name": "my-docs"}'`);
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
ClaudeOSKnowledgeBase,
|
|
createChatAPI,
|
|
streamingChatExample
|
|
};
|