diff --git a/bin/Tiltfile b/bin/Tiltfile index 5b3e72a7..41c2789e 100644 --- a/bin/Tiltfile +++ b/bin/Tiltfile @@ -39,10 +39,19 @@ docker_build( ] ) +docker_build( + 'localhost:5001/impress-mcp-server:latest', + context='../src/mcp_server', + dockerfile='../src/mcp_server/Dockerfile', +) + k8s_resource('impress-docs-backend-migrate', resource_deps=['postgres-postgresql']) k8s_resource('impress-docs-backend-createsuperuser', resource_deps=['impress-docs-backend-migrate']) k8s_resource('impress-docs-backend', resource_deps=['impress-docs-backend-migrate']) -k8s_yaml(local('cd ../src/helm && helmfile -n impress -e dev template .')) + +# helmfile in docker mount the current working directory and the helmfile.yaml +# requires the keycloak config in another directory +k8s_yaml(local('cd .. && helmfile -n impress -e ${DEV_ENV:-dev} template --file ./src/helm/helmfile.yaml')) migration = ''' set -eu diff --git a/src/helm/env.d/dev/values.impress.yaml.gotmpl b/src/helm/env.d/dev/values.impress.yaml.gotmpl index 57e92cd5..002101e8 100644 --- a/src/helm/env.d/dev/values.impress.yaml.gotmpl +++ b/src/helm/env.d/dev/values.impress.yaml.gotmpl @@ -29,19 +29,25 @@ backend: DJANGO_EMAIL_PORT: 1025 DJANGO_EMAIL_USE_SSL: False LOGGING_LEVEL_HANDLERS_CONSOLE: ERROR - LOGGING_LEVEL_LOGGERS_ROOT: INFO - LOGGING_LEVEL_LOGGERS_APP: INFO + LOGGING_LEVEL_LOGGERS_ROOT: DEBUG + LOGGING_LEVEL_LOGGERS_APP: DEBUG OIDC_USERINFO_SHORTNAME_FIELD: "given_name" OIDC_USERINFO_FULLNAME_FIELDS: "given_name,usual_name" + OIDC_OP_URL: https://keycloak.127.0.0.1.nip.io/realms/people OIDC_OP_JWKS_ENDPOINT: https://docs-keycloak.127.0.0.1.nip.io/realms/impress/protocol/openid-connect/certs OIDC_OP_AUTHORIZATION_ENDPOINT: https://docs-keycloak.127.0.0.1.nip.io/realms/impress/protocol/openid-connect/auth OIDC_OP_TOKEN_ENDPOINT: https://docs-keycloak.127.0.0.1.nip.io/realms/impress/protocol/openid-connect/token OIDC_OP_USER_ENDPOINT: https://docs-keycloak.127.0.0.1.nip.io/realms/impress/protocol/openid-connect/userinfo OIDC_OP_LOGOUT_ENDPOINT: https://docs-keycloak.127.0.0.1.nip.io/realms/impress/protocol/openid-connect/logout + OIDC_OP_INTROSPECTION_ENDPOINT: https://docs-keycloak.127.0.0.1.nip.io/realms/impress/protocol/openid-connect/token/introspect OIDC_RP_CLIENT_ID: impress OIDC_RP_CLIENT_SECRET: ThisIsAnExampleKeyForDevPurposeOnly OIDC_RP_SIGN_ALGO: RS256 OIDC_RP_SCOPES: "openid email" + OIDC_RS_CLIENT_ID: impress + OIDC_RS_CLIENT_SECRET: ThisIsAnExampleKeyForDevPurposeOnly + OIDC_RS_SIGNING_ALGO: RS256 + OIDC_RS_SCOPES: "openid,profile,email" LOGIN_REDIRECT_URL: https://impress.127.0.0.1.nip.io LOGIN_REDIRECT_URL_FAILURE: https://impress.127.0.0.1.nip.io LOGOUT_REDIRECT_URL: https://impress.127.0.0.1.nip.io @@ -171,3 +177,20 @@ ingressMedia: serviceMedia: host: minio.impress.svc.cluster.local port: 9000 + + +mcpServer: + replicas: 1 + + image: + repository: localhost:5001/impress-mcp-server + pullPolicy: Always + tag: "latest" + + envVars: + DOCS_API_URL: https://impress.127.0.0.1.nip.io/ + SERVER_TRANSPORT: STREAMABLE_HTTP + +ingressMcpServer: + enabled: true + host: impress.127.0.0.1.nip.io diff --git a/src/helm/impress/templates/_helpers.tpl b/src/helm/impress/templates/_helpers.tpl index 63a9f4b3..5f66ded0 100644 --- a/src/helm/impress/templates/_helpers.tpl +++ b/src/helm/impress/templates/_helpers.tpl @@ -178,6 +178,15 @@ Requires top level scope {{ include "impress.fullname" . }}-celery-worker {{- end }} +{{/* +Full name for the MCP server + +Requires top level scope +*/}} +{{- define "impress.mcpServer.fullname" -}} +{{ include "impress.fullname" . }}-mcp-server +{{- end }} + {{/* Usage : {{ include "impress.secret.dockerconfigjson.name" (dict "fullname" (include "impress.fullname" .) "imageCredentials" .Values.path.to.the.image1) }} */}} diff --git a/src/helm/impress/templates/ingress_mcp_server.yaml b/src/helm/impress/templates/ingress_mcp_server.yaml new file mode 100644 index 00000000..52edb618 --- /dev/null +++ b/src/helm/impress/templates/ingress_mcp_server.yaml @@ -0,0 +1,89 @@ +{{- if .Values.ingressMcpServer.enabled -}} +{{- $fullName := include "impress.fullname" . -}} +{{- if and .Values.ingressMcpServer.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingressMcpServer.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingressMcpServer.annotations "kubernetes.io/ingress.class" .Values.ingressMcpServer.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }}-mcp-server + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "impress.labels" . | nindent 4 }} + {{- with .Values.ingressMcpServer.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingressMcpServer.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingressMcpServer.className }} + {{- end }} + {{- if .Values.ingressMcpServer.tls.enabled }} + tls: + {{- if .Values.ingressMcpServer.host }} + - secretName: {{ .Values.ingressMcpServer.tls.secretName | default (printf "%s-tls" $fullName) | quote }} + hosts: + - {{ .Values.ingressMcpServer.host | quote }} + {{- end }} + {{- range .Values.ingressMcpServer.tls.additional }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- if .Values.ingressMcpServer.host }} + - host: {{ .Values.ingressMcpServer.host | quote }} + http: + paths: + - path: {{ .Values.ingressMcpServer.path | quote }} + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: Prefix + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ include "impress.mcpServer.fullname" . }} + port: + number: {{ .Values.mcpServer.service.port }} + {{- else }} + serviceName: {{ include "impress.mcpServer.fullname" . }} + servicePort: {{ .Values.mcpServer.service.port }} + {{- end }} + {{- with .Values.ingressMcpServer.customBackends }} + {{- toYaml . | nindent 10 }} + {{- end }} + {{- end }} + {{- range .Values.ingressMcpServer.hosts }} + - host: {{ . | quote }} + http: + paths: + - path: {{ $.Values.ingressMcpServer.path | quote }} + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: Prefix + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ include "impress.mcpServer.fullname" $ }} + port: + number: {{ $.Values.mcpServer.service.port }} + {{- else }} + serviceName: {{ include "impress.mcpServer.fullname" $ }} + servicePort: {{ $.Values.mcpServer.service.port }} + {{- end }} + {{- with $.Values.ingressMcpServer.customBackends }} + {{- toYaml . | nindent 10 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/src/helm/impress/templates/mcp_server_deployment.yaml b/src/helm/impress/templates/mcp_server_deployment.yaml new file mode 100644 index 00000000..20a46491 --- /dev/null +++ b/src/helm/impress/templates/mcp_server_deployment.yaml @@ -0,0 +1,153 @@ +{{- $envVars := include "impress.common.env" (list . .Values.mcpServer) -}} +{{- $fullName := include "impress.mcpServer.fullname" . -}} +{{- $component := "mcp-server" -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace | quote }} + annotations: + {{- with .Values.mcpServer.dpAnnotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "impress.common.labels" (list . $component) | nindent 4 }} +spec: + replicas: {{ .Values.backend.replicas }} + selector: + matchLabels: + {{- include "impress.common.selectorLabels" (list . $component) | nindent 6 }} + template: + metadata: + annotations: + {{- with .Values.backend.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "impress.common.selectorLabels" (list . $component) | nindent 8 }} + spec: + {{- if $.Values.image.credentials }} + imagePullSecrets: + - name: {{ include "impress.secret.dockerconfigjson.name" (dict "fullname" (include "impress.fullname" .) "imageCredentials" $.Values.image.credentials) }} + {{- end}} + shareProcessNamespace: {{ .Values.backend.shareProcessNamespace }} + containers: + {{- with .Values.mcpServer.sidecars }} + {{- toYaml . | nindent 8 }} + {{- end }} + - name: {{ .Chart.Name }} + image: "{{ (.Values.mcpServer.image | default dict).repository }}:{{ (.Values.mcpServer.image | default dict).tag }}" + imagePullPolicy: {{ (.Values.mcpServer.image | default dict).pullPolicy }} + {{- with .Values.mcpServer.command }} + command: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.mcpServer.args }} + args: + {{- toYaml . | nindent 12 }} + {{- end }} + env: + {{- if $envVars}} + {{- $envVars | indent 12 }} + {{- end }} + {{- with .Values.mcpServer.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: {{ .Values.mcpServer.service.targetPort }} + protocol: TCP + {{- if .Values.mcpServer.probes.liveness }} + livenessProbe: + {{- include "impress.probes.abstract" (merge .Values.mcpServer.probes.liveness (dict "targetPort" .Values.mcpServer.service.targetPort )) | nindent 12 }} + {{- end }} + {{- if .Values.mcpServer.probes.readiness }} + readinessProbe: + {{- include "impress.probes.abstract" (merge .Values.mcpServer.probes.readiness (dict "targetPort" .Values.mcpServer.service.targetPort )) | nindent 12 }} + {{- end }} + {{- if .Values.mcpServer.probes.startup }} + startupProbe: + {{- include "impress.probes.abstract" (merge .Values.mcpServer.probes.startup (dict "targetPort" .Values.mcpServer.service.targetPort )) | nindent 12 }} + {{- end }} + {{- with .Values.mcpServer.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + {{- range $index, $value := .Values.mountFiles }} + - name: "files-{{ $index }}" + mountPath: {{ $value.path }} + subPath: content + {{- end }} + {{- range $name, $volume := .Values.mcpServer.persistence }} + - name: "{{ $name }}" + mountPath: "{{ $volume.mountPath }}" + {{- end }} + {{- range .Values.mcpServer.extraVolumeMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath | default "" }} + readOnly: {{ .readOnly }} + {{- end }} + {{- with .Values.mcpServer.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.mcpServer.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.mcpServer.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + {{- range $index, $value := .Values.mountFiles }} + - name: "files-{{ $index }}" + configMap: + name: "{{ include "impress.fullname" $ }}-files-{{ $index }}" + {{- end }} + {{- range $name, $volume := .Values.mcpServer.persistence }} + - name: "{{ $name }}" + {{- if eq $volume.type "emptyDir" }} + emptyDir: {} + {{- else }} + persistentVolumeClaim: + claimName: "{{ $fullName }}-{{ $name }}" + {{- end }} + {{- end }} + {{- range .Values.mcpServer.extraVolumes }} + - name: {{ .name }} + {{- if .existingClaim }} + persistentVolumeClaim: + claimName: {{ .existingClaim }} + {{- else if .hostPath }} + hostPath: + {{ toYaml .hostPath | nindent 12 }} + {{- else if .csi }} + csi: + {{- toYaml .csi | nindent 12 }} + {{- else if .configMap }} + configMap: + {{- toYaml .configMap | nindent 12 }} + {{- else if .emptyDir }} + emptyDir: + {{- toYaml .emptyDir | nindent 12 }} + {{- else }} + emptyDir: {} + {{- end }} + {{- end }} +--- +{{ if .Values.mcpServer.pdb.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace | quote }} +spec: + maxUnavailable: 1 + selector: + matchLabels: + {{- include "impress.common.selectorLabels" (list . $component) | nindent 6 }} +{{ end }} diff --git a/src/helm/impress/templates/mcp_server_svc.yaml b/src/helm/impress/templates/mcp_server_svc.yaml new file mode 100644 index 00000000..640cb3e2 --- /dev/null +++ b/src/helm/impress/templates/mcp_server_svc.yaml @@ -0,0 +1,21 @@ +{{- $envVars := include "impress.common.env" (list . .Values.mcpServer) -}} +{{- $fullName := include "impress.mcpServer.fullname" . -}} +{{- $component := "mcp-server" -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "impress.common.labels" (list . $component) | nindent 4 }} + annotations: + {{- toYaml $.Values.mcpServer.service.annotations | nindent 4 }} +spec: + type: {{ .Values.mcpServer.service.type }} + ports: + - port: {{ .Values.mcpServer.service.port }} + targetPort: {{ .Values.mcpServer.service.targetPort }} + protocol: TCP + name: http + selector: + {{- include "impress.common.selectorLabels" (list . $component) | nindent 4 }} diff --git a/src/helm/impress/values.yaml b/src/helm/impress/values.yaml index f743f02d..5b236136 100644 --- a/src/helm/impress/values.yaml +++ b/src/helm/impress/values.yaml @@ -50,6 +50,31 @@ ingress: ## @param ingress.customBackends Add custom backends to ingress customBackends: [] +## @param ingressMcpServer.enabled whether to enable the Ingress or not +## @param ingressMcpServer.className IngressClass to use for the Ingress +## @param ingressMcpServer.host Host for the Ingress +## @param ingressMcpServer.path Path to use for the Ingress +ingressMcpServer: + enabled: false + className: null + host: impress.example.com + path: /mcp/docs/ + ## @param ingressMcpServer.hosts Additional host to configure for the Ingress + hosts: [] + # - chart-example.local + ## @param ingressMcpServer.tls.enabled Weather to enable TLS for the Ingress + ## @param ingressMcpServer.tls.secretName Secret name for TLS config + ## @skip ingressMcpServer.tls.additional + ## @extra ingressMcpServer.tls.additional[].secretName Secret name for additional TLS config + ## @extra ingressMcpServer.tls.additional[].hosts[] Hosts for additional TLS config + tls: + enabled: true + secretName: null + additional: [] + + ## @param ingressMcpServer.customBackends Add custom backends to ingress + customBackends: [] + ## @param ingressCollaborationWS.enabled whether to enable the Ingress or not ## @param ingressCollaborationWS.className IngressClass to use for the Ingress ## @param ingressCollaborationWS.host Host for the Ingress @@ -348,6 +373,93 @@ backend: timeoutSeconds: 5 +## @section mcpServer + +mcpServer: + ## @param mcpServer.image.repository Repository to use to pull impress's MCP server container image + ## @param mcpServer.image.tag impress's MCP server container tag + ## @param mcpServer.image.pullPolicy MCP server container image pull policy + image: + repository: lasuite/impress-mcp-server + pullPolicy: IfNotPresent + tag: "latest" + + ## @param mcpServer.command Override the MCP server container command + command: [] + + ## @param mcpServer.args Override the backend container args + args: [] + + ## @param mcpServer.envVars Configure MCP server container environment variables + envVars: [] + + ## @param mcpServer.replicas Amount of backend replicas + replicas: 3 + + ## @param mcpServer.podAnnotations Annotations to add to the MCP server Pod + podAnnotations: {} + + ## @param mcpServer.dpAnnotations Annotations to add to the MCP server Deployment + dpAnnotations: {} + + ## @param mcpServer.sidecars Add sidecars containers to MCP server deployment + sidecars: [] + + ## @param mcpServer.securityContext Configure MCP server Pod security context + securityContext: null + + ## @param mcpServer.service.type frontend Service type + ## @param mcpServer.service.port frontend Service listening port + ## @param mcpServer.service.targetPort frontend container listening port + ## @param mcpServer.service.annotations Annotations to add to the frontend Service + service: + type: ClusterIP + port: 80 + targetPort: 4200 + annotations: {} + + ## @param mcpServer.probes Configure probe for frontend + ## @extra mcpServer.probes.liveness.path Configure path for frontend HTTP liveness probe + ## @extra mcpServer.probes.liveness.targetPort Configure port for frontend HTTP liveness probe + ## @extra mcpServer.probes.liveness.initialDelaySeconds Configure initial delay for frontend liveness probe + ## @extra mcpServer.probes.liveness.initialDelaySeconds Configure timeout for frontend liveness probe + ## @extra mcpServer.probes.startup.path Configure path for frontend HTTP startup probe + ## @extra mcpServer.probes.startup.targetPort Configure port for frontend HTTP startup probe + ## @extra mcpServer.probes.startup.initialDelaySeconds Configure initial delay for frontend startup probe + ## @extra mcpServer.probes.startup.initialDelaySeconds Configure timeout for frontend startup probe + ## @extra mcpServer.probes.readiness.path Configure path for frontend HTTP readiness probe + ## @extra mcpServer.probes.readiness.targetPort Configure port for frontend HTTP readiness probe + ## @extra mcpServer.probes.readiness.initialDelaySeconds Configure initial delay for frontend readiness probe + ## @extra mcpServer.probes.readiness.initialDelaySeconds Configure timeout for frontend readiness probe + probes: {} + + ## @param mcpServer.resources Resource requirements for the frontend container + resources: {} + + ## @param mcpServer.persistence Additional volumes to create and mount on the backend. Used for debugging purposes + ## @extra mcpServer.persistence.volume-name.size Size of the additional volume + ## @extra mcpServer.persistence.volume-name.type Type of the additional volume, persistentVolumeClaim or emptyDir + ## @extra mcpServer.persistence.volume-name.mountPath Path where the volume should be mounted to + persistence: {} + + ## @param mcpServer.extraVolumeMounts Additional volumes to mount on the backend. + extraVolumeMounts: [ ] + + ## @param mcpServer.extraVolumes Additional volumes to mount on the backend. + extraVolumes: [] + + ## @param mcpServer.nodeSelector Node selector for the backend Pod + nodeSelector: {} + + ## @param mcpServer.affinity Affinity for the backend Pod + affinity: {} + + ## @param mcpServer.tolerations Tolerations for the backend Pod + tolerations: [] + + ## @param mcpServer.pdb.enabled Enable pdb on backend + pdb: + enabled: true ## @section frontend