feat: add MCP server with resources, tools, auth, and Traefik routing
- Add backend/app/mcp_server.py with FastMCP server (hms-cms) - Resources: design-rules, page-structure, sync-status - Tools: trigger_sync, get_sync_log, set_rentman_token, read_file, write_file, create_page, deploy, git_commit, git_status - Bearer token auth via MCP_AUTH_TOKEN env var - Mount MCP at /mcp with streamable HTTP transport - Integrate session manager in FastAPI lifespan - Add Traefik labels for external /mcp route on hms.media-on.de - Add git, docker.io, curl to backend Dockerfile - Mount repo root + docker socket in backend container - Add mcp>=1.0.0 to requirements.txt
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import type { $Fetch } from "nitropack";
|
||||
|
||||
/**
|
||||
* Base API client composable.
|
||||
* Uses server-side apiBase for SSR requests, public apiBase for client-side.
|
||||
*/
|
||||
export function useApi() {
|
||||
const config = useRuntimeConfig();
|
||||
// On server: config.apiBase (http://backend:8000)
|
||||
// On client: config.public.apiBase (/api)
|
||||
const apiBase = import.meta.server ? config.apiBase : config.public.apiBase;
|
||||
|
||||
const client: $Fetch = $fetch.create({
|
||||
baseURL: apiBase,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
async function get<T>(url: string, params?: Record<string, string | number | boolean | undefined>): Promise<T> {
|
||||
return client<T>(url, {
|
||||
method: "GET",
|
||||
params: params as Record<string, string> | undefined,
|
||||
});
|
||||
}
|
||||
|
||||
async function post<T>(url: string, body?: unknown): Promise<T> {
|
||||
return client<T>(url, {
|
||||
method: "POST",
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
async function put<T>(url: string, body?: unknown): Promise<T> {
|
||||
return client<T>(url, {
|
||||
method: "PUT",
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
async function del<T>(url: string): Promise<T> {
|
||||
return client<T>(url, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
apiBase,
|
||||
client,
|
||||
get,
|
||||
post,
|
||||
put,
|
||||
del,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useCartStore } from "~/stores/cart";
|
||||
import { storeToRefs } from "pinia";
|
||||
import type { EquipmentItem } from "~/composables/useEquipment";
|
||||
|
||||
/**
|
||||
* Cart composable – wraps the Pinia cart store for convenient use in components.
|
||||
* Provides reactive refs and action methods.
|
||||
*/export function useCart() {
|
||||
const store = useCartStore();
|
||||
|
||||
const { items, totalCount, isEmpty, hasItems, apiItems } = storeToRefs(store);
|
||||
|
||||
function addItem(equipment: EquipmentItem): void {
|
||||
store.addItem(equipment);
|
||||
}
|
||||
|
||||
function addItemByFields(item: {
|
||||
equipment_id: number;
|
||||
name: string;
|
||||
rental_price: number | null;
|
||||
image_url: string | null;
|
||||
}): void {
|
||||
store.addItemByFields(item);
|
||||
}
|
||||
|
||||
function removeItem(equipmentId: number): void {
|
||||
store.removeItem(equipmentId);
|
||||
}
|
||||
|
||||
function updateQuantity(equipmentId: number, quantity: number): void {
|
||||
store.updateQuantity(equipmentId, quantity);
|
||||
}
|
||||
|
||||
function incrementQuantity(equipmentId: number): void {
|
||||
store.incrementQuantity(equipmentId);
|
||||
}
|
||||
|
||||
function decrementQuantity(equipmentId: number): void {
|
||||
store.decrementQuantity(equipmentId);
|
||||
}
|
||||
|
||||
function clearCart(): void {
|
||||
store.clearCart();
|
||||
}
|
||||
|
||||
return {
|
||||
items,
|
||||
totalCount,
|
||||
isEmpty,
|
||||
hasItems,
|
||||
apiItems,
|
||||
addItem,
|
||||
addItemByFields,
|
||||
removeItem,
|
||||
updateQuantity,
|
||||
incrementQuantity,
|
||||
decrementQuantity,
|
||||
clearCart,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Equipment API composable.
|
||||
* Wraps useApi to provide typed equipment list, detail, and categories endpoints.
|
||||
*/
|
||||
|
||||
export interface EquipmentItem {
|
||||
id: number;
|
||||
rentman_id: string;
|
||||
name: string;
|
||||
number: string | null;
|
||||
category: string | null;
|
||||
description: string | null;
|
||||
image_url: string | null;
|
||||
rental_price: number | null;
|
||||
available: boolean;
|
||||
}
|
||||
|
||||
export interface EquipmentDetail {
|
||||
id: number;
|
||||
rentman_id: string;
|
||||
name: string;
|
||||
number: string | null;
|
||||
category: string | null;
|
||||
description: string | null;
|
||||
specifications: Record<string, unknown> | null;
|
||||
images: string[] | null;
|
||||
rental_price: number | null;
|
||||
brand: string | null;
|
||||
available: boolean;
|
||||
}
|
||||
|
||||
export interface PaginatedEquipment {
|
||||
items: EquipmentItem[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
total_pages: number;
|
||||
}
|
||||
|
||||
export type SortOption = "name_asc" | "name_desc";
|
||||
|
||||
export function useEquipment() {
|
||||
const api = useApi();
|
||||
|
||||
/**
|
||||
* Fetch paginated equipment list with search, category filter, and sort.
|
||||
*/
|
||||
async function list(params: {
|
||||
search?: string;
|
||||
category?: string;
|
||||
sort?: SortOption;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
} = {}): Promise<PaginatedEquipment> {
|
||||
return api.get<PaginatedEquipment>("/api/equipment", {
|
||||
search: params.search || undefined,
|
||||
category: params.category || undefined,
|
||||
sort: params.sort || "name_asc",
|
||||
page: params.page || 1,
|
||||
page_size: params.page_size || 20,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a single equipment detail by ID.
|
||||
*/
|
||||
async function detail(id: number | string): Promise<EquipmentDetail> {
|
||||
return api.get<EquipmentDetail>(`/api/equipment/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all available equipment categories.
|
||||
*/
|
||||
async function categories(): Promise<string[]> {
|
||||
return api.get<string[]>("/api/equipment/categories");
|
||||
}
|
||||
|
||||
return {
|
||||
list,
|
||||
detail,
|
||||
categories,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user