From 71bcc2f8c3495561d4c6d70f809e3499e2593cd8 Mon Sep 17 00:00:00 2001 From: Marcus Schiesser Date: Wed, 4 Dec 2024 14:42:59 +0700 Subject: [PATCH] init repo --- .gitignore | 4 + README.md | 70 ++++++++++++++++ package.json | 26 ++++++ src/index.ts | 222 ++++++++++++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 15 ++++ 5 files changed, 337 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 package.json create mode 100644 src/index.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f106e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +build/ +*.log +.env* \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e78c270 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# mcp-server-llamacloud MCP Server + +A MCP server connecting to a managed index on LlamaCloud + +This is a TypeScript-based MCP server that implements a simple notes system. It demonstrates core MCP concepts by providing: + +- Resources representing text notes with URIs and metadata +- Tools for creating new notes +- Prompts for generating summaries of notes + +## Features + +### Resources +- List and access notes via `note://` URIs +- Each note has a title, content and metadata +- Plain text mime type for simple content access + +### Tools +- `create_note` - Create new text notes + - Takes title and content as required parameters + - Stores note in server state + +### Prompts +- `summarize_notes` - Generate a summary of all stored notes + - Includes all note contents as embedded resources + - Returns structured prompt for LLM summarization + +## Development + +Install dependencies: +```bash +npm install +``` + +Build the server: +```bash +npm run build +``` + +For development with auto-rebuild: +```bash +npm run watch +``` + +## Installation + +To use with Claude Desktop, add the server config: + +On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json` +On Windows: `%APPDATA%/Claude/claude_desktop_config.json` + +```json +{ + "mcpServers": { + "mcp-server-llamacloud": { + "command": "/path/to/mcp-server-llamacloud/build/index.js" + } + } +} +``` + +### Debugging + +Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script: + +```bash +npm run inspector +``` + +The Inspector will provide a URL to access debugging tools in your browser. diff --git a/package.json b/package.json new file mode 100644 index 0000000..713c01d --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "mcp-server-llamacloud", + "version": "0.1.0", + "description": "A MCP server connecting to a managed index on LlamaCloud", + "private": true, + "type": "module", + "bin": { + "mcp-server-llamacloud": "./build/index.js" + }, + "files": [ + "build" + ], + "scripts": { + "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", + "prepare": "npm run build", + "watch": "tsc --watch", + "inspector": "npx @modelcontextprotocol/inspector build/index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "0.6.0" + }, + "devDependencies": { + "@types/node": "^20.11.24", + "typescript": "^5.3.3" + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..15d2be7 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,222 @@ +#!/usr/bin/env node + +/** + * This is a template MCP server that implements a simple notes system. + * It demonstrates core MCP concepts like resources and tools by allowing: + * - Listing notes as resources + * - Reading individual notes + * - Creating new notes via a tool + * - Summarizing all notes via a prompt + */ + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListResourcesRequestSchema, + ListToolsRequestSchema, + ReadResourceRequestSchema, + ListPromptsRequestSchema, + GetPromptRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; + +/** + * Type alias for a note object. + */ +type Note = { title: string, content: string }; + +/** + * Simple in-memory storage for notes. + * In a real implementation, this would likely be backed by a database. + */ +const notes: { [id: string]: Note } = { + "1": { title: "First Note", content: "This is note 1" }, + "2": { title: "Second Note", content: "This is note 2" } +}; + +/** + * Create an MCP server with capabilities for resources (to list/read notes), + * tools (to create new notes), and prompts (to summarize notes). + */ +const server = new Server( + { + name: "mcp-server-llamacloud", + version: "0.1.0", + }, + { + capabilities: { + resources: {}, + tools: {}, + prompts: {}, + }, + } +); + +/** + * Handler for listing available notes as resources. + * Each note is exposed as a resource with: + * - A note:// URI scheme + * - Plain text MIME type + * - Human readable name and description (now including the note title) + */ +server.setRequestHandler(ListResourcesRequestSchema, async () => { + return { + resources: Object.entries(notes).map(([id, note]) => ({ + uri: `note:///${id}`, + mimeType: "text/plain", + name: note.title, + description: `A text note: ${note.title}` + })) + }; +}); + +/** + * Handler for reading the contents of a specific note. + * Takes a note:// URI and returns the note content as plain text. + */ +server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + const url = new URL(request.params.uri); + const id = url.pathname.replace(/^\//, ''); + const note = notes[id]; + + if (!note) { + throw new Error(`Note ${id} not found`); + } + + return { + contents: [{ + uri: request.params.uri, + mimeType: "text/plain", + text: note.content + }] + }; +}); + +/** + * Handler that lists available tools. + * Exposes a single "create_note" tool that lets clients create new notes. + */ +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "create_note", + description: "Create a new note", + inputSchema: { + type: "object", + properties: { + title: { + type: "string", + description: "Title of the note" + }, + content: { + type: "string", + description: "Text content of the note" + } + }, + required: ["title", "content"] + } + } + ] + }; +}); + +/** + * Handler for the create_note tool. + * Creates a new note with the provided title and content, and returns success message. + */ +server.setRequestHandler(CallToolRequestSchema, async (request) => { + switch (request.params.name) { + case "create_note": { + const title = String(request.params.arguments?.title); + const content = String(request.params.arguments?.content); + if (!title || !content) { + throw new Error("Title and content are required"); + } + + const id = String(Object.keys(notes).length + 1); + notes[id] = { title, content }; + + return { + content: [{ + type: "text", + text: `Created note ${id}: ${title}` + }] + }; + } + + default: + throw new Error("Unknown tool"); + } +}); + +/** + * Handler that lists available prompts. + * Exposes a single "summarize_notes" prompt that summarizes all notes. + */ +server.setRequestHandler(ListPromptsRequestSchema, async () => { + return { + prompts: [ + { + name: "summarize_notes", + description: "Summarize all notes", + } + ] + }; +}); + +/** + * Handler for the summarize_notes prompt. + * Returns a prompt that requests summarization of all notes, with the notes' contents embedded as resources. + */ +server.setRequestHandler(GetPromptRequestSchema, async (request) => { + if (request.params.name !== "summarize_notes") { + throw new Error("Unknown prompt"); + } + + const embeddedNotes = Object.entries(notes).map(([id, note]) => ({ + type: "resource" as const, + resource: { + uri: `note:///${id}`, + mimeType: "text/plain", + text: note.content + } + })); + + return { + messages: [ + { + role: "user", + content: { + type: "text", + text: "Please summarize the following notes:" + } + }, + ...embeddedNotes.map(note => ({ + role: "user" as const, + content: note + })), + { + role: "user", + content: { + type: "text", + text: "Provide a concise summary of all the notes above." + } + } + ] + }; +}); + +/** + * Start the server using stdio transport. + * This allows the server to communicate via standard input/output streams. + */ +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +main().catch((error) => { + console.error("Server error:", error); + process.exit(1); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a14bee0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "outDir": "./build", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +}