Platform

Platform overviewArchitectureWorkflow orchestrationGitOps configurationGovernance and AAAAI and MCPKnowledge and contextRuntime and executionEvidence and monitoring

Use cases

All use casesProduction incidentRelease preparationHotfix to productionSecurity scan triage
Why NopsAIIntegrationsSecurity

Resources

All resourcesAI agent governanceMCP governanceMCP securitySelf-hosted platforms
PricingGitHub

Company

How a run worksAboutContactBook a demo

AI configuration API

The three registries a pipeline draws on: model profiles, agent roles, and MCP servers and profiles.

ReferenceAdministratorSecurityAutomation author

Key points

  • A pipeline names these resources; it cannot define them. That is the boundary that makes AI use reviewable.
  • Model resolution runs task, step, pipeline, team, then platform default.
  • Deleting a profile still in use answers 409 rather than breaking the pipelines that name it.
  • The test routes make a real provider or server call — the difference between "configured" and "working".
  • Tool discovery is what makes a profile reviewable: you grant named tools rather than whatever a server exposes.
  • The profile is the only place tool access narrows; pipeline, step, and task profiles are additive at run time.
  • allowed_scopes bounds where a profile may be used, which keeps a production tool set out of development runs.

Operations

GET/v1/system/modelsAuthorized

Lists platform model profiles.

Notes

A profile names a provider, a model, and a credential reference. model: in a pipeline names the profile, never the provider model id directly.

Call it

List model profilesapi-ai-configuration request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/models" | jq '.[] | {name, provider, enabled, is_default}'
Result

Every profile with its provider and whether it is the platform default. Credential values are never included.

Responses

200application/json

Model profiles.

[{
  "name": "reasoning-large",
  "provider": "anthropic",
  "model": "claude-sonnet-4-5",
  "credential_ref": "platform/anthropic",
  "enabled": true,
  "is_default": true,
  "allowed_scopes": ["platform/production"]
}]

When it fails

StatusCauseWhat to do
503Authorization is unavailable, so the list cannot be filtered.Check AAA.

Side effects

  • None.

Proven by

  • services/nopsai/llm_config_test.go
  • services/nopsai/llm_profiles.go
PUT/v1/system/modelsAuthorized

Replaces the platform model profile set.

Notes

This replaces the whole set. To change one profile use the single-profile PUT, which leaves the others alone.

Call it

Replace the profile setapi-ai-configuration request
curl -sX PUT "$NOPSAI_URL/v1/system/models" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  --data @models.json | jq
Result

The stored set. Profiles omitted from the body are removed.

Replace before running
  • models.json is the complete profile list.

Responses

200application/json

Profiles stored.

[{
  "name": "reasoning-large",
  "provider": "anthropic",
  "model": "claude-sonnet-4-5",
  "credential_ref": "platform/anthropic",
  "enabled": true,
  "is_default": true,
  "allowed_scopes": ["platform/production"]
}]

When it fails

StatusCauseWhat to do
400An unknown provider, a missing model, or a credential reference that does not resolve.The message names the profile and field.
500The profiles could not be stored.Retry.

Side effects

  • Removing a profile makes every pipeline naming it fail authorization on its next run.
  • Writes an audit record.

Proven by

  • services/nopsai/llm_config_test.go
  • services/nopsai/llm_profiles.go
PUT/v1/system/models/defaultAuthorized

Sets the platform default model profile.

Notes

Resolution order is task, step, pipeline, team, then this. Changing it moves the floor, not the ceiling.

Call it

Set the platform default modelapi-ai-configuration request
curl -sX PUT "$NOPSAI_URL/v1/system/models/default" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"reasoning-large"}' | jq
Result

The default a run falls back to when neither the task, step, pipeline, nor team names a model.

Responses

200application/json

Default set.

{ "name": "reasoning-large", "is_default": true }

When it fails

StatusCauseWhat to do
400The profile does not exist or is disabled.List profiles first.
500The default could not be stored.Retry.

Side effects

  • Changes the last step of model resolution for every team without its own default.
  • Writes an audit record.

Proven by

  • services/nopsai/llm_config_test.go
  • services/nopsai/llm_profiles.go
PUT/v1/system/models/{profileName}Authorized

Creates or replaces one model profile.

Parameters

NameInTypeRequiredDescription
profileNamepathstringRequiredProfile name.

Call it

Upsert a model profileapi-ai-configuration request
curl -sX PUT "$NOPSAI_URL/v1/system/models/reasoning-large" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"provider":"anthropic","model":"claude-sonnet-4-5","credential_ref":"platform/anthropic","enabled":true}' | jq
Result

The stored profile, leaving every other profile untouched.

Responses

200application/json

Profile stored.

{
  "name": "reasoning-large",
  "provider": "anthropic",
  "model": "claude-sonnet-4-5",
  "credential_ref": "platform/anthropic",
  "enabled": true,
  "is_default": true,
  "allowed_scopes": ["platform/production"]
}

When it fails

StatusCauseWhat to do
400An unknown provider, missing model, or unresolvable credential.The message names the field.
500The profile could not be stored.Retry.

Side effects

  • Takes effect for the next run that resolves this profile.
  • Writes an audit record.

Proven by

  • services/nopsai/llm_config_test.go
  • services/nopsai/llm_profiles.go
DELETE/v1/system/models/{profileName}Authorized

Deletes a model profile.

Notes

The 409 is a feature: deleting a model out from under a pipeline is refused rather than discovered at the next run.

Parameters

NameInTypeRequiredDescription
profileNamepathstringRequiredProfile to delete.

Call it

Delete a model profileapi-ai-configuration request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/models/reasoning-large" -w "%{http_code}\n"
Result

204, unless something still depends on it.

Responses

204

Profile deleted.

When it fails

StatusCauseWhat to do
400The name is missing.Name the profile.
404No profile with that name.It may already be deleted.
409The profile is still in use — as a default, or by a pipeline.Reassign the dependants first. The platform refuses rather than breaking them.
500The delete failed.Retry.

Side effects

  • Writes an audit record.

Proven by

  • services/nopsai/llm_config_test.go
  • services/nopsai/llm_profiles.go
POST/v1/system/models/{profileName}/testAuthorized

Tests a model profile against its provider.

Notes

Run this after every credential rotation. A rotated key that was never tested fails at the least convenient moment.

Parameters

NameInTypeRequiredDescription
profileNamepathstringRequiredProfile to test.

Call it

Test a model profileapi-ai-configuration request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/models/reasoning-large/test" | jq
Result

A real provider round trip: this is the difference between "configured" and "working".

Responses

200application/json

The provider answered.

{ "ok": true, "provider": "anthropic", "model": "claude-sonnet-4-5" }

When it fails

StatusCauseWhat to do
400The profile is incomplete or disabled.Finish configuring it first.
404No profile with that name.Confirm the name.
502The provider rejected the call or could not be reached.The message carries the provider’s response — usually a bad key, a wrong model id, or egress being blocked.

Side effects

  • Makes one real call to the provider, which may be billable.

Proven by

  • services/nopsai/llm_profile_provider_clients_test.go
  • services/nopsai/llm_profiles.go
GET/v1/system/agent-rolesAuthorized

Lists platform agent roles.

Notes

An agent role is behaviour, not capability: it shapes the prompt and does not grant tools or models.

Call it

List agent rolesapi-ai-configuration request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/agent-roles" | jq '.[] | {id, name, enabled, is_default}'
Result

Every persona a pipeline or step may name.

Responses

200application/json

Agent roles.

[{
  "id": "senior-release-engineer",
  "name": "Senior release engineer",
  "enabled": true,
  "is_default": false,
  "instructions": "Review changes for release risk before recommending a promotion."
}]

When it fails

StatusCauseWhat to do
503Authorization is unavailable.Check AAA.
500The query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/agent_profiles_test.go
  • services/nopsai/agent_profiles.go
  • pkg/models/agent_profile.go
POST/v1/system/agent-rolesAuthorized

Creates an agent role.

Call it

Create an agent roleapi-ai-configuration request
curl -sX POST "$NOPSAI_URL/v1/system/agent-roles" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":"senior-release-engineer","name":"Senior release engineer","instructions":"Review changes for release risk."}' | jq
Result

The created role, nameable from agent_role on a pipeline or step.

Responses

201application/json

Agent role created.

{
  "id": "senior-release-engineer",
  "name": "Senior release engineer",
  "enabled": true,
  "is_default": false,
  "instructions": "Review changes for release risk before recommending a promotion."
}

When it fails

StatusCauseWhat to do
400A missing id, name, or instructions.Validate first with the validate route.
409A role with that id already exists.Update it instead.
503Authorization is unavailable.Check AAA.
500The role could not be created.Retry.

Side effects

  • Writes an audit record.

Proven by

  • services/nopsai/agent_profiles_test.go
  • services/nopsai/agent_profiles.go
GET/v1/system/agent-roles/{profileID}Authorized

Reads one agent role.

Parameters

NameInTypeRequiredDescription
profileIDpathstringRequiredAgent role id.

Call it

Read an agent roleapi-ai-configuration request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/agent-roles/senior-release-engineer" | jq
Result

The role and the instructions that become part of the prompt.

Responses

200application/json

The agent role.

{
  "id": "senior-release-engineer",
  "name": "Senior release engineer",
  "enabled": true,
  "is_default": false,
  "instructions": "Review changes for release risk before recommending a promotion."
}

When it fails

StatusCauseWhat to do
404No role with that id.Confirm the id from the list.
500The role could not be loaded.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/agent_profiles_test.go
  • services/nopsai/agent_profiles.go
PUT/v1/system/agent-roles/{profileID}Authorized

Replaces an agent role.

Notes

Editing a widely used role changes behaviour across many pipelines at once. Check its usage first.

Parameters

NameInTypeRequiredDescription
profileIDpathstringRequiredAgent role id.

Call it

Update an agent roleapi-ai-configuration request
curl -sX PUT "$NOPSAI_URL/v1/system/agent-roles/senior-release-engineer" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  --data @role.json | jq
Result

The stored role. Runs already executing keep the instructions they started with.

Responses

200application/json

Role stored.

{
  "id": "senior-release-engineer",
  "name": "Senior release engineer",
  "enabled": true,
  "is_default": false,
  "instructions": "Review changes for release risk before recommending a promotion."
}

When it fails

StatusCauseWhat to do
400Invalid instructions.Validate first.
404No role with that id.Create it instead.
500The update failed.Retry.

Side effects

  • Changes the prompt for every future step naming this role.
  • Writes an audit record.

Proven by

  • services/nopsai/agent_profiles_test.go
  • services/nopsai/agent_profiles.go
PUT/v1/system/agent-roles/defaultAuthorized

Sets the platform default agent role.

Notes

default is a literal path segment, so no agent role may use it as an id.

Call it

Set the default agent roleapi-ai-configuration request
curl -sX PUT "$NOPSAI_URL/v1/system/agent-roles/default" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":"senior-release-engineer"}' | jq
Result

The role used when nothing else names one.

Responses

200application/json

Default set.

{ "id": "senior-release-engineer", "is_default": true }

When it fails

StatusCauseWhat to do
400The role does not exist or is disabled.List roles first.
500The default could not be stored.Retry.

Side effects

  • Changes behaviour for every LLM step that names no role.
  • Writes an audit record.

Proven by

  • services/nopsai/agent_profiles_test.go
  • services/nopsai/agent_profiles.go
DELETE/v1/system/agent-roles/{profileID}Authorized

Deletes an agent role.

Parameters

NameInTypeRequiredDescription
profileIDpathstringRequiredAgent role to delete.

Call it

Delete an agent roleapi-ai-configuration request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/agent-roles/senior-release-engineer" -w "%{http_code}\n"
Result

204, unless something still names it.

Responses

204

Role deleted.

When it fails

StatusCauseWhat to do
400The id is missing.Name the role.
403The caller may not delete this role.Team-owned roles are deleted through the team routes.
404No role with that id.It may already be deleted.
409The role is still in use, or is a default.Check its usage and reassign first.
500The delete failed.Retry.

Side effects

  • Writes an audit record.

Proven by

  • services/nopsai/agent_profiles_test.go
  • services/nopsai/agent_profiles.go
GET/v1/system/agent-roles/{profileID}/usageAuthorized

Reports what currently uses an agent role.

Notes

Call this before editing a role, not just before deleting one: an edit changes behaviour everywhere without any error.

Parameters

NameInTypeRequiredDescription
profileIDpathstringRequiredAgent role id.

Call it

Check usage before editing or deletingapi-ai-configuration request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/agent-roles/senior-release-engineer/usage" | jq
Result

The pipelines and teams that depend on the role — the answer to "what will I break?".

Responses

200application/json

Usage across pipelines and teams.

{
  "pipelines": ["platform/release-service"],
  "teams": ["platform"],
  "is_default": false
}

When it fails

StatusCauseWhat to do
400The id is missing.Name the role.
500The usage query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/agent_profiles_test.go
  • services/nopsai/agent_profiles.go
