Skip to content

Commit d7fd99c

Browse files
committed
feat: matching connectors with MCP demands
Signed-off-by: Tomas Weiss <tomas.weiss2@gmail.com>
1 parent 464d943 commit d7fd99c

8 files changed

Lines changed: 69 additions & 33 deletions

File tree

CLAUDE.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,10 @@ When filing issues to i-am-bee/agentstack:
1818

1919
## Rules for issue filing
2020
- Keep the ticket extremly concise
21-
- Don't provide implementation detail, your goal is define problem, with potential consequences not solution.
21+
- Don't provide implementation detail, your goal is define problem, with potential consequences not solution.
22+
23+
# TypeScript
24+
25+
## Code Style
26+
27+
- preferably don't use single letter variable names, make it at least a bit descriptive

apps/agentstack-sdk-py/src/agentstack_sdk/a2a/extensions/services/mcp.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class StdioTransport(pydantic.BaseModel):
3939
class StreamableHTTPTransport(pydantic.BaseModel):
4040
type: Literal["streamable_http"] = "streamable_http"
4141

42-
url: pydantic.AnyHttpUrl
42+
url: str
4343
headers: dict[str, str] | None = None
4444

4545

@@ -112,11 +112,7 @@ def handle_incoming_message(self, message: a2a.types.Message, context: RunContex
112112
if fullfilment.transport.type == "streamable_http":
113113
try:
114114
fullfilment.transport.url = pydantic.AnyHttpUrl(
115-
re.sub(
116-
r"^http[s]?://{platform_url}",
117-
platform_url,
118-
str(fullfilment.transport.url),
119-
)
115+
re.sub("^{platform_url}", platform_url, str(fullfilment.transport.url))
120116
)
121117
except Exception:
122118
logger.warning("Platform URL substitution failed", exc_info=True)

apps/agentstack-sdk-ts/src/client/api/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ export const connectorSchema = z.object({
9999
.nullable(),
100100
disconnect_reason: z.string().nullable(),
101101
metadata: z.record(z.string(), z.string()).nullable(),
102-
created_at: z.string(),
103102
});
104103

105104
export type Connector = z.infer<typeof connectorSchema>;

apps/agentstack-ui/src/modules/connectors/api/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
import { api } from '#api/index.ts';
77
import { ensureData } from '#api/utils.ts';
88
import { BASE_URL } from '#utils/constants.ts';
9+
import { ListConnectorsResponse } from 'agentstack-sdk';
910

10-
import type { CreateConnectorRequest, ListConnectorsResponse } from './types';
11+
import type { CreateConnectorRequest } from './types';
1112

1213
export async function createConnector(body: CreateConnectorRequest) {
1314
const response = await api.POST('/api/v1/connectors', { body });
@@ -72,5 +73,5 @@ interface Created {
7273

7374
export async function listConnectors(): Promise<ListConnectorsResponse | undefined> {
7475
const response = await api.GET('/api/v1/connectors', {});
75-
return ensureData(response);
76+
return ensureData(response) as ListConnectorsResponse;
7677
}

apps/agentstack-ui/src/modules/connectors/api/types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import type { ApiRequest, ApiResponse } from '#@types/utils.ts';
6+
import type { ApiRequest } from '#@types/utils.ts';
77

88
export type CreateConnectorRequest = ApiRequest<'/api/v1/connectors'>;
9-
export type ListConnectorsResponse = ApiResponse<'/api/v1/connectors', 'get', 'application/json', 200>;

apps/agentstack-ui/src/modules/connectors/components/ConnectorsView.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export const ConnectorsView = () => {
2626
client_id,
2727
client_secret,
2828
match_preset: false,
29+
// TODO: get name from the form
30+
metadata: {
31+
name: 'GitHub',
32+
},
2933
});
3034
},
3135
[createConnector],

apps/agentstack-ui/src/modules/runs/contexts/agent-demands/AgentDemandsProvider.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { type PropsWithChildren, useCallback, useEffect, useRef, useState } from
88

99
import type { AgentA2AClient } from '#api/a2a/types.ts';
1010
import { useApp } from '#contexts/App/index.ts';
11+
import { useListConnectors } from '#modules/connectors/api/queries/useListConnectors.ts';
1112
import type { RunFormValues } from '#modules/form/types.ts';
1213
import { useCreateContextToken } from '#modules/platform-context/api/mutations/useCreateContextToken.ts';
1314
import { useMatchProviders } from '#modules/platform-context/api/mutations/useMatchProviders.ts';
@@ -39,7 +40,7 @@ export function AgentDemandsProvider<UIGenericPart>({
3940
);
4041

4142
const {
42-
config: { featureFlags, contextTokenPermissions },
43+
config: { contextTokenPermissions },
4344
} = useApp();
4445
const { contextId } = usePlatformContext();
4546

@@ -115,6 +116,9 @@ export function AgentDemandsProvider<UIGenericPart>({
115116

116117
const [selectedMCPServers, setSelectedMCPServers] = useState<Record<string, string>>({});
117118

119+
const { data: connectorsData } = useListConnectors();
120+
const connectors = connectorsData?.items ?? [];
121+
118122
useEffect(() => {
119123
setSelectedMCPServers(
120124
Object.keys(agentClient?.demands.mcpDemands?.mcp_demands ?? {}).reduce(
@@ -168,20 +172,20 @@ export function AgentDemandsProvider<UIGenericPart>({
168172
selectedEmbeddingProviders,
169173
selectedMCPServers,
170174
providedSecrets,
171-
featureFlags,
172175
selectedSettings,
173176
formFulfillments: formFulfillmentsRef.current,
174177
oauthRedirectUri: fulfillmentsContext.oauthRedirectUri ?? null,
178+
connectors,
175179
});
176180
},
177181
[
178182
getContextToken,
179183
selectedLLMProviders,
180184
selectedEmbeddingProviders,
181185
selectedMCPServers,
182-
featureFlags,
183186
selectedSettings,
184187
demandedSecrets,
188+
connectors,
185189
],
186190
);
187191

apps/agentstack-ui/src/modules/runs/contexts/agent-demands/build-fulfillments.ts

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import type { AgentSettings, ContextToken, EmbeddingDemands, FormFulfillments, Fulfillments } from 'agentstack-sdk';
6+
import type {
7+
AgentSettings,
8+
Connector,
9+
ContextToken,
10+
EmbeddingDemands,
11+
FormFulfillments,
12+
Fulfillments,
13+
} from 'agentstack-sdk';
14+
import { ConnectorState } from 'agentstack-sdk';
715

816
import { BASE_URL } from '#utils/constants.ts';
9-
import type { FeatureFlags } from '#utils/feature-flags.ts';
1017

1118
interface BuildFulfillmentsParams {
1219
contextToken: ContextToken;
@@ -17,7 +24,7 @@ interface BuildFulfillmentsParams {
1724
selectedSettings: AgentSettings;
1825
formFulfillments: FormFulfillments;
1926
oauthRedirectUri: string | null;
20-
featureFlags: FeatureFlags;
27+
connectors: Connector[];
2128
}
2229

2330
export const buildFulfillments = ({
@@ -29,7 +36,7 @@ export const buildFulfillments = ({
2936
providedSecrets,
3037
formFulfillments,
3138
oauthRedirectUri,
32-
featureFlags,
39+
connectors,
3340
}: BuildFulfillmentsParams): Fulfillments => {
3441
return {
3542
getContextToken: () => contextToken,
@@ -105,27 +112,47 @@ export const buildFulfillments = ({
105112
);
106113
},
107114
mcp: async ({ mcp_demands }) => {
108-
if (!featureFlags.MCP) {
109-
return {
110-
mcp_fulfillments: {},
111-
};
112-
}
113-
115+
const connectedConnectors = connectors.filter((connector) => connector.state === ConnectorState.Connected);
114116
const allDemands = Object.keys(mcp_demands);
117+
const mcp_fulfillments: Record<string, any> = {};
115118

116-
return allDemands.reduce(
117-
(memo, demandKey) => {
118-
memo.mcp_fulfillments[demandKey] = {
119+
for (const demandKey of allDemands) {
120+
const clientProvided = selectedMCPServers[demandKey];
121+
if (clientProvided) {
122+
mcp_fulfillments[demandKey] = {
119123
transport: {
120124
type: 'streamable_http',
121-
url: selectedMCPServers[demandKey],
125+
url: clientProvided,
122126
},
123127
};
124128

125-
return memo;
126-
},
127-
{ mcp_fulfillments: {} },
128-
);
129+
continue;
130+
}
131+
132+
const demand = mcp_demands[demandKey];
133+
const suggestedNames = demand.suggested || [];
134+
135+
// TODO: what if we have multiple connectors with the same name?
136+
// currently we just randomly pick the latest connector
137+
const matchingConnectors = connectedConnectors.filter((connector) =>
138+
suggestedNames.some(
139+
(suggestedName) => connector.metadata?.name?.toLowerCase() === suggestedName.toLowerCase(),
140+
),
141+
);
142+
143+
if (matchingConnectors.length > 0) {
144+
const latestConnector = matchingConnectors[matchingConnectors.length - 1];
145+
146+
mcp_fulfillments[demandKey] = {
147+
transport: {
148+
type: 'streamable_http',
149+
url: `{platform_url}/api/v1/connectors/${latestConnector.id}/mcp`,
150+
},
151+
};
152+
}
153+
}
154+
155+
return { mcp_fulfillments };
129156
},
130157
oauth: async () => {
131158
return {

0 commit comments

Comments
 (0)