MCP-сервер для работы с базами данных 1С
Введение
Model Context Protocol (MCP) позволяет создавать специализированные серверы, которые расширяют возможности Claude Code доступом к внешним источникам данных. Для 1С-разработчика это означает прямой доступ к базам данных 1С прямо из AI-ассистента.
Зачем это нужно
Типичные сценарии:
- Быстрый анализ данных в базе без открытия 1С
- Генерация отчетов на основе реальных данных
- Поиск и анализ проблемных записей
- Выполнение диагностических запросов
- Создание тестовых данных
Реализация MCP-сервера для 1С
Архитектура решения
Claude Code <--MCP--> MCP Server <--COM/REST--> 1C:Enterprise
Вариант 1: Через COM-соединение (Windows)
// mcp-1c-server.js
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
// Для Windows - используем COM через node-activex
const ActiveXObject = require('activex');
class OneCServer {
constructor() {
this.server = new Server({
name: '1c-database-server',
version: '1.0.0',
}, {
capabilities: {
tools: {},
},
});
this.connection = null;
this.setupHandlers();
}
// Подключение к базе 1С
connect(connectionString) {
try {
const V83 = new ActiveXObject('V83.COMConnector');
this.connection = V83.Connect(connectionString);
return true;
} catch (error) {
throw new Error(`Ошибка подключения к 1С: ${error.message}`);
}
}
setupHandlers() {
// Список доступных инструментов
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'execute_query',
description: 'Выполнить запрос к базе данных 1С',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Текст запроса на языке запросов 1С',
},
connection_string: {
type: 'string',
description: 'Строка подключения к базе',
},
},
required: ['query', 'connection_string'],
},
},
{
name: 'get_metadata',
description: 'Получить метаданные конфигурации',
inputSchema: {
type: 'object',
properties: {
object_type: {
type: 'string',
description: 'Тип объекта: Справочник, Документ, РегистрСведений и т.д.',
},
connection_string: {
type: 'string',
description: 'Строка подключения к базе',
},
},
required: ['connection_string'],
},
},
{
name: 'execute_code',
description: 'Выполнить произвольный код на встроенном языке 1С',
inputSchema: {
type: 'object',
properties: {
code: {
type: 'string',
description: 'Код на встроенном языке 1С',
},
connection_string: {
type: 'string',
description: 'Строка подключения к базе',
},
},
required: ['code', 'connection_string'],
},
},
],
}));
// Обработка вызовов инструментов
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
// Подключаемся к базе
if (!this.connection) {
this.connect(args.connection_string);
}
switch (name) {
case 'execute_query':
return this.executeQuery(args.query);
case 'get_metadata':
return this.getMetadata(args.object_type);
case 'execute_code':
return this.executeCode(args.code);
default:
throw new Error(`Неизвестный инструмент: ${name}`);
}
});
}
executeQuery(queryText) {
try {
const query = this.connection.NewObject('Query');
query.Text = queryText;
const result = query.Execute();
const selection = result.Select();
const rows = [];
while (selection.Next()) {
const row = {};
for (let i = 0; i < result.Columns.Count(); i++) {
const column = result.Columns.Get(i);
row[column.Name] = selection[column.Name];
}
rows.push(row);
}
return {
content: [{
type: 'text',
text: JSON.stringify(rows, null, 2),
}],
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Ошибка выполнения запроса: ${error.message}`,
}],
isError: true,
};
}
}
getMetadata(objectType) {
try {
const metadata = this.connection.Metadata;
let metaObjects;
if (objectType) {
metaObjects = metadata[objectType];
} else {
// Возвращаем общую структуру метаданных
metaObjects = {
Справочники: Array.from(metadata.Справочники).map(s => s.Имя),
Документы: Array.from(metadata.Документы).map(d => d.Имя),
РегистрыСведений: Array.from(metadata.РегистрыСведений).map(r => r.Имя),
};
}
return {
content: [{
type: 'text',
text: JSON.stringify(metaObjects, null, 2),
}],
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Ошибка получения метаданных: ${error.message}`,
}],
isError: true,
};
}
}
executeCode(code) {
try {
const result = this.connection.Execute(code);
return {
content: [{
type: 'text',
text: String(result),
}],
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Ошибка выполнения кода: ${error.message}`,
}],
isError: true,
};
}
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
}
}
const server = new OneCServer();
server.run().catch(console.error);
Вариант 2: Через REST API (кросс-платформенный)
// mcp-1c-rest-server.js
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import axios from 'axios';
class OneC_REST_Server {
constructor(baseUrl, auth) {
this.baseUrl = baseUrl; // http://localhost/base/odata/standard.odata
this.auth = auth; // { username: 'admin', password: 'pass' }
this.server = new Server({
name: '1c-rest-server',
version: '1.0.0',
}, {
capabilities: { tools: {} },
});
this.setupHandlers();
}
setupHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'odata_query',
description: 'Выполнить OData запрос к базе 1С',
inputSchema: {
type: 'object',
properties: {
entity: { type: 'string', description: 'Имя сущности (например, Catalog_Номенклатура)' },
filter: { type: 'string', description: 'Фильтр OData' },
select: { type: 'string', description: 'Поля для выборки' },
top: { type: 'number', description: 'Количество записей' },
},
required: ['entity'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === 'odata_query') {
return this.executeODataQuery(args);
}
});
}
async executeODataQuery({ entity, filter, select, top }) {
try {
const params = new URLSearchParams();
if (filter) params.append('$filter', filter);
if (select) params.append('$select', select);
if (top) params.append('$top', top.toString());
const url = `${this.baseUrl}/${entity}?${params.toString()}`;
const response = await axios.get(url, {
auth: this.auth,
headers: { 'Accept': 'application/json' },
});
return {
content: [{
type: 'text',
text: JSON.stringify(response.data.value, null, 2),
}],
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Ошибка OData запроса: ${error.message}`,
}],
isError: true,
};
}
}
}
Конфигурация для Claude Code
Добавьте в файл конфигурации MCP (обычно ~/Library/Application Support/Claude/claude_desktop_config.json на macOS):
{
"mcpServers": {
"1c-database": {
"command": "node",
"args": ["/path/to/mcp-1c-server.js"],
"env": {
"CONNECTION_STRING": "File=\"C:\\Bases\\MyBase\";Usr=\"Admin\";Pwd=\"\";"
}
}
}
}
Примеры использования
Анализ данных
Команда в Claude Code:
"Используя MCP-сервер 1С, выполни запрос:
ВЫБРАТЬ
Номенклатура.Наименование,
СУММА(ДвиженияНоменклатуры.Количество) КАК Количество
ИЗ
РегистрНакопления.ДвиженияНоменклатуры КАК ДвиженияНоменклатуры
СГРУППИРОВАТЬ ПО
Номенклатура
УПОРЯДОЧИТЬ ПО
Количество УБЫВ
ПЕРВЫЕ 10"
Генерация отчета
"На основе данных из базы 1С создай Excel отчет с топ-10 товаров по продажам за последний месяц"
Диагностика проблем
"Найди в базе 1С все документы реализации, у которых сумма документа не совпадает с суммой табличной части"
Безопасность
Важно:
- Храните строки подключения в переменных окружения
- Используйте read-only подключения для аналитики
- Ограничьте права пользователя базы данных
- Логируйте все запросы к базе
- Не используйте в production без дополнительной защиты
Расширенные возможности
Кеширование метаданных
class CachedMetadataServer extends OneCServer {
constructor() {
super();
this.metadataCache = new Map();
}
async getMetadata(objectType) {
const cacheKey = objectType || 'all';
if (this.metadataCache.has(cacheKey)) {
return this.metadataCache.get(cacheKey);
}
const result = await super.getMetadata(objectType);
this.metadataCache.set(cacheKey, result);
return result;
}
}
Интеграция с логированием
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: '1c-mcp-server.log' }),
],
});
// Логируем каждый запрос
executeQuery(queryText) {
logger.info('Executing query', { query: queryText });
// ... выполнение запроса
}
Следующие шаги
- Добавьте поддержку HTTP-соединений
- Реализуйте пул соединений для нескольких баз
- Создайте веб-интерфейс для управления сервером
- Интегрируйте с системой мониторинга