Skip to content

TypeScript SDK

MIT licensed, zero runtime dependencies. @modelcontextprotocol/sdk is an optional peer, needed only for createTransport.

Terminal window
pnpm add @protogrid/sdk @modelcontextprotocol/sdk
import { createClient } from "@protogrid/sdk";
const registry = createClient({ baseUrl: "https://api.protogrid.dev" }); // apiKey (default: PROTOGRID_API_KEY), fetch, timeoutMs optional
const hits = await registry.search({ q: "send an email", class: ["R0", "R1"], min_trust: 60 });
const descriptor = await registry.getServer(hits.results[0].name);
const tools = await registry.listAllTools(hits.results[0].name);
const conn = await registry.getConnection(hits.results[0].name); // mcpServers block
const cli = await registry.getConnection(hits.results[0].name, "claude-code-cli");

Errors are ProtogridError with status, code (not_found, blocked, deleted, no_connection, bad_request, http_429 …), body and retryAfterMs.

check(url) probes any remote MCP server URL once, listed or not, and returns its quality checks and its readiness for the Claude and OpenAI directories (see Check your server). It waits up to 90 seconds by default (waitMs); a check still running then can be read later with getCheck(id).

const c = await registry.check("https://mcp.example.com/mcp");
if (c.status === "done") {
for (const d of c.result!.readiness) console.log(d.name, d.summary.blockers, "blockers");
console.log("quality", c.result!.quality.score, c.page);
}

A refused URL throws invalid_url; an exhausted hourly allowance throws check_quota_exceeded with retryAfterMs.

Connection blocks carry ${NAME} placeholders. The SDK fills them at the last moment from a map you provide; the registry never sees values.

import { placeholdersIn, substituteSecrets, secretsSatisfied } from "@protogrid/sdk";
placeholdersIn(conn.connection); // ["AGENTDM_TOKEN"]
secretsSatisfied(conn, process.env); // false until AGENTDM_TOKEN is set
substituteSecrets(conn.connection, process.env); // deep copy; throws MissingSecretsError listing what is missing
import { createTransport, findConnectable } from "@protogrid/sdk";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
const found = await findConnectable(registry, { q: "get the weather for a city" }, { secrets: process.env });
// first hit that is R0, or R1 with every secret present; L0 only with allowLocal: true
if (found) {
const mcp = new Client({ name: "my-agent", version: "1.0.0" });
await mcp.connect(await createTransport(found.connection, process.env));
const { tools } = await mcp.listTools();
const out = await mcp.callTool({ name: tools[0].name, arguments: { city: "Madrid" } });
}

createTransport builds a streamable-HTTP, SSE or stdio transport from the mcpServers entry. Bundles (.mcpb) have no transport.

R2 servers sit behind OAuth 2. The agent holds the tokens; the registry only exposes the metadata. The SDK supplies the official MCP SDK’s OAuthClientProvider from a pluggable TokenStore and runs the single human step, the one-time consent:

import { connectClient, createClient, FileTokenStore, loopbackConsent } from "@protogrid/sdk";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
const store = new FileTokenStore(".mcp-tokens.json"); // or MemoryTokenStore, or your own
const consent = await loopbackConsent({ port: 8765 }); // prints the URL a human must open once
const conn = await registry.getConnection("ac.snag/snag");
const mcp = new Client({ name: "my-agent", version: "1.0.0" });
await connectClient(mcp, conn, process.env, { oauth: { store, consent, clientName: "my agent" } });
  • First run: protected-resource and authorization-server discovery, dynamic client registration (or a Client ID Metadata Document when you pass clientMetadataUrl and the server supports it), PKCE, the consent URL, then the code exchange. Tokens land in the store.
  • Every later run: connectClient finds the tokens, refreshes when needed, and connects with no human step. findConnectable(..., { tokenStore }) then counts that R2 server as connectable.
  • Headless agents use manualConsent({ redirectUrl, onAuthorizationUrl, waitForCode }) to relay the URL and the code through whatever channel they have.
  • TokenStore is three async methods (get, set, delete); keys are <server name>:<tokens|client|verifier|state>.

toClaudeAgentSdk(conn, secrets) returns the mcpServers map the Claude Agent SDK’s query() options take (http, sse or stdio entries). toRawTransport(conn, secrets) returns the parameters for hand-built official-SDK transports.

Every wire shape is exported: SearchResponse, Descriptor, Connectability, DescriptorRemote, DescriptorPackage, Trust, ListToolsResponse, ConnectionResponse, McpServersConnection, ConnectionClass, ConnectionTarget, TrustFlag.

The repository ships sdks/typescript/examples/find-and-call.ts: search, pick an autonomous server, connect, list tools, call one, print the result.

Terminal window
pnpm --filter @protogrid/sdk example "get the current weather for a city" "Madrid"
npx tsx sdks/typescript/examples/oauth-consent.ts ac.snag/snag # R2: consent once
# both use the public registry; prefix with REGISTRY_URL=http://localhost:8080 for a local stack