├── .gitignore ├── tsconfig.json ├── package.json ├── README.md └── src └── index.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | *.log 4 | .env* -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fp-mcp", 3 | "version": "0.1.0", 4 | "description": "MCP server with Fireproof", 5 | "private": true, 6 | "type": "module", 7 | "bin": { 8 | "fp-mcp": "./build/index.js" 9 | }, 10 | "files": [ 11 | "build" 12 | ], 13 | "scripts": { 14 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 15 | "prepare": "npm run build", 16 | "watch": "tsc --watch", 17 | "inspector": "npx @modelcontextprotocol/inspector build/index.js" 18 | }, 19 | "dependencies": { 20 | "@fireproof/core": "^0.19.114", 21 | "@modelcontextprotocol/sdk": "0.6.0" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^20.11.24", 25 | "typescript": "^5.3.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fp-mcp MCP Server 2 | 3 | MCP server with Fireproof 4 | 5 | This is a TypeScript-based MCP server that implements a simple notes system. It demonstrates core MCP concepts by providing: 6 | 7 | - Resources representing text notes with URIs and metadata 8 | - Tools for creating new notes 9 | - Prompts for generating summaries of notes 10 | 11 | ## Features 12 | 13 | ### Resources 14 | - List and access notes via `note://` URIs 15 | - Each note has a title, content and metadata 16 | - Plain text mime type for simple content access 17 | 18 | ### Tools 19 | - `create_note` - Create new text notes 20 | - Takes title and content as required parameters 21 | - Stores note in server state 22 | 23 | ### Prompts 24 | - `summarize_notes` - Generate a summary of all stored notes 25 | - Includes all note contents as embedded resources 26 | - Returns structured prompt for LLM summarization 27 | 28 | ## Development 29 | 30 | Install dependencies: 31 | ```bash 32 | npm install 33 | ``` 34 | 35 | Build the server: 36 | ```bash 37 | npm run build 38 | ``` 39 | 40 | For development with auto-rebuild: 41 | ```bash 42 | npm run watch 43 | ``` 44 | 45 | ## Installation 46 | 47 | To use with Claude Desktop, add the server config: 48 | 49 | On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json` 50 | On Windows: `%APPDATA%/Claude/claude_desktop_config.json` 51 | 52 | ```json 53 | { 54 | "mcpServers": { 55 | "fp-mcp": { 56 | "command": "/path/to/fp-mcp/build/index.js" 57 | } 58 | } 59 | } 60 | ``` 61 | 62 | ### Debugging 63 | 64 | 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: 65 | 66 | ```bash 67 | npm run inspector 68 | ``` 69 | 70 | The Inspector will provide a URL to access debugging tools in your browser. 71 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * This is a template MCP server that implements a simple notes system. 5 | * It demonstrates core MCP concepts like resources and tools by allowing: 6 | * - Listing notes as resources 7 | * - Reading individual notes 8 | * - Creating new notes via a tool 9 | * - Summarizing all notes via a prompt 10 | */ 11 | 12 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 13 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 14 | import { 15 | CallToolRequestSchema, 16 | ListResourcesRequestSchema, 17 | ListToolsRequestSchema, 18 | ReadResourceRequestSchema, 19 | ListPromptsRequestSchema, 20 | GetPromptRequestSchema, 21 | } from "@modelcontextprotocol/sdk/types.js"; 22 | import { fireproof } from "@fireproof/core"; 23 | /** 24 | * Type alias for a note object. 25 | */ 26 | type Note = { title: string, content: string }; 27 | 28 | /** 29 | * Simple in-memory storage for notes. 30 | * In a real implementation, this would likely be backed by a database. 31 | */ 32 | const notesDb = fireproof("notes"); 33 | 34 | await notesDb.put({ title: "First Note", content: "This is note 1" }); 35 | await notesDb.put({ title: "Second Note", content: "This is note 2" }); 36 | 37 | /** 38 | * Create an MCP server with capabilities for resources (to list/read notes), 39 | * tools (to create new notes), and prompts (to summarize notes). 40 | */ 41 | const server = new Server( 42 | { 43 | name: "fp-mcp", 44 | version: "0.1.0", 45 | }, 46 | { 47 | capabilities: { 48 | resources: {}, 49 | tools: {}, 50 | prompts: {}, 51 | }, 52 | } 53 | ); 54 | 55 | /** 56 | * Handler for listing available notes as resources. 57 | * Each note is exposed as a resource with: 58 | * - A note:// URI scheme 59 | * - Plain text MIME type 60 | * - Human readable name and description (now including the note title) 61 | */ 62 | server.setRequestHandler(ListResourcesRequestSchema, async () => { 63 | const notes = await notesDb.query('_id'); 64 | return { 65 | resources: notes.rows.map(({id, value:note} ) => ({ 66 | uri: `note:///${id}`, 67 | mimeType: "text/plain", 68 | name: note.title, 69 | description: `A text note: ${note.title}` 70 | })) 71 | }; 72 | }); 73 | 74 | /** 75 | * Handler for reading the contents of a specific note. 76 | * Takes a note:// URI and returns the note content as plain text. 77 | */ 78 | server.setRequestHandler(ReadResourceRequestSchema, async (request) => { 79 | const url = new URL(request.params.uri); 80 | const id = url.pathname.replace(/^\//, ''); 81 | const note = notes[id]; 82 | 83 | if (!note) { 84 | throw new Error(`Note ${id} not found`); 85 | } 86 | 87 | return { 88 | contents: [{ 89 | uri: request.params.uri, 90 | mimeType: "text/plain", 91 | text: note.content 92 | }] 93 | }; 94 | }); 95 | 96 | /** 97 | * Handler that lists available tools. 98 | * Exposes a single "create_note" tool that lets clients create new notes. 99 | */ 100 | server.setRequestHandler(ListToolsRequestSchema, async () => { 101 | return { 102 | tools: [ 103 | { 104 | name: "create_note", 105 | description: "Create a new note", 106 | inputSchema: { 107 | type: "object", 108 | properties: { 109 | title: { 110 | type: "string", 111 | description: "Title of the note" 112 | }, 113 | content: { 114 | type: "string", 115 | description: "Text content of the note" 116 | } 117 | }, 118 | required: ["title", "content"] 119 | } 120 | } 121 | ] 122 | }; 123 | }); 124 | 125 | /** 126 | * Handler for the create_note tool. 127 | * Creates a new note with the provided title and content, and returns success message. 128 | */ 129 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 130 | switch (request.params.name) { 131 | case "create_note": { 132 | const title = String(request.params.arguments?.title); 133 | const content = String(request.params.arguments?.content); 134 | if (!title || !content) { 135 | throw new Error("Title and content are required"); 136 | } 137 | 138 | const id = String(Object.keys(notes).length + 1); 139 | notes[id] = { title, content }; 140 | 141 | return { 142 | content: [{ 143 | type: "text", 144 | text: `Created note ${id}: ${title}` 145 | }] 146 | }; 147 | } 148 | 149 | default: 150 | throw new Error("Unknown tool"); 151 | } 152 | }); 153 | 154 | /** 155 | * Handler that lists available prompts. 156 | * Exposes a single "summarize_notes" prompt that summarizes all notes. 157 | */ 158 | server.setRequestHandler(ListPromptsRequestSchema, async () => { 159 | return { 160 | prompts: [ 161 | { 162 | name: "summarize_notes", 163 | description: "Summarize all notes", 164 | } 165 | ] 166 | }; 167 | }); 168 | 169 | /** 170 | * Handler for the summarize_notes prompt. 171 | * Returns a prompt that requests summarization of all notes, with the notes' contents embedded as resources. 172 | */ 173 | server.setRequestHandler(GetPromptRequestSchema, async (request) => { 174 | if (request.params.name !== "summarize_notes") { 175 | throw new Error("Unknown prompt"); 176 | } 177 | 178 | const embeddedNotes = Object.entries(notes).map(([id, note]) => ({ 179 | type: "resource" as const, 180 | resource: { 181 | uri: `note:///${id}`, 182 | mimeType: "text/plain", 183 | text: note.content 184 | } 185 | })); 186 | 187 | return { 188 | messages: [ 189 | { 190 | role: "user", 191 | content: { 192 | type: "text", 193 | text: "Please summarize the following notes:" 194 | } 195 | }, 196 | ...embeddedNotes.map(note => ({ 197 | role: "user" as const, 198 | content: note 199 | })), 200 | { 201 | role: "user", 202 | content: { 203 | type: "text", 204 | text: "Provide a concise summary of all the notes above." 205 | } 206 | } 207 | ] 208 | }; 209 | }); 210 | 211 | /** 212 | * Start the server using stdio transport. 213 | * This allows the server to communicate via standard input/output streams. 214 | */ 215 | async function main() { 216 | const transport = new StdioServerTransport(); 217 | await server.connect(transport); 218 | } 219 | 220 | main().catch((error) => { 221 | console.error("Server error:", error); 222 | process.exit(1); 223 | }); 224 | --------------------------------------------------------------------------------