glpi-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ .git
2
+ .venv
3
+ .opencode
4
+ __pycache__
5
+ *.py[cod]
6
+ dist
7
+ build
8
+ *.egg-info
9
+ .DS_Store
@@ -0,0 +1,15 @@
1
+ # URL base del sitio GLPI (obligatoria), sin apirest.php. Ej: https://glpi.ejemplo.com
2
+ GLPI_BASE_URL=
3
+
4
+ # App Token generado en GLPI (Setup > General > API) (obligatorio)
5
+ GLPI_APP_TOKEN=
6
+
7
+ # User Token del usuario de API (obligatorio)
8
+ GLPI_USER_TOKEN=
9
+
10
+ # Transporte del servidor MCP: stdio | streamable-http (o alias http) | sse (default: stdio)
11
+ TRANSPORT=stdio
12
+
13
+ # Config solo para transporte http/sse
14
+ HOST=127.0.0.1
15
+ PORT=8000
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .env
5
+ dist/
6
+ build/
7
+ *.egg-info/
8
+ .DS_Store
9
+ .opencode
@@ -0,0 +1,144 @@
1
+ image: alpine:3.17.3
2
+
3
+ services:
4
+ - name: docker:dind
5
+
6
+ stages:
7
+ - test
8
+ - build
9
+ - environment
10
+ - deploy
11
+
12
+ sonar:
13
+ except:
14
+ - develop
15
+ - main
16
+ - cicd
17
+ - stage
18
+ stage: test
19
+ tags:
20
+ - sonar
21
+ image:
22
+ name: sonarsource/sonar-scanner-cli:11.1
23
+ entrypoint: ['']
24
+ cache:
25
+ key: '${CI_JOB_NAME}'
26
+ paths:
27
+ - .sonar/cache
28
+ script:
29
+ - sleep 10 && sonar-scanner -X -Dsonar.qualitygate.wait=true
30
+ -Dsonar.host.url=$SONAR_HOST_URL
31
+ -Dsonar.login=$SONAR_TOKEN
32
+
33
+ test:
34
+ except:
35
+ - develop
36
+ - main
37
+ - cicd
38
+ - stage
39
+ stage: test
40
+ tags:
41
+ - npm
42
+ image:
43
+ name: node:22.12.0-alpine
44
+ entrypoint: ['']
45
+ script:
46
+ - npm install
47
+ - npm test
48
+
49
+
50
+ ## Build the docker image to deploy
51
+ build:
52
+ image:
53
+ name: alpine:latest
54
+ entrypoint: ['']
55
+ only:
56
+ - develop
57
+ - main
58
+ stage: build
59
+ tags:
60
+ - server3
61
+ script:
62
+ - apk add docker
63
+ - export DOCKER_HOST=$DEV_DOCKER_HOST
64
+ - export DOCKER_IMAGE_NAME=$CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_SHA
65
+ - docker login gitlab.globalsoftm.com:5050 -u $DOCKER_USER -p $DOCKER_PASS
66
+
67
+ ## Build & push docker image
68
+ - docker build -t $DOCKER_IMAGE_NAME .
69
+ - docker push $DOCKER_IMAGE_NAME
70
+
71
+
72
+ ############ DEV ###########
73
+
74
+ environment:
75
+ image:
76
+ name: rarzate/kubectl-with-wget:latest
77
+ entrypoint: ['']
78
+ only:
79
+ - develop
80
+ stage: environment
81
+ tags:
82
+ - server3
83
+ script:
84
+ # kubectl config
85
+ - kubectl config use-context globalsoft/k8s-integration:desarrollo001
86
+ ### CHANGE THIS VARIABLES AS YOU NEEDS ###
87
+ - export DOMAIN=gservicio.com # Change for domain to deploy
88
+
89
+ # Define environment variables
90
+ - export PROJECT_NAME=${CI_PROJECT_NAME//_/-}
91
+ - export DOCKER_IMAGE_NAME=$CI_REGISTRY\\/$CI_PROJECT_NAMESPACE\\/$CI_PROJECT_NAME:$CI_COMMIT_SHA
92
+ - export BRANCH_NAME=`echo $CI_COMMIT_BRANCH | tr '[:upper:]' '[:lower:]'`
93
+ - export DNS_SUBDOMAIN=$BRANCH_NAME.$PROJECT_NAME
94
+ - export DNS_DOMAIN=$DNS_SUBDOMAIN.$DOMAIN
95
+ - export NAME_SPACE=$BRANCH_NAME-$PROJECT_NAME
96
+
97
+ # Create namespace
98
+ - kubectl get ns $NAME_SPACE || kubectl create ns $NAME_SPACE
99
+ - kubectl config set-context --current --namespace $NAME_SPACE
100
+
101
+ # Create secret for registry
102
+ - kubectl get secret gitlab-secret || kubectl create secret docker-registry gitlab-secret --docker-server=gitlab.globalsoftm.com:5050 --docker-username=$GITLAB_SECRET_USER --docker-password=$GITLAB_SECRET_PASSWORD --docker-email=$GITLAB_SECRET_EMAIL
103
+
104
+ # Create configmap for env vars
105
+ - env | grep "${CI_COMMIT_BRANCH}_ENV" | sed -e "s/${CI_COMMIT_BRANCH}_ENV=//" | base64 -d > env.txt
106
+ - kubectl get cm envvars && kubectl delete cm envvars || echo "Configmap does not extsts"
107
+ - kubectl create configmap envvars --from-file=env.txt
108
+
109
+ # Show result
110
+ - echo "DNS created $DNS_DOMAIN || K8s namespace created $NAME_SPACE"
111
+
112
+ deploy:
113
+ image:
114
+ name: rarzate/kubectl-with-wget:latest
115
+ entrypoint: ['']
116
+ only:
117
+ - develop
118
+ stage: deploy
119
+ tags:
120
+ - server3
121
+ script:
122
+ # kubectl config
123
+ - kubectl config use-context globalsoft/k8s-integration:desarrollo001
124
+ ### CHANGE THIS VARIABLES AS YOUR NEEDS ###
125
+ - export DOMAIN=gservicio.com # Change for domain to deploy
126
+
127
+ # Define environment variables
128
+ - export PROJECT_NAME=${CI_PROJECT_NAME//_/-}
129
+ - export DOCKER_IMAGE_NAME=$CI_REGISTRY\\/$CI_PROJECT_NAMESPACE\\/$CI_PROJECT_NAME:$CI_COMMIT_SHA
130
+ - export BRANCH_NAME=`echo $CI_COMMIT_BRANCH | tr '[:upper:]' '[:lower:]'`
131
+ - export DNS_SUBDOMAIN=$BRANCH_NAME.$PROJECT_NAME
132
+ - export DNS_DOMAIN=$DNS_SUBDOMAIN.$DOMAIN
133
+ - export NAME_SPACE=$BRANCH_NAME-$PROJECT_NAME
134
+
135
+ # Set namespace
136
+ - kubectl config set-context --current --namespace $NAME_SPACE
137
+
138
+ # Deploy application
139
+ - sed -i "s/\$DOCKER_IMAGE_NAME/$DOCKER_IMAGE_NAME/g" k8s/deploy.yml
140
+ - sed -i "s/\$DNS_DOMAIN/$DNS_DOMAIN/g" k8s/deploy.yml
141
+ - kubectl delete deployment backend && sleep 10
142
+ - kubectl apply -f k8s/deploy.yml
143
+ - echo "Application deployed on https://$DNS_DOMAIN K8s namespace created $NAME_SPACE"
144
+
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,44 @@
1
+ # AGENTS.md
2
+
3
+ ## Qué es
4
+
5
+ Servidor MCP en Python que expone tools para consultar la API REST de GLPI
6
+ vía HTTP. Stack: SDK `mcp` v1 (`MCPServer`, no FastMCP) + `httpx`, gestionado
7
+ con `uv` (Python 3.12, `requires-python >=3.11`).
8
+
9
+ ## Comandos
10
+
11
+ ```sh
12
+ uv run glpi-mcp # arranca el servidor (entrypoint server:main)
13
+ uv run python -m glpi_mcp # equivalente
14
+ ```
15
+
16
+ - Transporte controlado por `TRANSPORT`: `stdio` (default) | `streamable-http` (alias `http`) | `sse`. Para http/sse se usan `HOST`/`PORT` (default 127.0.0.1:8000).
17
+ - No hay tests, linter, formatter ni CI configurados. Verificación mínima:
18
+ `uv run python -c "import glpi_mcp.server; from glpi_mcp.config import get_settings; get_settings()"` (falla con `SettingsError` si no hay `.env`).
19
+
20
+ ## Configuración
21
+
22
+ - `.env` es obligatorio y está gitignored; copiar `.env.example`. Requiere 3 variables:
23
+ `GLPI_BASE_URL` (raíz del GLPI, sin `/apirest.php`), `GLPI_APP_TOKEN` (Setup > General > API) y
24
+ `GLPI_USER_TOKEN` (del usuario de API). Sin ellas el server sale con error.
25
+ `.env` se carga desde el cwd o desde la raíz del repo (`config._load_env`).
26
+ - **Autenticación**: la API de GLPI usa App Token + token de sesión. `client.request`
27
+ inicia una sesión por cada petición (initSession) y la cierra (killSession).
28
+ Los paths de endpoints deben usar el prefijo `/apirest.php/`.
29
+
30
+ ## Agregar una tool (flujo principal de trabajo)
31
+
32
+ 1. Crea `src/glpi_mcp/tools/<modulo>.py` con una función `register(mcp: MCPServer) -> None` y tools decoradas con `@mcp.tool()`.
33
+ 2. Regístralo en `TOOL_MODULES` de `src/glpi_mcp/server.py`.
34
+
35
+ Detalles no obvios:
36
+ - Las tools devuelven `str` (JSON serializado), no objetos; los `ApiError` se capturan y se devuelven como string.
37
+ - `client.request(method, path, params, json_body)` devuelve JSON parseado o texto; los paths se normalizan con `/` inicial.
38
+ - `client.init_session` / `client.kill_session` son públicos por si alguna tool necesita control manual de la sesión.
39
+
40
+ ## Gotchas
41
+
42
+ - La fuente de verdad de las tools registradas es `server.py:TOOL_MODULES` (actualmente vacío: aún no hay tools).
43
+ - `base_url` NO debe incluir `/apirest.php`; los paths de los endpoints sí lo llevan.
44
+ - Rama de git por defecto: `develop`. Sin commits iniciales todavía.
@@ -0,0 +1,24 @@
1
+ # syntax=docker/dockerfile:1
2
+
3
+ FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
4
+
5
+ ENV UV_COMPILE_BYTECODE=1 \
6
+ UV_LINK_MODE=copy \
7
+ PATH="/app/.venv/bin:$PATH"
8
+
9
+ WORKDIR /app
10
+
11
+ COPY pyproject.toml uv.lock ./
12
+ RUN --mount=type=cache,target=/root/.cache/uv \
13
+ uv sync --frozen --no-dev --no-install-project
14
+
15
+ COPY README.md ./
16
+ COPY src ./src
17
+ RUN --mount=type=cache,target=/root/.cache/uv \
18
+ uv sync --frozen --no-dev
19
+
20
+ RUN useradd --create-home --uid 1000 mcpuser && chown -R mcpuser:mcpuser /app
21
+ USER mcpuser
22
+
23
+
24
+ CMD ["glpi-mcp"]
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.5
2
+ Name: glpi-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server con tools para consultar la API REST de GLPI
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: httpx>=0.27.0
7
+ Requires-Dist: mcp>=1.0.0
8
+ Requires-Dist: python-dotenv>=1.0.0
9
+ Description-Content-Type: text/markdown
10
+
11
+ # GLPI MCP
12
+
13
+ Servidor MCP en Python para interactuar con la [API REST de GLPI](https://glpi-project.org) (App Token + User Token).
14
+
15
+ ## Requisitos
16
+
17
+ - Python 3.11+ (desarrollado con 3.12)
18
+ - [uv](https://docs.astral.sh/uv/)
19
+
20
+ ## Configuración
21
+
22
+ ```sh
23
+ cp .env.example .env
24
+ # Rellena GLPI_BASE_URL, GLPI_APP_TOKEN y GLPI_USER_TOKEN
25
+ ```
26
+
27
+ ## Uso
28
+
29
+ ```sh
30
+ uv sync
31
+ uv run glpi-mcp
32
+ ```
33
+
34
+ ## Transportes
35
+
36
+ `TRANSPORT` controla el transporte MCP: `stdio` (default), `streamable-http` (o `http`) y `sse`.
37
+
38
+ ## Estructura
39
+
40
+ ```
41
+ src/glpi_mcp/
42
+ ├── client.py # Cliente HTTP con auth por sesión GLPI
43
+ ├── config.py # Settings desde variables de entorno
44
+ ├── server.py # Registro de tools y arranque
45
+ └── tools/ # Módulos de tools (uno por área)
46
+ ```
@@ -0,0 +1,36 @@
1
+ # GLPI MCP
2
+
3
+ Servidor MCP en Python para interactuar con la [API REST de GLPI](https://glpi-project.org) (App Token + User Token).
4
+
5
+ ## Requisitos
6
+
7
+ - Python 3.11+ (desarrollado con 3.12)
8
+ - [uv](https://docs.astral.sh/uv/)
9
+
10
+ ## Configuración
11
+
12
+ ```sh
13
+ cp .env.example .env
14
+ # Rellena GLPI_BASE_URL, GLPI_APP_TOKEN y GLPI_USER_TOKEN
15
+ ```
16
+
17
+ ## Uso
18
+
19
+ ```sh
20
+ uv sync
21
+ uv run glpi-mcp
22
+ ```
23
+
24
+ ## Transportes
25
+
26
+ `TRANSPORT` controla el transporte MCP: `stdio` (default), `streamable-http` (o `http`) y `sse`.
27
+
28
+ ## Estructura
29
+
30
+ ```
31
+ src/glpi_mcp/
32
+ ├── client.py # Cliente HTTP con auth por sesión GLPI
33
+ ├── config.py # Settings desde variables de entorno
34
+ ├── server.py # Registro de tools y arranque
35
+ └── tools/ # Módulos de tools (uno por área)
36
+ ```
@@ -0,0 +1,103 @@
1
+ # apiVersion: v1
2
+ # kind: PersistentVolumeClaim
3
+ # metadata:
4
+ # name: parkit-pvc
5
+ # spec:
6
+ # storageClassName: oci-bv
7
+ # accessModes:
8
+ # - ReadWriteOnce
9
+ # resources:
10
+ # requests:
11
+ # storage: 5Gi
12
+ # ---
13
+ apiVersion: apps/v1
14
+ kind: Deployment
15
+ metadata:
16
+ name: backend
17
+ labels:
18
+ app: backend
19
+ spec:
20
+ replicas: 1
21
+ selector:
22
+ matchLabels:
23
+ app: backend
24
+ strategy:
25
+ rollingUpdate:
26
+ maxUnavailable: 25%
27
+ template:
28
+ metadata:
29
+ labels:
30
+ app: backend
31
+ spec:
32
+ imagePullSecrets:
33
+ - name: gitlab-secret
34
+ containers:
35
+ - image: $DOCKER_IMAGE_NAME
36
+ volumeMounts:
37
+ - name: envvars
38
+ mountPath: '/app/.env'
39
+ subPath: .env
40
+ readOnly: true
41
+
42
+ resources:
43
+ limits:
44
+ cpu: 200m
45
+ memory: 512Mi
46
+ requests:
47
+ cpu: 100m
48
+ memory: 256Mi
49
+
50
+ imagePullPolicy: Always
51
+ name: backend
52
+ ports:
53
+ - containerPort: 8000
54
+ protocol: TCP
55
+ securityContext:
56
+ capabilities:
57
+ drop:
58
+ - all
59
+ add: ['NET_BIND_SERVICE']
60
+ allowPrivilegeEscalation: false
61
+
62
+ restartPolicy: Always
63
+ volumes:
64
+ - name: envvars
65
+ configMap:
66
+ name: envvars
67
+ items:
68
+ - key: env.txt
69
+ path: .env
70
+
71
+ ---
72
+ apiVersion: v1
73
+ kind: Service
74
+ metadata:
75
+ name: backend-service
76
+ spec:
77
+ selector:
78
+ app: backend
79
+ ports:
80
+ - port: 80
81
+ protocol: TCP
82
+ targetPort: 8000
83
+ ---
84
+ apiVersion: networking.k8s.io/v1
85
+ kind: Ingress
86
+ metadata:
87
+ name: ingress-backed
88
+ annotations:
89
+ kubernetes.io/ingress.class: nginx
90
+ nginx.ingress.kubernetes.io/proxy-body-size: 2048m
91
+ nginx.org/client-max-body-size: 2048m
92
+ spec:
93
+ rules:
94
+ - host: '$DNS_DOMAIN'
95
+ http:
96
+ paths:
97
+ - pathType: Prefix
98
+ path: '/'
99
+ backend:
100
+ service:
101
+ name: backend-service
102
+ port:
103
+ number: 80
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+ "mcp": {
4
+ // "glpi": {
5
+ // "type": "remote",
6
+ // "url": "https://develop.gs-glpi-mcp.gservicio.com/mcp",
7
+ // "headers": {
8
+ // "Authorization": "Bearer {env:GLPI_MCP_TOKEN}"
9
+ // },
10
+ // "enabled": true
11
+ // }
12
+ "glpi": {
13
+ "type": "local",
14
+ "command": [
15
+ "uv",
16
+ "run",
17
+ "glpi-mcp"
18
+ ],
19
+ "enabled": true
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,24 @@
1
+ [project]
2
+ name = "glpi-mcp"
3
+ version = "0.1.0"
4
+ description = "MCP server con tools para consultar la API REST de GLPI"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "mcp>=1.0.0",
9
+ "httpx>=0.27.0",
10
+ "python-dotenv>=1.0.0",
11
+ ]
12
+
13
+ [project.scripts]
14
+ glpi-mcp = "glpi_mcp.server:main"
15
+
16
+ [build-system]
17
+ requires = ["hatchling"]
18
+ build-backend = "hatchling.build"
19
+
20
+ [tool.hatch.build.targets.wheel]
21
+ packages = ["src/glpi_mcp"]
22
+
23
+ [tool.uv]
24
+ package = true
@@ -0,0 +1,4 @@
1
+ from glpi_mcp.config import Settings, get_settings
2
+
3
+ __all__ = ["Settings", "get_settings"]
4
+ __version__ = "0.1.0"
@@ -0,0 +1,3 @@
1
+ from glpi_mcp.server import main
2
+
3
+ main()
@@ -0,0 +1,55 @@
1
+ """Autenticación Bearer para los transportes http/sse.
2
+
3
+ Integra el mecanismo de auth nativo del SDK mcp: el servidor envuelve el
4
+ endpoint con RequireAuthMiddleware cuando se le pasa un TokenVerifier, de
5
+ modo que cada petición sin header `Authorization: Bearer` (o con token
6
+ inválido) responde 401 + WWW-Authenticate.
7
+
8
+ Los tokens se comparan en tiempo constante (secrets.compare_digest) y se
9
+ aceptan varios a la vez para poder rotarlos con solapamiento.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import secrets
15
+
16
+ from mcp.server.auth.provider import AccessToken, TokenVerifier
17
+ from mcp.server.auth.settings import AuthSettings
18
+
19
+ from glpi_mcp.config import Settings
20
+
21
+ # Ruta del endpoint streamable-http que se expone con auth.
22
+ MCP_HTTP_PATH = "/mcp"
23
+
24
+
25
+ class StaticTokenVerifier(TokenVerifier):
26
+ """Valida un bearer token contra una lista fija de tokens permitidos."""
27
+
28
+ def __init__(self, tokens: tuple[str, ...]) -> None:
29
+ self._tokens = tokens
30
+
31
+ async def verify_token(self, token: str) -> AccessToken | None:
32
+ if not token:
33
+ return None
34
+ for valid in self._tokens:
35
+ if valid and secrets.compare_digest(token, valid):
36
+ return AccessToken(token=token, client_id="glpi-mcp", scopes=[])
37
+ return None
38
+
39
+
40
+ def build_auth(settings: Settings) -> tuple[AuthSettings, TokenVerifier] | None:
41
+ """Construye la configuración de auth del MCPServer.
42
+
43
+ Devuelve None si no hay tokens configurados (p. ej. transporte stdio).
44
+ issuer_url y resource_server_url son campos obligatorios de AuthSettings;
45
+ aquí alimentan el metadata RFC 9728 y el header WWW-Authenticate.
46
+ """
47
+ if not settings.bearer_tokens:
48
+ return None
49
+
50
+ base_url = f"http://{settings.host}:{settings.port}"
51
+ auth_settings = AuthSettings(
52
+ issuer_url=base_url,
53
+ resource_server_url=f"{base_url}{MCP_HTTP_PATH}",
54
+ )
55
+ return auth_settings, StaticTokenVerifier(settings.bearer_tokens)