# @github/mcp-registry

TypeScript SDK for the [MCP Registry](https://github.com/mcp) — includes a registry API client, generated types, and deterministic fingerprint computation for enterprise allowlist enforcement.

## Installation

```bash
npm install @github/mcp-registry
```

## Registry Client

```typescript
import { RegistryClient } from '@github/mcp-registry';

const client = new RegistryClient();

// Search for servers
const servers = await client.searchServers({ query: 'github', limit: 5 });
for (const s of servers) {
  console.log(`${s.server.name}@${s.server.version}`);
}

// Get a specific server
const server = await client.getServer({ name: 'io.github.user/repo' });

// Get a specific version
const pinned = await client.getServer({
  name: 'io.github.user/repo',
  version: '1.2.0',
});

// Parse "name@version" format
const entry = await client.getDsl('io.github.user/repo@1.2.0');
```

### Configuration

```typescript
const client = new RegistryClient({
  baseUrl: 'https://api.mcp.github.com/v0.1', // default
  cacheTtlMs: 5 * 60 * 1000,                  // 5 minutes (default)
  timeoutMs: 30_000,                           // 30 seconds (default)
  fetch: customFetch,                          // optional custom fetch
});
```

### Error Handling

The client returns `null` for 404s and throws `RegistryError` for other HTTP errors:

```typescript
import { RegistryClient, RegistryError } from '@github/mcp-registry';

const server = await client.getServer({ name: 'example/server' });
if (!server) {
  console.log('Not found');
}
```

## Fingerprint Computation

Deterministic SHA-256 fingerprints for enterprise allowlist enforcement — computes the same fingerprints as the registry backend.

```typescript
import { computeFingerprint, verifyFingerprint, commandToRegistryType } from '@github/mcp-registry';

// Remote server
const fp = computeFingerprint({
  remotes: [{ url: 'https://mcp.example.com/sse' }],
});

// Local package
const registryType = commandToRegistryType('npx'); // → "npm"
const fp2 = computeFingerprint({
  packages: [{ registryType: registryType!, identifier: '@playwright/mcp' }],
});

// Verify against the enterprise allowlist
const matches = verifyFingerprint(
  { packages: [{ registryType: 'npm', identifier: '@playwright/mcp' }] },
  allowlistEntry.server.fingerprints ?? {},
);
```

### Command Mapping

| Command | Registry Type |
| ------- | ------------- |
| `npx`, `npm` | `npm` |
| `uvx`, `pip` | `pypi` |
| `dotnet` | `nuget` |
| `docker` | `docker` |
| `brew` | `homebrew` |

## DSL Engine

The DSL engine extracts configurable fields from server definitions, validates user-provided configuration, and computes launch plans (the command + env + headers needed to start a server).

### Quick Start — Registry Client + DSL Engine

```typescript
import { RegistryClient, DslEngine } from '@github/mcp-registry';

// 1. Fetch a server definition from the registry
const client = new RegistryClient();
const response = await client.getServer({ name: 'io.github.user/repo' });
if (!response) throw new Error('Server not found');

// 2. Create a DSL engine from the response
const engine = DslEngine.fromServerResponse(response);

// 3. Discover what configuration the server needs
const requiredFields = engine.getRequiredFields();
for (const field of requiredFields) {
  console.log(`${field.key} (${field.type})${field.isSecret ? ' [secret]' : ''}`);
}

// 4. Validate user-provided configuration
const validation = engine.validate({
  environmentVariables: { API_KEY: 'sk-...' },
});
if (!validation.valid) {
  console.log('Missing:', validation.missingFields.map(f => f.key));
}

// 5. Resolve a launch plan
const result = engine.resolve({
  environmentVariables: { API_KEY: 'sk-...' },
});

if (result.complete && result.plan) {
  if (result.plan.type === 'package') {
    // Start a local process
    console.log('Command:', result.plan.command.join(' '));
    console.log('Env:', result.plan.env);
  } else {
    // Connect to a remote server
    console.log('Endpoint:', result.plan.endpoint);
    console.log('Headers:', result.plan.headers);
  }
}
```

### Extracting Fields

Use `extractConfigurableFields` directly when you don't need the full engine:

```typescript
import { extractConfigurableFields } from '@github/mcp-registry';
import type { ServerDetail } from '@github/mcp-registry';

const fields = extractConfigurableFields(server);
// fields: FieldSpec[] — key, type, required, isSecret, enumValues, etc.

// Extract fields for a specific remote instead of the default package
const remoteFields = extractConfigurableFields(server, { remoteIndex: 0 });
```

### Resolving Launch Plans

```typescript
import { resolveLaunchPlan } from '@github/mcp-registry';

// Package-based (local process)
const plan = resolveLaunchPlan({
  spec: server,
  config: { environmentVariables: { API_KEY: 'sk-...' } },
});
// plan.type === 'package'
// plan.command → ['npx', '-y', '@scope/mcp-server@1.0.0']
// plan.env → { API_KEY: 'sk-...' }

// Remote-based (HTTP connection)
const remotePlan = resolveLaunchPlan({
  spec: server,
  config: { headers: { Authorization: 'Bearer token' } },
  remoteIndex: 0,
});
// remotePlan.type === 'remote'
// remotePlan.endpoint → 'https://api.example.com/mcp'
// remotePlan.headers → { Authorization: 'Bearer token' }
```

### Enumerating Installation Choices

Use one engine call to inspect every advertised package and remote without
constructing an engine for each array index:

```typescript
const result = engine.enumerateInstallChoices(
  { headers: { Authorization: 'Bearer ...' } },
  {
    policy: choice => choice.transport === 'sse'
      ? { decision: 'denied', code: 'sse-disabled', reason: 'SSE is disabled by policy' }
      : { decision: 'allowed' },
  },
);

// Packages first, then remotes, preserving each source array's order.
for (const choice of result.advertisedChoices) {
  console.log(choice.choiceId, choice.transport, choice.validation, choice.policy);
  if (choice.eligible) {
    console.log(choice.configurableFields, choice.launchPlan);
  } else {
    console.log(choice.ineligibility);
  }
}

console.log(result.eligibleChoices);
```

The exact method signature is:

```typescript
enumerateInstallChoices(
  config?: ConfigValues,
  options?: EnumerateInstallChoicesOptions,
): InstallChoicesResult
```

Choice IDs are deterministic for a descriptor: `package:<index>` and
`remote:<index>`. Supply remote URL template values through
`ConfigValues.urlVariables`. Configuration errors do not hide structurally
eligible choices, so callers can present required fields while retaining the
inert launch plan. Invalid, unsupported, and policy-denied advertisements
remain in `advertisedChoices` with typed explanations.

## License

MIT
