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
29 },29 },
30 "kind": "openapi"30 "kind": "openapi"
31 },31 },
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 },
44 "github-graphql-2": {32 "github-graphql-2": {
45 "name": "GitHub GraphQL",33 "name": "GitHub GraphQL",
46 "namespace": "github.graphql",34 "namespace": "github.graphql",
@@ -61,7 +49,6 @@ · 14 unmodified lines
61 "axiom-mcp": {49 "axiom-mcp": {
62 "name": "Axiom MCP",50 "name": "Axiom MCP",
63 "namespace": "axiom.mcp",51 "namespace": "axiom.mcp",
64 "kind": "mcp",
65 "config": {52 "config": {
66 "endpoint": "https://mcp.axiom.co/mcp",53 "endpoint": "https://mcp.axiom.co/mcp",
67 "transport": "auto",54 "transport": "auto",
@@ -129,6 +116,19 @@ · 61 unmodified lines
129 "client_id_issued_at": 1774829875116 "client_id_issued_at": 1774829875
130 }117 }
131 }118 }
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 }
132 }132 }
133 }133 }
134 }134 }
packages/platform/server/src/index.test.ts+42−0
@@ -1334,6 +1334,48 @@ describe("local-executor-server", () => { · 1333 unmodified lines
1334 15_000,1334 15_000,
1335 );1335 );
1336 1336
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
1337 it.scoped("can run the same MCP elicitation flow more than once without interaction id collisions", () =>1379 it.scoped("can run the same MCP elicitation flow more than once without interaction id collisions", () =>
1338 Effect.gen(function* () {1380 Effect.gen(function* () {
1339 const demoServer = yield* Effect.acquireRelease(1381 const demoServer = yield* Effect.acquireRelease(
plugins/mcp/http/index.ts+22−0
@@ -55,6 +55,9 @@ type McpExecutorExtension = { · 54 unmodified lines
55 updateSource: (55 updateSource: (
56 input: McpUpdateSourceInput,56 input: McpUpdateSourceInput,
57 ) => Effect.Effect<Source, Error>;57 ) => Effect.Effect<Source, Error>;
58 refreshSource: (
59 sourceId: Source["id"],
60 ) => Effect.Effect<Source, Error>;
58 removeSource: (61 removeSource: (
59 sourceId: Source["id"],62 sourceId: Source["id"],
60 ) => Effect.Effect<boolean, Error>;63 ) => Effect.Effect<boolean, Error>;
@@ -117,6 +120,14 @@ export const McpHttpGroup = HttpApiGroup.make("mcp") · 56 unmodified lines
117 .addError(ControlPlaneNotFoundError)120 .addError(ControlPlaneNotFoundError)
118 .addError(ControlPlaneStorageError),121 .addError(ControlPlaneStorageError),
119 )122 )
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 )
120 .add(131 .add(
121 HttpApiEndpoint.del("removeSource")`/workspaces/${workspaceIdParam}/plugins/mcp/sources/${sourceIdParam}`132 HttpApiEndpoint.del("removeSource")`/workspaces/${workspaceIdParam}/plugins/mcp/sources/${sourceIdParam}`
122 .addSuccess(Schema.Struct({ removed: Schema.Boolean }))133 .addSuccess(Schema.Struct({ removed: Schema.Boolean }))
@@ -284,6 +295,17 @@ export const mcpHttpPlugin = (): ExecutorHttpPlugin< · 161 unmodified lines
284 ),295 ),
285 )296 )
286 )297 )
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 )
287 .handle("removeSource", ({ path }) =>309 .handle("removeSource", ({ path }) =>
288 resolveRequestedLocalWorkspace(310 resolveRequestedLocalWorkspace(
289 "mcp.removeSource",311 "mcp.removeSource",
plugins/mcp/react/components.tsx+32−0
@@ -733,6 +733,25 @@ export function McpDetailPage(props: { · 732 unmodified lines
733 client.mutation("mcp", "removeSource"),733 client.mutation("mcp", "removeSource"),
734 { mode: "promise" },734 { mode: "promise" },
735 );735 );
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 );
736 const [confirmDelete, setConfirmDelete] = useState(false);755 const [confirmDelete, setConfirmDelete] = useState(false);
737 const [isDeleting, setIsDeleting] = useState(false);756 const [isDeleting, setIsDeleting] = useState(false);
738 const configResult = useAtomValue(757 const configResult = useAtomValue(
@@ -815,6 +834,19 @@ export function McpDetailPage(props: { · 76 unmodified lines
815 <>834 <>
816 <Button835 <Button
817 variant="outline"836 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"
818 onClick={() =>850 onClick={() =>
819 void navigation.edit(props.source.id)}851 void navigation.edit(props.source.id)}
820 >852 >
plugins/mcp/sdk/index.ts+12−0
@@ -163,6 +163,9 @@ export type McpSdk = { · 162 unmodified lines
163 updateSource: (163 updateSource: (
164 input: McpUpdateSourceInput,164 input: McpUpdateSourceInput,
165 ) => Effect.Effect<Source, Error>;165 ) => Effect.Effect<Source, Error>;
166 refreshSource: (
167 sourceId: Source["id"],
168 ) => Effect.Effect<Source, Error>;
166 removeSource: (169 removeSource: (
167 sourceId: Source["id"],170 sourceId: Source["id"],
168 ) => Effect.Effect<boolean, Error>;171 ) => Effect.Effect<boolean, Error>;
@@ -948,6 +951,13 @@ export const mcpSdkPlugin = ( · 779 unmodified lines
948 outputSchema: SourceSchema,951 outputSchema: SourceSchema,
949 execute: ({ args, source }) => source.updateSource(args),952 execute: ({ args, source }) => source.updateSource(args),
950 },953 },
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 },
951 {961 {
952 name: "removeSource",962 name: "removeSource",
953 description: "Remove an MCP source and its stored plugin data.",963 description: "Remove an MCP source and its stored plugin data.",
@@ -1092,6 +1102,8 @@ export const mcpSdkPlugin = ( · 138 unmodified lines
1092 provideRuntime(source.createSource(input)),1102 provideRuntime(source.createSource(input)),
1093 updateSource: (input) =>1103 updateSource: (input) =>
1094 provideRuntime(source.updateSource(input)),1104 provideRuntime(source.updateSource(input)),
1105 refreshSource: (sourceId) =>
1106 provideRuntime(source.refreshSource(sourceId)),
1095 removeSource: (sourceId) =>1107 removeSource: (sourceId) =>
1096 provideRuntime(source.removeSource(sourceId)),1108 provideRuntime(source.removeSource(sourceId)),
1097 startOAuth: (input) =>1109 startOAuth: (input) =>