#!/usr/bin/env node
/**
* IT Glue MCP Server
*
* A Model Context Protocol server for IT Glue API integration.
* Provides tools for managing IT documentation including organizations,
* configurations, passwords, contacts, flexible assets, and more.
*
* Environment variables:
* - ITGLUE_API_KEY: Your IT Glue API key (required)
* - ITGLUE_REGION: API region - 'us', 'eu', or 'au' (default: 'us')
*/
import 'dotenv/config';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { ResponseFormat, JsonApiResponse, Organization, Configuration, Password, Contact, FlexibleAsset, FlexibleAssetType, FlexibleAssetField, Location, Domain, Expiration } from './types.js';
import { makeApiRequest, handleApiError, buildPaginationParams, buildFilterParams, buildSortParam, buildIncludeParam, checkPasswordAccess, getPasswordAccessStatus } from './services/api-client.js';
import { formatListResponse, formatSingleResponse, formatOrganizationMarkdown, formatConfigurationMarkdown, formatPasswordMarkdown, formatContactMarkdown, formatFlexibleAssetMarkdown, formatLocationMarkdown, formatDomainMarkdown, formatExpirationMarkdown, formatDate } from './services/formatters.js';
// Import schemas
import { ListOrganizationsSchema, GetOrganizationSchema, CreateOrganizationSchema, UpdateOrganizationSchema } from './schemas/organizations.js';
import { ListConfigurationsSchema, GetConfigurationSchema, CreateConfigurationSchema, UpdateConfigurationSchema } from './schemas/configurations.js';
import { ListPasswordsSchema, GetPasswordSchema, CreatePasswordSchema, UpdatePasswordSchema } from './schemas/passwords.js';
import { ListContactsSchema, GetContactSchema, CreateContactSchema, UpdateContactSchema } from './schemas/contacts.js';
import { ListFlexibleAssetsSchema, GetFlexibleAssetSchema, CreateFlexibleAssetSchema, UpdateFlexibleAssetSchema, ListFlexibleAssetTypesSchema, GetFlexibleAssetTypeSchema, ListFlexibleAssetFieldsSchema } from './schemas/flexible-assets.js';
import { ListLocationsSchema, GetLocationSchema, CreateLocationSchema, UpdateLocationSchema } from './schemas/locations.js';
import { ListDomainsSchema, GetDomainSchema } from './schemas/domains.js';
import { ListExpirationsSchema } from './schemas/expirations.js';
// Create MCP server instance
const server = new McpServer({
name: 'itglue-mcp-server',
version: '1.0.0'
});
// ============================================================================
// ORGANIZATION TOOLS
// ============================================================================
server.registerTool(
'itglue_list_organizations',
{
title: 'List IT Glue Organizations',
description: `List organizations in IT Glue with optional filtering and pagination.
Organizations represent client companies in IT Glue. Each organization contains
configurations, contacts, passwords, and other documentation.
Args:
- page (number): Page number, starting from 1 (default: 1)
- page_size (number): Items per page, max 1000 (default: 50)
- name (string): Filter by organization name (partial match)
- organization_type_id (number): Filter by organization type
- organization_status_id (number): Filter by status (Active, Inactive, etc.)
- psa_id (string): Filter by PSA integration ID
- sort (string): Sort field - name, id, updated_at, created_at
- sort_direction (string): asc or desc
- response_format (string): 'markdown' or 'json'
Returns: List of organizations with IDs, names, types, statuses, and IT Glue URLs.`,
inputSchema: ListOrganizationsSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const apiParams = {
...buildPaginationParams(params.page, params.page_size),
...buildFilterParams({
name: params.name,
organization_type_id: params.organization_type_id,
organization_status_id: params.organization_status_id,
psa_id: params.psa_id
}),
sort: buildSortParam(params.sort, params.sort_direction === 'desc')
};
const response = await makeApiRequest<JsonApiResponse<Organization[]>>('/organizations', 'GET', undefined, apiParams);
const { content, structuredContent } = formatListResponse(
response.data,
response.meta,
params.response_format,
formatOrganizationMarkdown,
'IT Glue Organizations'
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_get_organization',
{
title: 'Get IT Glue Organization',
description: `Get a single organization by ID.
Returns detailed information about an organization including name, type,
status, quick notes, alerts, and links to related resources.
Args:
- id (string|number): Organization ID (required)
- response_format (string): 'markdown' or 'json'
Returns: Organization details including all attributes and IT Glue URL.`,
inputSchema: GetOrganizationSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const response = await makeApiRequest<JsonApiResponse<Organization>>(`/organizations/${params.id}`);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatOrganizationMarkdown,
`Organization: ${response.data.attributes.name}`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_create_organization',
{
title: 'Create IT Glue Organization',
description: `Create a new organization in IT Glue.
Args:
- name (string): Organization name (required)
- organization_type_id (number): Organization type ID
- organization_status_id (number): Organization status ID
- short_name (string): Short name
- description (string): Description
- quick_notes (string): Quick notes
- alert (string): Alert message
- response_format (string): 'markdown' or 'json'
Returns: The created organization with its new ID.`,
inputSchema: CreateOrganizationSchema,
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
openWorldHint: true
}
},
async (params) => {
try {
const payload = {
data: {
type: 'organizations',
attributes: {
name: params.name,
...(params.organization_type_id && { 'organization-type-id': params.organization_type_id }),
...(params.organization_status_id && { 'organization-status-id': params.organization_status_id }),
...(params.short_name && { 'short-name': params.short_name }),
...(params.description && { description: params.description }),
...(params.quick_notes && { 'quick-notes': params.quick_notes }),
...(params.alert && { alert: params.alert })
}
}
};
const response = await makeApiRequest<JsonApiResponse<Organization>>('/organizations', 'POST', payload);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatOrganizationMarkdown,
`Created Organization: ${response.data.attributes.name}`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_update_organization',
{
title: 'Update IT Glue Organization',
description: `Update an existing organization in IT Glue.
Args:
- id (string|number): Organization ID (required)
- name (string): Organization name
- organization_type_id (number): Organization type ID
- organization_status_id (number): Organization status ID
- short_name (string): Short name
- description (string): Description
- quick_notes (string): Quick notes
- alert (string|null): Alert message (null to clear)
- response_format (string): 'markdown' or 'json'
Returns: The updated organization.`,
inputSchema: UpdateOrganizationSchema,
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const attributes: Record<string, unknown> = {};
if (params.name !== undefined) attributes.name = params.name;
if (params.organization_type_id !== undefined) attributes['organization-type-id'] = params.organization_type_id;
if (params.organization_status_id !== undefined) attributes['organization-status-id'] = params.organization_status_id;
if (params.short_name !== undefined) attributes['short-name'] = params.short_name;
if (params.description !== undefined) attributes.description = params.description;
if (params.quick_notes !== undefined) attributes['quick-notes'] = params.quick_notes;
if (params.alert !== undefined) attributes.alert = params.alert;
const payload = {
data: {
type: 'organizations',
attributes
}
};
const response = await makeApiRequest<JsonApiResponse<Organization>>(`/organizations/${params.id}`, 'PATCH', payload);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatOrganizationMarkdown,
`Updated Organization: ${response.data.attributes.name}`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
// ============================================================================
// CONFIGURATION TOOLS
// ============================================================================
server.registerTool(
'itglue_list_configurations',
{
title: 'List IT Glue Configurations',
description: `List configurations (devices/assets) in IT Glue with optional filtering.
Configurations represent IT assets like servers, workstations, network devices,
and other hardware/software that you document.
Args:
- page (number): Page number (default: 1)
- page_size (number): Items per page (default: 50, max: 1000)
- organization_id (number): Filter by organization
- name (string): Filter by name (partial match)
- configuration_type_id (number): Filter by type (Server, Workstation, etc.)
- configuration_status_id (number): Filter by status
- serial_number (string): Filter by serial number
- asset_tag (string): Filter by asset tag
- rmm_id (string): Filter by RMM integration ID
- archived (boolean): Filter by archived status
- sort (string): Sort field
- include (array): Related resources to include
Returns: List of configurations with details.`,
inputSchema: ListConfigurationsSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const apiParams: Record<string, unknown> = {
...buildPaginationParams(params.page, params.page_size),
...buildFilterParams({
organization_id: params.organization_id,
name: params.name,
configuration_type_id: params.configuration_type_id,
configuration_status_id: params.configuration_status_id,
serial_number: params.serial_number,
asset_tag: params.asset_tag,
rmm_id: params.rmm_id,
archived: params.archived
}),
sort: buildSortParam(params.sort, params.sort_direction === 'desc')
};
if (params.include && params.include.length > 0) {
apiParams.include = buildIncludeParam(params.include);
}
const response = await makeApiRequest<JsonApiResponse<Configuration[]>>('/configurations', 'GET', undefined, apiParams);
const { content, structuredContent } = formatListResponse(
response.data,
response.meta,
params.response_format,
formatConfigurationMarkdown,
'IT Glue Configurations'
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_get_configuration',
{
title: 'Get IT Glue Configuration',
description: `Get a single configuration (device/asset) by ID.
Returns detailed information about a configuration including hardware details,
network info, warranty status, and related resources.
Args:
- id (string|number): Configuration ID (required)
- response_format (string): 'markdown' or 'json'
- include (array): Related resources to include
Returns: Configuration details.`,
inputSchema: GetConfigurationSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const apiParams: Record<string, unknown> = {};
if (params.include && params.include.length > 0) {
apiParams.include = buildIncludeParam(params.include);
}
const response = await makeApiRequest<JsonApiResponse<Configuration>>(`/configurations/${params.id}`, 'GET', undefined, apiParams);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatConfigurationMarkdown,
`Configuration: ${response.data.attributes.name}`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_create_configuration',
{
title: 'Create IT Glue Configuration',
description: `Create a new configuration (device/asset) in IT Glue.
Args:
- organization_id (number): Organization ID (required)
- name (string): Configuration name (required)
- configuration_type_id (number): Type ID
- configuration_status_id (number): Status ID
- hostname (string): Hostname
- primary_ip (string): Primary IP address
- mac_address (string): MAC address
- serial_number (string): Serial number
- asset_tag (string): Asset tag
- manufacturer_id (number): Manufacturer ID
- model_id (number): Model ID
- operating_system_id (number): OS ID
- notes (string): Notes
- warranty_expires_at (string): Warranty expiration (YYYY-MM-DD)
- response_format (string): 'markdown' or 'json'
Returns: The created configuration.`,
inputSchema: CreateConfigurationSchema,
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
openWorldHint: true
}
},
async (params) => {
try {
const attributes: Record<string, unknown> = {
'organization-id': params.organization_id,
name: params.name
};
if (params.configuration_type_id) attributes['configuration-type-id'] = params.configuration_type_id;
if (params.configuration_status_id) attributes['configuration-status-id'] = params.configuration_status_id;
if (params.hostname) attributes.hostname = params.hostname;
if (params.primary_ip) attributes['primary-ip'] = params.primary_ip;
if (params.mac_address) attributes['mac-address'] = params.mac_address;
if (params.default_gateway) attributes['default-gateway'] = params.default_gateway;
if (params.serial_number) attributes['serial-number'] = params.serial_number;
if (params.asset_tag) attributes['asset-tag'] = params.asset_tag;
if (params.manufacturer_id) attributes['manufacturer-id'] = params.manufacturer_id;
if (params.model_id) attributes['model-id'] = params.model_id;
if (params.operating_system_id) attributes['operating-system-id'] = params.operating_system_id;
if (params.operating_system_notes) attributes['operating-system-notes'] = params.operating_system_notes;
if (params.location_id) attributes['location-id'] = params.location_id;
if (params.contact_id) attributes['contact-id'] = params.contact_id;
if (params.notes) attributes.notes = params.notes;
if (params.warranty_expires_at) attributes['warranty-expires-at'] = params.warranty_expires_at;
if (params.installed_by) attributes['installed-by'] = params.installed_by;
if (params.purchased_by) attributes['purchased-by'] = params.purchased_by;
if (params.purchased_at) attributes['purchased-at'] = params.purchased_at;
const payload = { data: { type: 'configurations', attributes } };
const response = await makeApiRequest<JsonApiResponse<Configuration>>('/configurations', 'POST', payload);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatConfigurationMarkdown,
`Created Configuration: ${response.data.attributes.name}`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_update_configuration',
{
title: 'Update IT Glue Configuration',
description: `Update an existing configuration in IT Glue.
Args:
- id (string|number): Configuration ID (required)
- name (string): Configuration name
- configuration_type_id (number): Type ID
- configuration_status_id (number): Status ID
- hostname (string|null): Hostname
- primary_ip (string|null): Primary IP address
- serial_number (string|null): Serial number
- asset_tag (string|null): Asset tag
- notes (string|null): Notes
- warranty_expires_at (string|null): Warranty expiration
- archived (boolean): Archive status
- response_format (string): 'markdown' or 'json'
Returns: The updated configuration.`,
inputSchema: UpdateConfigurationSchema,
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const attributes: Record<string, unknown> = {};
if (params.organization_id !== undefined) attributes['organization-id'] = params.organization_id;
if (params.name !== undefined) attributes.name = params.name;
if (params.configuration_type_id !== undefined) attributes['configuration-type-id'] = params.configuration_type_id;
if (params.configuration_status_id !== undefined) attributes['configuration-status-id'] = params.configuration_status_id;
if (params.hostname !== undefined) attributes.hostname = params.hostname;
if (params.primary_ip !== undefined) attributes['primary-ip'] = params.primary_ip;
if (params.mac_address !== undefined) attributes['mac-address'] = params.mac_address;
if (params.serial_number !== undefined) attributes['serial-number'] = params.serial_number;
if (params.asset_tag !== undefined) attributes['asset-tag'] = params.asset_tag;
if (params.notes !== undefined) attributes.notes = params.notes;
if (params.warranty_expires_at !== undefined) attributes['warranty-expires-at'] = params.warranty_expires_at;
if (params.archived !== undefined) attributes.archived = params.archived;
const payload = { data: { type: 'configurations', attributes } };
const response = await makeApiRequest<JsonApiResponse<Configuration>>(`/configurations/${params.id}`, 'PATCH', payload);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatConfigurationMarkdown,
`Updated Configuration: ${response.data.attributes.name}`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
// ============================================================================
// PASSWORD TOOLS
// ============================================================================
server.registerTool(
'itglue_list_passwords',
{
title: 'List IT Glue Passwords',
description: `List passwords in IT Glue with optional filtering.
NOTE: Password access requires the API key to have "Password Access" enabled.
If your key doesn't have this permission, you'll receive a 403 error.
Args:
- page (number): Page number (default: 1)
- page_size (number): Items per page (default: 50)
- organization_id (number): Filter by organization
- name (string): Filter by name (partial match)
- password_category_id (number): Filter by category
- url (string): Filter by URL
- archived (boolean): Filter by archived status
- show_password (boolean): Include actual password values (default: false)
- response_format (string): 'markdown' or 'json'
Returns: List of passwords (without actual values unless show_password is true).`,
inputSchema: ListPasswordsSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const apiParams = {
...buildPaginationParams(params.page, params.page_size),
...buildFilterParams({
organization_id: params.organization_id,
name: params.name,
password_category_id: params.password_category_id,
url: params.url,
archived: params.archived
}),
sort: buildSortParam(params.sort, params.sort_direction === 'desc'),
...(params.show_password && { 'show_password': 'true' })
};
const response = await makeApiRequest<JsonApiResponse<Password[]>>('/passwords', 'GET', undefined, apiParams);
const { content, structuredContent } = formatListResponse(
response.data,
response.meta,
params.response_format,
formatPasswordMarkdown,
'IT Glue Passwords'
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_get_password',
{
title: 'Get IT Glue Password',
description: `Get a single password by ID, optionally including the actual password value.
NOTE: Password access requires the API key to have "Password Access" enabled.
Args:
- id (string|number): Password ID (required)
- show_password (boolean): Include actual password value (default: true)
- response_format (string): 'markdown' or 'json'
Returns: Password details including username, URL, and optionally the password value.`,
inputSchema: GetPasswordSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const apiParams = params.show_password ? { 'show_password': 'true' } : {};
const response = await makeApiRequest<JsonApiResponse<Password>>(`/passwords/${params.id}`, 'GET', undefined, apiParams);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatPasswordMarkdown,
`Password: ${response.data.attributes.name}`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_create_password',
{
title: 'Create IT Glue Password',
description: `Create a new password entry in IT Glue.
Args:
- organization_id (number): Organization ID (required)
- name (string): Password entry name (required)
- password_category_id (number): Category ID
- username (string): Username
- password (string): Password value
- url (string): Associated URL
- notes (string): Notes
- password_folder_id (number): Folder ID
- response_format (string): 'markdown' or 'json'
Returns: The created password entry.`,
inputSchema: CreatePasswordSchema,
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
openWorldHint: true
}
},
async (params) => {
try {
const attributes: Record<string, unknown> = {
'organization-id': params.organization_id,
name: params.name
};
if (params.password_category_id) attributes['password-category-id'] = params.password_category_id;
if (params.username) attributes.username = params.username;
if (params.password) attributes.password = params.password;
if (params.url) attributes.url = params.url;
if (params.notes) attributes.notes = params.notes;
if (params.password_folder_id) attributes['password-folder-id'] = params.password_folder_id;
const payload = { data: { type: 'passwords', attributes } };
const response = await makeApiRequest<JsonApiResponse<Password>>('/passwords', 'POST', payload);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatPasswordMarkdown,
`Created Password: ${response.data.attributes.name}`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_update_password',
{
title: 'Update IT Glue Password',
description: `Update an existing password entry in IT Glue.
Args:
- id (string|number): Password ID (required)
- name (string): Password entry name
- password_category_id (number): Category ID
- username (string|null): Username
- password (string): Password value
- url (string|null): Associated URL
- notes (string|null): Notes
- archived (boolean): Archive status
- response_format (string): 'markdown' or 'json'
Returns: The updated password entry.`,
inputSchema: UpdatePasswordSchema,
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const attributes: Record<string, unknown> = {};
if (params.organization_id !== undefined) attributes['organization-id'] = params.organization_id;
if (params.name !== undefined) attributes.name = params.name;
if (params.password_category_id !== undefined) attributes['password-category-id'] = params.password_category_id;
if (params.username !== undefined) attributes.username = params.username;
if (params.password !== undefined) attributes.password = params.password;
if (params.url !== undefined) attributes.url = params.url;
if (params.notes !== undefined) attributes.notes = params.notes;
if (params.archived !== undefined) attributes.archived = params.archived;
const payload = { data: { type: 'passwords', attributes } };
const response = await makeApiRequest<JsonApiResponse<Password>>(`/passwords/${params.id}`, 'PATCH', payload);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatPasswordMarkdown,
`Updated Password: ${response.data.attributes.name}`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
// ============================================================================
// CONTACT TOOLS
// ============================================================================
server.registerTool(
'itglue_list_contacts',
{
title: 'List IT Glue Contacts',
description: `List contacts in IT Glue with optional filtering.
Contacts represent people associated with organizations - employees, vendors,
or other important individuals.
Args:
- page (number): Page number (default: 1)
- page_size (number): Items per page (default: 50)
- organization_id (number): Filter by organization
- first_name (string): Filter by first name
- last_name (string): Filter by last name
- contact_type_id (number): Filter by contact type
- important (boolean): Filter by important flag
- sort (string): Sort field
- response_format (string): 'markdown' or 'json'
Returns: List of contacts with names, titles, and contact information.`,
inputSchema: ListContactsSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const apiParams = {
...buildPaginationParams(params.page, params.page_size),
...buildFilterParams({
organization_id: params.organization_id,
first_name: params.first_name,
last_name: params.last_name,
contact_type_id: params.contact_type_id,
important: params.important,
psa_id: params.psa_id
}),
sort: buildSortParam(params.sort, params.sort_direction === 'desc')
};
const response = await makeApiRequest<JsonApiResponse<Contact[]>>('/contacts', 'GET', undefined, apiParams);
const { content, structuredContent } = formatListResponse(
response.data,
response.meta,
params.response_format,
formatContactMarkdown,
'IT Glue Contacts'
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_get_contact',
{
title: 'Get IT Glue Contact',
description: `Get a single contact by ID.
Args:
- id (string|number): Contact ID (required)
- response_format (string): 'markdown' or 'json'
Returns: Contact details including emails, phones, and notes.`,
inputSchema: GetContactSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const response = await makeApiRequest<JsonApiResponse<Contact>>(`/contacts/${params.id}`);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatContactMarkdown,
`Contact: ${response.data.attributes['first-name']} ${response.data.attributes['last-name']}`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_create_contact',
{
title: 'Create IT Glue Contact',
description: `Create a new contact in IT Glue.
Args:
- organization_id (number): Organization ID (required)
- first_name (string): First name (required)
- last_name (string): Last name (required)
- title (string): Job title
- contact_type_id (number): Contact type ID
- location_id (number): Location ID
- important (boolean): Mark as important
- notes (string): Notes
- contact_emails (array): Email addresses
- contact_phones (array): Phone numbers
- response_format (string): 'markdown' or 'json'
Returns: The created contact.`,
inputSchema: CreateContactSchema,
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
openWorldHint: true
}
},
async (params) => {
try {
const attributes: Record<string, unknown> = {
'organization-id': params.organization_id,
'first-name': params.first_name,
'last-name': params.last_name
};
if (params.title) attributes.title = params.title;
if (params.contact_type_id) attributes['contact-type-id'] = params.contact_type_id;
if (params.location_id) attributes['location-id'] = params.location_id;
if (params.important !== undefined) attributes.important = params.important;
if (params.notes) attributes.notes = params.notes;
if (params.contact_emails) {
attributes['contact-emails'] = params.contact_emails.map(e => ({
value: e.value,
'label-name': e.label_name,
primary: e.primary
}));
}
if (params.contact_phones) {
attributes['contact-phones'] = params.contact_phones.map(p => ({
value: p.value,
extension: p.extension,
'label-name': p.label_name,
primary: p.primary
}));
}
const payload = { data: { type: 'contacts', attributes } };
const response = await makeApiRequest<JsonApiResponse<Contact>>('/contacts', 'POST', payload);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatContactMarkdown,
`Created Contact: ${response.data.attributes['first-name']} ${response.data.attributes['last-name']}`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_update_contact',
{
title: 'Update IT Glue Contact',
description: `Update an existing contact in IT Glue.
Args:
- id (string|number): Contact ID (required)
- first_name (string): First name
- last_name (string): Last name
- title (string|null): Job title
- contact_type_id (number): Contact type ID
- location_id (number|null): Location ID
- important (boolean): Important flag
- notes (string|null): Notes
- contact_emails (array): Email addresses (replaces existing)
- contact_phones (array): Phone numbers (replaces existing)
- response_format (string): 'markdown' or 'json'
Returns: The updated contact.`,
inputSchema: UpdateContactSchema,
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const attributes: Record<string, unknown> = {};
if (params.organization_id !== undefined) attributes['organization-id'] = params.organization_id;
if (params.first_name !== undefined) attributes['first-name'] = params.first_name;
if (params.last_name !== undefined) attributes['last-name'] = params.last_name;
if (params.title !== undefined) attributes.title = params.title;
if (params.contact_type_id !== undefined) attributes['contact-type-id'] = params.contact_type_id;
if (params.location_id !== undefined) attributes['location-id'] = params.location_id;
if (params.important !== undefined) attributes.important = params.important;
if (params.notes !== undefined) attributes.notes = params.notes;
if (params.contact_emails) {
attributes['contact-emails'] = params.contact_emails.map(e => ({
value: e.value,
'label-name': e.label_name,
primary: e.primary
}));
}
if (params.contact_phones) {
attributes['contact-phones'] = params.contact_phones.map(p => ({
value: p.value,
extension: p.extension,
'label-name': p.label_name,
primary: p.primary
}));
}
const payload = { data: { type: 'contacts', attributes } };
const response = await makeApiRequest<JsonApiResponse<Contact>>(`/contacts/${params.id}`, 'PATCH', payload);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatContactMarkdown,
`Updated Contact: ${response.data.attributes['first-name']} ${response.data.attributes['last-name']}`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
// ============================================================================
// FLEXIBLE ASSET TOOLS
// ============================================================================
server.registerTool(
'itglue_list_flexible_assets',
{
title: 'List IT Glue Flexible Assets',
description: `List flexible assets in IT Glue with optional filtering.
Flexible assets are custom documentation types defined by flexible asset types.
Examples: Network documentation, Application runbooks, Vendor info, etc.
Args:
- page (number): Page number (default: 1)
- page_size (number): Items per page (default: 50)
- organization_id (number): Filter by organization
- flexible_asset_type_id (number): Filter by asset type (recommended)
- name (string): Filter by name
- archived (boolean): Filter by archived status
- sort (string): Sort field
- include (array): Related resources to include
- response_format (string): 'markdown' or 'json'
Returns: List of flexible assets with their trait values.`,
inputSchema: ListFlexibleAssetsSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const apiParams: Record<string, unknown> = {
...buildPaginationParams(params.page, params.page_size),
...buildFilterParams({
organization_id: params.organization_id,
flexible_asset_type_id: params.flexible_asset_type_id,
name: params.name,
archived: params.archived
}),
sort: buildSortParam(params.sort, params.sort_direction === 'desc')
};
if (params.include && params.include.length > 0) {
apiParams.include = buildIncludeParam(params.include);
}
const response = await makeApiRequest<JsonApiResponse<FlexibleAsset[]>>('/flexible_assets', 'GET', undefined, apiParams);
const { content, structuredContent } = formatListResponse(
response.data,
response.meta,
params.response_format,
formatFlexibleAssetMarkdown,
'IT Glue Flexible Assets'
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_get_flexible_asset',
{
title: 'Get IT Glue Flexible Asset',
description: `Get a single flexible asset by ID.
Args:
- id (string|number): Flexible asset ID (required)
- include (array): Related resources to include
- response_format (string): 'markdown' or 'json'
Returns: Flexible asset details including all trait values.`,
inputSchema: GetFlexibleAssetSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const apiParams: Record<string, unknown> = {};
if (params.include && params.include.length > 0) {
apiParams.include = buildIncludeParam(params.include);
}
const response = await makeApiRequest<JsonApiResponse<FlexibleAsset>>(`/flexible_assets/${params.id}`, 'GET', undefined, apiParams);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatFlexibleAssetMarkdown,
`Flexible Asset: ${response.data.attributes.name || response.data.id}`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_create_flexible_asset',
{
title: 'Create IT Glue Flexible Asset',
description: `Create a new flexible asset in IT Glue.
Before creating, use itglue_list_flexible_asset_types to get the type ID,
then itglue_list_flexible_asset_fields to see required trait fields.
Args:
- organization_id (number): Organization ID (required)
- flexible_asset_type_id (number): Flexible asset type ID (required)
- traits (object): Trait values as key-value pairs. Keys are field name-keys.
- response_format (string): 'markdown' or 'json'
Returns: The created flexible asset.`,
inputSchema: CreateFlexibleAssetSchema,
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
openWorldHint: true
}
},
async (params) => {
try {
const payload = {
data: {
type: 'flexible-assets',
attributes: {
'organization-id': params.organization_id,
'flexible-asset-type-id': params.flexible_asset_type_id,
traits: params.traits
}
}
};
const response = await makeApiRequest<JsonApiResponse<FlexibleAsset>>('/flexible_assets', 'POST', payload);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatFlexibleAssetMarkdown,
`Created Flexible Asset`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_update_flexible_asset',
{
title: 'Update IT Glue Flexible Asset',
description: `Update an existing flexible asset in IT Glue.
Args:
- id (string|number): Flexible asset ID (required)
- organization_id (number): Organization ID
- traits (object): Trait values to update
- archived (boolean): Archive status
- response_format (string): 'markdown' or 'json'
Returns: The updated flexible asset.`,
inputSchema: UpdateFlexibleAssetSchema,
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const attributes: Record<string, unknown> = {};
if (params.organization_id !== undefined) attributes['organization-id'] = params.organization_id;
if (params.traits !== undefined) attributes.traits = params.traits;
if (params.archived !== undefined) attributes.archived = params.archived;
const payload = { data: { type: 'flexible-assets', attributes } };
const response = await makeApiRequest<JsonApiResponse<FlexibleAsset>>(`/flexible_assets/${params.id}`, 'PATCH', payload);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatFlexibleAssetMarkdown,
`Updated Flexible Asset`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_list_flexible_asset_types',
{
title: 'List IT Glue Flexible Asset Types',
description: `List all flexible asset types defined in IT Glue.
Flexible asset types define the structure (fields/traits) for flexible assets.
Use this to find the type ID needed when creating or filtering flexible assets.
Args:
- page (number): Page number (default: 1)
- page_size (number): Items per page (default: 50)
- name (string): Filter by name
- enabled (boolean): Filter by enabled status
- include (array): Include 'flexible_asset_fields' to see field definitions
- response_format (string): 'markdown' or 'json'
Returns: List of flexible asset types with their IDs and names.`,
inputSchema: ListFlexibleAssetTypesSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const apiParams: Record<string, unknown> = {
...buildPaginationParams(params.page, params.page_size),
...buildFilterParams({
name: params.name,
enabled: params.enabled
})
};
if (params.include && params.include.length > 0) {
apiParams.include = buildIncludeParam(params.include);
}
const response = await makeApiRequest<JsonApiResponse<FlexibleAssetType[]>>('/flexible_asset_types', 'GET', undefined, apiParams);
const formatType = (type: FlexibleAssetType) => {
const a = type.attributes;
const lines = [`## ${a.name} (ID: ${type.id})`];
if (a.description) lines.push(`- **Description**: ${a.description}`);
if (a.icon) lines.push(`- **Icon**: ${a.icon}`);
lines.push(`- **Enabled**: ${a.enabled ? 'Yes' : 'No'}`);
lines.push(`- **Show in Menu**: ${a['show-in-menu'] ? 'Yes' : 'No'}`);
return lines.join('\n');
};
const { content, structuredContent } = formatListResponse(
response.data,
response.meta,
params.response_format,
formatType,
'IT Glue Flexible Asset Types'
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_get_flexible_asset_type',
{
title: 'Get IT Glue Flexible Asset Type',
description: `Get a single flexible asset type by ID.
Args:
- id (string|number): Flexible asset type ID (required)
- include (array): Include 'flexible_asset_fields' to see field definitions
- response_format (string): 'markdown' or 'json'
Returns: Flexible asset type details and optionally its fields.`,
inputSchema: GetFlexibleAssetTypeSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const apiParams: Record<string, unknown> = {};
if (params.include && params.include.length > 0) {
apiParams.include = buildIncludeParam(params.include);
}
const response = await makeApiRequest<JsonApiResponse<FlexibleAssetType>>(`/flexible_asset_types/${params.id}`, 'GET', undefined, apiParams);
const a = response.data.attributes;
const lines = [
`# Flexible Asset Type: ${a.name}`,
'',
`- **ID**: ${response.data.id}`,
`- **Description**: ${a.description || 'N/A'}`,
`- **Icon**: ${a.icon || 'N/A'}`,
`- **Enabled**: ${a.enabled ? 'Yes' : 'No'}`,
`- **Show in Menu**: ${a['show-in-menu'] ? 'Yes' : 'No'}`
];
// Include fields if they were included in the response
if (response.included) {
const fields = response.included.filter(item => item.type === 'flexible-asset-fields') as FlexibleAssetField[];
if (fields.length > 0) {
lines.push('');
lines.push('## Fields');
for (const field of fields.sort((a, b) => (a.attributes.order || 0) - (b.attributes.order || 0))) {
const fa = field.attributes;
lines.push(`### ${fa.name}`);
lines.push(`- **Kind**: ${fa.kind}`);
if (fa.hint) lines.push(`- **Hint**: ${fa.hint}`);
lines.push(`- **Required**: ${fa.required ? 'Yes' : 'No'}`);
if (fa['tag-type']) lines.push(`- **Tag Type**: ${fa['tag-type']}`);
lines.push('');
}
}
}
const structuredContent = {
id: response.data.id,
type: response.data.type,
...a,
fields: response.included?.filter(item => item.type === 'flexible-asset-fields')
};
const textContent = params.response_format === ResponseFormat.JSON
? JSON.stringify(structuredContent, null, 2)
: lines.join('\n');
return {
content: [{ type: 'text', text: textContent }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_list_flexible_asset_fields',
{
title: 'List IT Glue Flexible Asset Fields',
description: `List all fields for a specific flexible asset type.
Use this to understand what traits (fields) are available when creating
or updating flexible assets of a particular type.
Args:
- flexible_asset_type_id (number): The flexible asset type ID (required)
- response_format (string): 'markdown' or 'json'
Returns: List of fields with names, types, and requirements.`,
inputSchema: ListFlexibleAssetFieldsSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const response = await makeApiRequest<JsonApiResponse<FlexibleAssetField[]>>(
`/flexible_asset_types/${params.flexible_asset_type_id}/relationships/flexible_asset_fields`
);
const formatField = (field: FlexibleAssetField) => {
const a = field.attributes;
const lines = [`## ${a.name} (ID: ${field.id})`];
lines.push(`- **Kind**: ${a.kind}`);
if (a.hint) lines.push(`- **Hint**: ${a.hint}`);
lines.push(`- **Required**: ${a.required ? 'Yes' : 'No'}`);
lines.push(`- **Show in List**: ${a['show-in-list'] ? 'Yes' : 'No'}`);
lines.push(`- **Use for Title**: ${a['use-for-title'] ? 'Yes' : 'No'}`);
if (a['tag-type']) lines.push(`- **Tag Type**: ${a['tag-type']}`);
if (a['default-value']) lines.push(`- **Default Value**: ${a['default-value']}`);
return lines.join('\n');
};
const { content, structuredContent } = formatListResponse(
response.data,
response.meta,
params.response_format,
formatField,
'Flexible Asset Fields'
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
// ============================================================================
// LOCATION TOOLS
// ============================================================================
server.registerTool(
'itglue_list_locations',
{
title: 'List IT Glue Locations',
description: `List locations in IT Glue with optional filtering.
Locations are physical addresses associated with organizations.
Args:
- page (number): Page number (default: 1)
- page_size (number): Items per page (default: 50)
- organization_id (number): Filter by organization
- name (string): Filter by name
- city (string): Filter by city
- region_id (number): Filter by region/state
- country_id (number): Filter by country
- sort (string): Sort field
- response_format (string): 'markdown' or 'json'
Returns: List of locations with addresses.`,
inputSchema: ListLocationsSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const apiParams = {
...buildPaginationParams(params.page, params.page_size),
...buildFilterParams({
organization_id: params.organization_id,
name: params.name,
city: params.city,
region_id: params.region_id,
country_id: params.country_id,
psa_id: params.psa_id
}),
sort: buildSortParam(params.sort, params.sort_direction === 'desc')
};
const response = await makeApiRequest<JsonApiResponse<Location[]>>('/locations', 'GET', undefined, apiParams);
const { content, structuredContent } = formatListResponse(
response.data,
response.meta,
params.response_format,
formatLocationMarkdown,
'IT Glue Locations'
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_get_location',
{
title: 'Get IT Glue Location',
description: `Get a single location by ID.
Args:
- id (string|number): Location ID (required)
- response_format (string): 'markdown' or 'json'
Returns: Location details including full address.`,
inputSchema: GetLocationSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const response = await makeApiRequest<JsonApiResponse<Location>>(`/locations/${params.id}`);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatLocationMarkdown,
`Location: ${response.data.attributes.name}`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_create_location',
{
title: 'Create IT Glue Location',
description: `Create a new location in IT Glue.
Args:
- organization_id (number): Organization ID (required)
- name (string): Location name (required)
- primary (boolean): Is this the primary location
- address_1 (string): Address line 1
- address_2 (string): Address line 2
- city (string): City
- postal_code (string): Postal/ZIP code
- region_id (number): Region/state ID
- country_id (number): Country ID
- phone (string): Phone number
- fax (string): Fax number
- notes (string): Notes
- response_format (string): 'markdown' or 'json'
Returns: The created location.`,
inputSchema: CreateLocationSchema,
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
openWorldHint: true
}
},
async (params) => {
try {
const attributes: Record<string, unknown> = {
'organization-id': params.organization_id,
name: params.name
};
if (params.primary !== undefined) attributes.primary = params.primary;
if (params.address_1) attributes['address-1'] = params.address_1;
if (params.address_2) attributes['address-2'] = params.address_2;
if (params.city) attributes.city = params.city;
if (params.postal_code) attributes['postal-code'] = params.postal_code;
if (params.region_id) attributes['region-id'] = params.region_id;
if (params.country_id) attributes['country-id'] = params.country_id;
if (params.phone) attributes.phone = params.phone;
if (params.fax) attributes.fax = params.fax;
if (params.notes) attributes.notes = params.notes;
const payload = { data: { type: 'locations', attributes } };
const response = await makeApiRequest<JsonApiResponse<Location>>('/locations', 'POST', payload);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatLocationMarkdown,
`Created Location: ${response.data.attributes.name}`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_update_location',
{
title: 'Update IT Glue Location',
description: `Update an existing location in IT Glue.
Args:
- id (string|number): Location ID (required)
- name (string): Location name
- primary (boolean): Is this the primary location
- address_1 (string|null): Address line 1
- address_2 (string|null): Address line 2
- city (string|null): City
- postal_code (string|null): Postal/ZIP code
- region_id (number|null): Region/state ID
- country_id (number|null): Country ID
- phone (string|null): Phone number
- fax (string|null): Fax number
- notes (string|null): Notes
- response_format (string): 'markdown' or 'json'
Returns: The updated location.`,
inputSchema: UpdateLocationSchema,
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const attributes: Record<string, unknown> = {};
if (params.organization_id !== undefined) attributes['organization-id'] = params.organization_id;
if (params.name !== undefined) attributes.name = params.name;
if (params.primary !== undefined) attributes.primary = params.primary;
if (params.address_1 !== undefined) attributes['address-1'] = params.address_1;
if (params.address_2 !== undefined) attributes['address-2'] = params.address_2;
if (params.city !== undefined) attributes.city = params.city;
if (params.postal_code !== undefined) attributes['postal-code'] = params.postal_code;
if (params.region_id !== undefined) attributes['region-id'] = params.region_id;
if (params.country_id !== undefined) attributes['country-id'] = params.country_id;
if (params.phone !== undefined) attributes.phone = params.phone;
if (params.fax !== undefined) attributes.fax = params.fax;
if (params.notes !== undefined) attributes.notes = params.notes;
const payload = { data: { type: 'locations', attributes } };
const response = await makeApiRequest<JsonApiResponse<Location>>(`/locations/${params.id}`, 'PATCH', payload);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatLocationMarkdown,
`Updated Location: ${response.data.attributes.name}`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
// ============================================================================
// DOMAIN TOOLS
// ============================================================================
server.registerTool(
'itglue_list_domains',
{
title: 'List IT Glue Domains',
description: `List domains in IT Glue with optional filtering.
Domains track domain registrations and their expiration dates.
Args:
- page (number): Page number (default: 1)
- page_size (number): Items per page (default: 50)
- organization_id (number): Filter by organization
- name (string): Filter by domain name
- sort (string): Sort field (name, expires_on, etc.)
- include (array): Include 'passwords' for related credentials
- response_format (string): 'markdown' or 'json'
Returns: List of domains with registrar and expiration info.`,
inputSchema: ListDomainsSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const apiParams: Record<string, unknown> = {
...buildPaginationParams(params.page, params.page_size),
...buildFilterParams({
organization_id: params.organization_id,
name: params.name
}),
sort: buildSortParam(params.sort, params.sort_direction === 'desc')
};
if (params.include && params.include.length > 0) {
apiParams.include = buildIncludeParam(params.include);
}
const response = await makeApiRequest<JsonApiResponse<Domain[]>>('/domains', 'GET', undefined, apiParams);
const { content, structuredContent } = formatListResponse(
response.data,
response.meta,
params.response_format,
formatDomainMarkdown,
'IT Glue Domains'
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
server.registerTool(
'itglue_get_domain',
{
title: 'Get IT Glue Domain',
description: `Get a single domain by ID.
Args:
- id (string|number): Domain ID (required)
- include (array): Include 'passwords' for related credentials
- response_format (string): 'markdown' or 'json'
Returns: Domain details including registrar and expiration.`,
inputSchema: GetDomainSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const apiParams: Record<string, unknown> = {};
if (params.include && params.include.length > 0) {
apiParams.include = buildIncludeParam(params.include);
}
const response = await makeApiRequest<JsonApiResponse<Domain>>(`/domains/${params.id}`, 'GET', undefined, apiParams);
const { content, structuredContent } = formatSingleResponse(
response.data,
params.response_format,
formatDomainMarkdown,
`Domain: ${response.data.attributes.name}`
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
// ============================================================================
// EXPIRATION TOOLS
// ============================================================================
server.registerTool(
'itglue_list_expirations',
{
title: 'List IT Glue Expirations',
description: `List upcoming expirations across all resource types.
Useful for tracking warranties, SSL certificates, domain renewals,
and other time-sensitive items.
Args:
- page (number): Page number (default: 1)
- page_size (number): Items per page (default: 50)
- organization_id (number): Filter by organization
- resource_type_name (string): Filter by type (Configuration, Domain, etc.)
- range (string): Date range - past, today, week, month, quarter, year, all
- sort (string): Sort field
- response_format (string): 'markdown' or 'json'
Returns: List of items with expiration dates.`,
inputSchema: ListExpirationsSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params) => {
try {
const apiParams: Record<string, unknown> = {
...buildPaginationParams(params.page, params.page_size),
...buildFilterParams({
organization_id: params.organization_id,
resource_type_name: params.resource_type_name
}),
sort: buildSortParam(params.sort, params.sort_direction === 'desc')
};
// Add range filter
if (params.range && params.range !== 'all') {
apiParams['filter[range]'] = params.range;
}
const response = await makeApiRequest<JsonApiResponse<Expiration[]>>('/expirations', 'GET', undefined, apiParams);
const { content, structuredContent } = formatListResponse(
response.data,
response.meta,
params.response_format,
formatExpirationMarkdown,
'IT Glue Expirations'
);
return {
content: [{ type: 'text', text: content }],
structuredContent
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
// ============================================================================
// UTILITY TOOLS
// ============================================================================
import { z } from 'zod';
import { PaginationSchema, ResponseFormatSchema } from './schemas/common.js';
const CheckPasswordAccessSchema = z.object({}).strict();
server.registerTool(
'itglue_check_password_access',
{
title: 'Check IT Glue Password Access',
description: `Check if the configured API key has password access permission.
The IT Glue API key can be configured with or without password access.
Use this tool to verify your access level before attempting password operations.
Returns: Boolean indicating if password access is enabled.`,
inputSchema: CheckPasswordAccessSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async () => {
try {
const hasAccess = await checkPasswordAccess();
const message = hasAccess
? 'Password access is ENABLED. You can retrieve password values.'
: 'Password access is DISABLED. Your API key does not have permission to view password values. You can still list and create passwords, but cannot retrieve the actual password field.';
return {
content: [{ type: 'text', text: message }],
structuredContent: { password_access_enabled: hasAccess }
};
} catch (error) {
return {
content: [{ type: 'text', text: handleApiError(error) }],
isError: true
};
}
}
);
// ============================================================================
// SERVER STARTUP
// ============================================================================
async function main() {
// Validate required environment variable
if (!process.env.ITGLUE_API_KEY) {
console.error('ERROR: ITGLUE_API_KEY environment variable is required');
console.error('Set it in your environment or .env file');
process.exit(1);
}
const region = process.env.ITGLUE_REGION || 'us';
console.error(`IT Glue MCP Server starting...`);
console.error(`Region: ${region}`);
// Start the server with stdio transport
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('IT Glue MCP Server running via stdio');
}
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});