Skip to content

Commit a71092d

Browse files
author
saravmajestic
committed
fix: use single datamate gateway when .vscode/mcp.json has extension entry
1 parent ff6f9d4 commit a71092d

1 file changed

Lines changed: 69 additions & 14 deletions

File tree

‎packages/opencode/src/altimate/tools/datamate.ts‎

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import {
1313
} from "../../mcp/config"
1414
import { Instance } from "../../project/instance"
1515
import { Global } from "../../global"
16+
import { Log } from "../../util/log"
17+
18+
const log = Log.create({ service: "datamate" })
1619

1720
/** Project root for config resolution — falls back to cwd when no git repo is detected. */
1821
function projectRoot() {
@@ -81,7 +84,9 @@ export const DatamateManagerTool = Tool.define("datamate_manager", {
8184
"'list-config' shows all datamate entries saved in config files (project and global). " +
8285
"Config files: project config is at <project-root>/altimate-code.json, " +
8386
"global config is at ~/.config/altimate-code/altimate-code.json. " +
84-
"Datamate server names are prefixed with 'datamate-'. " +
87+
"When a VS Code extension datamate entry exists (.vscode/mcp.json has 'datamate' key), " +
88+
"'add' always uses the server name 'datamate' — tools are then prefixed 'datamate_'. " +
89+
"In standalone mode, server names follow 'datamate-<name>' pattern. " +
8590
"Do NOT use glob/grep/read to find config files — use 'list-config' instead.",
8691
parameters: z.object({
8792
operation: z.enum(["list", "list-integrations", "add", "create", "edit", "delete", "status", "remove", "list-config"]),
@@ -196,6 +201,10 @@ async function handleListIntegrations() {
196201
}
197202
}
198203

204+
// altimate_change start — server name used by the VS Code extension in .vscode/mcp.json
205+
const EXTENSION_DATAMATE_SERVER = "datamate"
206+
// altimate_change end
207+
199208
async function handleAdd(args: { datamate_id?: string; name?: string; scope?: "project" | "global" }) {
200209
if (!args.datamate_id) {
201210
return {
@@ -205,30 +214,76 @@ async function handleAdd(args: { datamate_id?: string; name?: string; scope?: "p
205214
}
206215
}
207216
try {
208-
// altimate_change start — detect transport from .vscode/mcp.json; fall back to cloud
209-
// Reads .vscode/mcp.json in the project root to determine whether the extension
210-
// registered an HTTP or stdio transport for the datamate MCP server. This lets
211-
// altimate-code honour whatever the VS Code extension wrote (HTTP when the local
212-
// mcp-engine is running, stdio when it is not), and falls back to the cloud MCP
213-
// config when no .vscode/mcp.json entry is found.
214217
const datamate = await AltimateApi.getDatamate(args.datamate_id)
215-
const serverName = args.name ?? `datamate-${slugify(datamate.name)}`
216218
const transport = await readVscodeMcpTransport(projectRoot())
219+
220+
// altimate_change start — single-gateway mode when extension is present
221+
// If .vscode/mcp.json has a "datamate" entry (written by the VS Code extension),
222+
// always use "datamate" as the server name regardless of which specific datamate
223+
// the user selected. This prevents duplicate tool sets — the extension's gateway
224+
// already serves all datamate tools through a single MCP connection.
225+
// In standalone/CLI mode (no .vscode/mcp.json datamate entry), fall back to the
226+
// original per-datamate naming with cloud URL.
227+
const serverName = transport !== null
228+
? EXTENSION_DATAMATE_SERVER
229+
: (args.name ?? `datamate-${slugify(datamate.name)}`)
230+
217231
const creds = transport ? undefined : await AltimateApi.getCredentials()
218232
const mcpConfig =
219233
transport?.type === "remote"
220234
? { type: "remote" as const, url: transport.url }
221235
: transport?.type === "local"
222-
? { type: "local" as const, command: ["datamate", "start-stdio", "--datamate", args.datamate_id] }
236+
// Extension stdio: no --datamate id needed — active teammate is resolved
237+
// by the extension over the ALTIMATE_EXTENSION_RPC socket at runtime.
238+
? { type: "local" as const, command: ["datamate", "start-stdio"] }
223239
: AltimateApi.buildMcpConfig(creds!, args.datamate_id)
224-
// altimate_change end
225240

226-
// Always save to config first so it persists for future sessions
227241
const isGlobal = args.scope === "global"
228242
const configPath = await resolveConfigPath(isGlobal ? Global.Path.config : projectRoot(), isGlobal)
229-
await addMcpToConfig(serverName, { ...mcpConfig, enabled: true }, configPath)
230243

231-
await MCP.add(serverName, mcpConfig)
244+
if (transport !== null) {
245+
// Extension mode: check if "datamate" is already wired up
246+
const existingNames = await listMcpInConfig(configPath)
247+
const staleEntries = existingNames.filter(
248+
(n) => n !== EXTENSION_DATAMATE_SERVER && n.startsWith("datamate-"),
249+
)
250+
if (staleEntries.length > 0) {
251+
log.info("handleAdd: stale per-datamate entries detected alongside extension gateway", {
252+
staleEntries,
253+
})
254+
}
255+
256+
if (existingNames.includes(EXTENSION_DATAMATE_SERVER)) {
257+
// Already in config — just ensure it is connected in this session
258+
const allStatus = await MCP.status()
259+
if (allStatus[EXTENSION_DATAMATE_SERVER]?.status === "connected") {
260+
const mcpTools = await MCP.tools()
261+
const toolCount = Object.keys(mcpTools).filter((k) =>
262+
k.startsWith(EXTENSION_DATAMATE_SERVER + "_"),
263+
).length
264+
const staleNote =
265+
staleEntries.length > 0
266+
? `\n\nNote: stale per-datamate entries found in config: ${staleEntries.join(", ")} — use operation 'remove' to clean them up.`
267+
: ""
268+
return {
269+
title: `Datamate '${datamate.name}': already connected via '${EXTENSION_DATAMATE_SERVER}'`,
270+
metadata: { serverName: EXTENSION_DATAMATE_SERVER, datamateId: args.datamate_id, toolCount },
271+
output: `Datamate tools are already available via the '${EXTENSION_DATAMATE_SERVER}' MCP server (${toolCount} tools active).${staleNote}`,
272+
}
273+
}
274+
// In config but not connected — reconnect
275+
await MCP.add(EXTENSION_DATAMATE_SERVER, mcpConfig)
276+
} else {
277+
// Not in config yet — write then connect
278+
await addMcpToConfig(EXTENSION_DATAMATE_SERVER, { ...mcpConfig, enabled: true }, configPath)
279+
await MCP.add(EXTENSION_DATAMATE_SERVER, mcpConfig)
280+
}
281+
} else {
282+
// Standalone/CLI mode — original behaviour: per-datamate name + cloud URL
283+
await addMcpToConfig(serverName, { ...mcpConfig, enabled: true }, configPath)
284+
await MCP.add(serverName, mcpConfig)
285+
}
286+
// altimate_change end
232287

233288
// Check connection status
234289
const allStatus = await MCP.status()
@@ -252,7 +307,7 @@ async function handleAdd(args: { datamate_id?: string; name?: string; scope?: "p
252307
return {
253308
title: `Datamate '${datamate.name}': connected as '${serverName}'`,
254309
metadata: { serverName, datamateId: args.datamate_id, toolCount, configPath },
255-
output: `Connected datamate '${datamate.name}' (ID: ${args.datamate_id}) as MCP server '${serverName}'.\n\n${toolCount} tools are now available from this datamate. They will be usable in the next message.\n\nConfiguration saved to ${configPath} for future sessions.`,
310+
output: `Connected datamate '${datamate.name}' (ID: ${args.datamate_id}) as MCP server '${serverName}'.\n\n${toolCount} tools are now available. They will be usable in the next message.\n\nConfiguration saved to ${configPath} for future sessions.`,
256311
}
257312
} catch (e) {
258313
return {

0 commit comments

Comments
 (0)