#
tokens: 6282/50000 19/19 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── LICENSE
├── NOTES.md
├── package.json
├── README.md
├── screenshot.png
├── scripts
│   └── create-service-account.js
├── src
│   ├── auth.ts
│   ├── config.ts
│   ├── server.ts
│   └── tools
│       ├── common.ts
│       ├── get-accounts.ts
│       ├── get-folder-contents.ts
│       ├── get-issue-comments.ts
│       ├── get-issue-root-causes.ts
│       ├── get-issue-types.ts
│       ├── get-issues.ts
│       ├── get-item-versions.ts
│       ├── get-projects.ts
│       └── index.ts
├── tsconfig.json
└── yarn.lock
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
node_modules
build
.env
*.log
.DS_Store
Thumbs.db
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
> IMPORTANT: This project has been moved to https://github.com/autodesk-platform-services/aps-mcp-server-nodejs.

---

<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

# aps-mcp-server

Experimental [Model Context Protocol](https://modelcontextprotocol.io) server build with Node.js, providing access to [Autodesk Platform Services](https://aps.autodesk.com) API, with fine-grained access control using the new _Secure Service Accounts_ feature.

![Screenshot](screenshot.png)

[YouTube Video](https://youtu.be/6DRSR9HlIds)

## Development

### Prerequisites

- [Node.js](https://nodejs.org)
- [APS app credentials](https://aps.autodesk.com/en/docs/oauth/v2/tutorials/create-app) (must be a _Server-to-Server_ application type)
- [Provisioned access to ACC or BIM360](https://get-started.aps.autodesk.com/#provision-access-in-other-products)

### Setup

#### Server

- Clone this repository
- Install dependencies: `yarn install`
- Build the TypeScript code: `yarn run build`
- Create a _.env_ file in the root folder of this project, and add your APS credentials:
    - `APS_CLIENT_ID` - your APS application client ID
    - `APS_CLIENT_SECRET` - your APS application client secret
- Create a new service account: `npx create-service-account <username> <first name> <last name>`, for example, `npx create-service-account ssa-test-user John Doe`
    - This script will output a bunch of environment variables with information about the new account:
        - `APS_SA_ID` -  your service account ID
        - `APS_SA_EMAIL` - your service account email
        - `APS_SA_KEY_ID` - your service account key ID
        - `APS_SA_PRIVATE_KEY` - your service account private key
- Add these environment variables to your _.env_ file

#### Autodesk Construction Cloud

- Register your APS application client ID as a custom integration
- Invite the service account email as a new member to your ACC project(s)

### Use with Inspector

- Run the [Model Context Protocol Inspector](https://modelcontextprotocol.io/docs/tools/inspector): `yarn run inspect`
- Open http://localhost:5173
- Hit `Connect` to start this MCP server and connect to it

### Use with Claude Desktop

- Make sure you have [Claude Desktop](https://claude.ai/download) installed
- Create a Claude Desktop config file if you don't have one yet:
    - On macOS: _~/Library/Application Support/Claude/claude\_desktop\_config.json_
    - On Windows: _%APPDATA%\Claude\claude\_desktop\_config.json_
- Add this MCP server to the config, using the absolute path of the _build/server.js_ file on your system, for example:
```json
{
    "mcpServers": {
        "autodesk-platform-services": {
            "command": "node",
            "args": [
                "/absolute/path/to/aps-mcp-server/build/server.js"
            ]
        }
    }
}
```
- Open Claude Desktop, and try some of the following test prompt:
    - What ACC projects do I have access to?
    - Give me a visual dashboard of all issues in project XYZ

> For more details on how to add MCP servers to Claude Desktop, see the [official documentation](https://modelcontextprotocol.io/quickstart/user).

### Use with Visual Studio Code & Copilot

- Make sure you have [enabled MCP servers in Visual Studio Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_enable-mcp-support-in-vs-code)
- Create _.vscode/mcp.json_ file in your workspace, and add the following JSON to it:

```json
{
    "servers": {
        "Autodesk Platform Services": {
            "type": "stdio",
            "command": "node",
            "args": [
                "/absolute/path/to/aps-mcp-server/build/server.js"
            ]
        }
    }
}
```

> For more details on how to add MCP servers to Visual Studio Code, see the [documentation](https://code.visualstudio.com/docs/copilot/chat/mcp-servers)

### Use with Cursor

- Create _.cursor/mcp.json_ file in your workspace, and add the following JSON to it:

```json
{
  "mcpServers": {
    "Autodesk Platform Services": {
      "command": "node",
      "args": [
        "/Users/brozp/Code/Temp/aps-mcp-server-node/build/server.js"
      ]
    }
  }
}
```

> For more details on how to add MCP servers to Cursor, see the [documentation](https://docs.cursor.com/context/model-context-protocol)

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
    "compilerOptions": {
        "target": "ES2022",
        "module": "Node16",
        "moduleResolution": "Node16",
        "outDir": "./build",
        "rootDir": "./src",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true
    },
    "include": [
        "src/**/*"
    ],
    "exclude": [
        "node_modules"
    ]
}
```

--------------------------------------------------------------------------------
/src/tools/index.ts:
--------------------------------------------------------------------------------

```typescript
export { getAccounts } from "./get-accounts.js";
export { getProjects } from "./get-projects.js";
export { getFolderContents } from "./get-folder-contents.js";
export { getItemVersions } from "./get-item-versions.js";
export { getIssues } from "./get-issues.js";
export { getIssueTypes } from "./get-issue-types.js";
export { getIssueRootCauses } from "./get-issue-root-causes.js";
export { getIssueComments } from "./get-issue-comments.js";
```

--------------------------------------------------------------------------------
/NOTES.md:
--------------------------------------------------------------------------------

```markdown
# TODO

## Authentication

Currently there's a [draft proposal](https://spec.modelcontextprotocol.io/specification/draft/basic/authorization/#22-basic-oauth-21-authorization) for adding OAuth 2.0 to Model Context Protocol. For now we'll use Secure Service Accounts.

## Fetch

For some reason, Claude Desktop is not able to use this server when it uses the built-in [fetch](https://nodejs.org/dist/latest-v22.x/docs/api/globals.html#fetch) method, perhaps because it's using an older version of Node.js. For now we're using the [node-fetch](https://www.npmjs.com/package/node-fetch) NPM module.
```

--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------

```typescript
import path from "node:path";
import url from "node:url";
import dotenv from "dotenv";

const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
dotenv.config({ path: path.resolve(__dirname, "..", ".env") });
const { APS_CLIENT_ID, APS_CLIENT_SECRET, APS_SA_ID, APS_SA_EMAIL, APS_SA_KEY_ID } = process.env;
const APS_SA_PRIVATE_KEY = process.env.APS_SA_PRIVATE_KEY ? Buffer.from(process.env.APS_SA_PRIVATE_KEY, "base64").toString("utf-8") : undefined;

export {
    APS_CLIENT_ID,
    APS_CLIENT_SECRET,
    APS_SA_ID,
    APS_SA_EMAIL,
    APS_SA_KEY_ID,
    APS_SA_PRIVATE_KEY
}
```

--------------------------------------------------------------------------------
/src/tools/get-accounts.ts:
--------------------------------------------------------------------------------

```typescript
import { DataManagementClient } from "@aps_sdk/data-management";
import { getAccessToken } from "./common.js";
import type { Tool } from "./common.js";

const schema = {};

export const getAccounts: Tool<typeof schema> = {
    title: "get-accounts",
    description: "List all available Autodesk Construction Cloud accounts",
    schema,
    callback: async () => {
        const accessToken = await getAccessToken(["data:read"]);
        const dataManagementClient = new DataManagementClient();
        const hubs = await dataManagementClient.getHubs({ accessToken });
        if (!hubs.data) {
            throw new Error("No accounts found");
        }
        return {
            content: hubs.data.map((hub) => ({
                type: "text",
                text: JSON.stringify({ id: hub.id, name: hub.attributes?.name })
            }))
        };
    }
};
```

--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import * as tools from "./tools/index.js";
import { APS_CLIENT_ID, APS_CLIENT_SECRET, APS_SA_ID, APS_SA_EMAIL, APS_SA_KEY_ID, APS_SA_PRIVATE_KEY } from "./config.js";

if (!APS_CLIENT_ID || !APS_CLIENT_SECRET || !APS_SA_ID || !APS_SA_EMAIL || !APS_SA_KEY_ID || !APS_SA_PRIVATE_KEY) {
    console.error("Missing one or more required environment variables: APS_CLIENT_ID, APS_CLIENT_SECRET, APS_SA_ID, APS_SA_EMAIL, APS_SA_KEY_ID, APS_SA_PRIVATE_KEY");
    process.exit(1);
}

const server = new McpServer({ name: "autodesk-platform-services", version: "0.0.1" });
for (const tool of Object.values(tools)) {
    server.tool(tool.title, tool.description, tool.schema, tool.callback);
}

try {
    await server.connect(new StdioServerTransport());
} catch (err) {
    console.error("Server error:", err);
}
```

--------------------------------------------------------------------------------
/src/tools/get-item-versions.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { DataManagementClient } from "@aps_sdk/data-management";
import { getAccessToken } from "./common.js";
import type { Tool } from "./common.js";

const schema = {
    projectId: z.string().nonempty(),
    itemId: z.string().nonempty()
};

export const getItemVersions: Tool<typeof schema> = {
    title: "get-item-versions",
    description: "List all versions of a document in Autodesk Construction Cloud",
    schema,
    callback: async ({ projectId, itemId }) => {
        // TODO: add pagination support
        const accessToken = await getAccessToken(["data:read"]);
        const dataManagementClient = new DataManagementClient();
        const versions = await dataManagementClient.getItemVersions(projectId, itemId, { accessToken });
        if (!versions.data) {
            throw new Error("No versions found");
        }
        return {
            content: versions.data.map((version) => ({ type: "text", text: JSON.stringify(version) }))
        };
    }
};
```

--------------------------------------------------------------------------------
/src/tools/get-issues.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { IssuesClient } from "@aps_sdk/construction-issues";
import { getAccessToken } from "./common.js";
import type { Tool } from "./common.js";

const schema = {
    projectId: z.string().nonempty()
};

export const getIssues: Tool<typeof schema> = {
    title: "get-issues",
    description: "List all available projects in an Autodesk Construction Cloud account",
    schema,
    callback: async ({ projectId }) => {
        // TODO: add pagination support
        const accessToken = await getAccessToken(["data:read"]);
        const issuesClient = new IssuesClient();
        projectId = projectId.replace("b.", ""); // the projectId should not contain the "b." prefix
        const issues = await issuesClient.getIssues(projectId, { accessToken });
        if (!issues.results) {
            throw new Error("No issues found");
        }
        return {
            content: issues.results.map((issue) => ({ type: "text", text: JSON.stringify(issue) }))
        };
    }
};
```

--------------------------------------------------------------------------------
/src/tools/get-issue-types.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { IssuesClient } from "@aps_sdk/construction-issues";
import { getAccessToken } from "./common.js";
import type { Tool } from "./common.js";

const schema = {
    projectId: z.string().nonempty()
};

export const getIssueTypes: Tool<typeof schema> = {
    title: "get-issue-types",
    description: "List all issue types in an Autodesk Construction Cloud project",
    schema,
    callback: async ({ projectId }) => {
        // TODO: add pagination support
        const accessToken = await getAccessToken(["data:read"]);
        const issuesClient = new IssuesClient();
        projectId = projectId.replace("b.", ""); // the projectId should not contain the "b." prefix
        const issueTypes = await issuesClient.getIssuesTypes(projectId, { accessToken });
        if (!issueTypes.results) {
            throw new Error("No issue types found");
        }
        return {
            content: issueTypes.results.map((issue) => ({ type: "text", text: JSON.stringify(issue) }))
        };
    }
};
```

--------------------------------------------------------------------------------
/src/tools/get-projects.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { DataManagementClient } from "@aps_sdk/data-management";
import { getAccessToken } from "./common.js";
import type { Tool } from "./common.js";

const schema = {
    accountId: z.string().nonempty()
};

export const getProjects: Tool<typeof schema> = {
    title: "get-projects",
    description: "List all available projects in an Autodesk Construction Cloud account",
    schema,
    callback: async ({ accountId }) => {
        // TODO: add pagination support
        const accessToken = await getAccessToken(["data:read"]);
        const dataManagementClient = new DataManagementClient();
        const projects = await dataManagementClient.getHubProjects(accountId, { accessToken });
        if (!projects.data) {
            throw new Error("No projects found");
        }
        return {
            content: projects.data.map((project) => ({
                type: "text",
                text: JSON.stringify({ id: project.id, name: project.attributes?.name })
            }))
        };
    }
};
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "aps-mcp-server",
  "version": "0.0.1",
  "type": "module",
  "private": true,
  "description": "Experimental [Model Context Protocol](https://modelcontextprotocol.io) server providing access to [Autodesk Platform Services](https://aps.autodesk.com) API.",
  "author": "Petr Broz <[email protected]>",
  "bin": {
    "aps-mcp-server": "./build/server.js",
    "create-service-account": "./scripts/create-service-account.js"
  },
  "scripts": {
    "build": "tsc",
    "inspect": "mcp-inspector node ./build/server.js"
  },
  "files": [
    "build"
  ],
  "dependencies": {
    "@aps_sdk/authentication": "^1.0.0",
    "@aps_sdk/construction-issues": "^1.1.0",
    "@aps_sdk/data-management": "^1.0.1",
    "@modelcontextprotocol/sdk": "^1.7.0",
    "dotenv": "^16.4.7",
    "jsonwebtoken": "^9.0.2",
    "node-fetch": "^3.3.2",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@modelcontextprotocol/inspector": "^0.6.0",
    "@types/jsonwebtoken": "^9.0.9",
    "@types/node": "^22.13.10",
    "typescript": "^5.8.2"
  }
}

```

--------------------------------------------------------------------------------
/src/tools/get-issue-comments.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { IssuesClient } from "@aps_sdk/construction-issues";
import { getAccessToken } from "./common.js";
import type { Tool } from "./common.js";

const schema = {
    projectId: z.string().nonempty(),
    issueId: z.string().nonempty()
};

export const getIssueComments: Tool<typeof schema> = {
    title: "get-issue-comments",
    description: "Retrieves a list of comments associated with an issue in Autodesk Construction Cloud.",
    schema,
    callback: async ({ projectId, issueId }) => {
        // TODO: add pagination support
        const accessToken = await getAccessToken(["data:read"]);
        const issuesClient = new IssuesClient();
        projectId = projectId.replace("b.", ""); // the projectId should not contain the "b." prefix
        const comments = await issuesClient.getComments(projectId, issueId, { accessToken})
        if (!comments.results) {
            throw new Error("No comments found");
        }
        return {
            content: comments.results.map((comment) => ({ type: "text", text: JSON.stringify(comment) }))
        };
    }
};
```

--------------------------------------------------------------------------------
/src/tools/get-issue-root-causes.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { IssuesClient } from "@aps_sdk/construction-issues";
import { getAccessToken } from "./common.js";
import type { Tool } from "./common.js";

const schema = {
    projectId: z.string().nonempty()
};

export const getIssueRootCauses: Tool<typeof schema> = {
    title: "get-issue-root-causes",
    description: "Retrieves a list of supported root cause categories and root causes that you can allocate to an issue in Autodesk Construction Cloud.",
    schema,
    callback: async ({ projectId }) => {
        // TODO: add pagination support
        const accessToken = await getAccessToken(["data:read"]);
        const issuesClient = new IssuesClient();
        projectId = projectId.replace("b.", ""); // the projectId should not contain the "b." prefix
        const rootCauses = await issuesClient.getRootCauseCategories(projectId, { accessToken });
        if (!rootCauses.results) {
            throw new Error("No root causes found");
        }
        return {
            content: rootCauses.results.map((rootCause) => ({ type: "text", text: JSON.stringify(rootCause) }))
        };
    }
};
```

--------------------------------------------------------------------------------
/src/tools/common.ts:
--------------------------------------------------------------------------------

```typescript
import { ZodRawShape } from "zod";
import { ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js";
import { APS_CLIENT_ID, APS_CLIENT_SECRET, APS_SA_ID, APS_SA_KEY_ID, APS_SA_PRIVATE_KEY } from "../config.js";
import { getServiceAccountAccessToken } from "../auth.js";

export interface Tool<Args extends ZodRawShape> {
    title: string;
    description: string;
    schema: Args;
    callback: ToolCallback<Args>;
}

const credentialsCache = new Map<string, { accessToken: string, expiresAt: number }>();

export async function getAccessToken(scopes: string[]): Promise<string> {
    const cacheKey = scopes.join("+");
    let credentials = credentialsCache.get(cacheKey);
    if (!credentials || credentials.expiresAt < Date.now()) {
        const { access_token, expires_in } = await getServiceAccountAccessToken(APS_CLIENT_ID!, APS_CLIENT_SECRET!, APS_SA_ID!, APS_SA_KEY_ID!, APS_SA_PRIVATE_KEY!, scopes);
        credentials = {
            accessToken: access_token,
            expiresAt: Date.now() + expires_in * 1000
        };
        credentialsCache.set(cacheKey, credentials);
    }
    return credentials.accessToken;
}
```

--------------------------------------------------------------------------------
/src/tools/get-folder-contents.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { DataManagementClient } from "@aps_sdk/data-management";
import { getAccessToken } from "./common.js";
import type { Tool } from "./common.js";

const schema = {
    accountId: z.string().nonempty(),
    projectId: z.string().nonempty(),
    folderId: z.string().optional()
};

export const getFolderContents: Tool<typeof schema> = {
    title: "get-folder-contents",
    description: "List contents of a project or a specific subfolder in Autodesk Construction Cloud",
    schema,
    callback: async ({ accountId, projectId, folderId }) => {
        // TODO: add pagination support
        const accessToken = await getAccessToken(["data:read"]);
        const dataManagementClient = new DataManagementClient();
        const contents = folderId
            ? await dataManagementClient.getFolderContents(projectId, folderId, { accessToken })
            : await dataManagementClient.getProjectTopFolders(accountId, projectId, { accessToken });
        if (!contents.data) {
            throw new Error("No contents found");
        }
        return {
            content: contents.data.map((item) => ({ type: "text", text: JSON.stringify(item) }))
        };
    }
};
```

--------------------------------------------------------------------------------
/scripts/create-service-account.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node

import process from "node:process";
import { getClientCredentialsAccessToken, createServiceAccount, createServiceAccountPrivateKey } from "../build/auth.js";
import { APS_CLIENT_ID, APS_CLIENT_SECRET } from "../build/config.js";

if (!APS_CLIENT_ID || !APS_CLIENT_SECRET) {
    console.error("Please set the APS_CLIENT_ID and APS_CLIENT_SECRET environment variables.");
    process.exit(1);
}
const [,, userName, firstName, lastName] = process.argv;
if (!userName || !firstName || !lastName) {
    console.error("Usage: node create-service-account.js <userName> <firstName> <lastName>");
    console.error("Example: node create-service-account.js test-robot Rob Robot");
    process.exit(1);
}

try {
    const credentials = await getClientCredentialsAccessToken(APS_CLIENT_ID, APS_CLIENT_SECRET, ["application:service_account:write", "application:service_account_key:write"]);
    const { serviceAccountId, email } = await createServiceAccount(userName, firstName, lastName, credentials.access_token);
    const { kid, privateKey } = await createServiceAccountPrivateKey(serviceAccountId, credentials.access_token);
    console.log("Service account created successfully!");
    console.log("Invite the following user to your project:", email);
    console.log("Include the following environment variables to your application:");
    console.log(`APS_SA_ID="${serviceAccountId}"`);
    console.log(`APS_SA_EMAIL="${email}"`);
    console.log(`APS_SA_KEY_ID="${kid}"`);
    console.log(`APS_SA_PRIVATE_KEY="${Buffer.from(privateKey).toString("base64")}"`);
} catch (err) {
    console.error(err);
    process.exit(1);
}
```

--------------------------------------------------------------------------------
/src/auth.ts:
--------------------------------------------------------------------------------

```typescript
import fetch from "node-fetch";
import jwt from "jsonwebtoken";

interface Credentials {
    access_token: string;
    token_type: string;
    expires_in: number;
}

/**
 * Generates an access token for APS using specific grant type.
 *
 * @param clientId The client ID provided by Autodesk.
 * @param clientSecret The client secret provided by Autodesk.
 * @param grantType The grant type for the access token.
 * @param scopes An array of scopes for which the token is requested.
 * @param assertion The JWT assertion for the access token.
 * @returns A promise that resolves to the access token response object.
 * @throws If the request for the access token fails.
 */
async function getAccessToken(clientId: string, clientSecret: string, grantType: string, scopes: string[], assertion?: string): Promise<Credentials> {
    const headers = {
        "Accept": "application/json",
        "Authorization": `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`,
        "Content-Type": "application/x-www-form-urlencoded"
    };
    const body = new URLSearchParams({
        "grant_type": grantType,
        "scope": scopes.join(" ")
    });
    if (assertion) {
        body.append("assertion", assertion);
    }
    const response = await fetch("https://developer.api.autodesk.com/authentication/v2/token", { method: "POST", headers, body });
    if (!response.ok) {
        throw new Error(`Could not generate access token: ${await response.text()}`);
    }
    const credentials = await response.json() as Credentials;
    return credentials;
}

/**
 * Creates a JWT assertion for OAuth 2.0 authentication.
 *
 * @param clientId The client ID of the application.
 * @param serviceAccountId The service account ID.
 * @param serviceAccountKeyId The key ID of the service account.
 * @param serviceAccountPrivateKey The private key of the service account.
 * @param scopes The scopes for the access token.
 * @returns The signed JWT assertion.
 */
function createAssertion(clientId: string, serviceAccountId: string, serviceAccountKeyId: string, serviceAccountPrivateKey: string, scopes: string[]) {
    // TODO: validate inputs
    const payload = {
        iss: clientId,
        sub: serviceAccountId,
        aud: "https://developer.api.autodesk.com/authentication/v2/token",
        exp: Math.floor(Date.now() / 1000) + 300, // 5 minutes
        scope: scopes
    };
    const options = {
        algorithm: "RS256" as jwt.Algorithm,
        header: { alg: "RS256", kid: serviceAccountKeyId }
    };
    return jwt.sign(payload, serviceAccountPrivateKey, options);
}

/**
 * Generates an access token for APS using client credentials ("two-legged") flow.
 *
 * @param clientId The client ID provided by Autodesk.
 * @param clientSecret The client secret provided by Autodesk.
 * @param scopes An array of scopes for which the token is requested.
 * @returns A promise that resolves to the access token response object.
 * @throws If the request for the access token fails.
 */
export async function getClientCredentialsAccessToken(clientId: string, clientSecret: string, scopes: string[]) {
    return getAccessToken(clientId, clientSecret, "client_credentials", scopes);
}

/**
 * Retrieves an access token for a service account using client credentials and JWT assertion.
 *
 * @param clientId The client ID for the OAuth application.
 * @param clientSecret The client secret for the OAuth application.
 * @param serviceAccountId The ID of the service account.
 * @param serviceAccountKeyId The key ID of the service account.
 * @param serviceAccountPrivateKey The private key of the service account.
 * @param scopes An array of scopes for the access token.
 * @returns A promise that resolves to the access token response object.
 * @throws If the access token could not be retrieved.
 */
export async function getServiceAccountAccessToken(clientId: string, clientSecret: string, serviceAccountId: string, serviceAccountKeyId: string, serviceAccountPrivateKey: string, scopes: string[]) {
    const assertion = createAssertion(clientId, serviceAccountId, serviceAccountKeyId, serviceAccountPrivateKey, scopes);
    return getAccessToken(clientId, clientSecret, "urn:ietf:params:oauth:grant-type:jwt-bearer", scopes, assertion);
}

/**
 * Creates a new service account with the given name.
 *
 * @param name The name of the service account to create (must be between 5 and 64 characters long).
 * @param firstName The first name of the service account.
 * @param lastName The last name of the service account.
 * @param accessToken The access token for authentication.
 * @returns A promise that resolves to the created service account response.
 * @throws If the request to create the service account fails.
 */
export async function createServiceAccount(name: string, firstName: string, lastName: string, accessToken: string) {
    const headers = {
        "Accept": "application/json",
        "Authorization": `Bearer ${accessToken}`,
        "Content-Type": "application/json"
    };
    const body = JSON.stringify({ name, firstName, lastName });
    const response = await fetch("https://developer.api.autodesk.com/authentication/v2/service-accounts", { method: "POST", headers, body });
    if (!response.ok) {
        throw new Error(`Could not create service account: ${await response.text()}`);
    }
    return response.json();
}

/**
 * Creates a private key for a given service account.
 *
 * @param serviceAccountId - The ID of the service account for which to create a private key.
 * @param accessToken - The access token used for authorization.
 * @returns A promise that resolves to the private key details.
 * @throws If the request to create the private key fails.
 */
export async function createServiceAccountPrivateKey(serviceAccountId: string, accessToken: string) {
    const headers = {
        "Accept": "application/json",
        "Authorization": `Bearer ${accessToken}`
    };
    const response = await fetch(`https://developer.api.autodesk.com/authentication/v2/service-accounts/${serviceAccountId}/keys`, { method: "POST", headers });
    if (!response.ok) {
        throw new Error(`Could not create service account private key: ${await response.text()}`);
    }
    return response.json();
}
```