도구 호출, 리소스 읽기, 프롬프트 가져오기

도구 호출, 리소스 읽기, 프롬프트 가져오기 (Call tools, read resources, get prompts)

이 페이지의 모든 코드 블록은 연결된 Client 위에서 실행돼요. 연결을 어떻게 맺는지는 서버에 연결하기 에서 다루고, 여기서는 메모리 안에서 도구 세 개, 리소스 하나, 프롬프트 하나를 등록한 orders 서버와 짝을 지어 확인해 볼게요.

도구 목록을 보고 하나 호출하기

listTools 는 서버가 광고하는 도구를 반환하고, callTool 은 평범한 arguments 객체로 이름이 있는 도구 하나를 호출합니다.

const { tools } = await client.listTools();
console.log(tools.map(tool => tool.name));

const result = await client.callTool({ name: 'lookup-order', arguments: { id: 'A-1041' } });
console.log(result.content);

result.content 는 도구 핸들러가 돌려준 content 배열을 그대로 담고 있어요.

[ 'lookup-order', 'order-total', 'export-orders' ]
[ { type: 'text', text: 'A-1041: 3 items, shipped' } ]

::: tip 실패한 도구 호출도 여전히 결과예요. content 를 믿기 전에 isError 를 확인하세요. 입력 스키마가 거부한 인자도 같은 방식으로 돌아옵니다. 오직 프로토콜 수준의 실패 — 알 수 없는 도구, 타임아웃 — 만이 throw 돼요. :::

SDK가 페이지를 다 돌게 맡기세요

listTools() 는 이미 모든 페이지를 다 돌았어요. 서버가 목록을 나눠서 주면 SDK가 nextCursor 를 페이지별로 따라가며, nextCursor 가 없는 하나의 합친 목록을 돌려줍니다. listPrompts(), listResources(), listResourceTemplates() 도 같은 방식으로 합쳐요.

cursor — 여러분 애플리케이션이 잡아 둔 어떤 페이지의 nextCursor — 를 넘기면 listTools 는 정확히 그 페이지만 날것 그대로 돌려줍니다.

const page = await client.listTools({ cursor: heldCursor });
console.log(
    page.tools.map(tool => tool.name),
    page.nextCursor
);

orders 서버는 도구 세 개를 페이지당 두 개씩 나눠 주고, heldCursor 가 두 번째 페이지를 가리켜요 — 도구 하나, 뒤따라갈 것 없음.

[ 'export-orders' ] undefined

::: warning ClientOptions.listMaxPages (기본 64)가 합치기 순회의 상한이에요. 페이지네이션이 끝나지 않는 서버는 코드가 LIST_PAGINATION_EXCEEDEDSdkError 로 호출을 거부합니다. listMaxPages: 0 으로 상한을 없앨 수 있고, 명시적인 cursor 호출은 절대 상한에 걸리지 않아요. :::

구조화된 출력 읽기

outputSchema 를 선언한 도구는 content 옆에 structuredContent 를 돌려줍니다. 이것의 타입은 unknown 이라, 존재하는지 확인하고 쓰기 전에 좁혀야 해요.

const details = await client.callTool({ name: 'order-total', arguments: { id: 'A-1041' } });

const total: unknown = details.structuredContent;
if (typeof total === 'object' && total !== null && 'currency' in total) {
    console.log(total);
}

order-total{ id, total, currency } 를 선언하고, 그 값이 그대로 돌아옵니다.

{ id: 'A-1041', total: 61.5, currency: 'EUR' }

이전 listTools() 가 클라이언트에게 도구의 outputSchema 를 주었다면, callTool 은 그에 맞춰 structuredContent 를 검증하고 어긋나는 결과를 거부합니다.

구조화 결과의 와이어 인코딩은 프로토콜 시대에 따라 달라요 — 프로토콜 버전 을 보세요.

리소스 읽기

listResources 는 서버가 노출하는 것을 이름으로 보여 주고, readResource 는 URI 하나를 가져옵니다.

const { resources } = await client.listResources();
console.log(resources.map(resource => resource.uri));

const { contents } = await client.readResource({ uri: 'orders://recent' });
console.log(contents[0]);

contents 의 각 항목은 urimimeType, 그리고 text 또는 base64 blob 을 담아요.