POST/v1/system/agent-roles/validateAuthorized

Validates an agent role definition without storing it.

Call it

Validate an agent roleapi-ai-configuration request
curl -sX POST "$NOPSAI_URL/v1/system/agent-roles/validate" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  --data @role.json | jq
Result

The valid, errors, warnings shape used by every validation route.

Responses

200application/json

Validation ran. Read valid rather than the status code.

{
  "valid": true,
  "errors": [],
  "warnings": []
}

When it fails

StatusCauseWhat to do
400The payload could not be read.Send a JSON role document.

Side effects

  • None.

Proven by

  • services/nopsai/agent_profiles_test.go
  • services/nopsai/agent_profiles.go
GET/v1/system/mcp/serversAuthorized

Lists configured MCP servers.

Notes

A server is a connection; a profile is permission to use some of its tools. Pipelines name profiles, never servers.

Call it

List MCP serversapi-ai-configuration request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/mcp/servers" | jq '.[] | {name, transport, enabled}'
Result

Every server the platform may connect to. Credentials are references, never values.

Responses

200application/json

MCP servers.

[{
  "name": "jira",
  "display_name": "Jira",
  "enabled": true,
  "transport": "http",
  "url": "https://mcp.example.com/jira",
  "auth_type": "credential",
  "credential_ref": "platform/jira-mcp"
}]

When it fails

StatusCauseWhat to do
503Authorization is unavailable.Check AAA.
500The query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/mcp_config_test.go
  • services/nopsai/mcp_registry.go
POST/v1/system/mcp/serversAuthorized

Registers an MCP server.

Call it

Register a serverapi-ai-configuration request
curl -sX POST "$NOPSAI_URL/v1/system/mcp/servers" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"jira","transport":"http","url":"https://mcp.example.com/jira","auth_type":"credential","credential_ref":"platform/jira-mcp"}' | jq
Result

The registered server. Nothing can use it until a profile names it.

Responses

200application/json

Server registered.

{
  "name": "jira",
  "display_name": "Jira",
  "enabled": true,
  "transport": "http",
  "url": "https://mcp.example.com/jira",
  "auth_type": "credential",
  "credential_ref": "platform/jira-mcp"
}

When it fails

StatusCauseWhat to do
400An unknown transport, a missing URL, or an unresolvable credential.The message names the field.
500The server could not be stored.Retry.

Side effects

  • Registers a connection target. Registration alone grants nothing.
  • Writes an audit record.

Proven by

  • services/nopsai/mcp_registry_persistence_test.go
  • services/nopsai/mcp_registry.go
GET/v1/system/mcp/servers/{serverName}Authorized

Reads one MCP server.

Parameters

NameInTypeRequiredDescription
serverNamepathstringRequiredServer name.

Call it

Read a serverapi-ai-configuration request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/mcp/servers/jira" | jq
Result

Transport, URL, and auth type. The credential is a reference.

Responses

200application/json

The server.

{
  "name": "jira",
  "display_name": "Jira",
  "enabled": true,
  "transport": "http",
  "url": "https://mcp.example.com/jira",
  "auth_type": "credential",
  "credential_ref": "platform/jira-mcp"
}

When it fails

StatusCauseWhat to do
404No server with that name.Confirm the name from the list.

Side effects

  • None.

Proven by

  • services/nopsai/mcp_config_test.go
  • services/nopsai/mcp_registry.go
PUT/v1/system/mcp/servers/{serverName}Authorized

Creates or replaces an MCP server.

Parameters

NameInTypeRequiredDescription
serverNamepathstringRequiredServer name.

Call it

Upsert a serverapi-ai-configuration request
curl -sX PUT "$NOPSAI_URL/v1/system/mcp/servers/jira" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  --data @server.json | jq
Result

The stored server. Test it before relying on it.

Responses

200application/json

Server stored.

{
  "name": "jira",
  "display_name": "Jira",
  "enabled": true,
  "transport": "http",
  "url": "https://mcp.example.com/jira",
  "auth_type": "credential",
  "credential_ref": "platform/jira-mcp"
}

When it fails

StatusCauseWhat to do
400An invalid transport, URL, or credential reference.The message names the field.
500The server could not be stored.Retry.

Side effects

  • Every profile naming this server picks up the change.
  • Writes an audit record.

Proven by

  • services/nopsai/mcp_registry_persistence_test.go
  • services/nopsai/mcp_registry.go
