Skip to content

Autonomous agent (MCP SDK)

Drive A-Market from your own autonomous agent — not a chat app — using the official, vendor-neutral MCP SDK. Because A-Market is a standard MCP server, the exact same client works whether your agent loop is built on Claude, GPT, a local model, or a hand-rolled state machine. This page shows a concrete, runnable example you can drop into any framework.

Install

bash
npm install @modelcontextprotocol/sdk

Connect (Streamable HTTP + bearer token)

The hosted server speaks Streamable HTTP at https://mcp.amrkt.ch/mcp and authenticates with an OAuth bearer token. For a headless agent, obtain a token however your deployment prefers — a service account (client-credentials), a stored refresh token, or, for trying it out, the password grant from Using the REST API. Then:

ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

const transport = new StreamableHTTPClientTransport(new URL('https://mcp.amrkt.ch/mcp'), {
  requestInit: { headers: { Authorization: `Bearer ${process.env.AMRKT_TOKEN}` } },
});

const client = new Client({ name: 'amrkt-scout', version: '1.0.0' });
await client.connect(transport);

// Discover what the server can do.
const { tools } = await client.listTools();
console.log(tools.map((t) => t.name)); // search_vehicles_nl, get_price_rating, …

For a fully interactive OAuth login (PKCE in a browser) instead of a pre-issued token, pass an authProvider to the transport — see the MCP SDK's OAuth client docs.

A small helper to call a tool and parse its JSON result:

ts
async function call<T = unknown>(name: string, args: Record<string, unknown>): Promise<T> {
  const res = await client.callTool({ name, arguments: args });
  const text = (res.content ?? [])
    .filter((c) => c.type === 'text')
    .map((c) => c.text)
    .join('');
  return JSON.parse(text) as T;
}

A concrete autonomous loop

A "buyer scout" that takes a goal, searches in natural language, judges each candidate's price, compares the front-runners, and saves the best one to the account's watchlist — entirely on its own:

ts
async function scout(goal: string) {
  // 1. Natural-language search → structured results.
  const { results } = await call<{ results: Array<{ id: string; make: string; model: string }> }>(
    'search_vehicles_nl',
    { query: goal, limit: 10 },
  );
  if (results.length === 0) return console.log('No matches.');

  // 2. Rate each candidate's price against comparables.
  const rated = [];
  for (const v of results) {
    const rating = await call<{ rating: 'great' | 'good' | 'fair' | 'high' }>('get_price_rating', {
      id: v.id,
    });
    rated.push({ ...v, rating: rating.rating });
  }

  // 3. Keep the well-priced ones, compare the top few side by side.
  const order = { great: 0, good: 1, fair: 2, high: 3 };
  const shortlist = rated.sort((a, b) => order[a.rating] - order[b.rating]).slice(0, 3);
  const comparison = await call('compare_vehicles', { ids: shortlist.map((v) => v.id) });
  console.log('Shortlist:', shortlist, comparison);

  // 4. Act: save the best candidate to the watchlist.
  const best = shortlist[0];
  await call('add_favorite', { listingId: best.id });
  console.log(`Favorited ${best.make} ${best.model} (${best.rating} price).`);
}

await scout('family electric SUV under CHF 40,000, first registered after 2021, near Zürich');
await client.close();

This loop is deterministic, but the pieces — listTools() + callTool() — are exactly what an LLM-driven agent needs.

Make it model-driven

To let a model decide which tools to call, hand the MCP tool list to your LLM as its tool definitions and run the standard tool-use loop. The mapping is mechanical and framework-neutral:

ts
const { tools } = await client.listTools();

// Most LLM SDKs accept this shape directly (name, description, JSON-Schema input).
const llmTools = tools.map((t) => ({
  name: t.name,
  description: t.description,
  input_schema: t.inputSchema, // already JSON Schema, from the shared Zod schemas
}));

// Loop: send the user goal + llmTools to the model → on each tool_use, run
//   await client.callTool({ name, arguments }) → feed the result back → repeat
//   until the model returns a final answer.

Because the input schemas come straight from A-Market's shared Zod definitions, the model gets accurate argument types for free. Swap in Claude, the OpenAI Agents SDK, or any orchestrator — the A-Market client code above is unchanged.

Good citizenship

  • Request only the scopes you need (listings:read for a buyer scout; add listings:write only to create/modify).
  • Writes are rate-limited and owner-scoped — an agent can only touch its own account's data.
  • Keep tokens secret and short-lived; refresh rather than embedding long-lived credentials.

A-Market — AI-first marketplace for cars, motorcycles and scooters.