@@ -13,6 +13,9 @@ import {
1313} from "../../mcp/config"
1414import { Instance } from "../../project/instance"
1515import { 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. */
1821function 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+
199208async 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