Skip to content

Commit 1f82a22

Browse files
committed
Trim namespace metadata cost
1 parent aa4c819 commit 1f82a22

4 files changed

Lines changed: 223 additions & 28 deletions

File tree

.changeset/five-plums-wave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"executor": patch
3+
---
4+
5+
Avoid generating source tool type previews when building namespace metadata, and cap MCP execute help examples to five tools per source.

packages/hosts/mcp/src/index.ts

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@ import type {
33
} from "@executor/platform-sdk/schema";
44
import {
55
EXECUTOR_SOURCES_ADD_HELP_LINES,
6-
RuntimeExecutionResolverService,
6+
RuntimeSourceCatalogStoreService,
77
createExecution,
88
getExecution,
99
resumeExecution,
1010
type ExecutorRuntime,
1111
} from "@executor/platform-sdk/runtime";
12-
import { ExecutionIdSchema } from "@executor/platform-sdk/schema";
1312
import * as Effect from "effect/Effect";
1413
import * as Cause from "effect/Cause";
1514
import * as Exit from "effect/Exit";
@@ -121,19 +120,33 @@ const interactionModeForServer = (server: McpServer): "live_form" | "detach" =>
121120
supportsManagedElicitation(server) ? "live_form" : "detach";
122121

123122
type CatalogLike = {
124-
listNamespaces: (input: { limit: number }) => Effect.Effect<
125-
ReadonlyArray<{ namespace: string; displayName?: string }>,
126-
unknown
127-
>;
123+
projected: {
124+
toolDescriptors: Record<string, { toolPath: readonly string[] }>;
125+
};
126+
source: {
127+
name: string;
128+
enabled: boolean;
129+
status: string;
130+
};
128131
};
129132