DELETE/v1/system/mcp/servers/{serverName}Authorized

Removes an MCP server.

Parameters

NameInTypeRequiredDescription
serverNamepathstringRequiredServer to remove.

Call it

Remove a serverapi-ai-configuration request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/mcp/servers/jira" -w "%{http_code}\n"
Result

204, unless a profile still names it.

Responses

204

Server removed.

When it fails

StatusCauseWhat to do
400The name is missing.Name the server.
404No server with that name.It may already be removed.
409A profile still references the server.Remove it from the profiles first.
500The delete failed.Retry.

Side effects

  • Writes an audit record.

Proven by

  • services/nopsai/mcp_registry_persistence_test.go
  • services/nopsai/mcp_registry.go
POST/v1/system/mcp/servers/{serverName}/testAuthorized

Tests connectivity and authentication to an MCP server.

Parameters

NameInTypeRequiredDescription
serverNamepathstringRequiredServer to test.

Call it

Test a serverapi-ai-configuration request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/mcp/servers/jira/test" | jq
Result

A real connection attempt, which is how you separate "configured" from "reachable".

Responses

200application/json

The server answered.

{ "ok": true, "server": "jira" }

When it fails

StatusCauseWhat to do
400The server is incomplete or disabled.Finish configuring it.
404No server with that name.Confirm the name.
502The server could not be reached or rejected the credentials.The message carries the failure — usually egress or an expired credential.

Side effects

  • Makes one real connection to the server.

Proven by

  • services/nopsai/mcp_config_test.go
  • services/nopsai/mcp_registry.go
POST/v1/system/mcp/servers/{serverName}/discover-toolsAuthorized

Asks a server which tools it offers.

Notes

Discovery is what makes a profile reviewable: you grant named tools rather than "whatever this server exposes".

Parameters

NameInTypeRequiredDescription
serverNamepathstringRequiredServer to query.

Call it

Discover a server’s toolsapi-ai-configuration request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/mcp/servers/jira/discover-tools" | jq
Result

The tool list, which is what a profile then narrows.

Responses

200application/json

Tools the server offers.

{
  "server": "jira",
  "tools": ["search", "get_issue", "create_issue"]
}

When it fails

StatusCauseWhat to do
400The server is incomplete or disabled.Finish configuring it.
404No server with that name.Confirm the name.
502The server could not be reached.Test the server first.
500The discovery response could not be read.The server answered something unexpected.

Side effects

  • Makes one real call to the server.

Proven by

  • services/nopsai/mcp_config_test.go
  • services/nopsai/mcp_registry.go
GET/v1/system/mcp/profilesAuthorized

Lists platform MCP profiles.

Call it

List MCP profilesapi-ai-configuration request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/mcp/profiles" | jq
Result

Each profile with the servers and tools it permits.

Responses

200application/json

MCP profiles.

[{
  "name": "jira-readonly",
  "enabled": true,
  "allowed_scopes": ["platform/production"],
  "servers": [{ "server": "jira", "tools": ["search", "get_issue"] }]
}]

When it fails

StatusCauseWhat to do
503Authorization is unavailable.Check AAA.
500The query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/mcp_config_test.go
  • services/nopsai/mcp_registry.go
POST/v1/system/mcp/profilesAuthorized

Creates an MCP profile.

Notes

allowed_scopes bounds where the profile may be used, which is how a production-only tool set stays out of development runs.

Call it

Create a profile from discovered toolsapi-ai-configuration request
curl -sX POST "$NOPSAI_URL/v1/system/mcp/profiles" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"jira-readonly","servers":[{"server":"jira","tools":["search","get_issue"]}],"allowed_scopes":["platform/production"]}' | jq
Result

The profile a pipeline may then name in mcp_profiles.

Responses

200application/json

Profile created.

{
  "name": "jira-readonly",
  "enabled": true,
  "allowed_scopes": ["platform/production"],
  "servers": [{ "server": "jira", "tools": ["search", "get_issue"] }]
}

When it fails

StatusCauseWhat to do
400An unknown server, an empty tool list, or an unusable scope.Discover the server’s tools first.
500The profile could not be stored.Retry.

Side effects

  • Creates a grantable tool set.
  • Writes an audit record.

Proven by

  • services/nopsai/mcp_config_test.go
  • services/nopsai/mcp_registry.go
GET/v1/system/mcp/profiles/{profileName}Authorized

