Compare commits
5
Commits
6ec12a100a
...
8a994ff47b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a994ff47b | ||
|
|
f3bbbfc25f | ||
|
|
e999437b93 | ||
|
|
f1dd6ab940 | ||
|
|
3d21e9136c |
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Skill Authoring
|
||||
description: "Use when creating or editing skills under src/personal_mcp/docs/skills/. Enforces the Agent Skills specification and FastMCP skill provider compatibility."
|
||||
applyTo: 'src/personal_mcp/docs/skills/**'
|
||||
---
|
||||
|
||||
# Skill Authoring
|
||||
|
||||
Before editing files under `src/personal_mcp/docs/skills/`, consult all of these references:
|
||||
|
||||
- [Agent Skills quickstart](https://agentskills.io/skill-creation/quickstart)
|
||||
- [Agent Skills best practices](https://agentskills.io/skill-creation/best-practices)
|
||||
- [FastMCP skill providers](https://gofastmcp.com/servers/providers/skills)
|
||||
|
||||
Treat each immediate subdirectory of `src/personal_mcp/docs/skills/` as an independent Agent Skill. Keep its structure and content compliant with the Agent Skills pattern and compatible with the FastMCP skill provider.
|
||||
|
||||
Reconcile the external guidance with the repository's existing skill conventions and contracts before making changes.
|
||||
+4
-1
@@ -29,6 +29,9 @@ FROM python:3.14-slim AS runtime
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PATH="/app/.venv/bin:$PATH" \
|
||||
PERSONAL_MCP_HOST=0.0.0.0 \
|
||||
PERSONAL_MCP_PORT=8765 \
|
||||
PERSONAL_MCP_RELOAD=false \
|
||||
PERSONAL_MCP_SITE_DIR=/app/site
|
||||
|
||||
WORKDIR /app
|
||||
@@ -51,4 +54,4 @@ RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
|
||||
USER appuser
|
||||
|
||||
CMD ["uvicorn", "personal_mcp.main:create_app", "--factory", "--host", "0.0.0.0", "--port", "8765"]
|
||||
ENTRYPOINT ["python", "-m", "personal_mcp"]
|
||||
+2
-2
@@ -4,7 +4,7 @@ version = "2.0.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"fastapi>=0.133.0",
|
||||
"fastmcp==4.0.0b1",
|
||||
"fastmcp==4.0.0b4",
|
||||
"pydantic-settings>=2",
|
||||
"pyyaml>=6.0.2",
|
||||
"python-json-logger>=4",
|
||||
@@ -13,7 +13,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
constraint-dependencies = ["fastmcp-slim==4.0.0b1"]
|
||||
constraint-dependencies = ["fastmcp-slim==4.0.0b4"]
|
||||
|
||||
[project.scripts]
|
||||
personal-mcp = "personal_mcp.__main__:main"
|
||||
|
||||
@@ -60,9 +60,7 @@ For skills:
|
||||
4. read `_manifest` when supporting material may be needed
|
||||
5. fetch only the supporting paths relevant to the task
|
||||
|
||||
Tool-only agents may call `search_skills` instead of retrieving the complete resource list. Search results contain only provider-derived names, descriptions, and canonical main-resource URIs; skill content remains available exclusively through the native resource contract.
|
||||
|
||||
For prompts, use the native MCP prompt APIs or their generic tool projection.
|
||||
For prompts, use the native MCP prompt APIs.
|
||||
|
||||
## Stability Policy
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Copilot interacts with MCP servers through independently exposed lanes:
|
||||
2. resources attached as read-only context
|
||||
3. server-provided prompts
|
||||
|
||||
This server publishes skills as native `skill://` resources and prompts as native MCP prompt objects. It also exposes `search_skills`, `list_resources`, and `read_resource` tools for agents whose tool catalog does not include direct MCP resource operations.
|
||||
This server publishes skills as native `skill://` resources, general docs as `resource://docs/{path*}` resources, and workflows as native MCP prompt objects. It intentionally publishes no compatibility tools that mirror resources or prompts.
|
||||
|
||||
## VS Code Feature Coverage
|
||||
|
||||
@@ -25,7 +25,7 @@ The server uses every FastMCP feature that applies to its read-only guidance wor
|
||||
| Feature | Usage |
|
||||
| --- | --- |
|
||||
| Server identity | The initialize response includes a stable name, usage instructions, and a self-contained icon for VS Code's MCP server UI. |
|
||||
| Tools | Compatibility tools have display titles, structured output schemas, and read-only, idempotent, closed-world annotations. FastMCP's default schema dereferencing remains enabled for clients such as VS Code that require flat schemas. |
|
||||
| Tools | No tools are published for this documentation-only server surface. Resource and prompt operations stay on native MCP capabilities. |
|
||||
| Resources | Documentation and skills use native resources and wildcard resource templates with explicit Markdown MIME types. |
|
||||
| Prompts | Declarative workflows use native prompt objects with descriptions, display titles, typed arguments, and slash-command access. |
|
||||
| Argument completion | Prompt arguments with authored `choices` are returned through `completion/complete` as the user types. |
|
||||
@@ -66,11 +66,10 @@ A successful `resources/list` response does not guarantee the picker appears in
|
||||
|
||||
For autonomous agents:
|
||||
|
||||
1. call `search_skills` with the task, capability, or technology
|
||||
2. compare the bounded main-skill matches
|
||||
3. call `read_resource` for one relevant `skill://<name>/SKILL.md`
|
||||
4. read `_manifest` only if supporting detail may be needed
|
||||
5. read only selected supporting files
|
||||
1. browse native resources and compare skill descriptions
|
||||
2. read one relevant `skill://<name>/SKILL.md`
|
||||
3. read `_manifest` only if supporting detail may be needed
|
||||
4. read only selected supporting files
|
||||
|
||||
For manual context attachment, browse the server's resources and attach the same bounded set of files.
|
||||
|
||||
@@ -102,13 +101,13 @@ A repo-level instruction should name the native retrieval order and context budg
|
||||
When a task matches a personal-mcp skill:
|
||||
|
||||
1. Prefer an already attached native skill resource.
|
||||
2. Otherwise call `search_skills` and select one `skill://<name>/SKILL.md` result by description.
|
||||
3. Call `read_resource` for the selected skill and read `_manifest` only when supporting material is needed.
|
||||
2. Otherwise browse MCP resources and select one `skill://<name>/SKILL.md` by description.
|
||||
3. Read the selected skill and read `_manifest` only when supporting material is needed.
|
||||
4. Load at most two candidate main files and only the relevant supporting paths.
|
||||
5. Reconcile guidance with the current repository before editing.
|
||||
```
|
||||
|
||||
Instructions steer behavior but do not force VS Code to attach resources automatically. The generic tools provide an agent-callable fallback when direct resource operations are absent from the deferred-tool catalog.
|
||||
Instructions steer behavior but do not force VS Code to attach resources automatically.
|
||||
|
||||
## Prompt Objects
|
||||
|
||||
@@ -118,7 +117,7 @@ Prompts remain separate from skills. When the client supports MCP prompt APIs, u
|
||||
|
||||
1. Use `MCP: List Servers` to confirm the server is enabled.
|
||||
2. Use `MCP: Browse Resources` to confirm native skill resources exist.
|
||||
3. Confirm `search_skills` and `read_resource` appear in the chat tool picker when autonomous retrieval is required.
|
||||
3. Confirm `Add Context > MCP Resources` lists server resources in the active chat surface.
|
||||
4. Restart the MCP server after changing skill files because production uses `reload=False`.
|
||||
5. Reload the VS Code window if the server is healthy but the resource or tool picker remains stale.
|
||||
|
||||
|
||||
@@ -14,14 +14,18 @@ Use this page for implementation-oriented links across MCP SDKs and FastMCP.
|
||||
!!! info "FastMCP sources"
|
||||
- [FastMCP project documentation](https://gofastmcp.com/)
|
||||
- [FastMCP server identity and behavior](https://gofastmcp.com/servers/server)
|
||||
- [FastMCP providers overview](https://gofastmcp.com/servers/providers/overview)
|
||||
- [FastMCP custom providers](https://gofastmcp.com/servers/providers/custom)
|
||||
- [FastMCP skills provider](https://gofastmcp.com/servers/providers/skills)
|
||||
- [FastMCP tools and annotations](https://gofastmcp.com/servers/tools)
|
||||
- [FastMCP resources and templates](https://gofastmcp.com/servers/resources)
|
||||
- [FastMCP prompts](https://gofastmcp.com/servers/prompts)
|
||||
- [FastMCP filesystem provider](https://gofastmcp.com/servers/providers/filesystem)
|
||||
- [FastMCP argument completion](https://gofastmcp.com/servers/completions)
|
||||
- [FastMCP component icons](https://gofastmcp.com/servers/icons)
|
||||
- [FastMCP Apps](https://gofastmcp.com/apps/overview)
|
||||
- [FastMCP GitHub repository](https://github.com/jlowin/fastmcp)
|
||||
- [FastMCP examples directory](https://github.com/jlowin/fastmcp/tree/main/examples)
|
||||
- [FastMCP GitHub repository](https://github.com/PrefectHQ/fastmcp)
|
||||
- [FastMCP examples directory](https://github.com/PrefectHQ/fastmcp/tree/main/examples)
|
||||
- [FastMCP PyPI package](https://pypi.org/project/fastmcp/)
|
||||
|
||||
## Server Implementation Patterns
|
||||
|
||||
@@ -52,26 +52,24 @@ Load [FastAPI and Uvicorn startup](./references/fastapi-uvicorn-startup.md) for:
|
||||
|
||||
Load [styling and customization](./references/styling-and-customization.md) for:
|
||||
|
||||
- progressive discovery through NiceGUI docs, constructors, and Quasar docs
|
||||
- Quasar props, slots, events, and NiceGUI customization methods
|
||||
- app-wide and page-level color themes, dark mode, and semantic CSS tokens
|
||||
- Tailwind for structural styling and static stylesheets for fine tuning
|
||||
- responsive layout and static asset conventions
|
||||
- Tailwind and Quasar breakpoint scales, container queries, and responsive testing
|
||||
- uniformly scaling dialogs on mobile
|
||||
- preserving Quasar field proportions
|
||||
- keeping detached `QSelect` menus anchored
|
||||
- sizing scrollable dialog cards under CSS `zoom`
|
||||
- validating zoomed controls with Playwright or a browser
|
||||
- Tailwind and Quasar utility classes
|
||||
- scoped CSS properties and stable application classes
|
||||
- responsive page composition and static asset loading
|
||||
- cosmetic treatment of controls, surfaces, typography, and visual states
|
||||
- visual validation at supported viewport sizes
|
||||
|
||||
### Special Component Customization
|
||||
### Component Mechanics And Customization
|
||||
|
||||
Load [special component customization](./references/special-component-customization.md) for:
|
||||
Load [component mechanics and customization](./references/component-mechanics-and-customization.md) for:
|
||||
|
||||
- the required source-research gate before generating component customizations
|
||||
- `ui.select` constructors, Quasar props, slots, detached popups, and option caveats
|
||||
- `ui.icon` names, icon families, sizing, colors, assets, and Material Symbol variants
|
||||
- component-specific accessibility, sanitization, and validation checks
|
||||
- the NiceGUI Python wrapper, element bridge, Quasar component, and Vue runtime boundaries
|
||||
- deciding between constructors, bindings, Quasar props, events, slots, and frontend methods
|
||||
- server-client state and event flow
|
||||
- detached content and external icon assets
|
||||
- source research against the installed NiceGUI and bundled Quasar versions
|
||||
- `ui.select` and `ui.icon` mechanics and caveats
|
||||
- dialog scaling when detached popup geometry must be preserved
|
||||
|
||||
### Bindable State
|
||||
|
||||
@@ -118,8 +116,8 @@ Load [source documentation](./references/source-documentation.md) when:
|
||||
### Page Or Component Work
|
||||
|
||||
1. Load [application architecture](./references/architecture.md) for page and component ownership decisions.
|
||||
2. Load [styling and customization](./references/styling-and-customization.md) for layout, responsive behavior, or visual customization.
|
||||
3. Add [special component customization](./references/special-component-customization.md) when the work targets `ui.select`, `ui.icon`, or another component with specialized Quasar behavior.
|
||||
2. Load [styling and customization](./references/styling-and-customization.md) for themes, layout, responsive presentation, utility classes, or CSS.
|
||||
3. Load [component mechanics and customization](./references/component-mechanics-and-customization.md) when behavior must be mapped across NiceGUI, Quasar, and Vue, or when detached content and component-specific behavior are involved.
|
||||
4. Add [interaction patterns](./references/interaction-patterns.md) or [bindable dataclasses](./references/binding-dataclasses.md) according to the page behavior.
|
||||
|
||||
### Debugging Or Production Review
|
||||
|
||||
+111
-2
@@ -1,6 +1,52 @@
|
||||
# NiceGUI Special Component Customization
|
||||
# NiceGUI Component Mechanics And Customization
|
||||
|
||||
Use this reference for components whose NiceGUI wrapper, Quasar implementation, popup behavior, slots, or external assets require component-specific handling. Start with [styling and customization](./styling-and-customization.md) for the general escalation workflow.
|
||||
Use this reference to understand how customization crosses the NiceGUI Python wrapper, Quasar component, Vue runtime, and browser DOM. It owns constructor behavior, prop translation, events, bindings, slots, frontend methods, detached content, and component-specific caveats. For themes, utility classes, CSS properties, responsive page composition, and other cosmetic work, load [visual styling and CSS](./styling-and-customization.md).
|
||||
|
||||
## Framework Boundary Model
|
||||
|
||||
A NiceGUI component is not a Python-rendered HTML fragment. Customization passes through several owners:
|
||||
|
||||
| Layer | Owns | Inspect when |
|
||||
| --- | --- | --- |
|
||||
| NiceGUI Python wrapper | constructor arguments, Python value normalization, validation, bindings, event callbacks, and update helpers | behavior may already have a typed Python API or wrapper-specific state rules |
|
||||
| NiceGUI element bridge | serialized props, classes, styles, events, slots, and frontend method calls | mapping a supported Vue or Quasar feature through NiceGUI |
|
||||
| Quasar Vue component | documented props, emitted events, named slots, public methods, popup behavior, accessibility, and internal state | the NiceGUI constructor does not expose a required component feature |
|
||||
| Vue and browser runtime | reactivity, rendered DOM, teleported content, CSS cascade, fonts, and static assets | diagnosing placement, asset loading, or content rendered outside the element subtree |
|
||||
|
||||
Treat the generated DOM beneath a Quasar component as private implementation detail. Work through the highest owning layer that expresses the requirement.
|
||||
|
||||
## How The APIs Map
|
||||
|
||||
Use this map after confirming the exact API against the installed NiceGUI and bundled Quasar versions:
|
||||
|
||||
| Requirement | NiceGUI surface | Underlying mechanic |
|
||||
| --- | --- | --- |
|
||||
| Wrapper-supported value or behavior | constructor argument, binding, or helper such as `set_options()` | Python normalizes state and synchronizes the component |
|
||||
| Additional Quasar option | `.props(...)` | values become props on the wrapped Vue component |
|
||||
| Browser or Quasar notification | constructor callback or `.on(...)` | an emitted frontend event is forwarded to a Python handler |
|
||||
| Semantic insertion point | `add_slot(...)` or a wrapper-specific slot API | content renders in a named Vue slot |
|
||||
| Imperative frontend action | a NiceGUI helper or `run_method(...)` | NiceGUI invokes a public method on the client component |
|
||||
| Page placement or appearance | `.classes(...)`, `.style(...)`, or an application stylesheet | CSS applies to the rendered element; detached content needs its own class hook |
|
||||
|
||||
Do not copy a Vue template into Python. Translate each part according to its owner: constructor data stays in Python, Quasar props go through `.props()`, emitted events go through callbacks or `.on()`, and named Vue slots go through NiceGUI's slot API.
|
||||
|
||||
## State And Event Flow
|
||||
|
||||
Server-driven changes and user-driven changes cross a client-server boundary:
|
||||
|
||||
1. Python creates the wrapper and serializes initial state to the client.
|
||||
2. Vue renders the Quasar component from those props and slots.
|
||||
3. A browser interaction causes Quasar to update client state or emit an event.
|
||||
4. NiceGUI forwards registered events to Python handlers.
|
||||
5. Python mutations return through bindings, wrapper helpers, or an explicit `update()`.
|
||||
|
||||
Use wrapper helpers and bindings when available because they preserve NiceGUI's value model. Directly changing a Python collection or constructing a raw JavaScript object does not imply that the client receives the change.
|
||||
|
||||
## Detached Content And Assets
|
||||
|
||||
Some Quasar components render menus, dialogs, tooltips, and similar content outside the field or trigger's DOM subtree. A descendant CSS selector beneath the Python-created element will not reach that content. Use the component's documented popup or content class prop, then style that application-owned class separately.
|
||||
|
||||
Icons and other externally defined visuals add another boundary: a valid Quasar icon name identifies an asset but does not load its font or stylesheet. Confirm both the naming convention and the application-level asset registration.
|
||||
|
||||
## Source Research Gate
|
||||
|
||||
@@ -17,6 +63,8 @@ For each component:
|
||||
|
||||
Use current upstream source only as a fallback when the target environment is unavailable. If installed and upstream behavior differ, follow the installed version and state the difference.
|
||||
|
||||
If the requirement is purely visual after this ownership check, continue in [visual styling and CSS](./styling-and-customization.md).
|
||||
|
||||
## `ui.select`
|
||||
|
||||
### Source Map
|
||||
@@ -77,6 +125,67 @@ with item_select.add_slot("prepend"):
|
||||
|
||||
Use `.on()` or `run_method()` only after confirming the event or method in the installed Quasar API. Prefer NiceGUI's `on_change`, `set_options()`, value bindings, and `is_showing_popup` when they cover the behavior.
|
||||
|
||||
### Worked Example: Responsive Dialog And Detached Select Popup
|
||||
|
||||
This example is mechanics-sensitive because a `QSelect` popup is detached from the dialog card. Scale the complete card to preserve Quasar's internal field proportions, but style the popup through its own class without changing its coordinate system.
|
||||
|
||||
Use normal field density and attach application classes through supported APIs:
|
||||
|
||||
```python
|
||||
ui.input("Name").props("outlined")
|
||||
ui.number("Quantity").props("outlined")
|
||||
ui.select(...).props("outlined popup-content-class=app-item-detail-menu")
|
||||
ui.textarea("Description").props("outlined autogrow")
|
||||
ui.card().classes("app-detail-card app-item-detail-card")
|
||||
```
|
||||
|
||||
```css
|
||||
:root {
|
||||
--item-dialog-scale: 1;
|
||||
--item-dialog-max-height: calc(100dvh - 3rem);
|
||||
}
|
||||
|
||||
.app-item-detail-card {
|
||||
width: min(50rem, 50vw);
|
||||
max-height: var(--item-dialog-max-height);
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
zoom: var(--item-dialog-scale);
|
||||
}
|
||||
|
||||
/* Restore Quasar's baseline if a global rule overrides it. */
|
||||
.app-item-detail-card .q-field,
|
||||
.app-item-detail-menu {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (max-width: 599px) {
|
||||
:root {
|
||||
--item-dialog-scale: 1.2;
|
||||
/* 75dvh becomes 90dvh after 1.2x zoom. */
|
||||
--item-dialog-max-height: 75dvh;
|
||||
}
|
||||
|
||||
.app-item-detail-card {
|
||||
width: 80vw;
|
||||
}
|
||||
|
||||
.app-item-detail-menu {
|
||||
font-size: 16.8px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Do not apply `zoom` or `transform: scale()` to `.app-item-detail-menu`. Quasar positions the detached menu from the unscaled anchor geometry; scaling the menu afterward separates it from its field. Enlarging its text preserves the positioning coordinate system.
|
||||
|
||||
The card's pre-zoom maximum height must account for the scale:
|
||||
|
||||
\[
|
||||
h_{\mathrm{pre}} = \frac{h_{\mathrm{visible}}}{s}
|
||||
\]
|
||||
|
||||
For a desired visual height of `90dvh` at \(1.2\times\), use `75dvh`. Apply scrolling to the card so the dimmed page remains stationary while the form reaches its final field. The `599px` media query matches the upper edge of Quasar's default extra-small breakpoint; verify it against custom Quasar breakpoint configuration.
|
||||
|
||||
## `ui.icon`
|
||||
|
||||
### Source Map
|
||||
@@ -1,75 +1,54 @@
|
||||
# NiceGUI Styling And Customization
|
||||
# NiceGUI Visual Styling And CSS
|
||||
|
||||
Use this reference to discover how a NiceGUI component can be customized, apply the least invasive supported mechanism, and introduce CSS without fighting Quasar's internal geometry.
|
||||
Use this reference for cosmetic and presentational work: themes, color roles, utility classes, CSS properties, responsive layout, and static assets. For the mechanics of how a NiceGUI Python element maps to a Quasar Vue component, including props, events, slots, methods, teleported content, and wrapper-owned state, load [component mechanics and customization](./component-mechanics-and-customization.md).
|
||||
|
||||
For package boundaries, dependency direction, and page or component ownership, load [application architecture](./architecture.md).
|
||||
|
||||
## Progressive Customization Workflow
|
||||
## Visual Styling Boundary
|
||||
|
||||
Increase the customization level only when the previous source does not expose what the design requires:
|
||||
This page owns how an element looks and fits into a page after the correct component and behavior have been chosen. Typical concerns include:
|
||||
|
||||
1. Read the NiceGUI documentation page for the component.
|
||||
2. Inspect the NiceGUI element function or class constructor.
|
||||
3. Identify the wrapped Quasar component and read its documentation.
|
||||
4. Use Quasar props, slots, and events through NiceGUI's native customization APIs.
|
||||
5. Use Tailwind classes for structural layout.
|
||||
6. Add a scoped static stylesheet for stable visual fine tuning.
|
||||
- application color roles and light or dark presentation
|
||||
- width, height, spacing, alignment, wrapping, and overflow
|
||||
- typography, borders, shadows, focus treatments, and state colors
|
||||
- responsive page composition and stable control dimensions
|
||||
- reusable application classes, CSS custom properties, and static assets
|
||||
|
||||
Stop as soon as the required behavior is supported. Do not begin by targeting Quasar's generated DOM or internal selectors.
|
||||
The companion [component mechanics and customization](./component-mechanics-and-customization.md) reference owns how behavior crosses framework boundaries. Use it when the question is whether a value belongs in a constructor, Quasar prop, Vue event, slot, method, binding, or teleported popup.
|
||||
|
||||
### 1. Start With The NiceGUI Component Page
|
||||
## Visual Styling Workflow
|
||||
|
||||
Find the component in the [NiceGUI documentation](https://nicegui.io/documentation). Check its examples, parameters, methods, events, bindings, and inheritance before writing CSS. The component page establishes the public NiceGUI API and often demonstrates the intended Quasar integration.
|
||||
Escalate only as far as the visual requirement needs:
|
||||
|
||||
Confirm the target project's installed NiceGUI version because the current online documentation can differ from the pinned release.
|
||||
1. Use a NiceGUI constructor argument when it directly expresses appearance, such as an icon, color, or size.
|
||||
2. Use documented Quasar appearance props through `.props(...)` for component variants such as `outlined`, `rounded`, or `dense`.
|
||||
3. Use Tailwind classes for page structure and common visual utilities.
|
||||
4. Use Quasar utility classes for Quasar spacing, typography, semantic colors, visibility, and positioning.
|
||||
5. Use `.style(...)` for a calculated runtime value or a short-lived visual probe.
|
||||
6. Move stable or repeated declarations into a scoped static stylesheet under an application-owned class.
|
||||
|
||||
### 2. Inspect The NiceGUI Constructor
|
||||
|
||||
Read the signature and implementation of the imported NiceGUI function or element class. The constructor reveals accepted Python parameters, defaults, event callbacks, validation, and values NiceGUI forwards to the frontend.
|
||||
|
||||
Use editor navigation or runtime inspection against the project's selected environment:
|
||||
Stop when the required presentation is achieved. If a proposed rule needs selectors such as `.q-field__control`, changes a popup's mounting or positioning behavior, or depends on generated Vue markup, resolve the component mechanics first instead of compensating with CSS.
|
||||
|
||||
```python
|
||||
from inspect import getsource, signature
|
||||
|
||||
from nicegui import ui
|
||||
|
||||
print(signature(ui.select))
|
||||
print(getsource(ui.select))
|
||||
```
|
||||
|
||||
When `ui.<name>` is a factory or alias, follow it to the element class in the [NiceGUI element sources](https://github.com/zauberzeug/nicegui/tree/main/nicegui/elements). Prefer the installed package source when behavior may differ by version.
|
||||
|
||||
### 3. Read The Underlying Quasar Component Docs
|
||||
|
||||
NiceGUI wraps Quasar components such as [`QInput`](https://quasar.dev/vue-components/input/), [`QSelect`](https://quasar.dev/vue-components/select/), and [`QDialog`](https://quasar.dev/vue-components/dialog/). Use the matching Quasar component page to discover its complete props, slots, events, methods, and behavior notes.
|
||||
|
||||
Map Quasar's Vue API onto the NiceGUI wrapper instead of copying a Vue template. Verify that a prop or slot exists in the Quasar version used by the installed NiceGUI release.
|
||||
|
||||
### 4. Apply Native Quasar Features Through NiceGUI
|
||||
|
||||
Use the NiceGUI element customization methods to reach the supported Quasar surface:
|
||||
|
||||
- `.props(...)` for Quasar properties and boolean flags
|
||||
- `.classes(...)` for Tailwind utilities and stable application class names
|
||||
- `.style(...)` for dynamic inline values or a quick, local probe
|
||||
- `.on(...)` for events that are not represented by a constructor callback
|
||||
- slots or child elements for Quasar extension points exposed by the wrapper
|
||||
|
||||
```python
|
||||
with ui.select(
|
||||
ui.select(
|
||||
options=items,
|
||||
label="Item",
|
||||
).props(
|
||||
"outlined clearable options-dense popup-content-class=app-item-menu"
|
||||
"outlined popup-content-class=app-item-menu"
|
||||
).classes(
|
||||
"w-full md:max-w-md"
|
||||
) as item_select:
|
||||
with item_select.add_slot("prepend"):
|
||||
ui.icon("inventory_2")
|
||||
"app-item-select w-full md:max-w-md"
|
||||
)
|
||||
```
|
||||
|
||||
Prefer constructor arguments when NiceGUI exposes the behavior directly. Use `.props()` for supported Quasar features that are not constructor parameters. Use slots when the Quasar docs define a semantic insertion point; do not reproduce that content with absolute positioning.
|
||||
```css
|
||||
.app-item-select {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.app-item-menu {
|
||||
max-height: min(24rem, 60dvh);
|
||||
}
|
||||
```
|
||||
|
||||
## Application Themes With NiceGUI And Quasar
|
||||
|
||||
@@ -200,6 +179,26 @@ with ui.column().classes("w-full max-w-6xl mx-auto gap-6 px-4"):
|
||||
|
||||
Use stable width, minimum-width, and flex constraints so labels, icons, validation messages, and loaded content do not shift the surrounding layout.
|
||||
|
||||
### Combine Tailwind With Quasar Utilities Deliberately
|
||||
|
||||
NiceGUI's `.classes()` accepts both Tailwind utilities and the CSS helpers bundled with Quasar. Keep Tailwind as the default for application layout and responsive structure, but use Quasar utilities when they express a Quasar-owned or framework-semantic concern more directly:
|
||||
|
||||
- [`q-m*` and `q-p*` spacing classes](https://quasar.dev/style/spacing) when spacing should follow Quasar's component scale
|
||||
- [typography helpers](https://quasar.dev/style/typography), such as `text-h6`, `text-subtitle2`, and `text-weight-medium`, for text that should follow Quasar's type system
|
||||
- [color palette classes](https://quasar.dev/style/color-palette), such as `text-primary`, `bg-positive`, and `text-negative`, so semantic colors track the palette configured by `app.colors()` or `ui.colors()`
|
||||
- [visibility helpers](https://quasar.dev/style/visibility), such as `gt-sm` and `lt-md`, when visibility should use Quasar's configured breakpoints
|
||||
- [positioning helpers](https://quasar.dev/style/positioning), such as `absolute-top-right`, when positioning content relative to a Quasar component
|
||||
|
||||
Mix the two systems by concern, not by writing competing declarations for the same CSS property. For example, `w-full q-pa-md text-primary` uses Tailwind for width and Quasar for component-scale padding and semantic color. Do not combine `p-4` with `q-pa-md`, or Tailwind and Quasar visibility helpers, on the same element; their cascade order can make the result version-dependent and difficult to review.
|
||||
|
||||
```python
|
||||
with ui.card().classes("w-full max-w-2xl q-pa-md"):
|
||||
ui.label("Inventory summary").classes("text-h6 text-primary")
|
||||
ui.label("Review required").classes("text-negative text-weight-medium")
|
||||
```
|
||||
|
||||
Quasar utilities are global classes, so they need no Vue-specific translation before being passed to `.classes()`. Confirm the available helpers and breakpoints against the Quasar version bundled by the installed NiceGUI release.
|
||||
|
||||
## Fine Tuning With Static Stylesheets
|
||||
|
||||
Move stable fine tuning into a static stylesheet after the structure and native component configuration are correct. Static stylesheets provide reusable selectors, media queries, pseudo-classes, CSS variables, and a clear cascade that inline declarations cannot provide.
|
||||
@@ -272,145 +271,9 @@ ui.add_head_html(
|
||||
)
|
||||
```
|
||||
|
||||
## Worked Example: Responsive Dialog Customization
|
||||
## Mechanics-Sensitive Visual Cases
|
||||
|
||||
This example begins with normal field density and Quasar popup props, then uses an application class and static stylesheet for the remaining responsive fine tuning. Use whole-card scaling when a form dialog must become uniformly larger on mobile while preserving Quasar's internal proportions. Keep detached select menus unscaled and make the card itself scrollable.
|
||||
|
||||
### Use Normal Field Density
|
||||
|
||||
Normal Quasar fields are approximately `56px` high, while dense fields are approximately `40px` high. Remove `dense` when larger controls are needed.
|
||||
|
||||
```python
|
||||
ui.input("Name").props("outlined")
|
||||
ui.number("Quantity").props("outlined")
|
||||
ui.select(...).props("outlined popup-content-class=app-item-detail-menu")
|
||||
ui.textarea("Description").props("outlined autogrow")
|
||||
```
|
||||
|
||||
Add a scoped class to the dialog card:
|
||||
|
||||
```python
|
||||
ui.card().classes("app-detail-card app-item-detail-card")
|
||||
```
|
||||
|
||||
### Scale The Complete Card
|
||||
|
||||
```css
|
||||
:root {
|
||||
--item-dialog-scale: 1;
|
||||
--item-dialog-max-height: calc(100dvh - 3rem);
|
||||
}
|
||||
|
||||
.app-item-detail-card {
|
||||
width: min(50rem, 50vw);
|
||||
max-height: var(--item-dialog-max-height);
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
zoom: var(--item-dialog-scale);
|
||||
}
|
||||
|
||||
/* Restore Quasar's baseline if a global rule overrides it. */
|
||||
.app-item-detail-card .q-field,
|
||||
.app-item-detail-menu {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (max-width: 599px) {
|
||||
:root {
|
||||
--item-dialog-scale: 1.2;
|
||||
/* 75dvh becomes 90dvh after 1.2x zoom. */
|
||||
--item-dialog-max-height: 75dvh;
|
||||
}
|
||||
|
||||
.app-item-detail-card {
|
||||
width: 80vw;
|
||||
}
|
||||
|
||||
.app-item-detail-menu {
|
||||
font-size: 16.8px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The main mobile tuning knob is:
|
||||
|
||||
```css
|
||||
--item-dialog-scale: 1.2;
|
||||
```
|
||||
|
||||
### Keep Detached Popups Unscaled
|
||||
|
||||
Do not apply `zoom` or `transform: scale()` to a `QSelect` popup menu. Quasar renders menus outside the dialog and positions them from the unscaled anchor geometry. Scaling the menu container afterward separates it from its field.
|
||||
|
||||
Avoid:
|
||||
|
||||
```css
|
||||
.app-item-detail-card,
|
||||
.app-item-detail-menu {
|
||||
zoom: 1.2;
|
||||
}
|
||||
```
|
||||
|
||||
Use:
|
||||
|
||||
```css
|
||||
.app-item-detail-card {
|
||||
zoom: 1.2;
|
||||
}
|
||||
|
||||
.app-item-detail-menu {
|
||||
font-size: 16.8px;
|
||||
}
|
||||
```
|
||||
|
||||
Use `popup-content-class=app-item-detail-menu` to target the detached menu and enlarge its text without changing its coordinate system.
|
||||
|
||||
### Account For Zoom When Scrolling
|
||||
|
||||
The card's pre-zoom maximum height must account for the scale:
|
||||
|
||||
\[
|
||||
\begin{aligned}
|
||||
h_{\mathrm{pre}} &= \frac{h_{\mathrm{visible}}}{s} \\
|
||||
\text{where } s &= \text{the zoom scale}
|
||||
\end{aligned}
|
||||
\]
|
||||
|
||||
For a desired visual height of `90dvh` at \(1.2\times\):
|
||||
|
||||
\[
|
||||
\frac{90\,\mathrm{dvh}}{1.2} = 75\,\mathrm{dvh}
|
||||
\]
|
||||
|
||||
Therefore:
|
||||
|
||||
```css
|
||||
--item-dialog-max-height: 75dvh;
|
||||
```
|
||||
|
||||
Apply scrolling to the card itself:
|
||||
|
||||
```css
|
||||
.app-item-detail-card {
|
||||
max-height: var(--item-dialog-max-height);
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
```
|
||||
|
||||
This keeps the dimmed page stationary while the form scrolls.
|
||||
|
||||
### Match The Quasar Breakpoint
|
||||
|
||||
Quasar's extra-small breakpoint ends at `599.98px`. A mobile-only rule can use:
|
||||
|
||||
```css
|
||||
@media (max-width: 599px) {
|
||||
/* Mobile rules. */
|
||||
}
|
||||
```
|
||||
|
||||
Confirm custom breakpoint values against the target application's Quasar configuration.
|
||||
Some visual requests depend on framework behavior before CSS can be chosen safely. Use [component mechanics and customization](./component-mechanics-and-customization.md) for detached menus and dialogs, named slots, icon asset families, Quasar internal geometry, frontend methods, and server-client state synchronization. Its responsive dialog example explains why a card can be scaled while a detached `QSelect` popup must remain in its original positioning coordinate system.
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
@@ -430,6 +293,10 @@ Confirm that page sections do not overlap, toolbars wrap on mobile, desktop pane
|
||||
- [NiceGUI color theming](https://nicegui.io/documentation/colors)
|
||||
- [NiceGUI dark mode](https://nicegui.io/documentation/dark_mode)
|
||||
- [Quasar components](https://quasar.dev/vue-components)
|
||||
- [Quasar spacing classes](https://quasar.dev/style/spacing)
|
||||
- [Quasar typography helpers](https://quasar.dev/style/typography)
|
||||
- [Quasar visibility helpers](https://quasar.dev/style/visibility)
|
||||
- [Quasar positioning helpers](https://quasar.dev/style/positioning)
|
||||
- [Quasar color palette and runtime brand variables](https://quasar.dev/style/color-palette)
|
||||
- [Quasar dark mode](https://quasar.dev/style/dark-mode)
|
||||
- [Quasar field](https://quasar.dev/vue-components/field/)
|
||||
|
||||
@@ -53,15 +53,15 @@ These utilities operate directly on the native `skill://` contract and require n
|
||||
In VS Code, skills can arrive through:
|
||||
|
||||
1. explicit attachment from `Add Context > MCP Resources` or `MCP: Browse Resources`
|
||||
2. the `search_skills` and `read_resource` tools for autonomous agents
|
||||
2. direct resource reads on selected `skill://<name>/SKILL.md` entries
|
||||
3. a slash-command prompt that names a specific native skill URI
|
||||
|
||||
Instructions can steer retrieval, but they do not guarantee automatic resource attachment in every chat surface. When the deferred-tool catalog omits direct MCP resource operations, use the tools; search derives matches from native provider metadata and `read_resource` delegates to the provider without duplicating skill content.
|
||||
Instructions can steer retrieval, but they do not guarantee automatic resource attachment in every chat surface. Use `MCP: Browse Resources` to confirm server-side availability, then attach only the minimum skill resources needed for the current task.
|
||||
|
||||
A reliable prompt is:
|
||||
|
||||
```text
|
||||
Call search_skills with the task or capability and select the best matching skill://.../SKILL.md result. Use read_resource for one selected skill, inspect its _manifest only if supporting detail is needed, and reconcile the guidance with this workspace.
|
||||
Browse MCP resources, select the best matching skill://.../SKILL.md entry by description, inspect its _manifest only if supporting detail is needed, and reconcile the guidance with this workspace.
|
||||
```
|
||||
|
||||
## Thin Shim Pattern
|
||||
|
||||
+6
-147
@@ -1,15 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Annotated
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from mcp.types import CompletionArgument
|
||||
from mcp.types import CompletionContext
|
||||
from mcp.types import Icon
|
||||
from mcp.types import PromptReference
|
||||
from pydantic import BaseModel
|
||||
from pydantic import Field
|
||||
from mcp_types import CompletionArgument
|
||||
from mcp_types import CompletionContext
|
||||
from mcp_types import Icon
|
||||
from mcp_types import PromptReference
|
||||
|
||||
from personal_mcp.prompts import create_prompts_provider
|
||||
from personal_mcp.prompts.models import MarkdownPrompt
|
||||
@@ -21,9 +16,8 @@ from personal_mcp.skills import create_skills_provider
|
||||
|
||||
_SERVER_INSTRUCTIONS = """Personal development guidance exposed as native MCP resources and prompts.
|
||||
|
||||
Use prompts for parameterized workflows. For task-specific guidance, search skills, select one
|
||||
skill://<name>/SKILL.md result, and read its manifest only when supporting detail is needed. Read-only
|
||||
compatibility tools expose the same resource catalog to clients without native resource access.
|
||||
Use prompts for parameterized workflows. For task-specific guidance, browse native skill resources,
|
||||
select one skill://<name>/SKILL.md resource, and read its manifest only when supporting detail is needed.
|
||||
"""
|
||||
_SERVER_ICON = Icon(
|
||||
src=(
|
||||
@@ -47,51 +41,6 @@ def _ro_annotations() -> dict[str, bool]:
|
||||
}
|
||||
|
||||
|
||||
class SkillSearchResult(BaseModel):
|
||||
"""Metadata needed to select a native skill resource."""
|
||||
|
||||
name: str
|
||||
description: str
|
||||
uri: str
|
||||
|
||||
|
||||
class SkillSearchResponse(BaseModel):
|
||||
"""Bounded skill matches for an agent search query."""
|
||||
|
||||
results: list[SkillSearchResult]
|
||||
|
||||
|
||||
def _skill_search_score(name: str, description: str, query: str) -> int:
|
||||
normalized_name = name.casefold().replace("-", " ")
|
||||
normalized_description = description.casefold()
|
||||
normalized_query = " ".join(query.casefold().split())
|
||||
terms = tuple(dict.fromkeys(re.findall(r"[a-z0-9]+", normalized_query)))
|
||||
if not terms:
|
||||
return 0
|
||||
|
||||
name_terms = set(normalized_name.split())
|
||||
description_terms = set(re.findall(r"[a-z0-9]+", normalized_description))
|
||||
score = 100 if normalized_query == normalized_name else 0
|
||||
matched_terms = 0
|
||||
for term in terms:
|
||||
term_score = 0
|
||||
if term in name_terms:
|
||||
term_score = 20
|
||||
elif term in normalized_name:
|
||||
term_score = 10
|
||||
elif term in description_terms:
|
||||
term_score = 4
|
||||
elif term in normalized_description:
|
||||
term_score = 1
|
||||
if term_score:
|
||||
matched_terms += 1
|
||||
score += term_score
|
||||
|
||||
if matched_terms == len(terms):
|
||||
score += 10
|
||||
return score
|
||||
|
||||
|
||||
def _register_components(mcp: FastMCP, registry: DocsRegistry) -> None:
|
||||
@mcp.resource(
|
||||
"resource://docs/{path*}",
|
||||
@@ -106,95 +55,6 @@ def _register_components(mcp: FastMCP, registry: DocsRegistry) -> None:
|
||||
return read_docs_markdown_path(registry, path)
|
||||
|
||||
|
||||
def _register_resource_tools(mcp: FastMCP) -> None:
|
||||
@mcp.tool(
|
||||
name="search_skills",
|
||||
title="Search Skills",
|
||||
description=(
|
||||
"Find task-specific skill guidance by capability, technology, problem, or workflow. "
|
||||
"Returns skill metadata and canonical URIs only; use read_resource to load a selected result."
|
||||
),
|
||||
tags={"search", "skills"},
|
||||
annotations=_ro_annotations(),
|
||||
)
|
||||
async def search_skills(
|
||||
query: Annotated[
|
||||
str,
|
||||
Field(
|
||||
min_length=2,
|
||||
max_length=300,
|
||||
description="Capability, technology, problem, or workflow to find.",
|
||||
),
|
||||
],
|
||||
limit: Annotated[
|
||||
int,
|
||||
Field(ge=1, le=10, description="Maximum number of matches to return."),
|
||||
] = 5,
|
||||
) -> SkillSearchResponse:
|
||||
resources = await mcp.list_resources()
|
||||
matches: list[tuple[int, SkillSearchResult]] = []
|
||||
for resource in resources:
|
||||
uri = str(resource.uri)
|
||||
if not uri.startswith("skill://") or not uri.endswith("/SKILL.md"):
|
||||
continue
|
||||
|
||||
name = uri.removeprefix("skill://").removesuffix("/SKILL.md")
|
||||
description = resource.description or ""
|
||||
score = _skill_search_score(name, description, query)
|
||||
if score:
|
||||
matches.append(
|
||||
(
|
||||
score,
|
||||
SkillSearchResult(name=name, description=description, uri=uri),
|
||||
)
|
||||
)
|
||||
|
||||
matches.sort(key=lambda match: (-match[0], match[1].name))
|
||||
return SkillSearchResponse(results=[match[1] for match in matches[:limit]])
|
||||
|
||||
@mcp.tool(
|
||||
name="list_resources",
|
||||
title="List Resources",
|
||||
description=(
|
||||
"List available MCP resources and URI templates. Use before read_resource to discover skill guidance."
|
||||
),
|
||||
annotations=_ro_annotations(),
|
||||
)
|
||||
async def list_resources() -> dict[str, list[dict[str, str | None]]]:
|
||||
resources = await mcp.list_resources()
|
||||
templates = await mcp.list_resource_templates()
|
||||
return {
|
||||
"resources": [
|
||||
{
|
||||
"uri": str(resource.uri),
|
||||
"name": resource.name,
|
||||
"description": resource.description,
|
||||
"mime_type": resource.mime_type,
|
||||
}
|
||||
for resource in resources
|
||||
],
|
||||
"templates": [
|
||||
{
|
||||
"uri_template": template.uri_template,
|
||||
"name": template.name,
|
||||
"description": template.description,
|
||||
"mime_type": template.mime_type,
|
||||
}
|
||||
for template in templates
|
||||
],
|
||||
}
|
||||
|
||||
@mcp.tool(
|
||||
name="read_resource",
|
||||
title="Read Resource",
|
||||
description="Read a resource URI returned by list_resources, including skill files, manifests, and references.",
|
||||
annotations=_ro_annotations(),
|
||||
)
|
||||
async def read_resource(uri: str) -> dict[str, object]:
|
||||
result = await mcp.read_resource(uri)
|
||||
return result.model_dump(mode="json", exclude_none=True)
|
||||
|
||||
|
||||
def _register_prompt_completions(mcp: FastMCP, provider: MarkdownPromptsProvider) -> None:
|
||||
@mcp.completion
|
||||
async def complete_prompt_argument(
|
||||
@@ -230,6 +90,5 @@ def create_mcp() -> FastMCP:
|
||||
prompts_provider = create_prompts_provider()
|
||||
mcp.add_provider(prompts_provider)
|
||||
mcp.add_provider(create_skills_provider())
|
||||
_register_resource_tools(mcp)
|
||||
_register_prompt_completions(mcp, prompts_provider)
|
||||
return mcp
|
||||
|
||||
@@ -4,7 +4,7 @@ from pathlib import PurePosixPath
|
||||
|
||||
import pytest
|
||||
|
||||
from personal_mcp.registry.models.common import parse_docs_path
|
||||
from personal_mcp.registry.models import parse_docs_path
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from pathlib import PurePosixPath
|
||||
|
||||
import pytest
|
||||
from personal_mcp.registry.models.registry import DocsRegistry
|
||||
|
||||
from personal_mcp.registry.load import read_docs_markdown_path
|
||||
from personal_mcp.registry.models import DocsRegistry
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
@@ -35,23 +35,15 @@ class TestMcpHttpEndpoints:
|
||||
"""Covers MCP transport endpoint smoke behavior."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tools_bridge_native_skill_resources(self, mcp_session_factory) -> None:
|
||||
"""Ensures tool-only clients can discover and read native skill resources."""
|
||||
async def test_does_not_publish_legacy_resource_bridge_tools(self, mcp_session_factory) -> None:
|
||||
"""Ensures deprecated compatibility tools are not exposed on the MCP route."""
|
||||
async with mcp_session_factory() as mcp_session:
|
||||
tools_result = await mcp_session.list_tools()
|
||||
list_result = await mcp_session.call_tool("list_resources")
|
||||
read_result = await mcp_session.call_tool(
|
||||
"read_resource",
|
||||
{"uri": "skill://mcp-details/SKILL.md"},
|
||||
)
|
||||
|
||||
assert {tool.name for tool in tools_result.tools} == {
|
||||
"list_resources",
|
||||
"read_resource",
|
||||
"search_skills",
|
||||
}
|
||||
assert "skill://mcp-details/SKILL.md" in list_result.content[0].text
|
||||
assert "# MCP Details" in read_result.content[0].text
|
||||
tool_names = {tool.name for tool in tools_result.tools}
|
||||
assert "search_skills" not in tool_names
|
||||
assert "list_resources" not in tool_names
|
||||
assert "read_resource" not in tool_names
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_accepts_initialize_jsonrpc_request(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from mcp.types import PromptReference
|
||||
from mcp_types import PromptReference
|
||||
|
||||
pytestmark = pytest.mark.smoke
|
||||
|
||||
|
||||
@@ -10,49 +10,19 @@ pytestmark = pytest.mark.smoke
|
||||
class TestMcpSkillsSurface:
|
||||
"""Covers native skill resources over the HTTP MCP surface."""
|
||||
|
||||
class TestCompatibilityTools:
|
||||
"""Covers metadata for tool-only MCP clients."""
|
||||
class TestTools:
|
||||
"""Covers absence of deprecated compatibility tools."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_exposes_safe_human_readable_tools(self, mcp_session_factory) -> None:
|
||||
"""Ensures clients receive display titles and complete safety hints."""
|
||||
async def test_does_not_expose_resource_bridge_tools(self, mcp_session_factory) -> None:
|
||||
"""Ensures native resources are not projected through legacy compatibility tools."""
|
||||
async with mcp_session_factory() as mcp_session:
|
||||
result = await mcp_session.list_tools()
|
||||
|
||||
tools = {tool.name: tool for tool in result.tools}
|
||||
assert tools["search_skills"].title == "Search Skills"
|
||||
assert tools["list_resources"].title == "List Resources"
|
||||
assert tools["read_resource"].title == "Read Resource"
|
||||
assert all(tool.annotations is not None for tool in tools.values())
|
||||
assert all(tool.annotations.read_only_hint for tool in tools.values() if tool.annotations is not None)
|
||||
assert all(tool.annotations.idempotent_hint for tool in tools.values() if tool.annotations is not None)
|
||||
assert all(
|
||||
tool.annotations.open_world_hint is False for tool in tools.values() if tool.annotations is not None
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_searches_skill_metadata_without_loading_content(self, mcp_session_factory) -> None:
|
||||
"""Ensures search returns bounded canonical skill pointers ranked by metadata."""
|
||||
async with mcp_session_factory() as mcp_session:
|
||||
result = await mcp_session.call_tool(
|
||||
"search_skills",
|
||||
{"query": "FastMCP protocol", "limit": 1},
|
||||
)
|
||||
|
||||
assert result.is_error is False
|
||||
assert result.structured_content == {
|
||||
"results": [
|
||||
{
|
||||
"name": "mcp-details",
|
||||
"description": (
|
||||
"Reference hub for MCP and FastMCP source documentation links. Use when you need "
|
||||
"authoritative protocol, SDK, transport, and deployment docs without loading broad "
|
||||
"implementation guidance."
|
||||
),
|
||||
"uri": "skill://mcp-details/SKILL.md",
|
||||
}
|
||||
]
|
||||
}
|
||||
tool_names = {tool.name for tool in result.tools}
|
||||
assert "search_skills" not in tool_names
|
||||
assert "list_resources" not in tool_names
|
||||
assert "read_resource" not in tool_names
|
||||
|
||||
class TestResources:
|
||||
"""Covers native skill resources, manifests, and file templates."""
|
||||
|
||||
@@ -7,7 +7,7 @@ resolution-markers = [
|
||||
]
|
||||
|
||||
[manifest]
|
||||
constraints = [{ name = "fastmcp-slim", specifier = "==4.0.0b1" }]
|
||||
constraints = [{ name = "fastmcp-slim", specifier = "==4.0.0b4" }]
|
||||
|
||||
[[package]]
|
||||
name = "aiofile"
|
||||
@@ -507,19 +507,19 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "fastmcp"
|
||||
version = "4.0.0b1"
|
||||
version = "4.0.0b4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "fastmcp-slim", extra = ["client", "server"] },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/eb/fd/e513c524bb3e296203f65bd8e9e726e9236bd3b59315e7cbdeb095662c01/fastmcp-4.0.0b1.tar.gz", hash = "sha256:f98d69588a73e1672840558641d5d0f111e207baffe001f3465713a53ebb6b4c", size = 42065171, upload-time = "2026-07-28T21:18:15.312Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/a1/032858e40579c3047ac6f3eb1e97b59db7ad649234d55094a9bcf2d560c1/fastmcp-4.0.0b4.tar.gz", hash = "sha256:e3a8b0f4a8300b3da15584e7bff62d464fa5458b52903d86a6a2e149c61c3edb", size = 42180304, upload-time = "2026-08-26T22:59:07.827Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/66/41b503ef852eff83f3f0c04f46cd1fc738c5374fd509384fc6b96e45918f/fastmcp-4.0.0b1-py3-none-any.whl", hash = "sha256:d66eb7b0763ffff2ae0fc573778ea25604dcb7e59769e5afaf9851a806eb1129", size = 8064, upload-time = "2026-07-28T21:18:12.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/11/132d9030c8b3235784352e7132286bf2e4d010f4f295ba4b4ccf2c44ebd1/fastmcp-4.0.0b4-py3-none-any.whl", hash = "sha256:2996abab765172bfcf2e7d4bdde77438390ecd79af5b5a3af586e97eedf8f5e4", size = 8088, upload-time = "2026-08-26T22:59:04.265Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastmcp-slim"
|
||||
version = "4.0.0b1"
|
||||
version = "4.0.0b4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mcp-types" },
|
||||
@@ -530,9 +530,9 @@ dependencies = [
|
||||
{ name = "rich" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bc/33/f207166aac88c6d8be1b2a82c54fecd1e04b075ef12d027aa17b3134fc0f/fastmcp_slim-4.0.0b1.tar.gz", hash = "sha256:158efb25720e0cb301711146b2d05d181de95cd91fe70e87c251ad14b123d665", size = 660081, upload-time = "2026-07-28T21:17:50.171Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4c/1a/89f4ade73ba26c04b573464686a2bc656b1f910d3f92f6e0950971a7c64f/fastmcp_slim-4.0.0b4.tar.gz", hash = "sha256:9a57dfa7be461d6a65903f450af6e6b51d26266f50977bf7c30508fefaab8816", size = 679556, upload-time = "2026-08-26T22:58:39.69Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/90/5d/fbe192d2ab50bb31b284fd0eb6c72d4347df7a57d836bee4f1973ffd1d7d/fastmcp_slim-4.0.0b1-py3-none-any.whl", hash = "sha256:dd907a3db5a2f479ca958c30157e227b4f7b340a56d333eb91de95719254597a", size = 827975, upload-time = "2026-07-28T21:17:48.747Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/7c/abf53a150e379563efc353e510ef412aae977929e7b289e1cd089002ffd3/fastmcp_slim-4.0.0b4-py3-none-any.whl", hash = "sha256:d20afd384efe388302d42280bdd2f5525af57d317e315f8e065a8de7517302b5", size = 852891, upload-time = "2026-08-26T22:58:38.348Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
@@ -1194,7 +1194,7 @@ test = [
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "fastapi", specifier = ">=0.133.0" },
|
||||
{ name = "fastmcp", specifier = "==4.0.0b1" },
|
||||
{ name = "fastmcp", specifier = "==4.0.0b4" },
|
||||
{ name = "pydantic-settings", specifier = ">=2" },
|
||||
{ name = "python-json-logger", specifier = ">=4" },
|
||||
{ name = "pyyaml", specifier = ">=6.0.2" },
|
||||
@@ -1932,11 +1932,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "uncalled-for"
|
||||
version = "0.3.2"
|
||||
version = "0.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b5/82/345cc927f7fbdae6065e7768759932fcc827fc20b29b45dfbafa2f1f7da4/uncalled_for-0.3.2.tar.gz", hash = "sha256:89f5dbcd71e2b8f47c030b1fa302e6cce2ec795d1ac565eeb6525c5fe55cb8a2", size = 50032, upload-time = "2026-05-06T13:38:25.204Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6b/5a/92ce0b3ea5481915f55da994c2c2c5f7a3c09949afde196ee89f8ab961aa/uncalled_for-0.4.0.tar.gz", hash = "sha256:335b95bd2422332ec210d518f314a16e4c640921c39fc8bf2ad095bd3538f4af", size = 56979, upload-time = "2026-08-10T14:51:46.247Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/25/2c87754f3a9e692315f7b811244090e68f362979fc8886b3fbd2985a1d8c/uncalled_for-0.3.2-py3-none-any.whl", hash = "sha256:0ff60b142c7d1f8070bde9d42afaa70aedc77dcc10998c227687e9c15713418e", size = 11444, upload-time = "2026-05-06T13:38:24.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/40/97cec87c077eb3291fc7905e6633e08b7ca593c57d30238444bcb6bb3d53/uncalled_for-0.4.0-py3-none-any.whl", hash = "sha256:16c4bb3337532e4bd5569adc192285976f3ad5305402256d34c67a12b5c968bd", size = 15502, upload-time = "2026-08-10T14:51:45.068Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+2
-2
@@ -107,8 +107,8 @@ nav = [
|
||||
{ "Overview" = "skills/nicegui/SKILL.md" },
|
||||
{ "App Architecture" = "skills/nicegui/references/architecture.md" },
|
||||
{ "Startup" = "skills/nicegui/references/fastapi-uvicorn-startup.md" },
|
||||
{ "Styling" = "skills/nicegui/references/styling-and-customization.md" },
|
||||
{ "Special Components" = "skills/nicegui/references/special-component-customization.md" },
|
||||
{ "Visual Styling" = "skills/nicegui/references/styling-and-customization.md" },
|
||||
{ "Component Mechanics" = "skills/nicegui/references/component-mechanics-and-customization.md" },
|
||||
{ "Binding" = "skills/nicegui/references/binding-dataclasses.md" },
|
||||
{ "Flows" = "skills/nicegui/references/interaction-patterns.md" },
|
||||
{ "Quality" = "skills/nicegui/references/troubleshooting-and-quality-gates.md" },
|
||||
|
||||
Reference in New Issue
Block a user