From 874e45c05c9205094d0b09d0f9db024230e608c6 Mon Sep 17 00:00:00 2001 From: arch3rPro <30855883+arch3rPro@users.noreply.github.com> Date: Fri, 8 Aug 2025 17:12:43 +0800 Subject: [PATCH] feat: add app MetaMCP --- README.md | 9 +++ apps/metamcp/2.4.5/data.yml | 87 +++++++++++++++++++++++++ apps/metamcp/2.4.5/docker-compose.yml | 65 ++++++++++++++++++ apps/metamcp/2.4.5/example.env | 35 ++++++++++ apps/metamcp/README-en.md | 46 +++++++++++++ apps/metamcp/README.md | 46 +++++++++++++ apps/metamcp/data.yml | 26 ++++++++ apps/metamcp/latest/data.yml | 87 +++++++++++++++++++++++++ apps/metamcp/latest/docker-compose.yml | 65 ++++++++++++++++++ apps/metamcp/latest/example.env | 35 ++++++++++ apps/metamcp/logo.png | Bin 0 -> 15107 bytes 11 files changed, 501 insertions(+) create mode 100644 apps/metamcp/2.4.5/data.yml create mode 100644 apps/metamcp/2.4.5/docker-compose.yml create mode 100644 apps/metamcp/2.4.5/example.env create mode 100644 apps/metamcp/README-en.md create mode 100644 apps/metamcp/README.md create mode 100644 apps/metamcp/data.yml create mode 100644 apps/metamcp/latest/data.yml create mode 100644 apps/metamcp/latest/docker-compose.yml create mode 100644 apps/metamcp/latest/example.env create mode 100644 apps/metamcp/logo.png diff --git a/README.md b/README.md index 3ea1967..71b4bba 100644 --- a/README.md +++ b/README.md @@ -543,6 +543,15 @@ AI驱动的开源代码知识库与文档协作平台,支持多模型、多数 + +MetaMCP +
MetaMCP +
+ +🚀 MCP聚合器、编排器、中间件、网关于一体的Docker解决方案 + +2.4.5 • [官网链接](https://github.com/metatool-ai/metamcp) + diff --git a/apps/metamcp/2.4.5/data.yml b/apps/metamcp/2.4.5/data.yml new file mode 100644 index 0000000..3784603 --- /dev/null +++ b/apps/metamcp/2.4.5/data.yml @@ -0,0 +1,87 @@ +additionalProperties: + formFields: + - default: 12008 + envKey: PANEL_APP_PORT_HTTP + labelEn: Web Port + labelZh: HTTP 端口 + required: true + rule: paramPort + type: number + label: + en: Web Port + zh: HTTP 端口 + - default: metamcp_db + envKey: POSTGRES_DB + labelEn: Database + labelZh: 数据库名 + required: true + rule: paramCommon + type: text + label: + en: Database + zh: 数据库名 + - default: metamcp_user + envKey: POSTGRES_USER + labelEn: User + labelZh: 数据库用户 + random: true + required: true + rule: paramCommon + type: text + label: + en: User + zh: 数据库用户 + - default: m3t4mcp + envKey: POSTGRES_PASSWORD + labelEn: Password + labelZh: 数据库用户密码 + random: true + required: true + rule: paramComplexity + type: password + label: + en: Password + zh: 数据库用户密码 + - default: "http://localhost:12008" + envKey: APP_URL + labelEn: Application URL + labelZh: 应用访问地址 + required: true + type: text + label: + en: Application URL + zh: 应用访问地址 + - default: "http://localhost:12008" + envKey: NEXT_PUBLIC_APP_URL + labelEn: Public Application URL + labelZh: 公共应用访问地址 + required: true + type: text + label: + en: Public Application URL + zh: 公共应用访问地址 + - default: "your-super-secret-key-change-this-in-production" + envKey: BETTER_AUTH_SECRET + labelEn: Auth Secret + labelZh: 认证密钥 + random: true + required: true + rule: paramComplexity + type: password + label: + en: Auth Secret + zh: 认证密钥 + - default: "true" + envKey: TRANSFORM_LOCALHOST_TO_DOCKER_INTERNAL + labelEn: Transform Localhost + labelZh: 转换本地主机 + required: true + type: select + values: + - label: "True" + value: "true" + - label: "False" + value: "false" + label: + en: Transform Localhost + zh: 转换本地主机 \ No newline at end of file diff --git a/apps/metamcp/2.4.5/docker-compose.yml b/apps/metamcp/2.4.5/docker-compose.yml new file mode 100644 index 0000000..eceb0d7 --- /dev/null +++ b/apps/metamcp/2.4.5/docker-compose.yml @@ -0,0 +1,65 @@ +version: "3" +services: + app: + container_name: ${CONTAINER_NAME} + image: ghcr.io/metatool-ai/metamcp:2.4.5 + pull_policy: always + ports: + - "${PANEL_APP_PORT_HTTP}:12008" + environment: + # Postgres connection details + POSTGRES_HOST: postgres + POSTGRES_PORT: 5432 + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + + # Database configuration (composed from above vars) + DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB} + + # Application URL configuration + APP_URL: ${APP_URL} + NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL} + + # Auth configuration + BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET} + + # Docker networking fix + TRANSFORM_LOCALHOST_TO_DOCKER_INTERNAL: ${TRANSFORM_LOCALHOST_TO_DOCKER_INTERNAL} + extra_hosts: + - "host.docker.internal:host-gateway" + depends_on: + postgres: + condition: service_healthy + restart: always + networks: + - 1panel-network + volumes: + - ./data:/app/data + labels: + createdBy: "Apps" + + postgres: + image: postgres:16-alpine + container_name: ${CONTAINER_NAME}-pg + restart: always + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + volumes: + - ./data/postgres:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: + - 1panel-network + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true diff --git a/apps/metamcp/2.4.5/example.env b/apps/metamcp/2.4.5/example.env new file mode 100644 index 0000000..f55da13 --- /dev/null +++ b/apps/metamcp/2.4.5/example.env @@ -0,0 +1,35 @@ +NODE_ENV=production + +# Postgres connection details +POSTGRES_HOST=postgres +POSTGRES_PORT=5432 +POSTGRES_USER=metamcp_user +POSTGRES_PASSWORD=m3t4mcp +POSTGRES_DB=metamcp_db + +# Database configuration (composed from above vars) +DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} + +# Application URL configuration +# This is the public URL where your application will be accessible +APP_URL=http://localhost:12008 +NEXT_PUBLIC_APP_URL=http://localhost:12008 + +# Auth configuration +BETTER_AUTH_SECRET=your-super-secret-key-change-this-in-production + +# OIDC Provider Configuration (Optional) +# Uncomment and configure these variables to enable OpenID Connect authentication +# Required for OIDC: +# OIDC_CLIENT_ID=your-oidc-client-id +# OIDC_CLIENT_SECRET=your-oidc-client-secret +# OIDC_DISCOVERY_URL=https://your-oidc-provider.com/.well-known/openid-configuration +# OIDC_AUTHORIZATION_URL=https://your-oidc-provider.com/auth + +# Optional OIDC Configuration: +# OIDC_PROVIDER_ID=oidc +# OIDC_SCOPES=openid email profile +# OIDC_PKCE=true + +# Docker networking fix +TRANSFORM_LOCALHOST_TO_DOCKER_INTERNAL=true diff --git a/apps/metamcp/README-en.md b/apps/metamcp/README-en.md new file mode 100644 index 0000000..5dfc195 --- /dev/null +++ b/apps/metamcp/README-en.md @@ -0,0 +1,46 @@ +# MetaMCP + +**MetaMCP** is an MCP proxy that allows you to dynamically aggregate MCP servers into a unified MCP server and apply middleware. MetaMCP itself is also an MCP server, so it can easily connect to **any** MCP client. + +![](https://cdn.jsdelivr.net/gh/xiaoY233/PicList@main/public/assets/MetaMCP.png) + +![](https://img.shields.io/badge/Copyright-arch3rPro-ff9800?style=flat&logo=github&logoColor=white) + + +## Key Features + +- 🏷️ **Group MCP servers into namespaces**, host them as meta-MCP, and assign public endpoints (SSE or Streamable HTTP) with authentication. Switch endpoint namespaces with one click. +- 🎯 **Choose only the tools you need when mixing MCP servers**. Apply other pluggable middleware such as observability, security, etc. +- 🔍 **Enhanced MCP inspector** that supports saving server configurations and checking if MetaMCP endpoints are available locally. +- 🔍 **Elasticsearch for MCP tool selection**. + +## Core Concepts + +### MCP Server +MCP server configuration that tells MetaMCP how to start an MCP server. + +### MetaMCP Namespace +- Group one or more MCP servers into a namespace +- Support enabling/disabling MCP servers at server or tool level +- Apply middleware to process MCP requests and responses at the namespace level + +### MetaMCP Endpoint +- Create endpoints and assign namespaces to them +- Multiple MCP servers within a namespace will be aggregated and output as a MetaMCP endpoint +- Optional API Key authentication or MCP Spec 2025-06-18 standard OAuth +- Exposed via SSE or Streamable HTTP transport protocols and OpenAPI endpoints + +### Middleware +- Intercept and transform MCP requests and responses at the namespace level +- Built-in example: "Filter inactive tools" - optimize tool context for LLMs + +## Use Cases + +- As infrastructure, hosting dynamically composed MCP servers through a unified endpoint +- Choose only the tools you need when mixing MCP servers +- Enhanced MCP inspector with support for saving server configurations +- Search engine for MCP tool selection + +## More Information + +For more details, visit the official documentation: https://docs.metamcp.com \ No newline at end of file diff --git a/apps/metamcp/README.md b/apps/metamcp/README.md new file mode 100644 index 0000000..e6c46b8 --- /dev/null +++ b/apps/metamcp/README.md @@ -0,0 +1,46 @@ +# MetaMCP + +**MetaMCP** 是一个 MCP 代理,允许你动态聚合 MCP 服务器为统一的 MCP 服务器,并应用中间件。MetaMCP 本身也是一个 MCP 服务器,因此可以轻松接入**任何** MCP 客户端。 + +![](https://cdn.jsdelivr.net/gh/xiaoY233/PicList@main/public/assets/MetaMCP.png) + +![](https://img.shields.io/badge/Copyright-arch3rPro-ff9800?style=flat&logo=github&logoColor=white) + + +## 主要特点 + +- 🏷️ **将 MCP 服务器分组到命名空间**,作为 meta-MCP 托管,并分配公共端点(SSE 或 Streamable HTTP),支持认证。一键切换端点的命名空间。 +- 🎯 **在混合 MCP 服务器时只选择你需要的工具**。可应用其他可插拔中间件,如可观测性、安全等。 +- 🔍 **作为增强版 MCP 检查器**,支持保存服务器配置,并可在本地检查 MetaMCP 端点是否可用。 +- 🔍 **作为 MCP 工具选择的 Elasticsearch**。 + +## 核心概念 + +### MCP 服务器 +MCP 服务器配置,告诉 MetaMCP 如何启动 MCP 服务器。 + +### MetaMCP 命名空间 +- 将一个或多个 MCP 服务器分组到命名空间 +- 支持在服务器或工具级别启用/禁用 MCP 服务器 +- 可在命名空间级别应用中间件处理 MCP 请求和响应 + +### MetaMCP 端点 +- 创建端点并为其分配命名空间 +- 命名空间内的多个 MCP 服务器将被聚合并作为 MetaMCP 端点输出 +- 可选择 API Key 认证或 MCP Spec 2025-06-18 标准 OAuth +- 通过 SSE 或 Streamable HTTP 传输协议以及 OpenAPI 端点对外提供服务 + +### 中间件 +- 在命名空间级别拦截并转换 MCP 请求和响应 +- 内置示例:"过滤非活跃工具"——为 LLM 优化工具上下文 + +## 使用场景 + +- 作为基础设施,通过统一端点托管动态组合的 MCP 服务器 +- 在混合 MCP 服务器时只选择需要的工具 +- 作为增强版 MCP 检查器,支持保存服务器配置 +- 作为 MCP 工具选择的搜索引擎 + +## 更多信息 + +更多详细信息,请访问官方文档:https://docs.metamcp.com \ No newline at end of file diff --git a/apps/metamcp/data.yml b/apps/metamcp/data.yml new file mode 100644 index 0000000..0ffdeae --- /dev/null +++ b/apps/metamcp/data.yml @@ -0,0 +1,26 @@ +name: MetaMCP +tags: + - 实用工具 +title: MCP 聚合器、编排器、中间件、网关于一体的 Docker 解决方案 +description: MCP 聚合器、编排器、中间件、网关于一体的 Docker 解决方案 +additionalProperties: + key: metamcp + name: MetaMCP + tags: + - Tool + shortDescZh: MCP 聚合器、编排器、中间件、网关于一体的解决方案 + shortDescEn: MCP aggregator, orchestrator, middleware, and gateway in one Docker solution + type: tool + crossVersionUpdate: true + limit: 0 + recommend: 0 + website: https://docs.metamcp.com + github: https://github.com/metatool-ai/metamcp + document: https://docs.metamcp.com + architectures: + - amd64 + - arm64 + description: + en: MCP aggregator, orchestrator, middleware, and gateway in one Docker solution + zh: MCP 聚合器、编排器、中间件、网关于一体的 Docker 解决方案 + memoryRequired: 2048 \ No newline at end of file diff --git a/apps/metamcp/latest/data.yml b/apps/metamcp/latest/data.yml new file mode 100644 index 0000000..3784603 --- /dev/null +++ b/apps/metamcp/latest/data.yml @@ -0,0 +1,87 @@ +additionalProperties: + formFields: + - default: 12008 + envKey: PANEL_APP_PORT_HTTP + labelEn: Web Port + labelZh: HTTP 端口 + required: true + rule: paramPort + type: number + label: + en: Web Port + zh: HTTP 端口 + - default: metamcp_db + envKey: POSTGRES_DB + labelEn: Database + labelZh: 数据库名 + required: true + rule: paramCommon + type: text + label: + en: Database + zh: 数据库名 + - default: metamcp_user + envKey: POSTGRES_USER + labelEn: User + labelZh: 数据库用户 + random: true + required: true + rule: paramCommon + type: text + label: + en: User + zh: 数据库用户 + - default: m3t4mcp + envKey: POSTGRES_PASSWORD + labelEn: Password + labelZh: 数据库用户密码 + random: true + required: true + rule: paramComplexity + type: password + label: + en: Password + zh: 数据库用户密码 + - default: "http://localhost:12008" + envKey: APP_URL + labelEn: Application URL + labelZh: 应用访问地址 + required: true + type: text + label: + en: Application URL + zh: 应用访问地址 + - default: "http://localhost:12008" + envKey: NEXT_PUBLIC_APP_URL + labelEn: Public Application URL + labelZh: 公共应用访问地址 + required: true + type: text + label: + en: Public Application URL + zh: 公共应用访问地址 + - default: "your-super-secret-key-change-this-in-production" + envKey: BETTER_AUTH_SECRET + labelEn: Auth Secret + labelZh: 认证密钥 + random: true + required: true + rule: paramComplexity + type: password + label: + en: Auth Secret + zh: 认证密钥 + - default: "true" + envKey: TRANSFORM_LOCALHOST_TO_DOCKER_INTERNAL + labelEn: Transform Localhost + labelZh: 转换本地主机 + required: true + type: select + values: + - label: "True" + value: "true" + - label: "False" + value: "false" + label: + en: Transform Localhost + zh: 转换本地主机 \ No newline at end of file diff --git a/apps/metamcp/latest/docker-compose.yml b/apps/metamcp/latest/docker-compose.yml new file mode 100644 index 0000000..d689b8c --- /dev/null +++ b/apps/metamcp/latest/docker-compose.yml @@ -0,0 +1,65 @@ +version: "3" +services: + app: + container_name: ${CONTAINER_NAME} + image: ghcr.io/metatool-ai/metamcp:latest + pull_policy: always + ports: + - "${PANEL_APP_PORT_HTTP}:12008" + environment: + # Postgres connection details + POSTGRES_HOST: postgres + POSTGRES_PORT: 5432 + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + + # Database configuration (composed from above vars) + DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB} + + # Application URL configuration + APP_URL: ${APP_URL} + NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL} + + # Auth configuration + BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET} + + # Docker networking fix + TRANSFORM_LOCALHOST_TO_DOCKER_INTERNAL: ${TRANSFORM_LOCALHOST_TO_DOCKER_INTERNAL} + extra_hosts: + - "host.docker.internal:host-gateway" + depends_on: + postgres: + condition: service_healthy + restart: always + networks: + - 1panel-network + volumes: + - ./data:/app/data + labels: + createdBy: "Apps" + + postgres: + image: postgres:16-alpine + container_name: ${CONTAINER_NAME}-pg + restart: always + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + volumes: + - ./data/postgres:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: + - 1panel-network + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true diff --git a/apps/metamcp/latest/example.env b/apps/metamcp/latest/example.env new file mode 100644 index 0000000..f55da13 --- /dev/null +++ b/apps/metamcp/latest/example.env @@ -0,0 +1,35 @@ +NODE_ENV=production + +# Postgres connection details +POSTGRES_HOST=postgres +POSTGRES_PORT=5432 +POSTGRES_USER=metamcp_user +POSTGRES_PASSWORD=m3t4mcp +POSTGRES_DB=metamcp_db + +# Database configuration (composed from above vars) +DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} + +# Application URL configuration +# This is the public URL where your application will be accessible +APP_URL=http://localhost:12008 +NEXT_PUBLIC_APP_URL=http://localhost:12008 + +# Auth configuration +BETTER_AUTH_SECRET=your-super-secret-key-change-this-in-production + +# OIDC Provider Configuration (Optional) +# Uncomment and configure these variables to enable OpenID Connect authentication +# Required for OIDC: +# OIDC_CLIENT_ID=your-oidc-client-id +# OIDC_CLIENT_SECRET=your-oidc-client-secret +# OIDC_DISCOVERY_URL=https://your-oidc-provider.com/.well-known/openid-configuration +# OIDC_AUTHORIZATION_URL=https://your-oidc-provider.com/auth + +# Optional OIDC Configuration: +# OIDC_PROVIDER_ID=oidc +# OIDC_SCOPES=openid email profile +# OIDC_PKCE=true + +# Docker networking fix +TRANSFORM_LOCALHOST_TO_DOCKER_INTERNAL=true diff --git a/apps/metamcp/logo.png b/apps/metamcp/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fabde86ee358d9e5447339e8f5a00aaf9acf3a87 GIT binary patch literal 15107 zcmV+eJN(3nP)>e3+|IhpOl3tQab4hbeO7F`j^m@7RCVhVQJkL9|7cXAK-o1MfAm{;< z6J8MjLICIhu>LNXiXw0TOaO8Kqye}kyo~-X7@<|GR?rf0^y$;bUu6gea6f=Y0dxd# zk01_J5Tz4N5J)_LKLBh4a99vfvGdbUKS5i}_aq*o34rzh;s7)S5UtniQM-0+#KgqV zpXJM!M^sc4A|fIX92^X-wz!L_QXH7gX5{4LAU!=Dsi~>BapMM?l$2CHIXSs}Qc}|Y zj7I86ZUQ(Wh~-ZJM~l}cRW~^Jc4ku2WAiJ*u_#D7D04^zUU!slt z|9lS#rKSPM1HiX!+m_$HeLHV9n|T$LqF}LDs4WqjBDMt}SFkm`uZ@^!!`I2M4!{tS zWNEe9Dec;|)BpVQ&-m-FztFaATWB;IAKy-;w8640wIyOxYuBzt`}XY(#MXqweu!X$ zzQczeBnH3%qUwqjEAksQZ1DHPMx~Une*JoCbAm@XAlPD&@s$T5uI&{7dqYD*ADA*_ z3K9|$@YGXJ`QXwjD(>mrxihsnVtd2}i7g5?S@f{0h#sU8fSp9S{rmUluU)(5?}v#> zDJLZ*h1#OvO^9t)E{Zma;z6PTYz_(vYV*}sUt#IerKni3Vi7KP}rli3WO;}yBSihp5P20E>lCf>eg z$dDnl;kkG3-uI@arXB{+TL`^8!W)6q1+ZSP*GI2fwaRDW$jr*Z$qY^0afJVb2$Y4O3s7TbTQ58*_#G>hajnJrJJ%ojY7Wo#$ zHc6P&xpU`gMx${(fYtyKJ-LRb0wJDbeYI-UqF1h5iH9G4*u&H9IUS#fZQK9Bz5_>b z^!O=cWMqM58L+Gd3d$e!g9$U9=Mc=-Cqc2^$vQNL)qwDkH~yLazKTwEMXCetOM58y3#=XjE&R|fDm z$vJ-e?KiK)VK$qwcHQrIy7v$aeswI)o;weNL9dEK#U;cxh>e_0JWp-r={_%E?Ycjx zEqMyD*^L`Fg4nRo@ljUomK^KU6EPVKhQ=fzCoanqc%H|qpMOK#6a6rD;%p=&o~ONY z)$mD0F2q)d%_N*n!q|zk5Z9?6wJBm-of7Z%Q5z$+<_W}>`}gk;p_5~w-BJn@2zlJE z2?+_QIDh_pj|y=0+I2iP=ye=9c1racDvCjDi`dw+gI=dL=Lz%X%>%J*0ILhD%`HqI z#Qog=)?07U)=9bSKXeS8d%lR({gV-9exmQL^@W_*h9{%H9b(41`kVzVirq+@rOAsC& zF6(B=7yd(!enXL!l`Xq~Dk{E&*j~?mui*Rzc>@fIZ4(KG}3R0S~ZvBw_6 zlTSV=yKobx&O>Tyn(8@J{06bTJE`fIFy%ej=MWnwwk~*)>W=SA31lz;{cEqiChG<~ z&tvwy&#?dCQQ3u3QBe|-ix8WX+fMT8tFJ-`v7B-dM}dTqHY}58w@P7ZkBTngFOrLMyo)2$z1hN)4@40v2MM-+s4B*0IFqNvI`EY}mxMUvx|d z@?Xb}9c5i`*FXDl;#7jCWmZusR!*KuptdT94jnq!Hmw_Cfjk-&6@?ZpTDZLs&jWK8 ze4);Br4sNE8=d>X7qV)!i0u;%0C>c@&nl2008Kk}>O?1tcE_27M4UKzMs4#b#Snut zo6U4U6G@26N0&wIS~ckCKfT`PlN@>tvC$K!&LBSFoZDlG?F(&x%>+%@I%$y|(Y<>0 zl6BLqJND3#fGVXAA`Fwsgxa-g;_1#E@V|$fqfYIb2n#KkXL-}+zcFI;lz{2QO>A}B zjy-7D!2QtOo;`cwhaY~37BsQP>OmeSs&C!8wX7TdyZ?wf60nqEu~-li5{$mRx?tnl zZ}G?a#q%>LJ9Lt-l*h1FUO=}l9T5~{kaZprNZr~onDEYAG&Ly4xZ7!<2Ead-%u+S;q=mC`YuRwXO+RzI=IECm%U_l1})ga`$2~nep-a z)6o90HeQ`iqKvo3&!Tgj1xigPvDqWX<)sFK7Ko9TV};dZt@Xcdxp47vzp!9URBeS?~hw!>jlPv)PQE-8y0T&;eeZ zfBa-TdOrIqScXNQnMnRvku++9QIK$~t9vJp&-?zzUoh(J>36L-pbA8VB(2@MKH>4Wo>f0@#5?aUfL3#N_sLTQQpzC$ z>D1v7kIq^3^KTeG^}SLtSWXp4DTe2HG;7ir)vL+RJxnGO=6(1jT{^8apbDfEV=|f1 zyNCR_&j?43ox-hKcN8mAprHz+6hlI!RxR)M=$!a7=b+Kvy-B$vsz6FHWy(aNYGu!s z?#|0If>C%yRg1-fS~1nBhx0e8KuR%HDpl}!;9JFt(cn1ub4_`R*=)wsT{>Xhs;@D3 z=6JtXA*%u@#Y9HB?|0Vksml@h1|DaB-Ed$*R$3;lZdtdWo;;?MSZ8uMpQ zg4RX8k(!nch3f@eq6(xGbNhCxSLao)RvB|HZ|%nK-i0QL5*Y|D$6} z{DCTvQq1)mx4bs;&Yd2^^ZmMeof}vz7Chgt8)i-$9FXd zcQJTCAL>cuB<^O51rN7;03XkPM^+n5V$ubqre%0t#-gGMq!dGv;w8&h6{($pB$ZRVI?e4} zB@iAw%slxY2x3@7K#M3whe$?;5NM%iG~gKqM)1hyEy#cwsazg1cq4LnGwoxfLEy~g zq$DPj2`@a`6O+e{@TmdTs~dw4=1#%Xv~;9gzll7f3E|{u&8Zi-gOj8yQu#u?47*LkggJZPd7#2LEfrZt=!m{9@0mqX48kkrX>9F9O zB@>5C*Rdz>Kb$q)L@sZpf>OF;oow_<|oi5Abx``aWzwEy5KH zj1G+qRS+9o9nY6(iwmYytVrFBJ(=;)u_fv&vx6kc58i)Yz>C9dHsixjzVW*_P*Ma! zJcbqwIx$tzM^haQnK0@ph=*Wpg2;!log)YWRzV2wiZo%}S8xntcQuaI*;`Oi7llz3 zyP-aPMF!p7hf{8Pis9c434kC_)?@tS8aS*;*vI1AGrue<9I|5xG#5d@`Vjx== z&#*NR&S(Tb!GonR-h=OBE$d=) zgJ6uF*Z*ENncYS1o82(o?|Dnd~X)42}Y-KZ85FRDAWn52{Y&a zk#DZB_ID}?;#l+RCQP6GF`X4YPzbnhj2He5VjAE_O&qE-q0}hFxOnd8I~0+K9M3Aa z2V;BR8eTyx2)q0Q4q;fqiwI?Otyl3}wP&DXbUy6@?hCA*Lr#Aue?oGOrQff?)OQzn zHAY2A0xrY~fes5b52G#I>Qts>9%b~&Ekccop@19`EdfA$_gw`X3$k3Vk+NO2%g1VTcp2T%cDY2r``A=X-12~%an zC)sBat`d@hBWxam7rctqL*ySKI-YT;0%kr9*y|WH!FajeC{*>MNT}!N(h=`W z92KzQ$jQmYd+#qo{~>SUOhS^&fl`5h-$N`Sj(Ui~G`1y7v^}HX9Wji9FR*t8g~a}% z{6g#RXD6BP~6H zt{7XnQU%%JWM*a-jvhZv*HHgs%MPTbrny{epSM`R1c+$-Di9)$Q0Oq5eHaF6NUqZA z{NETSQM(=%xjPDJIZgCu7%p<=)1kh1azumLc=sb5P_0U=nx#vzQ+iJ$E* z@(YpKG5~l71qr{iv?|u&sV1(aa%dEEQ6Fae=~Uz8c!(%JvAZtAVBkID@%`BaNKRLF zWnkhth&Vp|aE9XH%De9^q{dF3{t0K3a60}holt>zES*kA@6Yi(?F>o6Jx1sfPekEv zwOmH8N8OpT5q^IQum(M@e!B#$pK}wx1gXSx1VfA2Y+ICJ^rBV^hX=QviP~R*VeFH5 zCsP;Y8wFLljIBzy;5d;du;nG9d;?P5QW(i_%*p35Ec(`e3ysUSp)K-CU>0htRxVqJ ziWQ<2xd(xSL<3)b6U$euwRtG3C(!G4bRY2?Mw5w_iM&S=Zz39ZVHm9*XEmrjV-CXa zZ2{g4RC;a@YE79+_xlsSWpVS_I8eMzDT0wr_|7%J=AnvQKuRiMu~2R)LVvsuDucmyMT=oElC_fxU!xG27>q!{4jf$Tqog9oBr<+bP;I~gIm$Rdlx zTuh#WFzW3Y_-CIoo`W}=P<{AFl#T0@-!s(lZ%zNxqUQMV2x-v3=l7RGu?j>77BiSf zB)tV(HRIXbh;WSChnRzx5H6ts3oTgapn-;AzzY3rx$u(w9eM?!-k)DAUhmOV0fBsdZZxrze75nx_xlY|7PS5PV zUjfbtRC{w2;D@d}#U>EqJvyKcsxsm9cPFnPI#O!CgO&>RG4>e-C+yQOEET~8E(gDx zui}&3cudPagy~ra@JZG&Y{*SQk~x)Lu3(IZD3p?@nJU=tye-aaoT`*XCDv<#(e>UL zFla-)7RmJ+xAE+YZy+-(%d7L{Fly`!?A~)gnVf^?5!&!xRC#`I{)jah1lPL z)xgC`u~qZ3V9=duAdKO#*2?mQwq;0%Pq5DrL`Fugd!097vpE$zEVprlOUE4;!SVU; z(HNS~7b{2a%OI4|p{}kR9u297Rv~o|q6wv=4ta;CnDd?O6a0b?V1JUGHxa~U(1xLJ zi?6W$=y=@Dy5Mo&Esci7*|W(QHgYnp+6@a0_2_(e%z1wi^&EB2dlEP^ z16up-?&CB0LqDT?c1P;gZOA@#+UMnRMX`7gl9=~ELsVeG3ZA`;RNJ%!5_yW$dAU4~ zkInxfF7E)Qna*M-cbiViK*B0L!$M0v3PVLgyo-*}(0zHZ;H2p~K1$t*=dUcqiu8Z! z`;G~`ty_v?lFm5Sa@q0|iMA3eLAr1}*>nzcnjo({2@%PjeTUJv{|MTu?JH)^{RE4b ztWc~yMOQ8w_k6erx`>K)IVbN>&F=)+Z)Q2OJV| z$xa<&Ta-A482x@e9bHTZ@v-?LI0kg|p{EeBkSF=dw14sP3+H z3^1B7y3hKJIp&Oo{rSS0-j9ffT2oI{94Iaj63RS@die(E@+hg^PQ0!Ay4`+}9cA(s zj4{V!py?Q{@<^;HEjFpoENl6)>D?yx>&(WlPd+IgPys z3euOyQ!N&td{nGgD>o65{ImBUp6osl35n;u`poI+nHcigI4oZJqs#tLMS|yHs8kiP zpDsmk&3p3qF%rSERO0Elk7E%tVp@zg^L5FK@oSDu7r zSvnE*E5pYlJ15uU^GA=Hft|beDHc}oJcrujr$ZYSEgdpTH!Zn@4T(b3<8j4OtEIR= zsvrVY5N_X{#72P$xe8Dmk0m>^cr#uw9l|j_9fkQNUvS%;iE$Ud#4U5WWQm-@1BVuT zUX;u54h@Rp4{qUrxE9k88dAaQ+N~>BQ}Ftm@5mNOMn)E19ySi!ckWYcP!$^-f(COx zg`sjSCy(G5V%Z@b)#k$iWjl2QR}77ZiVB2$@CQ%@Ji|-o6^`CQs_NHqo8%X>EoX6@ zPp54gN<)(RTjn%;ba{m^^p}z7mf~2Y%h9Jyk=fyGx=J5;~fOPqJ8)^&blGET>VvHv|h ztOlNF_c;uPvR;WqqtW2_$#}f+);n}yk`x)4S$OsJiP*AjxAMbr%_f|k`X(}t?kl8m ztI%E2J9!on54To4%u@8*(Sk*NNc1pec(;|`Qtw{p^DxPBT9KT?9W2A*K*mXYeDPZs z4TM(g+@r8<-%D8Bd)V$+n@DQmk+w6v4z-9#w(s1F0WXb~6bbPhTettCsOPZ3FtoiX zdBIF@rgXcvq&%zGqZayS=0OF*6>*g-cPJ(h;x%GW29YpGehX4lAvhi+?leL|t8cgq zejOuB68F!YWb2q4;rV z*N7+;YKab~B8jYrM>;M*NN7c`y2=xg{JnE8y7YPx$4{QYu#uDS=@-k3$8$Kquq?6@ zP9bH@cTRs`*?!VNMB2zGM6_z9Xq$+F9t60TspK@Lm0g4Zr?({g#*$ENu_cL)Sq}tA zxOFM_G8SK03=Jc0H*l7DfhZj6{u!q($@ZrT`+~!&A@1of5FA#?yRxz@E?-GO&u3qu zIfp;QVTI9XaAU(S;5kv)=Y{96!drl-c5$@JyizD85CgQRgfQVZ=OkU)qgbnQdAoY? z9NvsgTuQOjTFHgP)&FH*#KN=hBbUn&ja+2xw(X}kmFNAOnhKZ@wCpew`rxu&54zH@ znt+%e%aW0fl+`QqEzezzm8;*a@IdH&v0xPJ)vVYz76v`S7`^lZf^q$;R4vw7J`<_% z->5QI5KnR;I~hyQe*tZwv%b4%2SGttMTO;%tn*C4D* z2FaO;`0~tr=0DXlDJ};vc#jGv} zI*D#K&W(XmA(ePPNhxpgfo}}NlblOGi9i4Q2@Ioks%qdx%P&9_T1hm(=f4lpw(V#B zM!6cT(5%~Buv%rSIF*8YSJ_GD^Z&R4-ZuATaJ5h04Ts4H5{>u!(K_>TPW+`glR@_jaahlbT+*X3WZl)e>z9t5+Ric z#^G4YQJeZM! zOyM^kjKm~Uj8h$zbA>4R8Omt!1Y4m*hFMmKFm@z-hJkmq;?ab3q?PX0(ucq?}Bs8oR+fL2HnJd51p}gW}25(o3#Yl%)u&>eL8T;hk7IN`G z`Ig;Lt@ALyOxNiSB9O4g&0HVgDlv6&tT83US4DF|F@X?a?BnytZ#dU=%NrLWraBXX z|1o71DXX_|97h+g@dTFDV&~}vNKW}%NGv^X1aS1ZlHnT9qGFrAsPe>+!15$KkD!=Z z2&xeynQwaf4CHc6Fx|M}!vfl)s0TTVTgc{xKTe^wx~|5e7yQZ1;4oNIGa$xw1Eg-=u0TNxP*fl~XmA);U5Avs zLwGG}Ae5@zmlj!g2N(6Ge2@l-) z`){xcuFI&XK*(o2z+bf`>dp~@b2FK{?}Y=0DcU+{$b`XCj0q?St77k|iw$eOu@i@t z&Yd2|H0nt_47XSvj>mt8Q)hkvnEZ3?#)(LH6W&F;iB!~Lg99QOv_{Qm-tvn`=z1W1 zo<(TGCeqcqUSPa>8F?2kdNwVihbZ3c`XAD91)0uEI6HhMspn^Ya_`)JQijoDg{}z= zxr$37BXSKzio?p-(XU{@b3HxwBpQt#$Buo2YghKz zJrLaq!mgbJg}7jmNUA*b63Rc)1H2cLWqS(oR{DyS5i@!mc+mulg*BAQ3a>Ok`mVi- zjz>rF2!z2FKAGktR;;OpRJ)h`FH(yu^T^_P^fDbm0-vpT%Scd=0l)sV7!TZkudL$@ zdOaqOdmYd9?MBxU^aLV|y}PC%JuQ*`Wq4O@A`T&zc&v#11*MIRVf3ip^9@8l+(l7O zLLV+wtK*)9UqV-=a>4#W9)fQ732=G2NI!7UXJvIovGi=aj7)6i&I`GS;3e$5f(y@K zqd++pFnH4(k1KqhqH|>BdkzD>{A3Pl)~M?7`ES2D6wmf~+G9_`aTff&X#{Ru-H-eQ zv7Ec5xCll)f;R)zdW=Aor(XgzzV9wha*oL6tx@mY4*)jQuIyem|Jaf7&p+UEJ)gs}Y8bJmF zP1+}o8v(Ofekem?YM!@X-NnC!*DI8=}F(pAGw7k9ju9u*&Z9f+wg+9V+0$@~BnyYP&%vd{ z&GwVWGeX4$BQXIsWC%mzeO;Mqs5NLZ8jN3rkQ(*DnT#F_g*ThP=ya(3><~2k_#1>b zYGJo}t@YlLp_M1$6$7O2+mGzi#j{Rfu?^K@z+eM^9^Ls`sKf+I93b&rA$GvoxsgG!5oc#dxt%|(UsMHh#aK0Tj8c1{jv&HaQ<2PzkhR*#cA7tkkV#rltnTGbu1 zG8x~A8zUsf98{@38jSlK*_Y1X&c4maNH~VPluO`pvxP=Rp?j(DXJYkw=*m_=XoFZp zG;4u~W-Xx!i-c2W2gZHg!MJNQ6yWl*@ZX&Gi^Y4OI0Zs5!h*NCf3Q^ZxNV=Shnv-- zkWlI)OET7T*J!Aw6k^ZeOL15k_poiWhGRJ8 z>2t%?@f;9bwE;qUH2|XrEO|LF-n@jo8!0fQr_q`(ZFo5J6{2CNR1Mn5GWkPig|6$? zF6>UX6IEF=4nHAN9E95&H^G#Wa(4=ZPS=S{tmG5$JX23H4i$etEO_j&+`=;MqT=FE z!gDw>Vazb>KX4Qm{=4i^{XOv{C$`K*BMpOc^&WNZX5cEk@*sS%CeJp5B}Q)o7&aJ& zO7&o$d3$>@}gT8TE$xkTwXd-mai-}aiBPJ5G%yczv0f{5}zgdo_V{8 z8W>?;3ZI8DmJ_ya6-r?+81g+w1!br8{BFfsBqn*jE;?v?f(b|0PsYvjJMFT!LR-&0 z!Cm_agd1`$EZdb38K+J8lf;QbFb!~F$}E@_9Qz@KUj{Jq;PIMeC$953lJAmN5}%De zrhgS3YD+waPN%~c3uh|oIS7Aj*?|S0EQVh1{m4j-)_~KS<{<0V8M_(`m)^q>hkdR( zJi0tz3OT}(YS5H>z95|2n>HftpMU5w3dM1ZOuW*Dj=i&IOaj=+fxm!m<8i!oK@%TbZ=xL3U z1H4o%mb>$LfXlj#%O8FUR)O`~-Qm|MFizqQMp^a=V?6RbM+R@kbLM@x3@Pv88>r{d z>+r?LGf}5@jAHkJ;5qsa8ST?Kt4ukmIJEd>K|a@KCDa!m_6@P==FNV?!g(eaeB!}G#t7JL6u|W*J3#w zA|9;eWWaJmDmTJ(s@`=aLhJFCEF=sbhP+Fc{3Z?rf z)?JBX#T74(_mw)IF#s1P&q2n)!+sYB{GEfyf#Z4V1wMFh5`OshW3RLW>8RV#U&h0WXg2JNKgBz!4r# z+MopF$$N3_hd94{GEA8%1%F`&&mpS?UMfD4dfx@&oI`vm`<};nxc>c8#19w@b6T3B z|6Vi#hb9gngPcChPu z?$`$6(u?r&YQmgw@G^{qNQiLrt|TF8!bBLaU7?i?f%5%{2{;d8;W#v?UmNphPxNBu{qH&H@LKfKK=bE;kJ+ zE51d-(4jD1Pod%nR067G#B(%`t&i`&`3S+mfxFZ($8q@K$6qmP-lw$8f%xRJ!mt`- zoj;Dm&)$NeLJgGvUssfC{UlgJX#Nst-gpR?H*?-)8)D0vk)3b~H-GyTsoS^HA-P1r zf&ASe&;*iaG-B%bHv(E5gdcwV1yg1$ptGHn6v$KBcnnt30fjfAYcSip+Y&l`NqrsE=HlO_-P%c%=nnj36AS9;m-Qy|fbe@iW`GQG2fy{)XxUz-^tJQ%u1cMC@0c!{X ztJ71J@MbeOV=f)5Zpq03Z^{GD@zgufYZc>&KcxwXPhzoH(5-U^kIuPu`wk{dnTu^Z z_Rwx91f5Pt=g%EHurHoy|45MrCy6<6Dgm#JoaFH`2PF}jG;0c$D&q2T=&R6k$nk;G zw&zm8rL%;Pq@KLAS`E&eO~RdAQcj+o7Or49q z{fFax@_$7tW3TQ{A~ZClNY_v)nfP5G)E)e2DYP&H$68nF*axG@eSx9Ff@dT@98a8S&($X`$ zE@%C^wY)x8rR3swfdmH|+zw!(4Rg~!@H$B*BDr+=8b12u8!t-4#P0&h%F0G+YP!?@Kep_^3onlJczun+AfXoV zAV-d#^!QvQ8Xr{xAHVS<`#e4`FE209#`35X1AZ3> z5x}lJ`!VPJMfCSgoB#ItL~D-sOGr%e_`KV9QUktgwu z)vM##e7r`Z2?>ek6)TfU;8N-YlAD)@P8}Zg=$ykxPT@LUA~GfTX(7LAf=R2@mD9}XxpYG zCXOBE@wuzkZUoD!-lMc~*9(M*qv?IISor=lk9z{=3#b+_@%~!n= z$Jxa5bO9Vy9CsH%27|1>sYnYZA`rSdVtTrBk6K4qmPMJU$bjzlO+3f2p#$*7ut8p( zfBa-Too}}^%wSTQAT~>ES59rDNDJBIX!=`fYO1Uo)vQr9Umv0(|comI!41+kS}25@E0*+ z=m4+I-?DWVI`X%l&CI0{-IMt+M3I}% zUmt=IuMYIebF5wW2VQ++BC=J_ao0j@wwX+U2wEVHGq7AE6Y)(o_`rp;7 zS5digdAoe&8vfk63u}M<9jD?G zX&)=K8_C^Iv-@HTwF@gm1A+(H4AFRyzl=uXj6Hkyphu4$ZZFcJc~k7!cPO9?ix~Q! zf9<5(pL{Ugw%=?vG5g@#L@xBanxqzKr~>?kBa5J3~!EWBN6HoORLjTHzsT3b{S-(aN(4c-@x5pCO-?V8H**qd>!mcmY_SDo={QK{}ZZAYc zF?RGT0kQ0{ib`P+8y!396oJw6AOeUC7U;ImUZJc4>d=l*!o@+Sw*Gz zXdGLg+NvD>_~Q@jrcDH5fgA;pux{Nt+1)G#KHobamq1ogflxs+;Q8LNjwLp~e*Jo~ zc~;N@MJy1Kx_@-y#0l)$wM*7TJ9m1VPU@_p5{MAnBsMCC-Me>#*gk*{1uak&=F$Xm9oR?TWyv*HVN zu}c-dL2U2m6`v!fW_4M|6Wb;>PHdgnywowS1VXaz+1IaM$Nc&8Wf!n=r3(0B;VguN z1goAzrPv_0Mr`i$g|j@WRVB7fY@FD-b?CVxh4sH6xyV7$MPxy8kqv9UL76gXb zDGFuFL{gh;?m-?bdiWm_Y+aJV1Y!ozD?2+oWy~1)F&zX?^23sk>3mdb?UssC5ZfX) z_WhENJ#LSC>#ettot;gd{5=I*m&9EUzGMJXmn~a{ufKLm@C&6@Om(bSx)1|j=tE~2 zQBmEcS<&#qy^hT+dY|MaLJgb8C_!|dsA zBRaaQ>P=LN46&i;a%C}l`Z$aqd!q-Qy{2+q436XHG!+g#fdj{Jbg)yShV?OK)GKJ)riItn+P!->;^N{+Xm$xeGht9vp%86&GE)J$U~iMj zw7zfOzR@dJuJl?YZQHb@uha2~*tX*z?Aw17M~|OEMn+ct^rjjOUG6N<24krx1Mv_f zpx`(QIF3hncqp3O8;h0?HbLAY524{b?gu*+1`$W!zI}-}s13^zhZRpeh!s-WJ$k)9 zdc%edXy3lQSA}*YGcyY(PoKq+V<&Os=qX&dc$rQdotu}JKM$9xmAeZ*fAnWikb%y4 zShIRnG`X)KomQ$*!+Hn{4fV-wNZ#DJb7vThMk0#V021Y%>!}B^LT=E-Xf&?s)2C0h zRjXE^UAuN(T*jWTuu!yY-ju$qP!~(`6;91p?nX$opwlWBVq0?#qtSRt@Eo$mVa40Y zp!SkDE;TiEYnLuv?p?TWA%+YY;^n1WL%ukJKCmW+P$?NKUc4A1MvOpiZtigak9iVS zi7AS7$qhT^=H~8t>7|!2V88&6THIAslprM~h1w#qNn)D-I(RD%_+SB@s{kGcFm&0n zWhu35*T&STQ)SH*ucG2hh|Lk(t5c^Ah%FME6m0WakuFRU5g!o)-~dU#Dpst>uV25O zS5YYrHf-2HZ7x6cr#9%>LijEq+VEv|lq}Q|yp)oXa=v@_?=1Ah7C7qtal#pe*4 zTDx{FtwrtDts4og&J)`cY_QPoBOc*H4DkU7V z*-UMT*p%Qg@&sG+d2Noj)x2^r0+{kQsc&r(Y0$=>RFmMZ;p@< zb#Rr6d$O~$v48)5I%nIub?dCNkC6QLa{%80xTx6874jgCgaddLz!^dHe3>$3_YRl%kdg=oW@mh_@P++Y&`?E%CAXbK=& zuh*k??b?WmiJ?EsmoJZ~s3=54M9_JH)wMhBPDmb+laoVhpi)y)>B>$?NlA2l4)Rl& zW&NfwSMD|de+rYj70co@N`_djARlbM@R|i67(g?l(HIvWAKx)PKK>p6