initial server
This commit is contained in:
-168
@@ -3,171 +3,3 @@ icon: lucide/rocket
|
||||
---
|
||||
|
||||
# Get started
|
||||
|
||||
For full documentation visit [zensical.org](https://zensical.org/docs/).
|
||||
|
||||
## Commands
|
||||
|
||||
* [`zensical new`][new] - Create a new project
|
||||
* [`zensical serve`][serve] - Start local web server
|
||||
* [`zensical build`][build] - Build your site
|
||||
|
||||
[new]: https://zensical.org/docs/usage/new/
|
||||
[serve]: https://zensical.org/docs/usage/preview/
|
||||
[build]: https://zensical.org/docs/usage/build/
|
||||
|
||||
## Examples
|
||||
|
||||
### Admonitions
|
||||
|
||||
> Go to [documentation](https://zensical.org/docs/authoring/admonitions/)
|
||||
|
||||
!!! note
|
||||
|
||||
This is a **note** admonition. Use it to provide helpful information.
|
||||
|
||||
!!! warning
|
||||
|
||||
This is a **warning** admonition. Be careful!
|
||||
|
||||
### Details
|
||||
|
||||
> Go to [documentation](https://zensical.org/docs/authoring/admonitions/#collapsible-blocks)
|
||||
|
||||
??? info "Click to expand for more info"
|
||||
|
||||
This content is hidden until you click to expand it.
|
||||
Great for FAQs or long explanations.
|
||||
|
||||
## Code Blocks
|
||||
|
||||
> Go to [documentation](https://zensical.org/docs/authoring/code-blocks/)
|
||||
|
||||
``` python hl_lines="2" title="Code blocks"
|
||||
def greet(name):
|
||||
print(f"Hello, {name}!") # (1)!
|
||||
|
||||
greet("Python")
|
||||
```
|
||||
|
||||
1. > Go to [documentation](https://zensical.org/docs/authoring/code-blocks/#code-annotations)
|
||||
|
||||
Code annotations allow to attach notes to lines of code.
|
||||
|
||||
Code can also be highlighted inline: `#!python print("Hello, Python!")`.
|
||||
|
||||
## Content tabs
|
||||
|
||||
> Go to [documentation](https://zensical.org/docs/authoring/content-tabs/)
|
||||
|
||||
=== "Python"
|
||||
|
||||
``` python
|
||||
print("Hello from Python!")
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
``` rs
|
||||
println!("Hello from Rust!");
|
||||
```
|
||||
|
||||
## Diagrams
|
||||
|
||||
> Go to [documentation](https://zensical.org/docs/authoring/diagrams/)
|
||||
|
||||
``` mermaid
|
||||
graph LR
|
||||
A[Start] --> B{Error?};
|
||||
B -->|Yes| C[Hmm...];
|
||||
C --> D[Debug];
|
||||
D --> B;
|
||||
B ---->|No| E[Yay!];
|
||||
```
|
||||
|
||||
## Footnotes
|
||||
|
||||
> Go to [documentation](https://zensical.org/docs/authoring/footnotes/)
|
||||
|
||||
Here's a sentence with a footnote.[^1]
|
||||
|
||||
Hover it, to see a tooltip.
|
||||
|
||||
[^1]: This is the footnote.
|
||||
|
||||
|
||||
## Formatting
|
||||
|
||||
> Go to [documentation](https://zensical.org/docs/authoring/formatting/)
|
||||
|
||||
- ==This was marked (highlight)==
|
||||
- ^^This was inserted (underline)^^
|
||||
- ~~This was deleted (strikethrough)~~
|
||||
- H~2~O
|
||||
- A^T^A
|
||||
- ++ctrl+alt+del++
|
||||
|
||||
## Icons, Emojis
|
||||
|
||||
> Go to [documentation](https://zensical.org/docs/authoring/icons-emojis/)
|
||||
|
||||
* :sparkles: `:sparkles:`
|
||||
* :rocket: `:rocket:`
|
||||
* :tada: `:tada:`
|
||||
* :memo: `:memo:`
|
||||
* :eyes: `:eyes:`
|
||||
|
||||
## Maths
|
||||
|
||||
> Go to [documentation](https://zensical.org/docs/authoring/math/)
|
||||
|
||||
$$
|
||||
\cos x=\sum_{k=0}^{\infty}\frac{(-1)^k}{(2k)!}x^{2k}
|
||||
$$
|
||||
|
||||
!!! warning "Needs configuration"
|
||||
Note that MathJax is included via a `script` tag on this page and is not
|
||||
configured in the generated default configuration to avoid including it
|
||||
in a pages that do not need it. See the documentation for details on how
|
||||
to configure it on all your pages if they are more Maths-heavy than these
|
||||
simple starter pages.
|
||||
|
||||
<script id="MathJax-script" src="https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js"></script>
|
||||
<script>
|
||||
window.MathJax = {
|
||||
tex: {
|
||||
inlineMath: [["\\(", "\\)"]],
|
||||
displayMath: [["\\[", "\\]"]],
|
||||
processEscapes: true,
|
||||
processEnvironments: true
|
||||
},
|
||||
options: {
|
||||
ignoreHtmlClass: ".*|",
|
||||
processHtmlClass: "arithmatex"
|
||||
}
|
||||
};
|
||||
|
||||
document$.subscribe(() => {
|
||||
MathJax.startup.output.clearCache()
|
||||
MathJax.typesetClear()
|
||||
MathJax.texReset()
|
||||
MathJax.typesetPromise()
|
||||
})
|
||||
</script>
|
||||
|
||||
## Task Lists
|
||||
|
||||
> Go to [documentation](https://zensical.org/docs/authoring/lists/#using-task-lists)
|
||||
|
||||
* [x] Install Zensical
|
||||
* [x] Configure `zensical.toml`
|
||||
* [x] Write amazing documentation
|
||||
* [ ] Deploy anywhere
|
||||
|
||||
## Tooltips
|
||||
|
||||
> Go to [documentation](https://zensical.org/docs/authoring/tooltips/)
|
||||
|
||||
[Hover me][example]
|
||||
|
||||
[example]: https://example.com "I'm a tooltip!"
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
---
|
||||
icon: simple/markdown
|
||||
---
|
||||
|
||||
# Markdown in 5min
|
||||
|
||||
## Headers
|
||||
|
||||
```
|
||||
# H1 Header
|
||||
## H2 Header
|
||||
### H3 Header
|
||||
#### H4 Header
|
||||
##### H5 Header
|
||||
###### H6 Header
|
||||
```
|
||||
|
||||
## Text formatting
|
||||
|
||||
```
|
||||
**bold text**
|
||||
*italic text*
|
||||
***bold and italic***
|
||||
~~strikethrough~~
|
||||
`inline code`
|
||||
```
|
||||
|
||||
## Links and images
|
||||
|
||||
```
|
||||
[Link text](https://example.com)
|
||||
[Link with title](https://example.com "Hover title")
|
||||

|
||||

|
||||
```
|
||||
|
||||
## Lists
|
||||
|
||||
```
|
||||
Unordered:
|
||||
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Nested item
|
||||
|
||||
Ordered:
|
||||
|
||||
1. First item
|
||||
2. Second item
|
||||
3. Third item
|
||||
```
|
||||
|
||||
## Blockquotes
|
||||
|
||||
```
|
||||
> This is a blockquote
|
||||
> Multiple lines
|
||||
>> Nested quote
|
||||
```
|
||||
|
||||
## Code blocks
|
||||
|
||||
````
|
||||
```javascript
|
||||
function hello() {
|
||||
console.log("Hello, world!");
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
## Tables
|
||||
|
||||
```
|
||||
| Header 1 | Header 2 | Header 3 |
|
||||
|----------|----------|----------|
|
||||
| Row 1 | Data | Data |
|
||||
| Row 2 | Data | Data |
|
||||
```
|
||||
|
||||
## Horizontal rule
|
||||
|
||||
```
|
||||
---
|
||||
or
|
||||
***
|
||||
or
|
||||
___
|
||||
```
|
||||
|
||||
## Task lists
|
||||
|
||||
```
|
||||
- [x] Completed task
|
||||
- [ ] Incomplete task
|
||||
- [ ] Another task
|
||||
```
|
||||
|
||||
## Escaping characters
|
||||
|
||||
```
|
||||
Use backslash to escape: \* \_ \# \`
|
||||
```
|
||||
|
||||
## Line breaks
|
||||
|
||||
```
|
||||
End a line with two spaces
|
||||
to create a line break.
|
||||
|
||||
Or use a blank line for a new paragraph.
|
||||
```
|
||||
@@ -0,0 +1,408 @@
|
||||
For a modern Python project in 2026, I would treat the MCP server exactly like any other deployable service:
|
||||
|
||||
* `src/` layout
|
||||
* `uv` for dependency management
|
||||
* skill modules as importable Python packages
|
||||
* FastMCP composition via `mount()`
|
||||
* Docker image built from the root package
|
||||
* resources/prompts kept close to the skill that owns them
|
||||
* avoid giant decorator files with hundreds of tools
|
||||
|
||||
FastMCP's mounting/composition model is specifically intended for this sort of modular organization. ([FastMCP][1])
|
||||
|
||||
I'd structure it something like:
|
||||
|
||||
```text
|
||||
personal-mcp/
|
||||
│
|
||||
├── pyproject.toml
|
||||
├── uv.lock
|
||||
├── README.md
|
||||
│
|
||||
├── Dockerfile
|
||||
├── .dockerignore
|
||||
│
|
||||
├── src/
|
||||
│ └── personal_mcp/
|
||||
│
|
||||
│ ├── __init__.py
|
||||
│ │
|
||||
│ ├── main.py
|
||||
│ │ # Creates the root MCP server
|
||||
│ │ # Mounts all skills
|
||||
│ │
|
||||
│ ├── settings.py
|
||||
│ │ # Pydantic settings
|
||||
│ │
|
||||
│ ├── graph/
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── capability_graph.py
|
||||
│ │
|
||||
│ ├── catalog/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── resources.py
|
||||
│ │ └── tools.py
|
||||
│ │
|
||||
│ └── skills/
|
||||
│
|
||||
│ ├── __init__.py
|
||||
│ │
|
||||
│ ├── nixos/
|
||||
│ │
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── server.py
|
||||
│ │ │
|
||||
│ │ ├── tools/
|
||||
│ │ │ └── rebuild.py
|
||||
│ │ │
|
||||
│ │ ├── prompts/
|
||||
│ │ │ └── expert.py
|
||||
│ │ │
|
||||
│ │ ├── resources/
|
||||
│ │ │ ├── overview.py
|
||||
│ │ │ └── troubleshooting.py
|
||||
│ │ │
|
||||
│ │ └── metadata.yaml
|
||||
│ │
|
||||
│ ├── homelab/
|
||||
│ │
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── server.py
|
||||
│ │ ├── tools/
|
||||
│ │ ├── prompts/
|
||||
│ │ ├── resources/
|
||||
│ │ └── metadata.yaml
|
||||
│ │
|
||||
│ └── knowledge_base/
|
||||
│ ├── __init__.py
|
||||
│ ├── server.py
|
||||
│ ├── tools/
|
||||
│ ├── prompts/
|
||||
│ ├── resources/
|
||||
│ └── metadata.yaml
|
||||
│
|
||||
└── tests/
|
||||
├── test_catalog.py
|
||||
├── test_nixos.py
|
||||
└── test_homelab.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## main.py
|
||||
|
||||
This is intentionally boring.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
||||
from personal_mcp.catalog.server import catalog_server
|
||||
from personal_mcp.skills.nixos.server import nixos_server
|
||||
from personal_mcp.skills.homelab.server import homelab_server
|
||||
|
||||
mcp = FastMCP("Personal MCP")
|
||||
|
||||
mcp.mount(catalog_server, namespace="catalog")
|
||||
mcp.mount(nixos_server, namespace="nixos")
|
||||
mcp.mount(homelab_server, namespace="homelab")
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
FastMCP namespaces resources, prompts, and tools automatically when mounted. ([FastMCP][2])
|
||||
|
||||
---
|
||||
|
||||
## Skill Metadata
|
||||
|
||||
Every skill gets metadata.
|
||||
|
||||
```yaml
|
||||
# skills/nixos/metadata.yaml
|
||||
|
||||
id: nixos
|
||||
|
||||
name: NixOS Administration
|
||||
|
||||
description: |
|
||||
Tools and guidance for managing NixOS systems.
|
||||
|
||||
tags:
|
||||
- nix
|
||||
- linux
|
||||
- systemd
|
||||
|
||||
capabilities:
|
||||
- package_management
|
||||
- service_debugging
|
||||
- flakes
|
||||
|
||||
depends_on:
|
||||
- certificates
|
||||
```
|
||||
|
||||
The catalog layer reads these files.
|
||||
|
||||
---
|
||||
|
||||
## Skill Server
|
||||
|
||||
Each skill owns its own MCP instance.
|
||||
|
||||
```python
|
||||
# skills/nixos/server.py
|
||||
|
||||
from fastmcp import FastMCP
|
||||
|
||||
nixos_server = FastMCP(
|
||||
"NixOS"
|
||||
)
|
||||
|
||||
from .tools.rebuild import *
|
||||
from .resources.overview import *
|
||||
from .prompts.expert import *
|
||||
```
|
||||
|
||||
The pattern is:
|
||||
|
||||
```text
|
||||
One skill
|
||||
=
|
||||
One FastMCP server
|
||||
```
|
||||
|
||||
This keeps ownership obvious.
|
||||
|
||||
---
|
||||
|
||||
## Skill Resource
|
||||
|
||||
Resources are your discovery layer.
|
||||
|
||||
```python
|
||||
# resources/overview.py
|
||||
|
||||
from .server import nixos_server
|
||||
|
||||
|
||||
@nixos_server.resource(
|
||||
"resource://skills/nixos"
|
||||
)
|
||||
def nixos_skill_description():
|
||||
return """
|
||||
NixOS administration skill.
|
||||
|
||||
Provides:
|
||||
- flake troubleshooting
|
||||
- package search
|
||||
- service diagnostics
|
||||
- generation management
|
||||
"""
|
||||
```
|
||||
|
||||
Resources are exactly what MCP intends for exposing contextual information and data to clients. ([fastmcp.mintlify.app][3])
|
||||
|
||||
---
|
||||
|
||||
## Skill Prompt
|
||||
|
||||
```python
|
||||
# prompts/expert.py
|
||||
|
||||
from .server import nixos_server
|
||||
|
||||
|
||||
@nixos_server.prompt
|
||||
def nixos_expert():
|
||||
return """
|
||||
Act as an expert NixOS administrator.
|
||||
|
||||
Prefer:
|
||||
- flakes
|
||||
- declarative configuration
|
||||
- modern systemd patterns
|
||||
|
||||
Avoid:
|
||||
- imperative package management
|
||||
"""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Skill Tool
|
||||
|
||||
```python
|
||||
# tools/rebuild.py
|
||||
|
||||
from .server import nixos_server
|
||||
|
||||
|
||||
@nixos_server.tool
|
||||
def explain_rebuild():
|
||||
"""
|
||||
Explain a nixos-rebuild failure.
|
||||
"""
|
||||
|
||||
return {
|
||||
"workflow": [
|
||||
"Inspect journal",
|
||||
"Check evaluation errors",
|
||||
"Verify inputs",
|
||||
"Retry build"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Catalog Layer
|
||||
|
||||
The catalog should be its own server.
|
||||
|
||||
Not a skill.
|
||||
|
||||
Its job is discovery.
|
||||
|
||||
```python
|
||||
catalog/
|
||||
├── server.py
|
||||
├── tools.py
|
||||
└── resources.py
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
# catalog/tools.py
|
||||
|
||||
@catalog_server.tool
|
||||
def list_skills():
|
||||
|
||||
return [
|
||||
"nixos",
|
||||
"homelab",
|
||||
"knowledge_base"
|
||||
]
|
||||
```
|
||||
|
||||
```python
|
||||
@catalog_server.tool
|
||||
def describe_skill(
|
||||
name: str
|
||||
):
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Capability Graph
|
||||
|
||||
This is where NetworkX belongs.
|
||||
|
||||
```python
|
||||
# graph/capability_graph.py
|
||||
|
||||
import networkx as nx
|
||||
|
||||
graph = nx.DiGraph()
|
||||
|
||||
graph.add_edge(
|
||||
"homelab",
|
||||
"certificates",
|
||||
relation="depends_on"
|
||||
)
|
||||
|
||||
graph.add_edge(
|
||||
"nixos",
|
||||
"homelab",
|
||||
relation="manages"
|
||||
)
|
||||
```
|
||||
|
||||
Expose it as:
|
||||
|
||||
```python
|
||||
resource://skills/graph
|
||||
resource://skills/nixos/dependencies
|
||||
```
|
||||
|
||||
Now agents can discover related capabilities without hardcoding them.
|
||||
|
||||
---
|
||||
|
||||
## pyproject.toml
|
||||
|
||||
Minimal UV setup:
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "personal-mcp"
|
||||
version = "0.1.0"
|
||||
|
||||
dependencies = [
|
||||
"fastmcp",
|
||||
"networkx",
|
||||
"pydantic-settings",
|
||||
"pyyaml",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
personal-mcp = "personal_mcp.main:mcp"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Docker
|
||||
|
||||
```dockerfile
|
||||
FROM python:3.13-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY pyproject.toml uv.lock ./
|
||||
|
||||
RUN pip install uv
|
||||
RUN uv sync --frozen
|
||||
|
||||
COPY src ./src
|
||||
|
||||
CMD ["uv", "run", "python", "-m", "personal_mcp.main"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
If I were building this for long-term growth, I'd make one additional change: treat every skill as a Python package with a common interface:
|
||||
|
||||
```python
|
||||
class Skill:
|
||||
id: str
|
||||
metadata: SkillMetadata
|
||||
|
||||
def create_server(self) -> FastMCP:
|
||||
...
|
||||
```
|
||||
|
||||
Then `main.py` simply discovers skills dynamically:
|
||||
|
||||
```python
|
||||
for skill in discover_skills():
|
||||
mcp.mount(
|
||||
skill.create_server(),
|
||||
namespace=skill.id,
|
||||
)
|
||||
```
|
||||
|
||||
At that point adding a new skill becomes:
|
||||
|
||||
```text
|
||||
mkdir skills/new_skill
|
||||
drop in metadata.yaml
|
||||
implement Skill
|
||||
```
|
||||
|
||||
and the server automatically exposes it. That's the closest thing to a plugin architecture while still remaining very Pythonic and Docker-friendly.
|
||||
|
||||
[1]: https://fastmcp.wiki/en/servers/composition?utm_source=chatgpt.com "Server Composition - FastMCP"
|
||||
[2]: https://gofastmcp.com/servers/composition?utm_source=chatgpt.com "Composing Servers - FastMCP"
|
||||
[3]: https://fastmcp.mintlify.app/servers/resources?utm_source=chatgpt.com "Resources & Templates - FastMCP"
|
||||
Reference in New Issue
Block a user