import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { XmiModel } from './model/xmi-model.js';
export function createServer(model: XmiModel): Server {
const server = new Server(
{
name: 'xmimcp',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'list_packages',
description: 'List all packages in the SysML model with optional filtering',
inputSchema: {
type: 'object',
properties: {
parentPackage: {
type: 'string',
description: 'Filter to children of this package (by name or xmi:id)',
},
recursive: {
type: 'boolean',
description: 'Include nested packages recursively',
default: false,
},
namePattern: {
type: 'string',
description: 'Regex pattern to filter package names',
},
},
},
},
{
name: 'get_package',
description: 'Get detailed information about a specific package',
inputSchema: {
type: 'object',
properties: {
identifier: {
type: 'string',
description: 'Package name or xmi:id',
},
},
required: ['identifier'],
},
},
{
name: 'find_classes',
description: 'Find classes/blocks by name, pattern, or package',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Exact class name to find',
},
namePattern: {
type: 'string',
description: 'Regex pattern for class names',
},
package: {
type: 'string',
description: 'Limit search to this package (name or id)',
},
recursive: {
type: 'boolean',
description: 'Search in nested packages',
default: true,
},
includeAbstract: {
type: 'boolean',
description: 'Include abstract classes',
default: true,
},
},
},
},
{
name: 'get_class_details',
description: 'Get full details of a class including properties and inheritance',
inputSchema: {
type: 'object',
properties: {
identifier: {
type: 'string',
description: 'Class name or xmi:id',
},
includeInherited: {
type: 'boolean',
description: 'Include inherited properties from parent classes',
default: false,
},
},
required: ['identifier'],
},
},
{
name: 'get_inheritance_hierarchy',
description: 'Get the inheritance chain for a class (parents and children)',
inputSchema: {
type: 'object',
properties: {
identifier: {
type: 'string',
description: 'Class name or xmi:id',
},
direction: {
type: 'string',
enum: ['ancestors', 'descendants', 'both'],
default: 'both',
},
},
required: ['identifier'],
},
},
{
name: 'list_enumerations',
description: 'List all enumerations with optional filtering',
inputSchema: {
type: 'object',
properties: {
package: {
type: 'string',
description: 'Filter to a specific package',
},
namePattern: {
type: 'string',
description: 'Regex pattern for enumeration names',
},
},
},
},
{
name: 'get_enumeration',
description: 'Get details of an enumeration including all literals',
inputSchema: {
type: 'object',
properties: {
identifier: {
type: 'string',
description: 'Enumeration name or xmi:id',
},
},
required: ['identifier'],
},
},
{
name: 'search_documentation',
description: 'Full-text search across all documentation/comments',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search terms (space-separated, all must match)',
},
elementTypes: {
type: 'array',
items: {
type: 'string',
enum: ['class', 'enumeration', 'package', 'datatype'],
},
description: 'Limit search to specific element types',
},
limit: {
type: 'number',
description: 'Maximum results to return',
default: 50,
},
},
required: ['query'],
},
},
{
name: 'search_by_name',
description: 'Search for any element by name across the entire model',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Name or partial name to search for',
},
exactMatch: {
type: 'boolean',
description: 'Require exact name match',
default: false,
},
elementTypes: {
type: 'array',
items: {
type: 'string',
enum: ['class', 'enumeration', 'package', 'datatype'],
},
description: 'Limit to specific element types',
},
},
required: ['query'],
},
},
{
name: 'resolve_reference',
description: 'Resolve an xmi:id reference to get element details',
inputSchema: {
type: 'object',
properties: {
xmiId: {
type: 'string',
description: 'The xmi:id to resolve',
},
},
required: ['xmiId'],
},
},
{
name: 'get_model_statistics',
description: 'Get statistics about the loaded SysML model',
inputSchema: {
type: 'object',
properties: {},
},
},
],
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'list_packages': {
const packages = model.listPackages({
parentPackage: args?.parentPackage as string | undefined,
recursive: args?.recursive as boolean | undefined,
namePattern: args?.namePattern as string | undefined,
});
const result = packages.map(p => ({
id: p.xmiId,
name: p.name,
path: p.path,
childPackageCount: p.childPackageIds.length,
childElementCount: p.childElementIds.length,
hasDocumentation: !!p.documentation,
}));
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
}
case 'get_package': {
const identifier = args?.identifier as string;
if (!identifier) {
return { content: [{ type: 'text', text: 'Error: identifier is required' }] };
}
const pkg = model.getPackage(identifier);
if (!pkg) {
return { content: [{ type: 'text', text: `Package not found: ${identifier}` }] };
}
const childPackages = model.getChildPackages(pkg.xmiId);
const classes = model.getClassesInPackage(pkg.xmiId, false);
const enums = model.listEnumerations({ package: pkg.xmiId });
const result = {
id: pkg.xmiId,
name: pkg.name,
path: pkg.path,
documentation: pkg.documentation,
childPackages: childPackages.map(p => ({ id: p.xmiId, name: p.name })),
classes: classes.map(c => ({ id: c.xmiId, name: c.name, isAbstract: c.isAbstract })),
enumerations: enums.map(e => ({ id: e.xmiId, name: e.name, literalCount: e.literals.length })),
};
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
}
case 'find_classes': {
const classes = model.findClasses({
name: args?.name as string | undefined,
namePattern: args?.namePattern as string | undefined,
package: args?.package as string | undefined,
recursive: args?.recursive as boolean | undefined,
includeAbstract: args?.includeAbstract as boolean | undefined,
});
const result = classes.map(c => ({
id: c.xmiId,
name: c.name,
packagePath: model.getPackagePath(c.xmiId),
isAbstract: c.isAbstract,
propertyCount: c.properties.length,
stereotypes: c.stereotypes,
hasDocumentation: !!c.documentation,
}));
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
}
case 'get_class_details': {
const identifier = args?.identifier as string;
if (!identifier) {
return { content: [{ type: 'text', text: 'Error: identifier is required' }] };
}
const includeInherited = args?.includeInherited as boolean;
let cls = model.getClass(identifier);
let properties;
if (includeInherited && cls) {
const result = model.getClassWithInheritedProperties(cls.xmiId);
cls = result.class;
properties = result.allProperties;
} else if (cls) {
properties = cls.properties;
}
if (!cls) {
return { content: [{ type: 'text', text: `Class not found: ${identifier}` }] };
}
const result = {
id: cls.xmiId,
name: cls.name,
packagePath: model.getPackagePath(cls.xmiId),
isAbstract: cls.isAbstract,
stereotypes: cls.stereotypes,
documentation: cls.documentation,
properties: properties?.map(p => ({
name: p.name,
type: p.typeName || p.typeId,
visibility: p.visibility,
aggregation: p.aggregation,
multiplicity: `${p.lowerBound}..${p.upperBound}`,
documentation: p.documentation,
})),
generalizations: cls.generalizations.map(g => ({
parentId: g.generalId,
parentName: g.generalName,
})),
};
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
}
case 'get_inheritance_hierarchy': {
const identifier = args?.identifier as string;
if (!identifier) {
return { content: [{ type: 'text', text: 'Error: identifier is required' }] };
}
const cls = model.getClass(identifier);
if (!cls) {
return { content: [{ type: 'text', text: `Class not found: ${identifier}` }] };
}
const direction = (args?.direction as 'ancestors' | 'descendants' | 'both') || 'both';
const hierarchy = model.getInheritanceChain(cls.xmiId, direction);
const result = {
class: {
id: cls.xmiId,
name: cls.name,
},
ancestors: hierarchy.ancestors.map(a => ({
id: a.xmiId,
name: a.name,
isAbstract: a.isAbstract,
})),
descendants: hierarchy.descendants.map(d => ({
id: d.xmiId,
name: d.name,
isAbstract: d.isAbstract,
})),
};
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
}
case 'list_enumerations': {
const enums = model.listEnumerations({
package: args?.package as string | undefined,
namePattern: args?.namePattern as string | undefined,
});
const result = enums.map(e => ({
id: e.xmiId,
name: e.name,
packagePath: model.getPackagePath(e.xmiId),
literalCount: e.literals.length,
hasDocumentation: !!e.documentation,
}));
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
}
case 'get_enumeration': {
const identifier = args?.identifier as string;
if (!identifier) {
return { content: [{ type: 'text', text: 'Error: identifier is required' }] };
}
const enumeration = model.getEnumeration(identifier);
if (!enumeration) {
return { content: [{ type: 'text', text: `Enumeration not found: ${identifier}` }] };
}
const result = {
id: enumeration.xmiId,
name: enumeration.name,
packagePath: model.getPackagePath(enumeration.xmiId),
documentation: enumeration.documentation,
literals: enumeration.literals.map(l => ({
name: l.name,
documentation: l.documentation,
})),
};
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
}
case 'search_documentation': {
const query = args?.query as string;
if (!query) {
return { content: [{ type: 'text', text: 'Error: query is required' }] };
}
const results = model.searchDocumentation(query, {
elementTypes: args?.elementTypes as string[] | undefined,
limit: args?.limit as number | undefined,
});
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
}
case 'search_by_name': {
const query = args?.query as string;
if (!query) {
return { content: [{ type: 'text', text: 'Error: query is required' }] };
}
const results = model.searchByName(query, {
exactMatch: args?.exactMatch as boolean | undefined,
elementTypes: args?.elementTypes as string[] | undefined,
});
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
}
case 'resolve_reference': {
const xmiId = args?.xmiId as string;
if (!xmiId) {
return { content: [{ type: 'text', text: 'Error: xmiId is required' }] };
}
const element = model.getElementById(xmiId);
if (!element) {
return { content: [{ type: 'text', text: `Element not found: ${xmiId}` }] };
}
// Return different details based on element type
const baseResult = {
id: element.xmiId,
type: element.xmiType,
name: element.name,
documentation: element.documentation,
};
return { content: [{ type: 'text', text: JSON.stringify(baseResult, null, 2) }] };
}
case 'get_model_statistics': {
const stats = model.getStatistics();
return { content: [{ type: 'text', text: JSON.stringify(stats, null, 2) }] };
}
default:
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }] };
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return { content: [{ type: 'text', text: `Error: ${message}` }] };
}
});
return server;
}
export async function startServer(model: XmiModel): Promise<void> {
const server = createServer(model);
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('XMI MCP server started');
}