discourse/plugins/discourse-ai/assets/stylesheets/modules/ai-bot/common/ai-tools.scss
Sam b2d73b346d
FEATURE: Add MCP server integration to AI agents (#38706)
Introduce support for Model Context Protocol (MCP) servers in the
discourse-ai plugin, allowing AI agents to connect to external tool
servers via the MCP standard.

Key additions:
- AiMcpServer model with CRUD admin UI, health tracking, and
  tool caching (hourly refresh via scheduled job)
- MCP client (Streamable HTTP transport) with session management
  and tool invocation
- Full OAuth 2.1 flow support (discovery, dynamic registration,
  authorization code grant, token refresh, and disconnect)
- MCP tool type for AI agents that proxies tool calls to remote
  MCP servers at runtime
- Agent editor updated to show combined tool/token counts from
  both local tools and MCP servers
- Agent import/export includes MCP server associations
- Admin secrets UI updated to surface MCP server usage
- Comprehensive specs for models, controllers, client, tool
  registry, and OAuth flow
2026-03-25 17:32:27 +11:00

368 lines
6.2 KiB
SCSS
Vendored

@use "lib/viewport";
.ai-tool-parameter {
padding: 1.5em;
border: 1px solid var(--primary-low-mid);
border-radius: var(--d-input-border-radius);
width: 100%;
box-sizing: border-box;
.form-kit__container-content {
flex-direction: column;
width: 100%;
}
}
.ai-tool-secret-contract {
padding: 1.5em;
border: 1px solid var(--primary-low-mid);
border-radius: var(--d-input-border-radius);
width: 100%;
box-sizing: border-box;
.form-kit__container-content {
flex-direction: column;
width: 100%;
}
}
.ai-tool-parameter__enum-values {
margin-block: 1rem;
.form-kit__container-content {
display: grid;
grid-template-columns: 1fr auto;
position: relative;
.form-kit__button.btn-icon-text {
justify-self: start;
grid-column: 1 / -1;
}
}
}
.ai-tool-editor {
@include viewport.from(lg) {
max-width: 80%;
}
position: relative;
#control-rag_uploads .rag-uploader {
h3,
p {
display: none;
}
}
}
.ai-tool-test-modal {
&__test-result div {
ul {
padding-left: 1em;
}
}
&__custom-raw {
margin-top: 1em;
img {
max-width: 100%;
height: auto;
}
}
}
.ai-tool-list-editor {
&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin: 0 0 1em 0;
h3 {
margin: 0;
}
}
&__section-title {
margin: 1.25rem 0 0.5rem;
}
&__empty-list-buttons {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
margin-top: 1em;
}
}
.ai-tool-list__credentials {
display: flex;
flex-wrap: wrap;
gap: 0.35em;
margin-top: 0.35em;
}
.ai-tool-list__credential-badge {
display: inline-flex;
align-items: center;
gap: 0.25em;
padding: 0.15em 0.5em;
border-radius: var(--d-border-radius);
font-size: var(--font-down-2);
line-height: 1.4;
&--bound {
background-color: var(--success-low);
color: var(--success);
}
&--missing {
background-color: var(--danger-low);
color: var(--danger);
}
}
.ai-tool-list__credential-not-set {
font-style: italic;
margin-left: 0.15em;
}
.ai-tool-list__mcp-meta {
margin-top: 0.35em;
color: var(--primary-medium);
font-size: var(--font-down-1);
}
.ai-tool-list__mcp-tools-button {
padding: 0;
border: 0;
background: transparent;
color: var(--tertiary);
font: inherit;
text-decoration: underline;
cursor: pointer;
&:hover,
&:focus-visible {
color: var(--tertiary-hover);
text-decoration-thickness: 2px;
}
}
.ai-mcp-server-tools-modal {
&__body {
display: grid;
gap: 1rem;
max-height: 70vh;
overflow-y: auto;
}
&__summary {
margin: 0;
color: var(--primary-medium);
}
&__tool {
padding: 1rem;
border: 1px solid var(--primary-low);
border-radius: var(--d-border-radius);
}
&__header {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 0.5rem;
align-items: baseline;
}
&__title {
font-weight: 700;
font-size: var(--font-up-1);
}
&__name {
color: var(--primary-medium);
font-size: var(--font-down-1);
}
&__description {
margin: 0.5rem 0 0;
color: var(--primary-high);
}
&__parameters-title {
margin: 1rem 0 0.5rem;
font-size: var(--font-0);
}
&__parameters {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 0.75rem;
}
&__parameter {
padding-top: 0.75rem;
border-top: 1px solid var(--primary-low);
&:first-child {
padding-top: 0;
border-top: 0;
}
}
&__parameter-meta {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
align-items: center;
}
&__parameter-type,
&__parameter-required {
padding: 0.1rem 0.45rem;
border-radius: 999px;
font-size: var(--font-down-2);
}
&__parameter-type {
background: var(--primary-low);
color: var(--primary-high);
}
&__parameter-required {
background: var(--danger-low);
color: var(--danger);
}
&__parameter-description,
&__no-parameters {
margin-top: 0.35rem;
color: var(--primary-medium);
}
}
.ai-mcp-server-editor {
&__oauth-state,
&__test-result {
margin-bottom: 1rem;
padding: 1rem;
border: 1px solid var(--primary-low);
border-radius: var(--d-border-radius);
h3 {
margin-top: 0;
}
ul {
margin-bottom: 0;
}
}
&__oauth-row {
display: grid;
grid-template-columns: minmax(10rem, 14rem) minmax(0, 1fr);
gap: 0.75rem;
align-items: start;
margin-top: 0.75rem;
@include viewport.until(md) {
grid-template-columns: 1fr;
gap: 0.25rem;
}
}
&__oauth-label {
color: var(--primary-medium);
font-weight: 600;
}
&__oauth-value {
min-width: 0;
overflow-wrap: anywhere;
}
}
.ai-tool-editor__templates {
&-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(16em, 1fr));
gap: 1em 2em;
margin-top: 1em;
padding-top: 1em;
border-top: 3px solid var(--primary-low);
}
&-list-item {
display: grid;
grid-template-rows: subgrid;
grid-row: span 4;
gap: 0;
margin-bottom: 2em;
@include viewport.from(sm) {
margin-bottom: 3em;
}
}
.admin-section-landing-item__description {
color: var(--primary-high);
margin: 0.25em 0 0.5em;
line-height: var(--line-height-large);
align-self: start;
@include viewport.from(sm) {
max-width: 17em;
}
}
button {
justify-self: start;
}
h4 {
font-size: var(--font-down-1);
font-weight: normal;
color: var(--primary-high);
margin: 0;
letter-spacing: 0.1px;
}
}
.ai-tool-preset-item {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.25em;
padding: 0.5em 0.75em;
cursor: pointer;
width: 100%;
box-sizing: border-box;
border-radius: var(--d-border-radius);
transition: background-color 0.15s ease-in-out;
&:hover {
background-color: var(--d-hover);
}
&:active {
background-color: var(--d-selected);
}
.ai-tool-preset-provider {
font-size: var(--font-down-1);
color: var(--primary-high);
line-height: 1.2;
}
.ai-tool-preset-model {
font-size: var(--font-0);
color: var(--primary);
line-height: 1.2;
font-weight: 500;
}
}