[ 'orders://recent' ]
{
  uri: 'orders://recent',
  mimeType: 'application/json',
  text: '["A-1041","A-1042"]'
}

파라미터가 있는 URI라면 listResourceTemplates() 가 서버의 URI 템플릿을 돌려줘요. 그중 하나를 펼쳐서 나온 URI를 readResource 에 넘기면 됩니다. 리소스가 바뀌었을 때 타이머로 다시 읽는 대신 반응하고 싶다면 구독 을 보세요.

프롬프트 가져오기

listPrompts 는 각 프롬프트를 인자와 함께 광고하고, getPrompt 는 인자를 채워 넣어 모델에 보낼 준비가 된 messages 를 돌려줍니다.

const { prompts } = await client.listPrompts();
console.log(prompts.map(prompt => prompt.name));

const prompt = await client.getPrompt({ name: 'summarize-order', arguments: { id: 'A-1041', tone: 'terse' } });
console.log(prompt.messages);

서버의 템플릿이 두 인자가 모두 치환된 채로 돌아옵니다.

[ 'summarize-order' ]
[
  {
    role: 'user',
    content: {
      type: 'text',
      text: 'Write a terse status update for order A-1041.'
    }
  }
]

인자 자동완성

complete 는 사용자가 인자를 입력하는 동안 서버에 제안을 요청해요. ref 는 프롬프트(또는 리소스 템플릿)를 가리키고, argument 는 부분 입력값을 담습니다.

const { completion } = await client.complete({
    ref: { type: 'ref/prompt', name: 'summarize-order' },
    argument: { name: 'tone', value: 'f' }
});
console.log(completion.values);

서버가 tone 에 대해 받아들이는 값들과 f 를 대조합니다.

[ 'formal', 'friendly' ]

긴 호출의 진행 상황 추적하기

모든 동작은 두 번째 인자로 요청 옵션을 받아요. onprogress 는 서버가 이 호출에 대해 내보내는 각 notifications/progress 를 받고, resetTimeoutOnProgress 는 업데이트가 올 때마다 요청 타임아웃을 다시 시작하며, maxTotalTimeout 은 절대 상한입니다.

const exported = await client.callTool(
    { name: 'export-orders', arguments: { format: 'csv' } },
    {
        onprogress: update => console.log(update),
        resetTimeoutOnProgress: true,
        maxTotalTimeout: 600_000
    }
);
console.log(exported.content);

호출이 진행 중인 동안에도 업데이트가 흘러들어 오고, 반환 타입은 바뀌지 않아요.

{ progress: 1, total: 2, message: 'exported A-1041' }
{ progress: 2, total: 2, message: 'exported A-1042' }
[ { type: 'text', text: '2 orders exported as csv' } ]

연결 확인하기

pingping 요청을 보내고 서버가 돌려주는 빈 결과로 resolve 돼요. SDK는 양쪽 모두에서 ping 에 자동으로 응답하므로, 어느 쪽도 핸들러를 등록하지 않습니다.

const pong = await client.ping({ timeout: 5000 });
console.log(pong);

orders 서버는 즉시 응답합니다.

{}

응답을 멈춘 서버는 timeout 이 지나면 코드가 REQUEST_TIMEOUTSdkError 로 호출을 거부해요.

ping 은 2025 시대 메서드예요 — 프로토콜 버전 을 보세요.

요약

  • listTools, listResources, listResourceTemplates, listPrompts 는 모든 페이지를 합치고, { cursor } 로 단일 원본 페이지를 가져오며, listMaxPages 가 순회를 제한해요.
  • callTool 은 모델용 content 를, 도구가 outputSchema 를 선언했다면 애플리케이션용 structuredContent 를 돌려줍니다.
  • readResource({ uri })getPrompt({ name, arguments }) 는 도구와 같은 목록-후-가져오기 모양을 따릅니다.
  • complete() 는 프롬프트나 리소스 템플릿 인자의 서버 제안을 돌려줘요.
  • 요청 옵션의 onprogress 가 호출의 반환 타입을 바꾸지 않고 진행 업데이트를 흘려보냅니다.
  • ping() 은 서버가 여전히 응답하는지 확인하고, 양쪽 모두 ping에 자동으로 답해요.