History

refresh mcp

Rhys Sullivan committed 7 months ago

Commit d2bd3bd1 parent a61c8ec5 files · +121 −13Browse files

Unified · Split

5 files
  1. .executor/executor.jsonc +13 −13
  2. packages/platform/server/src/index.test.ts +42 −0
  3. plugins/mcp/http/index.ts +22 −0
  4. plugins/mcp/react/components.tsx +32 −0
  5. plugins/mcp/sdk/index.ts +12 −0
.executor/executor.jsonc+13−13
@@ -29,18 +29,6 @@ · 28 unmodified lines
2929 },
3030 "kind": "openapi"
3131 },
32− "github-graphql": {
33− "name": "GitHub GraphQL",
34− "namespace": "github.graphql",
35− "config": {
36− "endpoint": "https://api.github.com/graphql",
37− "defaultHeaders": null,
38− "auth": {
39− "kind": "none"
40− }
41− },
42− "kind": "graphql"
43− },
4432 "github-graphql-2": {
4533 "name": "GitHub GraphQL",
4634 "namespace": "github.graphql",
@@ -61,7 +49,6 @@ · 14 unmodified lines
6149 "axiom-mcp": {
6250 "name": "Axiom MCP",
6351 "namespace": "axiom.mcp",
64− "kind": "mcp",
6552 "config": {
6653 "endpoint": "https://mcp.axiom.co/mcp",
6754 "transport": "auto",
@@ -129,6 +116,19 @@ · 61 unmodified lines
129116 "client_id_issued_at": 1774829875
130117 }
131118 }
119+ },
120+ "kind": "mcp"
121+ },
122+ "linear-graphql": {
123+ "name": "Linear GraphQL",
124+ "namespace": "linear.graphql",
125+ "kind": "graphql",
126+ "config": {
127+ "endpoint": "https://api.linear.app/graphql",
128+ "defaultHeaders": null,
129+ "auth": {
130+ "kind": "none"
131+ }
132132 }
133133 }
134134 }
packages/platform/server/src/index.test.ts+42−0
@@ -1334,6 +1334,48 @@ describe("local-executor-server", () => { · 1333 unmodified lines
13341334 15_000,
13351335 );
13361336
1337+ it.scoped("refreshes an MCP source through the API client", () =>
1338+ Effect.gen(function* () {
1339+ const demoServer = yield* Effect.acquireRelease(
1340+ Effect.promise(() => startMcpElicitationDemoServer()),
1341+ (server) => Effect.promise(() => server.close()).pipe(Effect.orDie),
1342+ );
1343+ const { installation, client } = yield* createApiClientHarness();
1344+
1345+ const created = yield* client.mcp.createSource({
1346+ path: {
1347+ workspaceId: installation.scopeId,
1348+ },
1349+ payload: {
1350+ name: "Demo",
1351+ endpoint: demoServer.endpoint,
1352+ transport: "streamable-http",
1353+ queryParams: null,
1354+ headers: null,
1355+ command: null,
1356+ args: null,
1357+ env: null,
1358+ cwd: null,
1359+ auth: {
1360+ kind: "none",
1361+ },
1362+ },
1363+ });
1364+
1365+ const refreshed = yield* client.mcp.refreshSource({
1366+ path: {
1367+ workspaceId: installation.scopeId,
1368+ sourceId: created.id,
1369+ },
1370+ });
1371+
1372+ expect(refreshed.id).toBe(created.id);
1373+ expect(refreshed.kind).toBe("mcp");
1374+ expect(refreshed.status).toBe("connected");
1375+ }),
1376+ 15_000,
1377+ );
1378+
13371379 it.scoped("can run the same MCP elicitation flow more than once without interaction id collisions", () =>
13381380 Effect.gen(function* () {
13391381 const demoServer = yield* Effect.acquireRelease(
plugins/mcp/http/index.ts+22−0
@@ -55,6 +55,9 @@ type McpExecutorExtension = { · 54 unmodified lines
5555 updateSource: (
5656 input: McpUpdateSourceInput,
5757 ) => Effect.Effect<Source, Error>;
58+ refreshSource: (
59+ sourceId: Source["id"],
60+ ) => Effect.Effect<Source, Error>;
5861 removeSource: (
5962 sourceId: Source["id"],
6063 ) => Effect.Effect<boolean, Error>;
@@ -117,6 +120,14 @@ export const McpHttpGroup = HttpApiGroup.make("mcp") · 56 unmodified lines
117120 .addError(ControlPlaneNotFoundError)
118121 .addError(ControlPlaneStorageError),
119122 )
123+ .add(
124+ HttpApiEndpoint.post("refreshSource")`/workspaces/${workspaceIdParam}/plugins/mcp/sources/${sourceIdParam}/refresh`
125+ .addSuccess(SourceSchema)
126+ .addError(ControlPlaneBadRequestError)
127+ .addError(ControlPlaneForbiddenError)
128+ .addError(ControlPlaneNotFoundError)
129+ .addError(ControlPlaneStorageError),
130+ )
120131 .add(
121132 HttpApiEndpoint.del("removeSource")`/workspaces/${workspaceIdParam}/plugins/mcp/sources/${sourceIdParam}`
122133 .addSuccess(Schema.Struct({ removed: Schema.Boolean }))
@@ -284,6 +295,17 @@ export const mcpHttpPlugin = (): ExecutorHttpPlugin< · 161 unmodified lines
284295 ),
285296 )
286297 )
298+ .handle("refreshSource", ({ path }) =>
299+ resolveRequestedLocalWorkspace(
300+ "mcp.refreshSource",
301+ path.workspaceId,
302+ ).pipe(
303+ Effect.flatMap(() => executor.mcp.refreshSource(path.sourceId)),
304+ Effect.mapError((cause) =>
305+ mapPluginStorageError("mcp.refreshSource", cause)
306+ ),
307+ )
308+ )
287309 .handle("removeSource", ({ path }) =>
288310 resolveRequestedLocalWorkspace(
289311 "mcp.removeSource",
plugins/mcp/react/components.tsx+32−0
@@ -733,6 +733,25 @@ export function McpDetailPage(props: { · 732 unmodified lines
733733 client.mutation("mcp", "removeSource"),
734734 { mode: "promise" },
735735 );
736+ const refreshSource = useAtomSet(
737+ client.mutation("mcp", "refreshSource"),
738+ { mode: "promise" },
739+ );
740+ const refreshMutation = useExecutorMutation<Source["id"], Source>(async (sourceId) =>
741+ refreshSource({
742+ path: {
743+ workspaceId: props.source.scopeId,
744+ sourceId,
745+ },
746+ reactivityKeys: {
747+ sources: [props.source.scopeId],
748+ source: [props.source.scopeId, sourceId],
749+ sourceInspection: [props.source.scopeId, sourceId],
750+ sourceInspectionTool: [props.source.scopeId, sourceId],
751+ sourceDiscovery: [props.source.scopeId, sourceId],
752+ },
753+ })
754+ );
736755 const [confirmDelete, setConfirmDelete] = useState(false);
737756 const [isDeleting, setIsDeleting] = useState(false);
738757 const configResult = useAtomValue(
@@ -815,6 +834,19 @@ export function McpDetailPage(props: { · 76 unmodified lines
815834 <>
816835 <Button
817836 variant="outline"
837+ size="sm"
838+ type="button"
839+ onClick={() => {
840+ void refreshMutation.mutateAsync(props.source.id);
841+ }}
842+ disabled={refreshMutation.status === "pending"}
843+ >
844+ {refreshMutation.status === "pending" ? "Refreshing..." : "Refresh"}
845+ </Button>
846+ <Button
847+ variant="outline"
848+ size="sm"
849+ type="button"
818850 onClick={() =>
819851 void navigation.edit(props.source.id)}
820852 >
plugins/mcp/sdk/index.ts+12−0
@@ -163,6 +163,9 @@ export type McpSdk = { · 162 unmodified lines
163163 updateSource: (
164164 input: McpUpdateSourceInput,
165165 ) => Effect.Effect<Source, Error>;
166+ refreshSource: (
167+ sourceId: Source["id"],
168+ ) => Effect.Effect<Source, Error>;
166169 removeSource: (
167170 sourceId: Source["id"],
168171 ) => Effect.Effect<boolean, Error>;
@@ -948,6 +951,13 @@ export const mcpSdkPlugin = ( · 779 unmodified lines
948951 outputSchema: SourceSchema,
949952 execute: ({ args, source }) => source.updateSource(args),
950953 },
954+ {
955+ name: "refreshSource",
956+ description: "Refresh an MCP source and resync its catalog.",
957+ inputSchema: McpSourceIdInputSchema,
958+ outputSchema: SourceSchema,
959+ execute: ({ args, source }) => source.refreshSource(args.sourceId),
960+ },
951961 {
952962 name: "removeSource",
953963 description: "Remove an MCP source and its stored plugin data.",
@@ -1092,6 +1102,8 @@ export const mcpSdkPlugin = ( · 138 unmodified lines
10921102 provideRuntime(source.createSource(input)),
10931103 updateSource: (input) =>
10941104 provideRuntime(source.updateSource(input)),
1105+ refreshSource: (sourceId) =>
1106+ provideRuntime(source.refreshSource(sourceId)),
10951107 removeSource: (sourceId) =>
10961108 provideRuntime(source.removeSource(sourceId)),
10971109 startOAuth: (input) =>