130-
const buildExecuteWorkflowText = (namespaces: readonly string[] = []): string =>
133+
const executeDescriptionToolsPerSource = 5;
134+
135+
const buildExecuteWorkflowText = (
136+
sourceToolExamples: ReadonlyArray<{
137+
sourceName: string;
138+
toolPaths: readonly string[];
139+
}> = [],
140+
): string =>
131141
[
132142
"Execute TypeScript in sandbox; call tools via discovery workflow.",
133-
...(namespaces.length > 0
143+
...(sourceToolExamples.length > 0
134144
? [
135-
"Available namespaces:",
136-
...namespaces.map((namespace) => `- ${namespace}`),
145+
"Available source tool examples:",
146+
...sourceToolExamples.flatMap((source) => [
147+
`${source.sourceName}:`,
148+
...source.toolPaths.map((toolPath) => `- ${toolPath}`),
149+
]),
137150
]
138151
: []),
139152
"Workflow:",
@@ -152,28 +165,29 @@ const loadExecuteDescription = (runtime: ExecutorRuntime): Promise<string> =>
152165
runControlPlane(
153166
runtime,
154167
Effect.gen(function* () {
155-
const resolveExecutionEnvironment = yield* RuntimeExecutionResolverService;
156-
const environment = yield* resolveExecutionEnvironment({
168+
const sourceCatalogStore = yield* RuntimeSourceCatalogStoreService;
169+
const catalogs = yield* sourceCatalogStore.loadWorkspaceSourceCatalogs({
157170
scopeId: runtime.localInstallation.scopeId,
158171
actorScopeId: runtime.localInstallation.actorScopeId,
159-
executionId: ExecutionIdSchema.make("exec_mcp_help"),
160172
});
161173

162-
const catalog = environment.catalog as CatalogLike | undefined;
163-
if (!catalog) {
174+
const sourceToolExamples = (catalogs as ReadonlyArray<CatalogLike>)
175+
.filter((catalog) => catalog.source.enabled && catalog.source.status === "connected")
176+
.map((catalog) => ({
177+
sourceName: catalog.source.name,
178+
toolPaths: Object.values(catalog.projected.toolDescriptors)
179+
.map((descriptor) => descriptor.toolPath.join("."))
180+
.filter((toolPath) => toolPath.length > 0)
181+
.sort((left, right) => left.localeCompare(right))
182+
.slice(0, executeDescriptionToolsPerSource),
183+
}))
184+
.filter((catalog) => catalog.toolPaths.length > 0);
185+
186+
if (sourceToolExamples.length === 0) {
164187
return defaultExecuteDescription;
165188
}
166189

167-
const namespaces = yield* catalog.listNamespaces({ limit: 200 }).pipe(
168-
Effect.map((items) =>
169-
items.length > 0
170-
? items.map((item) => item.displayName ?? item.namespace)
171-
: ["none discovered yet"],
172-
),
173-
Effect.catchAll(() => Effect.succeed(["none discovered yet"])),
174-
);
175-
176-
return buildExecuteWorkflowText(namespaces);
190+
return buildExecuteWorkflowText(sourceToolExamples);
177191
}).pipe(Effect.catchAll(() => Effect.succeed(defaultExecuteDescription))),
178192
);
179193

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import {
2+
describe,
3+
expect,
4+
it,
5+
} from "@effect/vitest";
6+
import * as Effect from "effect/Effect";
7+
8+
import {
9+
createScopeSourceCatalog,
10+
} from "./source-catalog";
11+
12+
const makeLoadedCatalog = (input: {
13+
sourceId: string;
14+
toolPaths: ReadonlyArray<string>;
15+
enabled?: boolean;
16+
status?: "connected" | "error";
17+
}) => ({
18+
source: {
19+
id: input.sourceId,
20+
scopeId: "ws_test",
21+
name: input.sourceId,
22+
kind: "openapi",
23+
endpoint: `https://example.test/${input.sourceId}`,
24+
status: input.status ?? "connected",
25+
enabled: input.enabled ?? true,
26+
namespace: null,
27+
bindingVersion: 1,
28+
binding: {},
29+
importAuthPolicy: "reuse_runtime",
30+
importAuth: {
31+
kind: "none",
32+
},
33+
auth: {
34+
kind: "none",
35+
},
36+
sourceHash: null,
37+
lastError: null,
38+
createdAt: 0,
39+
updatedAt: 0,
40+
},
41+
projected: {
42+
toolDescriptors: Object.fromEntries(
43+
input.toolPaths.map((path, index) => [
44+
`cap_${index}`,
45+
{
46+
toolPath: path.split("."),
47+
},
48+
]),
49+
),
50+
},
51+
}) as any;
52+
53+
const noopScopeConfigStore = {
54+
load: () => Effect.die("unexpected config load"),
55+
writeProject: () => Effect.void,
56+
resolveRelativePath: (path: string) => Effect.succeed(path),
57+
} as any;
58+
59+
const noopScopeStateStore = {
60+
load: () => Effect.die("unexpected scope state load"),
61+
write: () => Effect.void,
62+
} as any;
63+
64+
const noopSourceArtifactStore = {
65+
build: () => Effect.die("unexpected source artifact build"),
66+
read: () => Effect.die("unexpected source artifact read"),
67+
write: () => Effect.void,
68+
remove: () => Effect.void,
69+
} as any;
70+
71+
describe("scope source catalog", () => {
72+
it.effect("lists namespaces from projected tool paths without loading the tool index", () =>
73+
Effect.gen(function* () {
74+
const catalog = createScopeSourceCatalog({
75+
scopeId: "ws_test" as any,
76+
actorScopeId: "acc_test" as any,
77+
sourceCatalogStore: {
78+
loadWorkspaceSourceCatalogs: () => Effect.succeed([
79+
makeLoadedCatalog({
80+
sourceId: "github",
81+
toolPaths: [
82+
"github.repos.get",
83+
"github.repos.list",
84+
"github.issues.get",
85+
],
86+
}),
87+
makeLoadedCatalog({
88+
sourceId: "linear",
89+
toolPaths: [
90+
"linear.issue.create",
91+
],
92+
}),
93+
makeLoadedCatalog({
94+
sourceId: "disabled",
95+
toolPaths: [
96+
"disabled.admin.delete",
97+
],
98+
enabled: false,
99+
}),
100+
]),
101+
loadWorkspaceSourceCatalogToolIndex: () => Effect.die("unexpected tool index load"),
102+
loadWorkspaceSourceCatalogToolByPath: () => Effect.die("unexpected tool lookup"),
103+
} as any,
104+
scopeConfigStore: noopScopeConfigStore,
105+
scopeStateStore: noopScopeStateStore,
106+
sourceArtifactStore: noopSourceArtifactStore,
107+
runtimeLocalScope: null,
108+
});
109+
110+
const namespaces = yield* catalog.listNamespaces({ limit: 10 });
111+
112+
expect(namespaces).toEqual([
113+
{
114+
namespace: "github.issues",
115+
toolCount: 1,
116+
},
117+
{
118+
namespace: "github.repos",
119+
toolCount: 2,
120+
},
121+
{
122+
namespace: "linear.issue",
123+
toolCount: 1,
124+
},
125+
]);
126+
}));
127+
});

packages/platform/sdk/src/runtime/execution/scope/source-catalog.ts

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
createToolCatalogFromEntries,
33
type ToolCatalog,
4+
type ToolNamespace,
45
} from "@executor/codemode-core";
56
import type {
67
ScopeId,
@@ -80,6 +81,49 @@ const hasSubstringMatch = (value: string, queryToken: string): boolean => {
8081
const queryTokenWeight = (token: string): number =>
8182
LOW_SIGNAL_QUERY_TOKENS.has(token) ? 0.25 : 1;
8283

84+
const namespaceFromPath = (path: string): string => {
85+
const [first, second] = path.split(".");
86+
return second ? `${first}.${second}` : first;
87+
};
88+
89+
const sortNamespaces = (namespaces: Iterable<ToolNamespace>): ToolNamespace[] =>
90+
[...namespaces].sort((left, right) => left.namespace.localeCompare(right.namespace));
91+
92+
const loadWorkspaceCatalogNamespaces = (input: {
93+
scopeId: Source["scopeId"];
94+
actorScopeId: ScopeId;
95+
sourceCatalogStore: Effect.Effect.Success<typeof RuntimeSourceCatalogStoreService>;
96+
}): Effect.Effect<
97+
readonly ToolNamespace[],
98+
Error,
99+
ScopeStorageServices
100+
> =>
101+
Effect.gen(function* () {
102+
const catalogs = yield* input.sourceCatalogStore.loadWorkspaceSourceCatalogs({
103+
scopeId: input.scopeId,
104+
actorScopeId: input.actorScopeId,
105+
});
106+
107+
const namespaces = new Map<string, ToolNamespace>();
108+
109+
for (const catalog of catalogs) {
110+
if (!catalog.source.enabled || catalog.source.status !== "connected") {
111+
continue;
112+
}
113+
114+
for (const descriptor of Object.values(catalog.projected.toolDescriptors)) {
115+
const namespace = namespaceFromPath(descriptor.toolPath.join("."));
116+
const existing = namespaces.get(namespace);
117+
namespaces.set(namespace, {
118+
namespace,
119+
toolCount: (existing?.toolCount ?? 0) + 1,
120+
});
121+
}
122+
}
123+
124+
return sortNamespaces(namespaces.values());
125+
});
126+
83127
export const loadWorkspaceCatalogTools = (input: {
84128
scopeId: Source["scopeId"];
85129
actorScopeId: ScopeId;
@@ -291,9 +335,14 @@ export const createScopeSourceCatalog = (input: {
291335
return {
292336
listNamespaces: ({ limit }) =>
293337
provideRuntimeLocalScope(
294-
Effect.flatMap(createSharedCatalog(false), (catalog) =>
295-
catalog.listNamespaces({ limit }),
296-
),
338+
provideWorkspaceStorage(Effect.map(
339+
loadWorkspaceCatalogNamespaces({
340+
scopeId: input.scopeId,
341+
actorScopeId: input.actorScopeId,
342+
sourceCatalogStore: input.sourceCatalogStore,
343+
}),
344+
(namespaces) => namespaces.slice(0, limit),
345+
)),
297346
input.runtimeLocalScope,
298347
),
299348

0 commit comments

Comments
 (0)