Skip to main content
Use MCP tools when you want the MKA1 API to call tools from an external MCP server during a response. Define the MCP server in tools. Limit which tools the model can call with allowed_tools. Use require_approval when you want your app to pause and ask the end user before the tool runs. Use X-On-Behalf-Of for the MKA1 API end user. Pass upstream MCP server credentials in the MCP tool definition.

Call an MCP tool directly

Set require_approval to 'never' when the tool can run immediately.
import { SDK } from '@meetkai/mka1';
import type * as components from '@meetkai/mka1/models/components';

const mka1 = new SDK({
  bearerAuth: `Bearer ${YOUR_API_KEY}`,
});

const response = await mka1.llm.responses.create(
  {
    model: 'auto',
    instructions:
      'You are a project management assistant with access to Linear via MCP. Use Linear tools when the user asks about tasks, bugs, or projects. Keep the final answer terse.',
    input: 'List my most recent Linear issue assigned to me.',
    store: true,
    stream: false,
    tools: [
      {
        type: 'mcp',
        serverLabel: 'Linear MCP',
        serverDescription: 'Access Linear issues through MCP.',
        serverUrl: 'https://mcp.linear.app/mcp',
        allowedTools: ['issues.list'],
        headers: {
          Authorization: `Bearer ${process.env.LINEAR_API_KEY}`,
        },
        requireApproval: 'never',
      },
    ],
  },
  { headers: { 'X-On-Behalf-Of': '<end-user-id>' } },
);

// MCP tool calls appear as function_call items in the output
const mcpCalls = response.output.filter(
  (item): item is components.MCPToolCall => item.type === 'mcp_call',
);

const assistantText = response.output
  .filter(
    (item): item is components.OutputMessage =>
      item.type === 'message' && item.role === 'assistant',
  )
  .flatMap(item =>
    item.content.flatMap(content =>
      content.type === 'output_text' ? [content.text] : [],
    ),
  )
  .join('\n\n')
  .trim();

console.log(assistantText);
This is the simplest flow. The model calls the allowed MCP tool and returns the final assistant message in one request. The response output array contains:
  1. function_call — the model’s call to the MCP-discovered tool
  2. function_call_output — the data returned by the MCP server
  3. message — the model’s text response summarizing the results

Require end-user approval

Set require_approval to 'always' when your app should stop and wait for an approval decision. In this flow, create the response in background mode, poll it, and look for an mcp_approval_request item in output.
import { SDK } from '@meetkai/mka1';
import type * as components from '@meetkai/mka1/models/components';

const mka1 = new SDK({
  bearerAuth: `Bearer ${YOUR_API_KEY}`,
});

// Step 1: Create a background response with approval required
let pendingResponse = await mka1.llm.responses.create(
  {
    model: 'auto',
    instructions:
      'You are a project management assistant with access to Linear via MCP. Use Linear tools when the user asks about tasks, bugs, or projects. Keep the final answer terse.',
    input: 'List my most recent Linear issue assigned to me.',
    background: true,
    store: true,
    stream: false,
    tools: [
      {
        type: 'mcp',
        serverLabel: 'Linear MCP',
        serverDescription: 'Access Linear issues through MCP.',
        serverUrl: 'https://mcp.linear.app/mcp',
        allowedTools: ['issues.list'],
        headers: {
          Authorization: `Bearer ${process.env.LINEAR_API_KEY}`,
        },
        requireApproval: 'always',
      },
    ],
  },
  { headers: { 'X-On-Behalf-Of': '<end-user-id>' } },
);

// Step 2: Poll until an approval request appears
let approvalRequest: components.MCPApprovalRequest | undefined;

while (
  pendingResponse.status === 'queued' ||
  pendingResponse.status === 'in_progress'
) {
  approvalRequest = pendingResponse.output.find(
    (item): item is components.MCPApprovalRequest =>
      item.type === 'mcp_approval_request',
  );

  if (approvalRequest) break;

  await new Promise(resolve => setTimeout(resolve, 1000));

  pendingResponse = await mka1.llm.responses.get(
    { responseId: pendingResponse.id },
    { headers: { 'X-On-Behalf-Of': '<end-user-id>' } },
  );
}

if (!approvalRequest) {
  throw new Error(`No approval request found. Response ended with ${pendingResponse.status}.`);
}

// Step 3: Show the user what the model wants to do
console.log('Server:', approvalRequest.serverLabel);
console.log('Tool:', approvalRequest.name);
console.log('Arguments:', approvalRequest.arguments);

// Step 4: Send the approval (or denial) to continue
const approve = true; // Replace with your UI decision

const continuedResponse = await mka1.llm.responses.create(
  {
    model: 'auto',
    previousResponseId: pendingResponse.id,
    input: [
      {
        type: 'mcp_approval_response',
        approvalRequestId: approvalRequest.id,
        approve,
      },
    ],
    store: true,
    stream: false,
  },
  { headers: { 'X-On-Behalf-Of': '<end-user-id>' } },
);

const assistantText = continuedResponse.output
  .filter(
    (item): item is components.OutputMessage =>
      item.type === 'message' && item.role === 'assistant',
  )
  .flatMap(item =>
    item.content.flatMap(content =>
      content.type === 'output_text' ? [content.text] : [],
    ),
  )
  .join('\n\n')
  .trim();

console.log(assistantText);
If the end user denies the tool call, send approve: false. You can also include a reason field in the mcp_approval_response item. For approval UIs, show:
  • server_label — which MCP server is being used
  • name — which tool the model wants to call
  • arguments — what arguments it plans to send

MCP tool definition reference

ParameterTypeDefaultDescription
type"mcp"Required. Identifies this as an MCP tool.
server_labelstringRequired. Display name for the MCP server.
server_urlstringURL of the MCP server endpoint.
server_descriptionstringOptional description of the server’s purpose.
allowed_toolsstring[]Limit which tools the model can call.
headersobjectHeaders to pass to the MCP server (e.g. auth tokens).
require_approval"always" | "never""always"Whether to pause for end-user approval before calling.
connector_idstringUse a preconfigured connector instead of a custom server URL.
Credentials passed in headers are automatically masked in stored responses and streaming events.

Next steps