MCP install targets

An MCP install target owns one client's Terminay MCP registration. Terminay supplies the server command; the target writes exactly that into the client's own configuration file.

Manifest contribution

JSON
"permissions": ["mcp-registration"],
"contributes": {
  "mcpInstallTargets": [{
    "id": "com.example.agent/example-agent",
    "displayName": "Example Agent"
  }]
}

displayName is the client's name as Install Terminay MCP shows it. A manifest may declare up to 16 targets.

Runtime contract

TypeScript
context.subscriptions.add(
  context.mcp.registerInstallTarget(
    'com.example.agent/example-agent',
    exampleMcpInstallTarget,
  ),
);

The runtime implements status, install, and uninstall. Each receives one request object.

Request memberMeaning
server.command, server.args, server.env?The host-supplied launch command for the Terminay MCP server. Write exactly this and nothing else.
signalAborts on the call's deadline or when the target is disposed.
TypeScript
import { defineMcpInstallTarget } from '@terminay/extension-api';

const sameServer = (entry, server) =>
  JSON.stringify(entry) ===
  JSON.stringify({
    command: server.command,
    args: server.args,
    env: server.env ?? {},
  });

export const exampleMcpInstallTarget = defineMcpInstallTarget({
  async status({ server }) {
    try {
      const entry = (await readConfig()).mcpServers?.terminay;
      if (entry === undefined)
        return { state: 'not-installed', configPath: configPath() };
      return sameServer(entry, server)
        ? { state: 'installed', configPath: configPath() }
        : {
            state: 'changed',
            configPath: configPath(),
            message: 'The terminay entry points at a different command',
          };
    } catch {
      return {
        state: 'error',
        configPath: configPath(),
        message: 'The configuration file is not valid JSON',
      };
    }
  },
  async install({ server }) {
    const config = await readConfig();
    config.mcpServers = {
      ...config.mcpServers,
      terminay: {
        command: server.command,
        args: server.args,
        env: server.env ?? {},
      },
    };
    await writeConfig(config); // Write a temporary file, then rename it.
    return { ok: true, installed: true };
  },
  async uninstall() {
    const config = await readConfig();
    if (config.mcpServers?.terminay !== undefined) {
      delete config.mcpServers.terminay;
      await writeConfig(config);
    }
    return { ok: true, installed: false };
  },
});

Results

MethodReturns
statusstate, configPath (the file the target inspects), and an optional message.
install, uninstallBooleans ok and installed, with optional message and error.
StateMeaning
not-installedThe client has no Terminay entry.
installedThe entry matches the supplied command.
changedAn entry exists but differs from the supplied command.
unavailableThe target cannot be used on this server.
errorThe configuration could not be read.

Results are closed objects. message and error are at most 1,024 characters and configPath at most 4,096. When the server has no MCP server command, Terminay reports every target unavailable and does not call the extension.