Reads one MCP profile.

Parameters

NameInTypeRequiredDescription
profileNamepathstringRequiredProfile name.

Call it

Read a profileapi-ai-configuration request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/mcp/profiles/jira-readonly" | jq
Result

The exact tools this profile permits, per server.

Responses

200application/json

The profile.

{
  "name": "jira-readonly",
  "enabled": true,
  "allowed_scopes": ["platform/production"],
  "servers": [{ "server": "jira", "tools": ["search", "get_issue"] }]
}

When it fails

StatusCauseWhat to do
404No profile with that name.Confirm the name.

Side effects

  • None.

Proven by

  • services/nopsai/mcp_config_test.go
  • services/nopsai/mcp_registry.go
PUT/v1/system/mcp/profiles/{profileName}Authorized

Creates or replaces an MCP profile.

Notes

This is the only place tool access narrows. Pipeline, step, and task profiles are additive at run time.

Parameters

NameInTypeRequiredDescription
profileNamepathstringRequiredProfile name.

Call it

Narrow a profile’s toolsapi-ai-configuration request
curl -sX PUT "$NOPSAI_URL/v1/system/mcp/profiles/jira-readonly" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"servers":[{"server":"jira","tools":["search"]}]}' | jq
Result

The stored profile. Every pipeline naming it is narrowed at once.

Responses

200application/json

Profile stored.

{
  "name": "jira-readonly",
  "enabled": true,
  "allowed_scopes": ["platform/production"],
  "servers": [{ "server": "jira", "tools": ["search", "get_issue"] }]
}

When it fails

StatusCauseWhat to do
400An unknown server or tool.Rediscover the server’s tools; the offer may have changed.
500The profile could not be stored.Retry.

Side effects

  • Changes what every pipeline naming the profile can reach.
  • Writes an audit record.

Proven by

  • services/nopsai/mcp_config_test.go
  • services/nopsai/mcp_registry.go
DELETE/v1/system/mcp/profiles/{profileName}Authorized

Deletes an MCP profile.

Parameters

NameInTypeRequiredDescription
profileNamepathstringRequiredProfile to delete.

Call it

Delete a profileapi-ai-configuration request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/mcp/profiles/jira-readonly" -w "%{http_code}\n"
Result

204, unless a pipeline still names it.

Responses

204

Profile deleted.

When it fails

StatusCauseWhat to do
400The name is missing.Name the profile.
404No profile with that name.It may already be deleted.
409A pipeline still names the profile.Remove the reference first.
500The delete failed.Retry.

Side effects

  • Writes an audit record.

Proven by

  • services/nopsai/mcp_config_test.go
  • services/nopsai/mcp_registry.go
POST/v1/system/mcp/profiles/{profileName}/testAuthorized

Tests every server a profile names.

Parameters

NameInTypeRequiredDescription
profileNamepathstringRequiredProfile to test.

Call it

Test a profile end to endapi-ai-configuration request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/mcp/profiles/jira-readonly/test" | jq
Result

One result per server the profile names, so a partially broken profile is visible before a run needs it.

Responses

200application/json

Per-server results.

{
  "profile": "jira-readonly",
  "servers": [{ "server": "jira", "ok": true }]
}

When it fails

StatusCauseWhat to do
400The profile is empty or disabled.Add at least one server.
404No profile with that name.Confirm the name.
502A server could not be reached.Test that server directly to isolate it.
500The test could not run.Platform fault.

Side effects

  • Makes one real call per server in the profile.

Proven by

  • services/nopsai/mcp_config_test.go
  • services/nopsai/mcp_registry.go

How it works

Read the three registries as three separate questions. A model profile answers "which provider and model may this run use", an agent role answers "how should it behave", and an MCP profile answers "which tools may it call". Conflating them is the usual reason a change has no effect: raising a governance level does not add tools, and swapping an agent role does not change the model.

The 409 on delete is deliberate. A model or profile that a pipeline still names cannot be removed, so the failure happens at configuration time with a clear message rather than at the next run with an authorization error.

Test after every credential rotation. A rotated key that was never tested fails at the least convenient moment, and the test route is one call.

Implementation evidence

  • services/nopsai/llm_profiles.go

    Model profile storage, defaults, and provider tests.

  • services/nopsai/agent_profiles.go

    Agent role storage, usage, and validation.

  • services/nopsai/mcp_registry.go

    MCP server registration, discovery, and profiles.