Skip to content

Commit 9730265

Browse files
committed
npx @modelcontextprotocol/create-server mcp-server-commands
0 parents  commit 9730265

File tree

5 files changed

+337
-0
lines changed

5 files changed

+337
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules/
2+
build/
3+
*.log
4+
.env*

README.md

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# mcp-server-commands MCP Server
2+
3+
An MCP server to run arbitrary commands
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+
"mcp-server-commands": {
56+
"command": "/path/to/mcp-server-commands/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.

package.json

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "mcp-server-commands",
3+
"version": "0.1.0",
4+
"description": "An MCP server to run arbitrary commands",
5+
"private": true,
6+
"type": "module",
7+
"bin": {
8+
"mcp-server-commands": "./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+
"@modelcontextprotocol/sdk": "0.6.0"
21+
},
22+
"devDependencies": {
23+
"@types/node": "^20.11.24",
24+
"typescript": "^5.3.3"
25+
}
26+
}

src/index.ts

+222
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
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+
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 notes: { [id: string]: Note } = {
33+
"1": { title: "First Note", content: "This is note 1" },
34+
"2": { title: "Second Note", content: "This is note 2" }
35+
};
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: "mcp-server-commands",
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+
return {
64+
resources: Object.entries(notes).map(([id, note]) => ({
65+
uri: `note:///${id}`,
66+
mimeType: "text/plain",
67+
name: note.title,
68+
description: `A text note: ${note.title}`
69+
}))
70+
};
71+
});
72+
73+
/**
74+
* Handler for reading the contents of a specific note.
75+
* Takes a note:// URI and returns the note content as plain text.
76+
*/
77+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
78+
const url = new URL(request.params.uri);
79+
const id = url.pathname.replace(/^\//, '');
80+
const note = notes[id];
81+
82+
if (!note) {
83+
throw new Error(`Note ${id} not found`);
84+
}
85+
86+
return {
87+
contents: [{
88+
uri: request.params.uri,
89+
mimeType: "text/plain",
90+
text: note.content
91+
}]
92+
};
93+
});
94+
95+
/**
96+
* Handler that lists available tools.
97+
* Exposes a single "create_note" tool that lets clients create new notes.
98+
*/
99+
server.setRequestHandler(ListToolsRequestSchema, async () => {
100+
return {
101+
tools: [
102+
{
103+
name: "create_note",
104+
description: "Create a new note",
105+
inputSchema: {
106+
type: "object",
107+
properties: {
108+
title: {
109+
type: "string",
110+
description: "Title of the note"
111+
},
112+
content: {
113+
type: "string",
114+
description: "Text content of the note"
115+
}
116+
},
117+
required: ["title", "content"]
118+
}
119+
}
120+
]
121+
};
122+
});
123+
124+
/**
125+
* Handler for the create_note tool.
126+
* Creates a new note with the provided title and content, and returns success message.
127+
*/
128+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
129+
switch (request.params.name) {
130+
case "create_note": {
131+
const title = String(request.params.arguments?.title);
132+
const content = String(request.params.arguments?.content);
133+
if (!title || !content) {
134+
throw new Error("Title and content are required");
135+
}
136+
137+
const id = String(Object.keys(notes).length + 1);
138+
notes[id] = { title, content };
139+
140+
return {
141+
content: [{
142+
type: "text",
143+
text: `Created note ${id}: ${title}`
144+
}]
145+
};
146+
}
147+
148+
default:
149+
throw new Error("Unknown tool");
150+
}
151+
});
152+
153+
/**
154+
* Handler that lists available prompts.
155+
* Exposes a single "summarize_notes" prompt that summarizes all notes.
156+
*/
157+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
158+
return {
159+
prompts: [
160+
{
161+
name: "summarize_notes",
162+
description: "Summarize all notes",
163+
}
164+
]
165+
};
166+
});
167+
168+
/**
169+
* Handler for the summarize_notes prompt.
170+
* Returns a prompt that requests summarization of all notes, with the notes' contents embedded as resources.
171+
*/
172+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
173+
if (request.params.name !== "summarize_notes") {
174+
throw new Error("Unknown prompt");
175+
}
176+
177+
const embeddedNotes = Object.entries(notes).map(([id, note]) => ({
178+
type: "resource" as const,
179+
resource: {
180+
uri: `note:///${id}`,
181+
mimeType: "text/plain",
182+
text: note.content
183+
}
184+
}));
185+
186+
return {
187+
messages: [
188+
{
189+
role: "user",
190+
content: {
191+
type: "text",
192+
text: "Please summarize the following notes:"
193+
}
194+
},
195+
...embeddedNotes.map(note => ({
196+
role: "user" as const,
197+
content: note
198+
})),
199+
{
200+
role: "user",
201+
content: {
202+
type: "text",
203+
text: "Provide a concise summary of all the notes above."
204+
}
205+
}
206+
]
207+
};
208+
});
209+
210+
/**
211+
* Start the server using stdio transport.
212+
* This allows the server to communicate via standard input/output streams.
213+
*/
214+
async function main() {
215+
const transport = new StdioServerTransport();
216+
await server.connect(transport);
217+
}
218+
219+
main().catch((error) => {
220+
console.error("Server error:", error);
221+
process.exit(1);
222+
});

tsconfig.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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+
}

0 commit comments

Comments
 (0)