157 lines
5.5 KiB
JavaScript
157 lines
5.5 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import assert from 'node:assert/strict'
|
|
import { createOntologyMcpServer } from '../mcp-server.mjs'
|
|
|
|
const TOKEN = 'ontology-mcp-smoke-token'
|
|
const server = createOntologyMcpServer({
|
|
internalTokens: [TOKEN],
|
|
allowedOrigins: [],
|
|
maxBodyBytes: 1024 * 1024,
|
|
})
|
|
|
|
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve))
|
|
const address = server.address()
|
|
const baseUrl = `http://127.0.0.1:${address.port}`
|
|
|
|
try {
|
|
const health = await fetch(`${baseUrl}/healthz`)
|
|
const healthPayload = await health.json()
|
|
assert.equal(health.ok, true)
|
|
assert.equal(healthPayload.ok, true)
|
|
assert.equal(healthPayload.mcp.readOnly, true)
|
|
|
|
const initialized = await rpc(baseUrl, TOKEN, 1, 'initialize', { protocolVersion: '2025-06-18' })
|
|
assert.equal(initialized.result.protocolVersion, '2025-06-18')
|
|
assert.equal(initialized.result.capabilities.tools.listChanged, false)
|
|
|
|
const tools = await rpc(baseUrl, TOKEN, 2, 'tools/list', {})
|
|
const toolNames = tools.result.tools.map((tool) => tool.name).sort()
|
|
assert.deepEqual(toolNames, [
|
|
'ontology_get_entity',
|
|
'ontology_get_guardrails',
|
|
'ontology_resolve_context',
|
|
'ontology_search',
|
|
'ontology_status',
|
|
])
|
|
|
|
const search = await rpc(baseUrl, TOKEN, 3, 'tools/call', {
|
|
name: 'ontology_search',
|
|
arguments: { query: 'юнит гелиос', limit: 10 },
|
|
})
|
|
assert.equal(search.result.isError, undefined)
|
|
assert.equal(search.result.structuredContent.query, 'юнит гелиос')
|
|
assert.equal(search.result.structuredContent.results.some((item) => item.entity?.id === 'gelios.unit'), true)
|
|
|
|
const entity = await rpc(baseUrl, TOKEN, 4, 'tools/call', {
|
|
name: 'ontology_get_entity',
|
|
arguments: { term: 'helius' },
|
|
})
|
|
assert.equal(entity.result.structuredContent.entity.id, 'gelios.integration')
|
|
assert.equal(JSON.stringify(entity.result.structuredContent).includes('/Users/'), false)
|
|
|
|
const signalState = await rpc(baseUrl, TOKEN, 41, 'tools/call', {
|
|
name: 'ontology_get_entity',
|
|
arguments: { entityId: 'gelios.signal_state' },
|
|
})
|
|
assert.equal(signalState.result.structuredContent.entity.valueContract.field, 'signal_state')
|
|
assert.deepEqual(
|
|
signalState.result.structuredContent.entity.valueContract.values.map((item) => item.value),
|
|
['active', 'inactive'],
|
|
)
|
|
|
|
const guardrails = await rpc(baseUrl, TOKEN, 5, 'tools/call', {
|
|
name: 'ontology_get_guardrails',
|
|
arguments: { entityId: 'gelios.command_dispatch' },
|
|
})
|
|
assert.equal(guardrails.result.structuredContent.rules.some((rule) => rule.id === 'guardrail.gelios.commands_are_red_domain'), true)
|
|
|
|
const directTracker = await rpc(baseUrl, TOKEN, 51, 'tools/call', {
|
|
name: 'ontology_get_entity',
|
|
arguments: { term: 'B2 tracker' },
|
|
})
|
|
assert.equal(directTracker.result.structuredContent.entity.id, 'device.tracking_device')
|
|
|
|
const infrastructureHost = await rpc(baseUrl, TOKEN, 52, 'tools/call', {
|
|
name: 'ontology_get_entity',
|
|
arguments: { term: 'внешний VPS' },
|
|
})
|
|
assert.equal(infrastructureHost.result.structuredContent.entity.id, 'infrastructure.host')
|
|
|
|
const restrictedIdentifierGuardrails = await rpc(baseUrl, TOKEN, 53, 'tools/call', {
|
|
name: 'ontology_get_guardrails',
|
|
arguments: { entityId: 'device.restricted_identifier' },
|
|
})
|
|
assert.equal(
|
|
restrictedIdentifierGuardrails.result.structuredContent.rules.some(
|
|
(rule) => rule.id === 'guardrail.device.identifiers_are_restricted_not_identity',
|
|
),
|
|
true,
|
|
)
|
|
|
|
const hostGuardrails = await rpc(baseUrl, TOKEN, 54, 'tools/call', {
|
|
name: 'ontology_get_guardrails',
|
|
arguments: { entityId: 'infrastructure.host' },
|
|
})
|
|
assert.equal(
|
|
hostGuardrails.result.structuredContent.rules.some(
|
|
(rule) => rule.id === 'guardrail.infrastructure.host_is_not_connection',
|
|
),
|
|
true,
|
|
)
|
|
|
|
const positionObservation = await rpc(baseUrl, TOKEN, 55, 'tools/call', {
|
|
name: 'ontology_get_entity',
|
|
arguments: { entityId: 'observation.position_observation' },
|
|
})
|
|
assert.equal(
|
|
positionObservation.result.structuredContent.relations.some(
|
|
(relation) => relation.id === 'gelios.position_fix.maps_to_position_observation',
|
|
),
|
|
true,
|
|
)
|
|
|
|
const unauthorized = await fetch(`${baseUrl}/mcp`, {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json' },
|
|
body: JSON.stringify({ jsonrpc: '2.0', id: 6, method: 'tools/list', params: {} }),
|
|
})
|
|
assert.equal(unauthorized.status, 401)
|
|
|
|
console.log(JSON.stringify({
|
|
ok: true,
|
|
checks: [
|
|
'health',
|
|
'mcp_initialize',
|
|
'read_only_tool_catalog',
|
|
'gelios_alias_resolution',
|
|
'gelios_value_contract_visible',
|
|
'gelios_command_guardrail_visible',
|
|
'direct_b2_tracker_alias_resolution',
|
|
'infrastructure_host_alias_resolution',
|
|
'restricted_identifier_guardrail_visible',
|
|
'host_connection_conflation_blocked',
|
|
'gelios_position_neutral_mapping_visible',
|
|
'evidence_paths_not_exposed',
|
|
'internal_bearer_required',
|
|
],
|
|
}, null, 2))
|
|
} finally {
|
|
await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()))
|
|
}
|
|
|
|
async function rpc(baseUrl, token, id, method, params) {
|
|
const response = await fetch(`${baseUrl}/mcp`, {
|
|
method: 'POST',
|
|
headers: {
|
|
authorization: `Bearer ${token}`,
|
|
'content-type': 'application/json',
|
|
accept: 'application/json, text/event-stream',
|
|
'mcp-protocol-version': '2025-06-18',
|
|
},
|
|
body: JSON.stringify({ jsonrpc: '2.0', id, method, params }),
|
|
})
|
|
assert.equal(response.ok, true)
|
|
return response.json()
|
|
}
|