Skip to content

Commit 3f52c60

Browse files
committed
refresh mcp
1 parent 3fa2178 commit 3f52c60

5 files changed

Lines changed: 121 additions & 13 deletions

File tree

.executor/executor.jsonc

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,6 @@
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 @@
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 @@
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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,48 @@ describe("local-executor-server", () => {
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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ type McpExecutorExtension = {
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")
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<
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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,25 @@ export function McpDetailPage(props: {
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: {
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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ export type McpSdk = {
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 = (
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 = (
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) =>

0 commit comments

Comments
 (0)