initial server
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
.venv
|
||||||
|
__pycache__
|
||||||
|
.cache*
|
||||||
-168
@@ -3,171 +3,3 @@ icon: lucide/rocket
|
|||||||
---
|
---
|
||||||
|
|
||||||
# Get started
|
# 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"
|
||||||
@@ -3,5 +3,16 @@ name = "prompts"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"fastmcp>=2.10.0",
|
||||||
"zensical>=0.0.45",
|
"zensical>=0.0.45",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
personal-mcp = "personal_mcp.main:main"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["src/personal_mcp"]
|
||||||
|
|||||||
+21
-5
@@ -41,9 +41,10 @@ Produce:
|
|||||||
1. Frame the baseline architecture.
|
1. Frame the baseline architecture.
|
||||||
2. Choose optional extensions (DB, AI, docs) using decision points below.
|
2. Choose optional extensions (DB, AI, docs) using decision points below.
|
||||||
3. Map modules, dependencies, and key boundaries.
|
3. Map modules, dependencies, and key boundaries.
|
||||||
4. Define key functions/classes and configuration surfaces.
|
4. Define async behavior and UI responsiveness expectations.
|
||||||
5. Produce phased checklist with rollout or migration notes when relevant.
|
5. Define key functions/classes and configuration surfaces.
|
||||||
6. Run completion checks before returning.
|
6. Produce phased checklist with rollout or migration notes when relevant.
|
||||||
|
7. Run completion checks before returning.
|
||||||
|
|
||||||
### 1) Baseline architecture
|
### 1) Baseline architecture
|
||||||
|
|
||||||
@@ -131,15 +132,27 @@ Prefer:
|
|||||||
|
|
||||||
Avoid reverse imports from services into API or UI modules.
|
Avoid reverse imports from services into API or UI modules.
|
||||||
|
|
||||||
### 5) Testing minimums
|
### 5) Async and UI responsiveness rules
|
||||||
|
|
||||||
|
- Prefer `async def` for page handlers, service methods, and integrations when the call path includes I/O.
|
||||||
|
- Use non-blocking clients/libraries where possible so long-running I/O does not freeze UI updates.
|
||||||
|
- Do not run blocking calls (`time.sleep`, blocking HTTP/database clients) in UI event handlers.
|
||||||
|
- For heavy CPU work, offload to worker/background execution and keep the UI loop free.
|
||||||
|
- Show progress states for long actions (disable action button, show spinner/progress text, re-enable on completion).
|
||||||
|
- Stream or chunk incremental results to the UI when workflows are multi-step or long-running.
|
||||||
|
- Keep cancellation and timeout behavior explicit for user-triggered long tasks.
|
||||||
|
- Ensure exceptions from async tasks are surfaced with user-friendly feedback and logged for diagnostics.
|
||||||
|
|
||||||
|
### 6) Testing minimums
|
||||||
|
|
||||||
- Test FastAPI health route behavior.
|
- Test FastAPI health route behavior.
|
||||||
- Test page registration wiring.
|
- Test page registration wiring.
|
||||||
- If DB enabled: session lifecycle and rollback behavior tests.
|
- If DB enabled: session lifecycle and rollback behavior tests.
|
||||||
- If AI enabled: graph happy path and interrupt/resume coverage.
|
- If AI enabled: graph happy path and interrupt/resume coverage.
|
||||||
- If docs enabled: mounted docs route returns index page.
|
- If docs enabled: mounted docs route returns index page.
|
||||||
|
- For async flows: test long-running actions preserve UI responsiveness (loading state, completion state, and error state).
|
||||||
|
|
||||||
### 6) Styling architecture
|
### 7) Styling architecture
|
||||||
|
|
||||||
- Keep structure and layout in Python modules using NiceGUI class composition.
|
- Keep structure and layout in Python modules using NiceGUI class composition.
|
||||||
- Keep visual polish in shared CSS files, loaded once at startup.
|
- Keep visual polish in shared CSS files, loaded once at startup.
|
||||||
@@ -151,6 +164,7 @@ Avoid reverse imports from services into API or UI modules.
|
|||||||
- Pages are modularized (not single-file UI).
|
- Pages are modularized (not single-file UI).
|
||||||
- Health endpoint exists on FastAPI side.
|
- Health endpoint exists on FastAPI side.
|
||||||
- Dependency direction is clean and one-way.
|
- Dependency direction is clean and one-way.
|
||||||
|
- Async-first guidance is applied where I/O exists, with explicit non-blocking UX states.
|
||||||
- Optional DB/AI/docs decisions are explicit and reflected in structure.
|
- Optional DB/AI/docs decisions are explicit and reflected in structure.
|
||||||
- Output includes architecture summary and package-organized checklist.
|
- Output includes architecture summary and package-organized checklist.
|
||||||
|
|
||||||
@@ -171,6 +185,8 @@ Return:
|
|||||||
|
|
||||||
- Do not collapse all pages into one file.
|
- Do not collapse all pages into one file.
|
||||||
- Do not use globals or implicit global side effects.
|
- Do not use globals or implicit global side effects.
|
||||||
|
- Do not block UI event handlers with synchronous I/O or long CPU tasks.
|
||||||
|
- Always define loading/progress/error states for long user-triggered actions.
|
||||||
- Keep code minimal but production-minded.
|
- Keep code minimal but production-minded.
|
||||||
- Prefer clarity and maintainability over clever abstractions.
|
- Prefer clarity and maintainability over clever abstractions.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
name: python-logging-dictconfig
|
||||||
|
description: 'Set up idiomatic Python logging with logging.config.dictConfig. Use when creating or refactoring logging setup, standardizing handlers/formatters, and enforcing centralized config.'
|
||||||
|
argument-hint: 'Target context (single script, package, FastAPI app, or CLI) and desired log destinations'
|
||||||
|
---
|
||||||
|
|
||||||
|
# Idiomatic Python Logging with dictConfig
|
||||||
|
|
||||||
|
Use this skill to produce a minimal, centralized logging setup using `logging.config.dictConfig`.
|
||||||
|
|
||||||
|
Load references only when needed:
|
||||||
|
- Python logging overview and hierarchy: [./references/python-logging-docs.md](./references/python-logging-docs.md)
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
- A project configures logging ad hoc with `basicConfig` across multiple modules.
|
||||||
|
- You need one canonical logging configuration for app startup.
|
||||||
|
- You need consistent formatting and levels across console/file handlers.
|
||||||
|
- You want library modules to use named loggers without configuring logging themselves.
|
||||||
|
|
||||||
|
## Inputs To Collect
|
||||||
|
1. Runtime type: script, library, web app, worker, CLI.
|
||||||
|
2. Destinations: stdout only, file only, or both.
|
||||||
|
3. Desired default level: `INFO`, `DEBUG`, etc.
|
||||||
|
4. Whether third-party loggers should be tuned (for example `uvicorn`, `sqlalchemy`).
|
||||||
|
|
||||||
|
If missing, assume:
|
||||||
|
- stdout handler
|
||||||
|
- human-readable formatter
|
||||||
|
- root level `INFO`
|
||||||
|
- `disable_existing_loggers: False`
|
||||||
|
|
||||||
|
## Procedure
|
||||||
|
1. Define a single `LOGGING` dictionary in one startup-oriented module (for example `logging_config.py`).
|
||||||
|
2. Include `version: 1` and set `disable_existing_loggers: False` unless there is a specific reason to silence existing loggers.
|
||||||
|
3. Define formatters first, then handlers, then logger routing (`root` and optional named `loggers`).
|
||||||
|
4. Use `logging.config.dictConfig(LOGGING)` exactly once during application startup.
|
||||||
|
5. In all modules, get loggers via `logger = logging.getLogger(__name__)` and never call `basicConfig`.
|
||||||
|
6. Keep libraries configuration-free: libraries should emit logs, applications decide routing.
|
||||||
|
7. Verify behavior with a quick smoke check at multiple levels (`DEBUG`, `INFO`, `WARNING`, `ERROR`).
|
||||||
|
|
||||||
|
## Minimal Baseline Template
|
||||||
|
```python
|
||||||
|
# logging_config.py
|
||||||
|
from logging.config import dictConfig
|
||||||
|
|
||||||
|
LOGGING = {
|
||||||
|
"version": 1,
|
||||||
|
"disable_existing_loggers": False,
|
||||||
|
"formatters": {
|
||||||
|
"standard": {
|
||||||
|
"format": "%(asctime)s %(levelname)s %(name)s: %(message)s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers": {
|
||||||
|
"console": {
|
||||||
|
"class": "logging.StreamHandler",
|
||||||
|
"level": "INFO",
|
||||||
|
"formatter": "standard",
|
||||||
|
"stream": "ext://sys.stdout",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"level": "INFO",
|
||||||
|
"handlers": ["console"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def configure_logging() -> None:
|
||||||
|
dictConfig(LOGGING)
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
# app startup
|
||||||
|
from .logging_config import configure_logging
|
||||||
|
|
||||||
|
configure_logging()
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
# any module
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.info("module initialized")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Completion Checks
|
||||||
|
1. `dictConfig` is called once at startup, not per module.
|
||||||
|
2. No `basicConfig` calls remain.
|
||||||
|
3. Modules use `getLogger(__name__)`.
|
||||||
|
4. Logs appear at expected level and destination.
|
||||||
|
5. Third-party logger noise is intentionally configured or left at defaults.
|
||||||
|
|
||||||
|
## Branching Guidance
|
||||||
|
- If structured logs are required: switch formatter output to JSON while keeping `dictConfig` topology unchanged.
|
||||||
|
- If both console and file output are needed: add a file handler and attach it to `root`.
|
||||||
|
- If a specific framework logger is too noisy: add a named logger override under `loggers`.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Python Logging References
|
||||||
|
|
||||||
|
Use these official Python docs when applying this skill.
|
||||||
|
|
||||||
|
## Core Documentation
|
||||||
|
- Logging HOWTO: https://docs.python.org/3/howto/logging.html
|
||||||
|
- Logging Cookbook: https://docs.python.org/3/howto/logging-cookbook.html
|
||||||
|
- logging API reference: https://docs.python.org/3/library/logging.html
|
||||||
|
- logging.config reference: https://docs.python.org/3/library/logging.config.html
|
||||||
|
|
||||||
|
## dictConfig-Specific
|
||||||
|
- Dictionary schema details (`version`, formatters, handlers, loggers, root): https://docs.python.org/3/library/logging.config.html#logging-config-dictschema
|
||||||
|
- `logging.config.dictConfig` function: https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig
|
||||||
|
|
||||||
|
## Practical Notes
|
||||||
|
- Prefer app-level centralized config with one startup call to `dictConfig`.
|
||||||
|
- In modules, use `logging.getLogger(__name__)`.
|
||||||
|
- Avoid calling `basicConfig` in libraries or scattered modules.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Personal MCP server package."""
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
from fastmcp import FastMCP
|
||||||
|
|
||||||
|
from personal_mcp.skills.fastapi_uv_docker.server import fastapi_uv_docker_server
|
||||||
|
from personal_mcp.skills.pytest_scaffolding.server import pytest_scaffolding_server
|
||||||
|
from personal_mcp.skills.python_logging_dictconfig.server import (
|
||||||
|
python_logging_dictconfig_server,
|
||||||
|
)
|
||||||
|
|
||||||
|
mcp = FastMCP("personal-mcp")
|
||||||
|
|
||||||
|
mcp.mount(pytest_scaffolding_server, namespace="pytest_scaffolding")
|
||||||
|
mcp.mount(python_logging_dictconfig_server, namespace="python_logging_dictconfig")
|
||||||
|
mcp.mount(fastapi_uv_docker_server, namespace="fastapi_uv_docker")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Run the root MCP server."""
|
||||||
|
mcp.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Mounted skill servers for the personal MCP app."""
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from personal_mcp.skills.fastapi_uv_docker.server import fastapi_uv_docker_server
|
||||||
|
|
||||||
|
__all__ = ["fastapi_uv_docker_server"]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
id: fastapi-uv-docker
|
||||||
|
name: FastAPI uv Docker
|
||||||
|
description: Provide fast migration guidance to FastAPI plus uv plus Docker.
|
||||||
|
tags:
|
||||||
|
- fastapi
|
||||||
|
- uv
|
||||||
|
- docker
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
from fastmcp import FastMCP
|
||||||
|
|
||||||
|
fastapi_uv_docker_server = FastMCP("fastapi-uv-docker")
|
||||||
|
|
||||||
|
|
||||||
|
@fastapi_uv_docker_server.tool()
|
||||||
|
def fastapi_uv_docker_mvp_checklist(current_state: str = "bare python project") -> list[str]:
|
||||||
|
"""Return a compact migration checklist for FastAPI + uv + Docker."""
|
||||||
|
return [
|
||||||
|
f"Current state: {current_state}",
|
||||||
|
"Create src/ package layout for the FastAPI app.",
|
||||||
|
"Manage dependencies with uv and keep uv.lock committed.",
|
||||||
|
"Use an app factory and lifespan hooks.",
|
||||||
|
"Add /healthz endpoint for operational checks.",
|
||||||
|
"Build with a multi-stage Dockerfile and run as non-root user.",
|
||||||
|
]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from personal_mcp.skills.pytest_scaffolding.server import pytest_scaffolding_server
|
||||||
|
|
||||||
|
__all__ = ["pytest_scaffolding_server"]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
id: pytest-scaffolding
|
||||||
|
name: Pytest Scaffolding
|
||||||
|
description: Scaffold a maintainable pytest structure quickly.
|
||||||
|
tags:
|
||||||
|
- pytest
|
||||||
|
- testing
|
||||||
|
- python
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
from fastmcp import FastMCP
|
||||||
|
|
||||||
|
pytest_scaffolding_server = FastMCP("pytest-scaffolding")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest_scaffolding_server.tool()
|
||||||
|
def propose_pytest_mvp_tree(target_scope: str = "src/") -> str:
|
||||||
|
"""Return a minimal test scaffold plan for a target scope."""
|
||||||
|
return (
|
||||||
|
f"MVP pytest scaffold for {target_scope}:\n"
|
||||||
|
"1. Mirror the source subtree under tests/.\n"
|
||||||
|
"2. Add one happy-path test and one edge-case test per core module.\n"
|
||||||
|
"3. Keep fast tests isolated from integration/external dependencies.\n"
|
||||||
|
"4. Use uv run pytest as the canonical runner."
|
||||||
|
)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from personal_mcp.skills.python_logging_dictconfig.server import (
|
||||||
|
python_logging_dictconfig_server,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = ["python_logging_dictconfig_server"]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
id: python-logging-dictconfig
|
||||||
|
name: Python Logging DictConfig
|
||||||
|
description: Provide minimal logging.config.dictConfig setup guidance.
|
||||||
|
tags:
|
||||||
|
- logging
|
||||||
|
- python
|
||||||
|
- observability
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
from fastmcp import FastMCP
|
||||||
|
|
||||||
|
python_logging_dictconfig_server = FastMCP("python-logging-dictconfig")
|
||||||
|
|
||||||
|
|
||||||
|
@python_logging_dictconfig_server.tool()
|
||||||
|
def logging_dictconfig_template(level: str = "INFO") -> dict:
|
||||||
|
"""Return a minimal dictConfig template for application startup."""
|
||||||
|
normalized = level.upper()
|
||||||
|
return {
|
||||||
|
"version": 1,
|
||||||
|
"disable_existing_loggers": False,
|
||||||
|
"formatters": {
|
||||||
|
"standard": {
|
||||||
|
"format": "%(asctime)s %(levelname)s %(name)s: %(message)s",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers": {
|
||||||
|
"console": {
|
||||||
|
"class": "logging.StreamHandler",
|
||||||
|
"level": normalized,
|
||||||
|
"formatter": "standard",
|
||||||
|
"stream": "ext://sys.stdout",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"level": normalized,
|
||||||
|
"handlers": ["console"],
|
||||||
|
},
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user