This is page 1 of 2. Use http://codebase.md/lightfate/ssh-tools-mcp?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .gitignore
├── main.py
├── mcp_requirements.txt
├── pyproject.toml
├── python_mcp.md
├── README.md
├── requirements.txt
└── ssh_server.py
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Python
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 | *.so
6 | .Python
7 | build/
8 | develop-eggs/
9 | dist/
10 | downloads/
11 | eggs/
12 | .eggs/
13 | lib/
14 | lib64/
15 | parts/
16 | sdist/
17 | var/
18 | wheels/
19 | *.egg-info/
20 | .installed.cfg
21 | *.egg
22 |
23 | # Virtual Environment
24 | .env
25 | .venv
26 | env/
27 | venv/
28 | ENV/
29 |
30 | # IDE
31 | .idea/
32 | .vscode/
33 | *.swp
34 | *.swo
35 | .cursor/
36 |
37 | # Logs
38 | *.log
39 |
40 | # Local development
41 | .DS_Store
42 | Thumbs.db
43 |
44 | # Project specific
45 | uv.lock
```
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
```
1 | mcp>=0.1.0
2 | paramiko>=3.4.0
```
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
```python
1 | def main():
2 | print("Hello from ssh-tools-mcp!")
3 |
4 |
5 | if __name__ == "__main__":
6 | main()
7 |
```
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
```toml
1 | [project]
2 | name = "ssh-tools-mcp"
3 | version = "0.1.0"
4 | description = "Add your description here"
5 | readme = "README.md"
6 | requires-python = ">=3.10"
7 | dependencies = [
8 | "httpx>=0.28.1",
9 | "mcp[cli]",
10 | "paramiko",
11 | ]
12 |
```
--------------------------------------------------------------------------------
/ssh_server.py:
--------------------------------------------------------------------------------
```python
1 | from mcp.server.fastmcp import FastMCP
2 | import paramiko
3 | from dataclasses import dataclass
4 | from typing import Optional
5 | import json
6 |
7 | # Create an MCP server
8 | mcp = FastMCP("SSH Tools")
9 |
10 | class SSHConnection:
11 | def __init__(self, hostname: str, password: str, username: str = "root", port: int = 22):
12 | self.hostname = hostname
13 | self.username = username
14 | self.password = password
15 | self.port = port
16 | self.client = None
17 |
18 | def connect(self):
19 | if self.client is not None:
20 | return
21 |
22 | self.client = paramiko.SSHClient()
23 | self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
24 | self.client.connect(
25 | hostname=self.hostname,
26 | username=self.username,
27 | password=self.password,
28 | port=self.port
29 | )
30 |
31 | def disconnect(self):
32 | if self.client:
33 | self.client.close()
34 | self.client = None
35 |
36 | def execute_command(self, command: str) -> str:
37 | if not self.client:
38 | self.connect()
39 |
40 | stdin, stdout, stderr = self.client.exec_command(command)
41 | output = stdout.read().decode()
42 | error = stderr.read().decode()
43 |
44 | if error:
45 | return f"Error: {error}"
46 | return output
47 |
48 | @dataclass
49 | class SSHConnectionInfo:
50 | hostname: str
51 | password: str
52 | username: str = "root"
53 | port: int = 22
54 |
55 | # Global connection storage
56 | current_connection: Optional[SSHConnection] = None
57 |
58 | @mcp.tool()
59 | def connect_ssh(hostname: str, password: str, username: str = "root", port: int = 22) -> str:
60 | """Connect to a remote server via SSH
61 |
62 | Args:
63 | hostname: The IP address or hostname of the server
64 | password: The SSH password
65 | username: The SSH username (default: root)
66 | port: The SSH port (default: 22)
67 | """
68 | global current_connection
69 |
70 | try:
71 | if current_connection:
72 | current_connection.disconnect()
73 |
74 | connection = SSHConnection(hostname, password, username, port)
75 | connection.connect()
76 | current_connection = connection
77 | return "Successfully connected to the server!"
78 | except Exception as e:
79 | return f"Failed to connect: {str(e)}"
80 |
81 | @mcp.tool()
82 | def run_command(command: str) -> str:
83 | """Run a command on the connected SSH server
84 |
85 | Args:
86 | command: The command to execute
87 | """
88 | global current_connection
89 |
90 | if not current_connection:
91 | return "Error: Not connected to any server. Please connect first using connect_ssh."
92 |
93 | try:
94 | return current_connection.execute_command(command)
95 | except Exception as e:
96 | return f"Failed to execute command: {str(e)}"
97 |
98 | @mcp.tool()
99 | def disconnect_ssh() -> str:
100 | """Disconnect from the current SSH server"""
101 | global current_connection
102 |
103 | if current_connection:
104 | current_connection.disconnect()
105 | current_connection = None
106 | return "Successfully disconnected from the server!"
107 | return "Not connected to any server."
108 |
109 | if __name__ == "__main__":
110 | mcp.run()
```
--------------------------------------------------------------------------------
/python_mcp.md:
--------------------------------------------------------------------------------
```markdown
1 | # MCP Python SDK
2 |
3 | <div align="center">
4 |
5 | <strong>Python implementation of the Model Context Protocol (MCP)</strong>
6 |
7 | [![PyPI][pypi-badge]][pypi-url]
8 | [![MIT licensed][mit-badge]][mit-url]
9 | [![Python Version][python-badge]][python-url]
10 | [![Documentation][docs-badge]][docs-url]
11 | [![Specification][spec-badge]][spec-url]
12 | [![GitHub Discussions][discussions-badge]][discussions-url]
13 |
14 | </div>
15 |
16 | <!-- omit in toc -->
17 | ## Table of Contents
18 |
19 | - [MCP Python SDK](#mcp-python-sdk)
20 | - [Overview](#overview)
21 | - [Installation](#installation)
22 | - [Quickstart](#quickstart)
23 | - [What is MCP?](#what-is-mcp)
24 | - [Core Concepts](#core-concepts)
25 | - [Server](#server)
26 | - [Resources](#resources)
27 | - [Tools](#tools)
28 | - [Prompts](#prompts)
29 | - [Images](#images)
30 | - [Context](#context)
31 | - [Running Your Server](#running-your-server)
32 | - [Development Mode](#development-mode)
33 | - [Claude Desktop Integration](#claude-desktop-integration)
34 | - [Direct Execution](#direct-execution)
35 | - [Examples](#examples)
36 | - [Echo Server](#echo-server)
37 | - [SQLite Explorer](#sqlite-explorer)
38 | - [Advanced Usage](#advanced-usage)
39 | - [Low-Level Server](#low-level-server)
40 | - [Writing MCP Clients](#writing-mcp-clients)
41 | - [MCP Primitives](#mcp-primitives)
42 | - [Server Capabilities](#server-capabilities)
43 | - [Documentation](#documentation)
44 | - [Contributing](#contributing)
45 | - [License](#license)
46 |
47 | [pypi-badge]: https://img.shields.io/pypi/v/mcp.svg
48 | [pypi-url]: https://pypi.org/project/mcp/
49 | [mit-badge]: https://img.shields.io/pypi/l/mcp.svg
50 | [mit-url]: https://github.com/modelcontextprotocol/python-sdk/blob/main/LICENSE
51 | [python-badge]: https://img.shields.io/pypi/pyversions/mcp.svg
52 | [python-url]: https://www.python.org/downloads/
53 | [docs-badge]: https://img.shields.io/badge/docs-modelcontextprotocol.io-blue.svg
54 | [docs-url]: https://modelcontextprotocol.io
55 | [spec-badge]: https://img.shields.io/badge/spec-spec.modelcontextprotocol.io-blue.svg
56 | [spec-url]: https://spec.modelcontextprotocol.io
57 | [discussions-badge]: https://img.shields.io/github/discussions/modelcontextprotocol/python-sdk
58 | [discussions-url]: https://github.com/modelcontextprotocol/python-sdk/discussions
59 |
60 | ## Overview
61 |
62 | The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This Python SDK implements the full MCP specification, making it easy to:
63 |
64 | - Build MCP clients that can connect to any MCP server
65 | - Create MCP servers that expose resources, prompts and tools
66 | - Use standard transports like stdio and SSE
67 | - Handle all MCP protocol messages and lifecycle events
68 |
69 | ## Installation
70 |
71 | We recommend using [uv](https://docs.astral.sh/uv/) to manage your Python projects:
72 |
73 | ```bash
74 | uv add "mcp[cli]"
75 | ```
76 |
77 | Alternatively:
78 | ```bash
79 | pip install mcp
80 | ```
81 |
82 | ## Quickstart
83 |
84 | Let's create a simple MCP server that exposes a calculator tool and some data:
85 |
86 | ```python
87 | # server.py
88 | from mcp.server.fastmcp import FastMCP
89 |
90 | # Create an MCP server
91 | mcp = FastMCP("Demo")
92 |
93 | # Add an addition tool
94 | @mcp.tool()
95 | def add(a: int, b: int) -> int:
96 | """Add two numbers"""
97 | return a + b
98 |
99 | # Add a dynamic greeting resource
100 | @mcp.resource("greeting://{name}")
101 | def get_greeting(name: str) -> str:
102 | """Get a personalized greeting"""
103 | return f"Hello, {name}!"
104 | ```
105 |
106 | You can install this server in [Claude Desktop](https://claude.ai/download) and interact with it right away by running:
107 | ```bash
108 | mcp install server.py
109 | ```
110 |
111 | Alternatively, you can test it with the MCP Inspector:
112 | ```bash
113 | mcp dev server.py
114 | ```
115 |
116 | ## What is MCP?
117 |
118 | The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can:
119 |
120 | - Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
121 | - Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
122 | - Define interaction patterns through **Prompts** (reusable templates for LLM interactions)
123 | - And more!
124 |
125 | ## Core Concepts
126 |
127 | ### Server
128 |
129 | The FastMCP server is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:
130 |
131 | ```python
132 | # Add lifespan support for startup/shutdown with strong typing
133 | from dataclasses import dataclass
134 | from typing import AsyncIterator
135 | from mcp.server.fastmcp import FastMCP
136 |
137 | # Create a named server
138 | mcp = FastMCP("My App")
139 |
140 | # Specify dependencies for deployment and development
141 | mcp = FastMCP("My App", dependencies=["pandas", "numpy"])
142 |
143 | @dataclass
144 | class AppContext:
145 | db: Database # Replace with your actual DB type
146 |
147 | @asynccontextmanager
148 | async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
149 | """Manage application lifecycle with type-safe context"""
150 | try:
151 | # Initialize on startup
152 | await db.connect()
153 | yield AppContext(db=db)
154 | finally:
155 | # Cleanup on shutdown
156 | await db.disconnect()
157 |
158 | # Pass lifespan to server
159 | mcp = FastMCP("My App", lifespan=app_lifespan)
160 |
161 | # Access type-safe lifespan context in tools
162 | @mcp.tool()
163 | def query_db(ctx: Context) -> str:
164 | """Tool that uses initialized resources"""
165 | db = ctx.request_context.lifespan_context["db"]
166 | return db.query()
167 | ```
168 |
169 | ### Resources
170 |
171 | Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects:
172 |
173 | ```python
174 | @mcp.resource("config://app")
175 | def get_config() -> str:
176 | """Static configuration data"""
177 | return "App configuration here"
178 |
179 | @mcp.resource("users://{user_id}/profile")
180 | def get_user_profile(user_id: str) -> str:
181 | """Dynamic user data"""
182 | return f"Profile data for user {user_id}"
183 | ```
184 |
185 | ### Tools
186 |
187 | Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects:
188 |
189 | ```python
190 | @mcp.tool()
191 | def calculate_bmi(weight_kg: float, height_m: float) -> float:
192 | """Calculate BMI given weight in kg and height in meters"""
193 | return weight_kg / (height_m ** 2)
194 |
195 | @mcp.tool()
196 | async def fetch_weather(city: str) -> str:
197 | """Fetch current weather for a city"""
198 | async with httpx.AsyncClient() as client:
199 | response = await client.get(f"https://api.weather.com/{city}")
200 | return response.text
201 | ```
202 |
203 | ### Prompts
204 |
205 | Prompts are reusable templates that help LLMs interact with your server effectively:
206 |
207 | ```python
208 | @mcp.prompt()
209 | def review_code(code: str) -> str:
210 | return f"Please review this code:\n\n{code}"
211 |
212 | @mcp.prompt()
213 | def debug_error(error: str) -> list[Message]:
214 | return [
215 | UserMessage("I'm seeing this error:"),
216 | UserMessage(error),
217 | AssistantMessage("I'll help debug that. What have you tried so far?")
218 | ]
219 | ```
220 |
221 | ### Images
222 |
223 | FastMCP provides an `Image` class that automatically handles image data:
224 |
225 | ```python
226 | from mcp.server.fastmcp import FastMCP, Image
227 | from PIL import Image as PILImage
228 |
229 | @mcp.tool()
230 | def create_thumbnail(image_path: str) -> Image:
231 | """Create a thumbnail from an image"""
232 | img = PILImage.open(image_path)
233 | img.thumbnail((100, 100))
234 | return Image(data=img.tobytes(), format="png")
235 | ```
236 |
237 | ### Context
238 |
239 | The Context object gives your tools and resources access to MCP capabilities:
240 |
241 | ```python
242 | from mcp.server.fastmcp import FastMCP, Context
243 |
244 | @mcp.tool()
245 | async def long_task(files: list[str], ctx: Context) -> str:
246 | """Process multiple files with progress tracking"""
247 | for i, file in enumerate(files):
248 | ctx.info(f"Processing {file}")
249 | await ctx.report_progress(i, len(files))
250 | data, mime_type = await ctx.read_resource(f"file://{file}")
251 | return "Processing complete"
252 | ```
253 |
254 | ## Running Your Server
255 |
256 | ### Development Mode
257 |
258 | The fastest way to test and debug your server is with the MCP Inspector:
259 |
260 | ```bash
261 | mcp dev server.py
262 |
263 | # Add dependencies
264 | mcp dev server.py --with pandas --with numpy
265 |
266 | # Mount local code
267 | mcp dev server.py --with-editable .
268 | ```
269 |
270 | ### Claude Desktop Integration
271 |
272 | Once your server is ready, install it in Claude Desktop:
273 |
274 | ```bash
275 | mcp install server.py
276 |
277 | # Custom name
278 | mcp install server.py --name "My Analytics Server"
279 |
280 | # Environment variables
281 | mcp install server.py -v API_KEY=abc123 -v DB_URL=postgres://...
282 | mcp install server.py -f .env
283 | ```
284 |
285 | ### Direct Execution
286 |
287 | For advanced scenarios like custom deployments:
288 |
289 | ```python
290 | from mcp.server.fastmcp import FastMCP
291 |
292 | mcp = FastMCP("My App")
293 |
294 | if __name__ == "__main__":
295 | mcp.run()
296 | ```
297 |
298 | Run it with:
299 | ```bash
300 | python server.py
301 | # or
302 | mcp run server.py
303 | ```
304 |
305 | ## Examples
306 |
307 | ### Echo Server
308 |
309 | A simple server demonstrating resources, tools, and prompts:
310 |
311 | ```python
312 | from mcp.server.fastmcp import FastMCP
313 |
314 | mcp = FastMCP("Echo")
315 |
316 | @mcp.resource("echo://{message}")
317 | def echo_resource(message: str) -> str:
318 | """Echo a message as a resource"""
319 | return f"Resource echo: {message}"
320 |
321 | @mcp.tool()
322 | def echo_tool(message: str) -> str:
323 | """Echo a message as a tool"""
324 | return f"Tool echo: {message}"
325 |
326 | @mcp.prompt()
327 | def echo_prompt(message: str) -> str:
328 | """Create an echo prompt"""
329 | return f"Please process this message: {message}"
330 | ```
331 |
332 | ### SQLite Explorer
333 |
334 | A more complex example showing database integration:
335 |
336 | ```python
337 | from mcp.server.fastmcp import FastMCP
338 | import sqlite3
339 |
340 | mcp = FastMCP("SQLite Explorer")
341 |
342 | @mcp.resource("schema://main")
343 | def get_schema() -> str:
344 | """Provide the database schema as a resource"""
345 | conn = sqlite3.connect("database.db")
346 | schema = conn.execute(
347 | "SELECT sql FROM sqlite_master WHERE type='table'"
348 | ).fetchall()
349 | return "\n".join(sql[0] for sql in schema if sql[0])
350 |
351 | @mcp.tool()
352 | def query_data(sql: str) -> str:
353 | """Execute SQL queries safely"""
354 | conn = sqlite3.connect("database.db")
355 | try:
356 | result = conn.execute(sql).fetchall()
357 | return "\n".join(str(row) for row in result)
358 | except Exception as e:
359 | return f"Error: {str(e)}"
360 | ```
361 |
362 | ## Advanced Usage
363 |
364 | ### Low-Level Server
365 |
366 | For more control, you can use the low-level server implementation directly. This gives you full access to the protocol and allows you to customize every aspect of your server, including lifecycle management through the lifespan API:
367 |
368 | ```python
369 | from contextlib import asynccontextmanager
370 | from typing import AsyncIterator
371 |
372 | @asynccontextmanager
373 | async def server_lifespan(server: Server) -> AsyncIterator[dict]:
374 | """Manage server startup and shutdown lifecycle."""
375 | try:
376 | # Initialize resources on startup
377 | await db.connect()
378 | yield {"db": db}
379 | finally:
380 | # Clean up on shutdown
381 | await db.disconnect()
382 |
383 | # Pass lifespan to server
384 | server = Server("example-server", lifespan=server_lifespan)
385 |
386 | # Access lifespan context in handlers
387 | @server.call_tool()
388 | async def query_db(name: str, arguments: dict) -> list:
389 | ctx = server.request_context
390 | db = ctx.lifespan_context["db"]
391 | return await db.query(arguments["query"])
392 | ```
393 |
394 | The lifespan API provides:
395 | - A way to initialize resources when the server starts and clean them up when it stops
396 | - Access to initialized resources through the request context in handlers
397 | - Type-safe context passing between lifespan and request handlers
398 |
399 | ```python
400 | from mcp.server.lowlevel import Server, NotificationOptions
401 | from mcp.server.models import InitializationOptions
402 | import mcp.server.stdio
403 | import mcp.types as types
404 |
405 | # Create a server instance
406 | server = Server("example-server")
407 |
408 | @server.list_prompts()
409 | async def handle_list_prompts() -> list[types.Prompt]:
410 | return [
411 | types.Prompt(
412 | name="example-prompt",
413 | description="An example prompt template",
414 | arguments=[
415 | types.PromptArgument(
416 | name="arg1",
417 | description="Example argument",
418 | required=True
419 | )
420 | ]
421 | )
422 | ]
423 |
424 | @server.get_prompt()
425 | async def handle_get_prompt(
426 | name: str,
427 | arguments: dict[str, str] | None
428 | ) -> types.GetPromptResult:
429 | if name != "example-prompt":
430 | raise ValueError(f"Unknown prompt: {name}")
431 |
432 | return types.GetPromptResult(
433 | description="Example prompt",
434 | messages=[
435 | types.PromptMessage(
436 | role="user",
437 | content=types.TextContent(
438 | type="text",
439 | text="Example prompt text"
440 | )
441 | )
442 | ]
443 | )
444 |
445 | async def run():
446 | async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
447 | await server.run(
448 | read_stream,
449 | write_stream,
450 | InitializationOptions(
451 | server_name="example",
452 | server_version="0.1.0",
453 | capabilities=server.get_capabilities(
454 | notification_options=NotificationOptions(),
455 | experimental_capabilities={},
456 | )
457 | )
458 | )
459 |
460 | if __name__ == "__main__":
461 | import asyncio
462 | asyncio.run(run())
463 | ```
464 |
465 | ### Writing MCP Clients
466 |
467 | The SDK provides a high-level client interface for connecting to MCP servers:
468 |
469 | ```python
470 | from mcp import ClientSession, StdioServerParameters
471 | from mcp.client.stdio import stdio_client
472 |
473 | # Create server parameters for stdio connection
474 | server_params = StdioServerParameters(
475 | command="python", # Executable
476 | args=["example_server.py"], # Optional command line arguments
477 | env=None # Optional environment variables
478 | )
479 |
480 | # Optional: create a sampling callback
481 | async def handle_sampling_message(message: types.CreateMessageRequestParams) -> types.CreateMessageResult:
482 | return types.CreateMessageResult(
483 | role="assistant",
484 | content=types.TextContent(
485 | type="text",
486 | text="Hello, world! from model",
487 | ),
488 | model="gpt-3.5-turbo",
489 | stopReason="endTurn",
490 | )
491 |
492 | async def run():
493 | async with stdio_client(server_params) as (read, write):
494 | async with ClientSession(read, write, sampling_callback=handle_sampling_message) as session:
495 | # Initialize the connection
496 | await session.initialize()
497 |
498 | # List available prompts
499 | prompts = await session.list_prompts()
500 |
501 | # Get a prompt
502 | prompt = await session.get_prompt("example-prompt", arguments={"arg1": "value"})
503 |
504 | # List available resources
505 | resources = await session.list_resources()
506 |
507 | # List available tools
508 | tools = await session.list_tools()
509 |
510 | # Read a resource
511 | content, mime_type = await session.read_resource("file://some/path")
512 |
513 | # Call a tool
514 | result = await session.call_tool("tool-name", arguments={"arg1": "value"})
515 |
516 | if __name__ == "__main__":
517 | import asyncio
518 | asyncio.run(run())
519 | ```
520 |
521 | ### MCP Primitives
522 |
523 | The MCP protocol defines three core primitives that servers can implement:
524 |
525 | | Primitive | Control | Description | Example Use |
526 | |-----------|-----------------------|-----------------------------------------------------|------------------------------|
527 | | Prompts | User-controlled | Interactive templates invoked by user choice | Slash commands, menu options |
528 | | Resources | Application-controlled| Contextual data managed by the client application | File contents, API responses |
529 | | Tools | Model-controlled | Functions exposed to the LLM to take actions | API calls, data updates |
530 |
531 | ### Server Capabilities
532 |
533 | MCP servers declare capabilities during initialization:
534 |
535 | | Capability | Feature Flag | Description |
536 | |-------------|------------------------------|------------------------------------|
537 | | `prompts` | `listChanged` | Prompt template management |
538 | | `resources` | `subscribe`<br/>`listChanged`| Resource exposure and updates |
539 | | `tools` | `listChanged` | Tool discovery and execution |
540 | | `logging` | - | Server logging configuration |
541 | | `completion`| - | Argument completion suggestions |
542 |
543 | ## Documentation
544 |
545 | - [Model Context Protocol documentation](https://modelcontextprotocol.io)
546 | - [Model Context Protocol specification](https://spec.modelcontextprotocol.io)
547 | - [Officially supported servers](https://github.com/modelcontextprotocol/servers)
548 |
549 | ## Contributing
550 |
551 | We are passionate about supporting contributors of all levels of experience and would love to see you get involved in the project. See the [contributing guide](CONTRIBUTING.md) to get started.
552 |
553 | ## License
554 |
555 | This project is licensed under the MIT License - see the LICENSE file for details.
```