mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
Add configurable MCP workspace guidance (#11839)
* Add configurable MCP workspace guidance Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix Instructions passing, tweak UI --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,10 @@ class Team extends Model {
|
||||
@observable
|
||||
defaultUserRole: UserRole;
|
||||
|
||||
@Field
|
||||
@observable
|
||||
guidanceMCP: string | null;
|
||||
|
||||
@Field
|
||||
@observable
|
||||
preferences: TeamPreferences | null;
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { TeamPreference } from "@shared/types";
|
||||
import { TeamValidation } from "@shared/validations";
|
||||
import Heading from "~/components/Heading";
|
||||
import Scene from "~/components/Scene";
|
||||
import Switch from "~/components/Switch";
|
||||
@@ -30,6 +31,18 @@ function Features() {
|
||||
[team, t]
|
||||
);
|
||||
|
||||
const handleGuidanceMCPChange = React.useCallback(
|
||||
async (ev: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
team.guidanceMCP = ev.target.value || null;
|
||||
},
|
||||
[team]
|
||||
);
|
||||
|
||||
const handleGuidanceMCPBlur = React.useCallback(async () => {
|
||||
await team.save();
|
||||
toast.success(t("Settings saved"));
|
||||
}, [team, t]);
|
||||
|
||||
const handleCopied = React.useCallback(() => {
|
||||
toast.success(t("Copied to clipboard"));
|
||||
}, [t]);
|
||||
@@ -46,6 +59,7 @@ function Features() {
|
||||
<SettingRow
|
||||
name={TeamPreference.MCP}
|
||||
label={t("MCP server")}
|
||||
border={!team.getPreference(TeamPreference.MCP)}
|
||||
description={
|
||||
<>
|
||||
<Text type="secondary" as="p">
|
||||
@@ -97,6 +111,31 @@ function Features() {
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
{team.getPreference(TeamPreference.MCP) && (
|
||||
<SettingRow
|
||||
name="guidanceMCP"
|
||||
label={t("Additional guidance")}
|
||||
description={
|
||||
<>
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
{t(
|
||||
"You can use these optional instructions to tell MCP clients how to use your knowledge base."
|
||||
)}
|
||||
</div>
|
||||
<Input
|
||||
id="guidanceMCP"
|
||||
type="textarea"
|
||||
rows={6}
|
||||
value={team.guidanceMCP ?? ""}
|
||||
maxLength={TeamValidation.maxGuidanceMCPLength}
|
||||
onChange={handleGuidanceMCPChange}
|
||||
onBlur={handleGuidanceMCPBlur}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SettingRow
|
||||
name="answers"
|
||||
label={t("AI answers")}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
return queryInterface.addColumn("teams", "guidanceMCP", {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
});
|
||||
},
|
||||
|
||||
down: async (queryInterface) => {
|
||||
return queryInterface.removeColumn("teams", "guidanceMCP");
|
||||
},
|
||||
};
|
||||
@@ -187,6 +187,14 @@ class Team extends ParanoidModel<
|
||||
@SkipChangeset
|
||||
approximateTotalAttachmentsSize: number;
|
||||
|
||||
@AllowNull
|
||||
@Length({
|
||||
max: TeamValidation.maxGuidanceMCPLength,
|
||||
msg: `MCP guidance must be ${TeamValidation.maxGuidanceMCPLength} characters or less`,
|
||||
})
|
||||
@Column(DataType.TEXT)
|
||||
guidanceMCP: string | null;
|
||||
|
||||
@AllowNull
|
||||
@Column(DataType.JSONB)
|
||||
preferences: TeamPreferences | null;
|
||||
|
||||
@@ -20,5 +20,6 @@ export default function presentTeam(team: Team) {
|
||||
inviteRequired: team.inviteRequired,
|
||||
allowedDomains: team.allowedDomains?.map((d) => d.name),
|
||||
preferences: team.preferences,
|
||||
guidanceMCP: team.guidanceMCP,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import { EmailDisplay, TOCPosition, UserRole } from "@shared/types";
|
||||
import { TeamValidation } from "@shared/validations";
|
||||
import { BaseSchema } from "@server/routes/api/schema";
|
||||
|
||||
export const TeamsUpdateSchema = BaseSchema.extend({
|
||||
@@ -32,6 +33,8 @@ export const TeamsUpdateSchema = BaseSchema.extend({
|
||||
inviteRequired: z.boolean().optional(),
|
||||
/** Domains allowed to sign-in with SSO */
|
||||
allowedDomains: z.array(z.string()).optional(),
|
||||
/** Workspace guidance provided to MCP clients on connection */
|
||||
guidanceMCP: z.string().max(TeamValidation.maxGuidanceMCPLength).nullish(),
|
||||
/** Team preferences */
|
||||
preferences: z
|
||||
.object({
|
||||
|
||||
@@ -27,9 +27,10 @@ const router = new Router();
|
||||
* scopes granted to the current token.
|
||||
*
|
||||
* @param scopes - the OAuth scopes granted to the access token.
|
||||
* @param instructions - optional workspace guidance to send to clients.
|
||||
* @returns a configured McpServer ready to be connected to a transport.
|
||||
*/
|
||||
function createMcpServer(scopes: string[]): McpServer {
|
||||
function createMcpServer(scopes: string[], instructions?: string): McpServer {
|
||||
const server = new McpServer(
|
||||
{
|
||||
name: "outline",
|
||||
@@ -39,6 +40,7 @@ function createMcpServer(scopes: string[]): McpServer {
|
||||
capabilities: {
|
||||
tools: {},
|
||||
},
|
||||
...(instructions ? { instructions } : {}),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -68,7 +70,10 @@ router.post(
|
||||
throw NotFoundError();
|
||||
}
|
||||
|
||||
const server = createMcpServer(scope ?? []);
|
||||
const server = createMcpServer(
|
||||
scope ?? [],
|
||||
user.team.guidanceMCP ?? undefined
|
||||
);
|
||||
const transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: undefined,
|
||||
});
|
||||
|
||||
@@ -1250,6 +1250,8 @@
|
||||
"Allow members to connect to this workspace with MCP to read and write data.": "Allow members to connect to this workspace with MCP to read and write data.",
|
||||
"Use the following endpoint to connect to the MCP server from your app. Find out more about setup in <a>the docs</a>.": "Use the following endpoint to connect to the MCP server from your app. Find out more about setup in <a>the docs</a>.",
|
||||
"Copy URL": "Copy URL",
|
||||
"Additional guidance": "Additional guidance",
|
||||
"You can use these optional instructions to tell MCP clients how to use your knowledge base.": "You can use these optional instructions to tell MCP clients how to use your knowledge base.",
|
||||
"AI answers": "AI answers",
|
||||
"Use AI to get direct answers to questions in search. This feature requires a paid license.": "Use AI to get direct answers to questions in search. This feature requires a paid license.",
|
||||
"Add people to {{groupName}}": "Add people to {{groupName}}",
|
||||
|
||||
@@ -132,6 +132,9 @@ export const TeamValidation = {
|
||||
|
||||
/** The maximum length of the team subdomain for self-hosted */
|
||||
maxSubdomainSelfHostedLength: 255,
|
||||
|
||||
/** The maximum length of MCP workspace guidance */
|
||||
maxGuidanceMCPLength: 2000,
|
||||
};
|
||||
|
||||
export const UserValidation = {
|
||||
|
||||
Reference in New Issue
Block a user