Skip to main content
Glama

system_import_account

Import a NEAR account into the local keystore using a private key or a JSON file. Allows subsequent tools to use the account for transactions.

Instructions

Import an account into the local keystore. This will allow the user to use this account with other tools. Remember mainnet accounts are created with a .near suffix, and testnet accounts are created with a .testnet suffix.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
argsYes

Implementation Reference

  • The tool 'system_import_account' is registered via mcp.tool() with its name, description, schema, and handler.
    mcp.tool(
      'system_import_account',
      noLeadingWhitespace`
      Import an account into the local keystore.
      This will allow the user to use this account with other tools.
      Remember mainnet accounts are created with a .near suffix,
      and testnet accounts are created with a .testnet suffix.`,
      {
        args: z.union([
          z.object({
            op: z.literal('import_from_private_key'),
            accountId: z.string(),
            networkId: z.enum(['testnet', 'mainnet']).default('mainnet'),
            privateKey: z
              .string()
              .describe(
                'The private key for the account. If provided, this will be used to import the account.',
              ),
          }),
          z.object({
            op: z.literal('import_from_file'),
            filePath: z.string().describe(
              noLeadingWhitespace`
                The path to the file containing the account id, public key, and private key.
                The file should be in JSON format and the filename should be something
                like \`<accountId>.<networkId>.json\`.`,
            ),
          }),
        ]),
      },
      async (args, _) => {
        switch (args.args.op) {
          case 'import_from_private_key':
            const connection = await connect({
              networkId: args.args.networkId,
              nodeUrl: getEndpointsByNetwork(args.args.networkId)[0]!,
            });
    
            const serializedPrivateKeyResult: Result<KeyPair, Error> = (() => {
              if (args.args.privateKey) {
                try {
                  return {
                    ok: true,
                    value: KeyPair.fromString(
                      args.args.privateKey as KeyPairString,
                    ),
                  };
                } catch (e) {
                  return { ok: false, error: new Error(e as string) };
                }
              }
              return { ok: false, error: new Error('No private key provided') };
            })();
            if (!serializedPrivateKeyResult.ok) {
              return {
                content: [
                  {
                    type: 'text',
                    text: `Failed to import private key. Error: ${serializedPrivateKeyResult.error}`,
                  },
                ],
              };
            }
    
            const accountResult: Result<Account, Error> = await getAccount(
              args.args.accountId,
              connection,
            );
            if (!accountResult.ok) {
              return {
                content: [
                  { type: 'text', text: `Error: ${accountResult.error}` },
                ],
              };
            }
            // at this point we know the account exists, so we can import the private key
            // ensure that the private key being imported matches a full access key
            const accessKeys = await accountResult.value.getAccessKeys();
            if (
              !accessKeys.some(
                (key) =>
                  key.public_key ===
                  serializedPrivateKeyResult.value.getPublicKey().toString(),
              )
            ) {
              return {
                content: [
                  {
                    type: 'text',
                    text: `Error: Account ${args.args.accountId} does not have matching access key for private key being imported.`,
                  },
                ],
              };
            }
    
            // we found a matching access key, so we can import the account
            await keystore.setKey(
              args.args.networkId,
              args.args.accountId,
              serializedPrivateKeyResult.value,
            );
    
            return {
              content: [
                {
                  type: 'text',
                  text: `Account imported: ${args.args.accountId}`,
                },
              ],
            };
          case 'import_from_file':
            const filePath = args.args.filePath;
            const readKeyFileResult: Result<[string, KeyPair], Error> =
              await (async () => {
                try {
                  return { ok: true, value: await readKeyFile(filePath) };
                } catch (e) {
                  return { ok: false, error: new Error(e as string) };
                }
              })();
            if (!readKeyFileResult.ok) {
              return {
                content: [
                  {
                    type: 'text',
                    text: `Error: ${readKeyFileResult.error}`,
                  },
                ],
              };
            }
            const [accountId, keypair] = readKeyFileResult.value;
    
            const networkIdResult: Result<string, Error> =
              getNetworkFromAccountId(accountId);
            if (!networkIdResult.ok) {
              return {
                content: [
                  {
                    type: 'text',
                    text: `Error: ${networkIdResult.error}`,
                  },
                ],
              };
            }
            const networkId = networkIdResult.value;
    
            const fromFileConnection = await connect({
              networkId,
              nodeUrl: getEndpointsByNetwork(networkId)[0]!,
            });
            const fromFileAccountResult: Result<Account, Error> =
              await getAccount(accountId, fromFileConnection);
            if (!fromFileAccountResult.ok) {
              return {
                content: [
                  {
                    type: 'text',
                    text: `Error: ${fromFileAccountResult.error}`,
                  },
                ],
              };
            }
    
            const fromFileAccessKeys =
              await fromFileAccountResult.value.getAccessKeys();
            if (
              !fromFileAccessKeys.some(
                (key) => key.public_key === keypair.getPublicKey().toString(),
              )
            ) {
              return {
                content: [
                  {
                    type: 'text',
                    text: `Error: Account ${accountId} does not have matching access key for private key being imported.`,
                  },
                ],
              };
            }
    
            await keystore.setKey(networkId, accountId, keypair);
            return {
              content: [
                {
                  type: 'text',
                  text: `Account imported: ${accountId}`,
                },
              ],
            };
          default:
            return {
              content: [{ type: 'text', text: 'Invalid operation' }],
            };
        }
      },
    );
  • Input schema for system_import_account: a discriminated union between 'import_from_private_key' (accountId, networkId, privateKey) and 'import_from_file' (filePath).
    {
      args: z.union([
        z.object({
          op: z.literal('import_from_private_key'),
          accountId: z.string(),
          networkId: z.enum(['testnet', 'mainnet']).default('mainnet'),
          privateKey: z
            .string()
            .describe(
              'The private key for the account. If provided, this will be used to import the account.',
            ),
        }),
        z.object({
          op: z.literal('import_from_file'),
          filePath: z.string().describe(
            noLeadingWhitespace`
              The path to the file containing the account id, public key, and private key.
              The file should be in JSON format and the filename should be something
              like \`<accountId>.<networkId>.json\`.`,
          ),
        }),
      ]),
    },
  • Handler function for system_import_account. Supports two modes: import_from_private_key (parses the private key, verifies it matches a full access key on-chain, then stores it in keystore) and import_from_file (reads a key file, validates the account exists and the key matches, then stores it).
      async (args, _) => {
        switch (args.args.op) {
          case 'import_from_private_key':
            const connection = await connect({
              networkId: args.args.networkId,
              nodeUrl: getEndpointsByNetwork(args.args.networkId)[0]!,
            });
    
            const serializedPrivateKeyResult: Result<KeyPair, Error> = (() => {
              if (args.args.privateKey) {
                try {
                  return {
                    ok: true,
                    value: KeyPair.fromString(
                      args.args.privateKey as KeyPairString,
                    ),
                  };
                } catch (e) {
                  return { ok: false, error: new Error(e as string) };
                }
              }
              return { ok: false, error: new Error('No private key provided') };
            })();
            if (!serializedPrivateKeyResult.ok) {
              return {
                content: [
                  {
                    type: 'text',
                    text: `Failed to import private key. Error: ${serializedPrivateKeyResult.error}`,
                  },
                ],
              };
            }
    
            const accountResult: Result<Account, Error> = await getAccount(
              args.args.accountId,
              connection,
            );
            if (!accountResult.ok) {
              return {
                content: [
                  { type: 'text', text: `Error: ${accountResult.error}` },
                ],
              };
            }
            // at this point we know the account exists, so we can import the private key
            // ensure that the private key being imported matches a full access key
            const accessKeys = await accountResult.value.getAccessKeys();
            if (
              !accessKeys.some(
                (key) =>
                  key.public_key ===
                  serializedPrivateKeyResult.value.getPublicKey().toString(),
              )
            ) {
              return {
                content: [
                  {
                    type: 'text',
                    text: `Error: Account ${args.args.accountId} does not have matching access key for private key being imported.`,
                  },
                ],
              };
            }
    
            // we found a matching access key, so we can import the account
            await keystore.setKey(
              args.args.networkId,
              args.args.accountId,
              serializedPrivateKeyResult.value,
            );
    
            return {
              content: [
                {
                  type: 'text',
                  text: `Account imported: ${args.args.accountId}`,
                },
              ],
            };
          case 'import_from_file':
            const filePath = args.args.filePath;
            const readKeyFileResult: Result<[string, KeyPair], Error> =
              await (async () => {
                try {
                  return { ok: true, value: await readKeyFile(filePath) };
                } catch (e) {
                  return { ok: false, error: new Error(e as string) };
                }
              })();
            if (!readKeyFileResult.ok) {
              return {
                content: [
                  {
                    type: 'text',
                    text: `Error: ${readKeyFileResult.error}`,
                  },
                ],
              };
            }
            const [accountId, keypair] = readKeyFileResult.value;
    
            const networkIdResult: Result<string, Error> =
              getNetworkFromAccountId(accountId);
            if (!networkIdResult.ok) {
              return {
                content: [
                  {
                    type: 'text',
                    text: `Error: ${networkIdResult.error}`,
                  },
                ],
              };
            }
            const networkId = networkIdResult.value;
    
            const fromFileConnection = await connect({
              networkId,
              nodeUrl: getEndpointsByNetwork(networkId)[0]!,
            });
            const fromFileAccountResult: Result<Account, Error> =
              await getAccount(accountId, fromFileConnection);
            if (!fromFileAccountResult.ok) {
              return {
                content: [
                  {
                    type: 'text',
                    text: `Error: ${fromFileAccountResult.error}`,
                  },
                ],
              };
            }
    
            const fromFileAccessKeys =
              await fromFileAccountResult.value.getAccessKeys();
            if (
              !fromFileAccessKeys.some(
                (key) => key.public_key === keypair.getPublicKey().toString(),
              )
            ) {
              return {
                content: [
                  {
                    type: 'text',
                    text: `Error: Account ${accountId} does not have matching access key for private key being imported.`,
                  },
                ],
              };
            }
    
            await keystore.setKey(networkId, accountId, keypair);
            return {
              content: [
                {
                  type: 'text',
                  text: `Account imported: ${accountId}`,
                },
              ],
            };
          default:
            return {
              content: [{ type: 'text', text: 'Invalid operation' }],
            };
        }
      },
    );
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must cover behavioral traits. It mentions keystore import and suffix conventions, but does not disclose side effects (e.g., overwrite behavior), error conditions, or auth/permissions needed. The description is insufficient for a mutation tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise with three short sentences. Every sentence adds unique value: purpose, usage context, and a naming tip. No redundant or irrelevant information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite the concise description, it lacks important details such as return values, error handling, the two distinct import methods, and prerequisites (e.g., keystore existence). The input schema complexity is not addressed in the description, making it incomplete for an agent to use correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already describes privateKey and filePath. The tool description adds value by explaining the .near/.testnet suffix convention for accountId, which is undocumented in the schema. However, it does not describe the 'op' parameter or the structure of the 'args' object, so schema description coverage is low.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'Import an account into the local keystore', which is a specific verb and resource. It distinguishes the tool from siblings like system_list_local_keypairs and system_remove_local_account by its import function.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides a context hint ('This will allow the user to use this account with other tools') and a naming convention tip for account IDs. However, it lacks guidance on when to use import_from_private_key vs import_from_file, and does not mention prerequisites or when not to use the tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nearai/near-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server