feat: complete SHD MCP plugin workflows and widget
Validate SHD MCP plugin / validate (push) Has been cancelled

This commit is contained in:
2026-08-24 17:25:38 +03:00
parent 39fb5c02b7
commit 08cf3672a4
86 changed files with 1707 additions and 5 deletions
@@ -1,6 +1,6 @@
{
"name": "shd-mcp-plugin",
"version": "0.1.0",
"version": "0.4.0",
"description": "Ready-to-install SHD workflows backed by the authenticated SHD MCP server.",
"author": {
"name": "SHD",
@@ -16,13 +16,19 @@
"tasks",
"finance",
"CRM",
"documents",
"inventory",
"organizations",
"notifications",
"realtime",
"Gitea",
"MCP"
],
"skills": "./skills/",
"interface": {
"displayName": "SHD MCP",
"shortDescription": "SHD projects and operations",
"longDescription": "Installable SHD MCP workflows for project status, ProjectBase audits, tasks, files, finance, CRM and safe operational workflows.",
"longDescription": "Installable SHD MCP workflows for projects, ProjectBase, tasks, files, finance, CRM, documents, access control, inventory, notifications, realtime, status monitoring and linked Gitea operations.",
"developerName": "SHD",
"category": "Productivity",
"capabilities": [
+26
View File
@@ -15,8 +15,34 @@ modules. Skills do not grant permissions or bypass server-side ACLs.
- guarded task workflows;
- project file inspection and actions;
- finance and CRM read/audit workflows;
- finance account, balance, payment and settlement audits;
- analytics, comparisons, reports and task/proposal metrics;
- entity resolution and duplicate-candidate analysis;
- estimates, proposals and estimate-document-contract workflows;
- scheduling events, slots and booking positions;
- Wiki, notes, revisions and note-database workflows;
- discussions, channels, topics, messages and attachments;
- documents, templates, PDFs, revisions and public-link workflows;
- organizations, members, invitations, ACL and two-factor policies;
- notifications, preferences and read-state workflows;
- inventory, assets, stock, procurement and stocktake workflows;
- agent/controller diagnostics, tunnels, endpoints and runtime actions;
- status-page monitors, groups, history and incidents;
- realtime sessions, event cursors and operational activity;
- Terms documents, revisions, completeness and contract discussions;
- linked Gitea issues, labels, comments and attachments;
- safe bulk-change planning and validation rules.
When the connected MCP host supports MCP Apps UI, the active-projects workflow
also has an inline widget: it sorts by the nearest completion date, filters by
name/code, shows status pills and loads one projects read-only details. The
same tools still return normal structured data for hosts that do not render
widgets.
The active-projects widget is versioned in `widgets/active-projects/v1/` with a
manifest, SHA256 checksum and source/provenance note. The SHD MCP backend reads
that artifact for the `ui://shd/active-projects/v1.html` resource.
## MCP connection
The package points to `https://shd.xyz.su/mcp` and requests OAuth for that MCP
@@ -18,7 +18,11 @@ description: Use when the user asks for all active SHD projects, project deadlin
`Проект | Дата завершения | Статус`
6. Preserve the server's project name/code and status. If a deadline is a
6. If the user is in a UI-capable MCP host, call `shd_render_projects_widget`
after the data call, passing the returned `data` array as `projects` and the
returned `meta` object as `meta`. The widget is optional; the plain table
remains the fallback for hosts without MCP Apps UI.
7. Preserve the server's project name/code and status. If a deadline is a
datetime, format it with its returned timezone when available; otherwise
keep the value unmodified and state that timezone data was unavailable.
@@ -31,3 +35,5 @@ description: Use when the user asks for all active SHD projects, project deadlin
- If the server indicates more records than the tool can return, report that
the current tool limit was reached; do not claim a complete list.
- This workflow is read-only and must not update, archive or delete projects.
See `references/project-widget.md` for the data/render boundary.
@@ -0,0 +1,5 @@
interface:
display_name: Active SHD projects
short_description: Project deadlines and statuses
policy:
allow_implicit_invocation: false
@@ -0,0 +1,5 @@
# Project widget contract
Call the data tool first. Pass only the returned project array and metadata to
the render tool. The widget is a presentation layer: it must not invent dates,
statuses or project rows and must keep the plain structured table as fallback.
@@ -0,0 +1,26 @@
---
name: shd-agents-operations
description: Use when the user asks to inspect SHD agents, controllers, diagnostics, tunnels, access rules, endpoints, monitoring or agent runtime actions.
---
# SHD agents operations
See `references/agent-operations.md` before touching runtime, network or access
configuration.
## Diagnose first
- Resolve the controller, user agent or tunnel identity from
`shd_agents_overview`, status and diagnostic tools.
- Read current access policies, settings, history and async status before
proposing an action.
- Keep diagnostics, configuration, endpoint publication and network routing as
separate operations. Never expose credentials, private keys or tunnel data.
## High-impact actions
Creating/closing tunnels, publishing or revoking endpoints, replacing access
policies, rebooting an agent, changing exit-network routing or enabling
monitoring requires an explicit target and user request. For asynchronous
actions, return the operation ID, poll only as needed and report terminal
status. Do not retry a network or reboot action blindly.
@@ -0,0 +1,5 @@
interface:
display_name: "SHD agents operations"
short_description: "Diagnose agents and guarded runtime actions"
policy:
allow_implicit_invocation: false
@@ -0,0 +1,14 @@
# Agent operations contract
Start with `shd_agents_overview`, `shd_get_controller_status`,
`shd_agents_get_controller_diagnostics`, `shd_agents_get_diagnostics`, history
and the relevant access-policy tools. Use `shd_agents_get_async_status` for an
operation already returned by the server.
Treat tunnels, endpoint publication/revocation, access-policy replacement,
agent reboot, exit-node/network routing and controller/user-agent settings as
high-impact mutations. Tools such as `shd_agents_create_tunnel`,
`shd_agents_close_tunnel`, `shd_agents_publish_controller_endpoint`,
`shd_agents_revoke_controller_endpoint`, `shd_agents_reboot_user_agent` and
`shd_agents_force_exit_network_route` require explicit authorization and exact
scope. Preserve audit/readback and never print secrets returned by diagnostics.
@@ -0,0 +1,33 @@
---
name: shd-analytics
description: Use when the user asks for SHD project or company analytics, trends, comparisons, metrics, charts, reports, or time summaries.
---
# SHD analytics and reports
## Scope
1. Resolve the organization or project scope before aggregating values. Use an
exact `project_code` when the user supplied one; otherwise resolve it with
`shd_list_projects`.
2. Use `shd_get_project_context` for a bounded cross-module summary and request
only the sections needed for the question.
3. For task effort use `shd_get_task_time_report`; use
`shd_export_task_time_report` only when the user asks for an export.
4. For proposal analytics use `shd_get_proposal_public_analytics` only for a
resolved proposal or public analytics scope accepted by its schema.
5. If the requested metric does not map to a known tool, call
`shd_capabilities` and report the unavailable capability instead of inventing
an endpoint or field.
## Reporting rules
- State the period, scope, currency and timezone returned by SHD.
- Distinguish totals returned by the server from calculations made from rows.
- Preserve null, unavailable and permission-denied sections as separate states.
- Do not infer profitability, completion, approval or trend direction from one
record or from a missing section.
- Present chart-ready data as a compact table with labels, units and source
fields. Use a UI resource only when the server advertises a matching widget.
See `references/analytics-contract.md` before composing a multi-section report.
@@ -0,0 +1,5 @@
interface:
display_name: SHD analytics
short_description: Metrics, comparisons and reports
policy:
allow_implicit_invocation: false
@@ -0,0 +1,9 @@
# Analytics contract
- Every metric has an explicit scope, period, unit and source operation.
- A server total is authoritative; a locally calculated total must be marked as
calculated and list the rows used.
- A comparison must use the same period boundaries and unit on both sides.
- Missing data, denied access and a zero value are different outcomes.
- Recommendations follow the evidence and are not written back to SHD unless a
separate mutation is explicitly requested.
@@ -0,0 +1,26 @@
---
name: shd-discussions
description: Use when the user asks to find, read, search, create or edit SHD discussion channels, topics, messages or attachments.
---
# SHD discussions
See `references/discussion-contract.md` for the tool map and message safety
rules.
## Resolve and read
- Resolve the organization or project context before selecting a channel.
- Use `shd_list_discussion_channels` and `shd_list_discussion_topics` before
reading messages when an ID is not already known.
- Use bounded reads with `shd_list_discussion_messages` or
`shd_search_discussions`; preserve pagination and returned identifiers.
- Treat private channels and messages as server-authorized data. Do not infer
membership or quote content outside the returned scope.
## Mutations
Only create or edit a message, or upload an attachment, when the user asks for
that exact operation. Confirm the target topic, preserve the latest message
identity/version when available, and report the server result. Never delete or
rewrite discussion history by guessing a topic from its display name.
@@ -0,0 +1,5 @@
interface:
display_name: "SHD discussions"
short_description: "Search and manage discussion threads"
policy:
allow_implicit_invocation: false
@@ -0,0 +1,14 @@
# Discussion contract
Use the narrowest tool for the request:
- discovery: `shd_list_discussion_channels`,
`shd_list_discussion_topics`;
- content: `shd_list_discussion_messages`, `shd_search_discussions`;
- writes: `shd_add_discussion_message`, `shd_update_discussion_message`;
- files: `shd_upload_discussion_attachment`.
Resolve the canonical channel/topic/message identifiers before a write. Keep
searches bounded, distinguish no results from denied scope, and retain the
server's pagination or conflict fields. A message edit or attachment upload is
a side effect: require explicit user intent and report the final server result.
@@ -0,0 +1,26 @@
---
name: shd-documents
description: Use when the user asks to inspect, generate, send, publish, revise or audit SHD documents, templates, comments or public links.
---
# SHD documents
See `references/document-safety.md` before handling publication, PDF or
contract-related document actions.
## Read and prepare
- Resolve the project and document identifiers before reading content.
- Read the document, relevant template/configuration, revisions and comments
needed for the requested result; do not treat a filename as authorization.
- Keep document status, approval state, external links and public links
separate in the report.
- Use the document's returned revision or version when a write accepts one.
## Mutations and publication
Generating a PDF, sending a document, saving a contract, publishing a public
link or changing document state requires an explicit request. Explain the
target and audience before publication, preserve the server's ACL and expiry
rules, and perform readback when the tool provides it. Never expose private
links, bearer values or document content outside the authorized result.
@@ -0,0 +1,5 @@
interface:
display_name: "SHD documents"
short_description: "Inspect and safely manage documents"
policy:
allow_implicit_invocation: false
@@ -0,0 +1,14 @@
# Document safety contract
Useful read tools include `shd_list_contracts`, `shd_get_contract`,
`shd_list_document_revisions`, `shd_list_document_templates`,
`shd_list_document_comments`, `shd_list_document_external_links` and
`shd_list_document_public_links`. PDF inspection uses
`shd_download_document_pdf`; generation uses `shd_generate_document_pdf`.
Treat these as separate side effects: `shd_send_document`,
`shd_publish_document_public_link`, `shd_save_document_contract` and
`shd_document_contract_workflow`. Before any of them, resolve the document,
read the current state and require the exact requested action. Report recipient,
visibility, expiry, revision and server validation when returned. A public link
is never a harmless preview and must not be created just to test access.
@@ -0,0 +1,26 @@
---
name: shd-entity-resolution
description: Use when the user asks to find a CRM entity, resolve duplicate names, compare counterparties, or determine whether records refer to the same business object.
---
# SHD entity resolution
## Resolve first
1. Prefer an exact returned ID, code or external reference over a name.
2. Search with `shd_list_crm`, then read candidates with
`shd_get_crm_entity`.
3. Inspect `shd_list_crm_entity_links` when relationships, projects or deals
are part of the identity decision.
4. Compare only fields returned by SHD. Report candidate records, matching
evidence, conflicting evidence and unresolved fields separately.
## Safety
- A similar name is not proof of a duplicate.
- Do not merge, delete, reassign ownership or change a relationship while
resolving identity.
- A write requires an exact target, an explicit user request and the current
server validation contract. Read the record again after a permitted write.
See `references/entity-resolution.md` for the candidate-report format.
@@ -0,0 +1,5 @@
interface:
display_name: SHD entity resolution
short_description: Resolve CRM records and duplicates
policy:
allow_implicit_invocation: false
@@ -0,0 +1,8 @@
# Entity-resolution report
Report each candidate as:
`candidate → identifiers → matching fields → conflicting fields → related records → confidence → next action`
Confidence is an explanation of returned evidence, not a server permission or
an authorization to merge records.
@@ -0,0 +1,28 @@
---
name: shd-estimate-management
description: Use when the user asks to inspect, compare, validate, create or update SHD estimates, proposals, terms or document contracts.
---
# SHD estimates and proposal documents
## Read path
1. Resolve the project, proposal, terms document or contract before acting.
2. Use `shd_list_proposals` and `shd_get_proposal` for proposal registers and
details.
3. Use `shd_list_terms_documents`, `shd_get_terms_document`,
`shd_get_terms_document_completeness` and `shd_validate_terms_document` for
terms and completeness checks.
4. Inspect template capabilities before proposing a template-driven change.
5. Report line items, versions, completeness, approvals and publication state
only when those fields are returned by the server.
## Mutations
- Creating, replacing items, requesting approval, publishing, restoring or
deleting a proposal/document requires an exact explicit request.
- Read the latest version and use the tool's concurrency and idempotency fields
before a write.
- Never publish or request approval as a side effect of an audit or comparison.
See `references/estimate-safety.md` before a proposal or terms mutation.
@@ -0,0 +1,5 @@
interface:
display_name: SHD estimates and proposals
short_description: Estimate and document workflows
policy:
allow_implicit_invocation: false
@@ -0,0 +1,7 @@
# Estimate and proposal safety
- Draft, validated, approved and published are separate states.
- A calculated total is not an approval and a generated PDF is not a
publication.
- Compare revisions before restoring or replacing a document.
- Keep the project, counterparty and version identifiers in the final report.
@@ -0,0 +1,25 @@
---
name: shd-event-positions
description: Use when the user asks about SHD events, booking availability, scheduling slots, event types or booking positions.
---
# SHD events and scheduling
## Read path
1. Start with `shd_get_scheduling_overview` when the scope is broad.
2. Use `shd_list_scheduling_event_types`,
`shd_list_scheduling_availability`, `shd_list_scheduling_slots` and
`shd_list_scheduling_bookings` for the exact requested view.
3. Preserve the returned timezone, slot state, booking state and integration
status. Do not turn an available slot into a confirmed booking.
## Mutations
`shd_hold_scheduling_slot`, `shd_create_scheduling_booking`,
`shd_confirm_scheduling_booking`, `shd_reschedule_scheduling_booking` and
`shd_cancel_scheduling_booking` are explicit writes. Read the current slot or
booking first, pass the current concurrency/idempotency values accepted by the
schema and report the final server state.
See `references/scheduling-safety.md` for the state boundaries.
@@ -0,0 +1,5 @@
interface:
display_name: SHD events and scheduling
short_description: Slots, bookings and event positions
policy:
allow_implicit_invocation: false
@@ -0,0 +1,7 @@
# Scheduling state boundaries
- Availability is not a hold.
- A hold is not a booking.
- A booking is not confirmed until SHD returns confirmation.
- A cancellation or reschedule must identify the exact booking and preserve
the server's conflict response.
@@ -33,3 +33,5 @@ Do not approve, publish, delete, merge, move a deal stage, create a payment or
change ownership unless the user explicitly requests that exact operation.
Report IDs, resolved links, changed fields and server validation separately from
recommendations.
See `references/finance-crm-audit.md` for the finance/CRM audit boundary.
@@ -0,0 +1,5 @@
interface:
display_name: SHD finance and CRM
short_description: Finance and customer workflows
policy:
allow_implicit_invocation: false
@@ -0,0 +1,6 @@
# Finance and CRM audit boundary
Finance documents, payments, allocations, CRM entities, links and stages are
different objects. Report their identifiers and returned state separately.
Use the dedicated `shd-financial-account-audit` workflow for reconciliation,
balances, settlements or audit-log questions.
@@ -0,0 +1,30 @@
---
name: shd-financial-account-audit
description: Use when the user asks to audit SHD finance accounts, balances, payments, documents, allocations, settlements or finance history.
---
# SHD financial audit
## Read path
1. Resolve project, legal entity, counterparty and period before reading.
2. Use `shd_list_finance_refs` for allowed categories, accounts and reference
values when the request depends on them.
3. Read documents and payments with `shd_list_finance_documents`,
`shd_get_finance_document`, `shd_list_finance_payments` and
`shd_get_finance_payment`.
4. Use `shd_get_finance_account_balance` and
`shd_list_finance_audit_log` when balances or history are requested.
5. Reconcile only records returned for the same scope and period. Mark missing
links, currency differences and permission gaps explicitly.
## Settlement safety
- Allocation, removal of allocation, payment creation, approval, deletion and
document replacement are writes; do not perform them during an audit.
- A document total is not proof of payment, and a payment is not proof of
allocation.
- For an explicitly requested write, read the latest payment/document and use
the server's idempotency and concurrency contract, then read back the result.
See `references/finance-audit.md` for the reconciliation format.
@@ -0,0 +1,5 @@
interface:
display_name: SHD financial audit
short_description: Balances, payments and settlements
policy:
allow_implicit_invocation: false
@@ -0,0 +1,8 @@
# Finance audit format
Report:
`scope → period → currency → source record → observed amount/state → linked record → discrepancy → priority`
Do not hide an unresolved counterparty, currency conversion, allocation or
permission boundary behind an aggregate number.
@@ -0,0 +1,24 @@
---
name: shd-gitea
description: Use when the user asks to inspect or manage SHD-linked Gitea issues, labels, comments or issue attachments.
---
# SHD Gitea
See `references/gitea-contract.md` for repository and issue safety.
## Read and resolve
- Resolve the canonical repository and issue identifiers returned by SHD before
acting.
- Read the issue, labels and current state before creating a comment or
changing labels.
- Keep Gitea state separate from SHD project/task state; do not claim that a
label or issue update changed the SHD record unless the server says so.
## Mutations
Creating an issue, commenting, changing labels or uploading an attachment
requires an explicit request. Report repository, issue number, changed labels,
comment/attachment result and server validation. Never put credentials or
private attachment URLs in the response or repository.
@@ -0,0 +1,5 @@
interface:
display_name: "SHD Gitea"
short_description: "Review and update linked Gitea issues"
policy:
allow_implicit_invocation: false
@@ -0,0 +1,11 @@
# Gitea contract
Use `shd_list_gitea_issues` and `shd_list_gitea_labels` for discovery. Writes
are `shd_create_gitea_issue`, `shd_comment_gitea_issue`,
`shd_update_gitea_issue_labels` and `shd_upload_gitea_issue_attachment`.
Resolve the repository and issue from returned server data, preserve the
current issue state before a label update, and use an idempotency key when the
tool schema offers one. A comment or attachment is an external side effect;
require exact user intent and report readback. Do not assume Gitea permissions
from SHD project permissions alone.
@@ -0,0 +1,26 @@
---
name: shd-inventory
description: Use when the user asks to inspect or change SHD inventory items, assets, stock, procurement, reservations, receipts or stocktakes.
---
# SHD inventory
See `references/inventory-safety.md` before any stock, asset or procurement
mutation.
## Inspect
- Resolve the project, location, item, asset or procurement identity from
server results.
- Read current stock, lots, reservations, movements, receipts, maintenance,
assignments and audit data relevant to the question.
- Keep available, reserved, received, written-off and counted quantities
distinct. Do not calculate an authoritative balance from partial pages.
## Mutations
Reserve, release, receive, write off or close a stocktake only on an explicit
request. Creating/updating resources and generic inventory actions require the
same read-before-write and conflict/idempotency handling. Report quantities,
location, source document, server validation and readback; never silently
repair a discrepancy by changing stock.
@@ -0,0 +1,5 @@
interface:
display_name: "SHD inventory"
short_description: "Audit stock, assets and procurement"
policy:
allow_implicit_invocation: false
@@ -0,0 +1,14 @@
# Inventory safety contract
Read tools cover items/assets, procurement, locations, movements, lots,
reservations, receipts, stocktakes, labels, assignments, maintenance and the
inventory audit log. Use the narrowest `shd_list_inventory_*` or
`shd_get_inventory_*` tool and preserve pagination.
Side-effecting operations include `shd_reserve_inventory_stock`,
`shd_release_inventory_stock`, `shd_receive_inventory_stock`,
`shd_write_off_inventory_stock`, `shd_close_inventory_stocktake`,
`shd_create_inventory_resource`, `shd_update_inventory_resource` and
`shd_inventory_action`. Resolve the exact target and latest quantity first;
require explicit confirmation for irreversible or bulk operations and verify
the resulting movement/audit record.
@@ -0,0 +1,25 @@
---
name: shd-notifications
description: Use when the user asks to inspect SHD notifications, notification modules, read state or preferences, or mark notifications read.
---
# SHD notifications
See `references/notification-contract.md` for read-state and preference
boundaries.
## Read
- Use `shd_list_notifications` with bounded filters and preserve notification
IDs and timestamps.
- Use module, read-state and preference tools only for the authenticated user
or the explicitly authorized scope.
- Distinguish unread, read, muted and unavailable data; do not invent a missing
notification source.
## State changes
Marking one or more notifications read or changing preferences changes user
state. Do it only when explicitly requested, use the returned IDs, and report
the number and final state. Never mark a whole feed read merely because it was
displayed or summarized.
@@ -0,0 +1,5 @@
interface:
display_name: "SHD notifications"
short_description: "Review alerts and notification state"
policy:
allow_implicit_invocation: false
@@ -0,0 +1,11 @@
# Notification contract
Read with `shd_list_notifications`, `shd_list_notification_modules`,
`shd_get_notification_read_state`, `shd_get_notification_preferences` and,
for document-specific items, `shd_list_document_notifications`.
State-changing tools are `shd_update_notification_preferences`,
`shd_mark_notification_read`, `shd_mark_all_notifications_read` and
`shd_mark_notifications_read`. Require explicit scope for bulk marking,
prefer stable notification IDs, and report server readback. A list response is
not authorization to alter every item in it.
@@ -0,0 +1,26 @@
---
name: shd-organizations-acl
description: Use when the user asks to inspect or change SHD organizations, members, invitations, roles, access audits or two-factor policies.
---
# SHD organizations and ACL
See `references/organization-acl.md` for identity resolution and mutation
boundaries.
## Inspect
- Resolve the canonical organization ID; never use a guessed name as a write
target.
- Read members, invitations, audit entries and the current two-factor policy
before explaining access.
- Report organization, user, role, invitation state and policy as separate
facts. A denied or incomplete list is not proof that a member is absent.
## Change access
Inviting, adding, updating or removing a member, changing a two-factor policy,
or creating, archiving or restoring an organization requires explicit intent.
Show the exact organization and user scope before the mutation. Preserve server
authorization, conflict checks and audit readback; never broaden access to
make a failed request succeed.
@@ -0,0 +1,5 @@
interface:
display_name: "SHD organizations and ACL"
short_description: "Audit members, roles and access policies"
policy:
allow_implicit_invocation: false
@@ -0,0 +1,13 @@
# Organization and ACL contract
Read with `shd_list_organization_members`, `shd_list_organization_invitations`,
`shd_list_organization_audit` and
`shd_get_organization_two_factor_policy`. Resolve users and organizations from
server-returned IDs.
Mutations include invitation operations, member add/update/remove,
organization create/update/archive/restore and
`shd_update_organization_two_factor_policy`. Every mutation needs explicit
authorization, the latest relevant state, and final server/audit readback when
available. Do not disclose invitation secrets or use an email/name match as a
substitute for a returned user identity.
@@ -5,6 +5,8 @@ description: Use when the user asks to inspect ProjectBase tables, validate proj
# ProjectBase audit
See `references/audit-contract.md` for the bounded read and evidence format.
## Workflow
1. Resolve the canonical Base identity. A supplied `project_code` is accepted
@@ -0,0 +1,5 @@
interface:
display_name: ProjectBase audit
short_description: Read-only schema and data audit
policy:
allow_implicit_invocation: false
@@ -0,0 +1,20 @@
# ProjectBase audit contract
Use this contract for every read-only ProjectBase audit.
1. Resolve the canonical Base identity before selecting tables. Treat a
user-provided project code as an alias until the server returns the
authoritative identity.
2. Discover tables only when the request does not name them. Do not load every
table just to find a possible issue.
3. Read the schema before records for every selected table. Keep the table
name explicit in each subsequent request.
4. Bound record reads with the smallest useful limit and preserve the server's
pagination or truncation metadata.
5. Report evidence as `table → record → field → observed value → reason →
priority`. Mark a finding as unverified when the schema or record needed to
prove it was unavailable.
Never repair an audit finding inside the audit workflow. A repair requires a
separate proposal, an explicit mutation request, the current schema and
records, the server's validation/dry-run contract, and final readback.
@@ -26,3 +26,5 @@ version/confirmation fields accepted by its schema.
Do not expose bearer tokens or raw secret material in prose or logs. Return a
temporary URL only when the user explicitly asks for it. Do not overwrite,
publish, share or delete a path merely because the user asked to inspect it.
See `references/file-actions.md` for the file-action boundary.
@@ -0,0 +1,5 @@
interface:
display_name: SHD project files
short_description: Inspect and safely manage files
policy:
allow_implicit_invocation: false
@@ -0,0 +1,5 @@
# File action boundary
Resolve the exact project path first. Inspection, temporary link retrieval,
share/zip, rename/move and delete are separate intents. Mutating actions need
the server's current version, confirmation and idempotency fields.
@@ -37,3 +37,5 @@ Separate the result into:
Do not convert a task deadline into a project deadline, infer a completion date
from activity, or claim deployment success from a status field. This workflow is
read-only unless the user separately requests a specific change.
See `references/context-sections.md` for section selection and unavailable data.
@@ -0,0 +1,5 @@
interface:
display_name: SHD project status
short_description: Project summaries and risks
policy:
allow_implicit_invocation: false
@@ -0,0 +1,5 @@
# Project context sections
Use only the sections requested by the user. Available sections currently
include `summary`, `tasks`, `discussions`, `documents`, `finance`, `terms` and
`agents`; unavailable or denied sections must remain visible in the report.
@@ -0,0 +1,23 @@
---
name: shd-realtime-activity
description: Use when the user asks to inspect SHD realtime events, start or terminate a realtime session, or review operational activity and audit history.
---
# SHD realtime and activity
See `references/realtime-contract.md` for session lifecycle and event handling.
## Read and observe
- Resolve the authorized project/module scope before reading events or audit
history.
- Use bounded event reads and preserve event IDs, cursors and timestamps.
- Distinguish historical audit records from a live realtime session; do not
claim that an event stream is complete when it was truncated or disconnected.
## Session lifecycle
Starting or terminating a realtime session is a stateful operation. Require an
explicit request, retain the returned session ID and scope, avoid duplicate
starts on retry, and terminate only the requested session. Report connection
or terminal state rather than fabricating an event.
@@ -0,0 +1,5 @@
interface:
display_name: "SHD realtime and activity"
short_description: "Inspect events and manage live sessions"
policy:
allow_implicit_invocation: false
@@ -0,0 +1,11 @@
# Realtime and activity contract
Use `shd_start_realtime_session`, `shd_get_realtime_events` and
`shd_terminate_realtime_session` for the session lifecycle. Use the relevant
read-only audit/activity tool for historical records, such as
`shd_get_audit_history`, when the request is about what already happened.
Keep session IDs, scopes, cursors and last-seen timestamps separate. A retry
must not create a second session unless the server explicitly supports an
idempotency key. Termination requires the exact returned session ID and should
be confirmed by terminal readback.
@@ -18,8 +18,26 @@ readback. Do not invent fields, statuses, identifiers, routes or capabilities.
- Tasks, task projects, task status or task dates: use Tasks tools and the
`shd-task-workflow` workflow.
- Project files and paths: use Files tools and the `shd-project-files` workflow.
- Discussions, channels, topics, messages or attachments: use Discussions tools and the `shd-discussions` workflow.
- Documents, templates, revisions, PDFs or publication links: use Documents tools and the `shd-documents` workflow.
- Organizations, members, invitations, roles or two-factor policy: use Organizations tools and the `shd-organizations-acl` workflow.
- Notifications, preferences or read state: use Notifications tools and the `shd-notifications` workflow.
- Inventory, stock, assets, procurement or stocktakes: use Inventory tools and the `shd-inventory` workflow.
- Agents, controllers, diagnostics, tunnels or network access: use Agents tools and the `shd-agents-operations` workflow.
- Status-page monitors, history, groups or incidents: use Status Page tools and the `shd-status-page` workflow.
- Realtime sessions, event cursors or historical activity: use Realtime tools and the `shd-realtime-activity` workflow.
- Terms documents, revisions, completeness or contract discussions: use Terms tools and the `shd-terms-contracts` workflow.
- Linked Gitea issues, labels, comments or attachments: use Gitea tools and the `shd-gitea` workflow.
- Finance or CRM records: use the specific module tools and the
`shd-finance-crm` workflow; use project context for a high-level overview.
- Finance reconciliation, balances, settlements or audit history:
`shd-financial-account-audit`.
- Proposals, estimates, quotes or estimate-document contracts:
`shd-estimate-management`.
- Scheduling, event types, slots or bookings: `shd-event-positions`.
- Wiki, notes, pages, revisions or note databases: `shd-wiki-management`.
- Entity matching or possible duplicates: `shd-entity-resolution`.
- Analytics, comparisons, trends or reports: `shd-analytics`.
- A multi-record change: use `shd-safe-bulk-change` before any mutation.
Use `shd_capabilities` only when the available module or permission boundary is
@@ -43,3 +61,5 @@ Never ask for or expose access tokens, passwords, cookies, private keys, raw
public-link bearer tokens or integration secrets. Never use a user-provided
project name as an identifier when SHD has not resolved it to a returned
project code or ID.
See `references/module-map.md` for the current high-level routing map.
@@ -0,0 +1,5 @@
interface:
display_name: SHD routing and safety
short_description: Route requests to safe MCP tools
policy:
allow_implicit_invocation: false
@@ -0,0 +1,25 @@
# SHD module map
- Projects and project context → `shd_list_projects`, `shd_get_project`,
`shd_get_project_context`.
- ProjectBase → `shd_project_db_*`, `shd_project_base_*`.
- Tasks → `shd_list_tasks`, `shd_get_task`, task mutation tools.
- Files → `shd_list_files`, file action tools.
- Discussions → channel, topic, message search and attachment tools.
- Documents → document, template, revision, PDF and publication-link tools.
- Organizations and ACL → members, invitations, audit and two-factor policy
tools.
- Notifications → notification, preference and read-state tools.
- Inventory → stock, asset, procurement, reservation and stocktake tools.
- Agents → controller/user-agent status, diagnostics, tunnels, endpoints and
network-operation tools.
- Status Page → monitor, history, group and incident tools.
- Realtime and activity → session, event-cursor and audit/activity tools.
- Terms and contracts → Terms documents, completeness, revisions and point
discussion tools.
- Gitea → linked issue, label, comment and attachment tools.
- Finance and CRM → module-specific finance/CRM tools.
- Proposals, terms and document contracts → proposal/terms/document tools.
- Scheduling → `shd_get_scheduling_overview` and scheduling tools.
- Wiki and notes → note-space, page, revision and database tools.
- Unknown or permission-sensitive capability → `shd_capabilities` first.
@@ -26,3 +26,5 @@ record filter. Do not convert a failed or conflicting row into a new record.
After execution, report created, updated, skipped, rejected and conflicted
rows, then use the server's persisted readback when available. Destructive
operations require explicit confirmation and remain subject to server ACL.
See `references/write-preflight.md` for the required preflight record.
@@ -0,0 +1,5 @@
interface:
display_name: SHD safe bulk changes
short_description: Plan and validate bulk mutations
policy:
allow_implicit_invocation: false
@@ -0,0 +1,7 @@
# Write preflight
Before a multi-record write, record:
`module → scope → match rule → current count → affected count → skipped/ambiguous count → validation result → idempotency key`
Stop on an ambiguous match, validation failure, conflict or missing readback.
@@ -0,0 +1,22 @@
---
name: shd-status-page
description: Use when the user asks to inspect SHD status-page monitors, history, groups or incidents, or to change public status monitoring.
---
# SHD status page
See `references/status-page-safety.md` for monitor and incident boundaries.
## Read
- Resolve the status-page scope and monitor IDs before reporting health.
- Read current monitors and history; distinguish current state from historical
observations and from an incident declaration.
- Report unavailable, stale or unauthorized monitors explicitly.
## Mutations
Publishing an incident, creating/updating/deleting a group or monitor changes
an externally visible monitoring surface. Require an explicit request, exact
scope and current readback. Do not publish an incident to test the integration
and do not infer an outage from one missing response.
@@ -0,0 +1,5 @@
interface:
display_name: "SHD status page"
short_description: "Inspect monitors and publish incidents safely"
policy:
allow_implicit_invocation: false
@@ -0,0 +1,14 @@
# Status-page safety contract
Read with `shd_get_status_page_monitors` and
`shd_get_status_page_monitor_history`. Mutations are
`shd_publish_status_page_incident`, `shd_create_status_page_group`,
`shd_update_status_page_group`, `shd_delete_status_page_group`,
`shd_create_status_page_monitor`, `shd_update_status_page_monitor` and
`shd_delete_status_page_monitor`.
Before a mutation, resolve the canonical monitor/group IDs, read the latest
state and show the public scope, message, duration or target being changed.
Report server validation and final status. Deletion and incident publication
are consequential actions and must never be inferred from a question asking for
diagnosis.
@@ -5,6 +5,9 @@ description: Use when the user asks to find, inspect, create, update, reschedule
# SHD task workflow
See `references/task-contract.md` for identifier resolution, conflict handling
and mutation readback requirements.
## Read and resolve
- Use `shd_list_task_projects` when the request concerns a task project or its
@@ -0,0 +1,5 @@
interface:
display_name: SHD task workflow
short_description: Read and safely update tasks
policy:
allow_implicit_invocation: false
@@ -0,0 +1,20 @@
# Task workflow contract
Resolve task context before acting:
- use a canonical task-project identifier when the server returns one;
- search by explicit project, task ID or bounded query rather than guessing
from a display name;
- read task-project metadata before changing status, sprint, tags or custom
fields;
- retain `updated_at`, version or equivalent conflict data from the latest
read whenever the mutation schema supports it.
For a write, report the exact operation, task ID, changed fields, idempotency
key when used, server validation, conflict result and readback state. A
conflict or ambiguous match is a stop condition: show the current state and
ask for a narrower target instead of overwriting it.
Delete and archive operations require their server-side confirmation contract.
Never treat a proposed task change or a natural-language suggestion as write
authorization.
@@ -0,0 +1,24 @@
---
name: shd-terms-contracts
description: Use when the user asks to inspect, compare, discuss, revise or restore SHD Terms documents and contract points.
---
# SHD Terms and contracts
See `references/terms-contract.md` for revision, completeness and discussion
rules.
## Inspect
- Resolve the canonical Terms document and project context.
- Read completeness, current revision and relevant history before comparing or
summarizing contractual content.
- Keep document text, comments, point discussions and completeness findings
distinct; identify unavailable sections instead of filling them in.
## Changes
Restoring a revision or adding a contract comment/discussion point requires an
explicit request and the exact document/point scope. Preserve the latest
revision or conflict token where available, report the created comment or
restored revision, and never present a draft as an approved contract.
@@ -0,0 +1,5 @@
interface:
display_name: "SHD Terms and contracts"
short_description: "Review revisions and contract completeness"
policy:
allow_implicit_invocation: false
@@ -0,0 +1,11 @@
# Terms and contract contract
Read with `shd_list_terms_documents`, `shd_get_terms_document`,
`shd_get_terms_document_completeness`, `shd_list_terms_document_revisions` and
`shd_get_terms_document_revision`. Use `shd_terms_description_comment` and
`shd_terms_point_discussion` only for explicit comment/discussion requests.
`shd_restore_terms_document_revision` is a mutation: resolve the document and
revision, read current state, require confirmation and report readback. Never
restore merely because an older revision looks cleaner, and never confuse
completeness analysis with legal approval.
@@ -0,0 +1,26 @@
---
name: shd-wiki-management
description: Use when the user asks to search, read, compare, create or update SHD Wiki, notes, pages, collections, databases, revisions or attachments.
---
# SHD Wiki and notes
## Read path
1. Resolve the note space and page tree with `shd_list_note_spaces`,
`shd_get_note_space_tree` and `shd_list_note_pages`.
2. Search with `shd_search_note_blocks`, then read the selected page with
`shd_read_note_document` or `shd_get_note_page`.
3. Use revision and operation tools to compare versions before proposing a
replacement or restore.
4. Keep attachments, backlinks, comments, database records and page content as
separate objects in the report.
## Mutations
Creating, replacing, moving, archiving, deleting, importing, granting members
or changing review state requires an exact explicit request. Read the current
object first, preserve the latest revision/version and use the dedicated
operation contract. Read back the persisted result after a successful write.
See `references/wiki-safety.md` for content and revision boundaries.
@@ -0,0 +1,5 @@
interface:
display_name: SHD Wiki and notes
short_description: Pages, revisions and note databases
policy:
allow_implicit_invocation: false
@@ -0,0 +1,7 @@
# Wiki safety
- A search result is not the full page.
- A revision diff is not a write proposal.
- A replacement must preserve the selected page and revision identifiers.
- Deleting a page, collection, database or attachment requires an exact target
and the server's explicit confirmation fields.
@@ -0,0 +1,3 @@
See the repository root LICENSE: the SHD MCP Plugin License applies to this
widget.
This file is a local provenance notice; it does not grant additional rights.
@@ -0,0 +1 @@
49b3ab8629540f2c1075c3e096e2990055ec56181e6dd02dcd998bc98d9c7069 index.html
@@ -0,0 +1,12 @@
# SHD active-projects widget provenance
This is an SHD-authored MCP Apps resource. It is not copied from an external
widget bundle and has no OpenAI upstream dependency.
- Resource URI: `ui://shd/active-projects/v1.html`
- Media type: `text/html;profile=mcp-app`
- Source: `plugins/shd-mcp-plugin/widgets/active-projects/v1/index.html`
- Backend loader: `SHD_MCP_WIDGET_ROOT` or the repository path documented in
`scripts/mcp/mcp/resources/active-projects-widget.mjs`
- Integrity: verify `index.html` against `SHA256SUMS`.
- License: repository root `LICENSE`.
@@ -0,0 +1,496 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
:root {
color-scheme: light dark;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--bg: #ffffff;
--surface: #f6f8fb;
--surface-hover: #eef3f9;
--border: #d9e1ec;
--text: #152235;
--muted: #63728a;
--accent: #1769e0;
--accent-soft: #e7f0ff;
--success: #16794b;
--success-soft: #e6f7ef;
--warning: #9a6100;
--warning-soft: #fff4d8;
--danger: #b42318;
--danger-soft: #ffebe9;
--shadow: 0 8px 24px rgba(25, 55, 95, .08);
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #172131;
--surface: #202c3e;
--surface-hover: #29384c;
--border: #39495f;
--text: #eef4fc;
--muted: #a8b6c9;
--accent: #75a9ff;
--accent-soft: #263f68;
--success: #70d4a4;
--success-soft: #173d31;
--warning: #f4c467;
--warning-soft: #4b3918;
--danger: #ff9b91;
--danger-soft: #512522;
--shadow: 0 8px 24px rgba(0, 0, 0, .22);
}
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--text); }
button, input { font: inherit; }
button { color: inherit; }
.app { padding: 16px; max-width: 900px; margin: 0 auto; }
.header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
h1 { margin: 0; font-size: 20px; line-height: 1.25; letter-spacing: -.01em; }
.subtitle { margin: 5px 0 0; color: var(--muted); font-size: 13px; }
.toolbar { display: flex; align-items: center; gap: 8px; margin-top: 16px; }
.search { flex: 1; min-width: 0; border: 1px solid var(--border); border-radius: 9px; padding: 9px 11px; background: var(--bg); color: var(--text); outline: none; }
.search:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.button { border: 1px solid var(--border); border-radius: 9px; padding: 8px 11px; background: var(--surface); cursor: pointer; white-space: nowrap; }
.button:hover { background: var(--surface-hover); }
.button:focus-visible, .project-row:focus-visible { outline: 3px solid var(--accent-soft); outline-offset: 2px; }
.button:disabled { cursor: wait; opacity: .62; }
.summary { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0; }
.summary-item { border-radius: 999px; padding: 5px 9px; background: var(--surface); color: var(--muted); font-size: 12px; }
.summary-item strong { color: var(--text); font-weight: 650; }
.table { border: 1px solid var(--border); border-radius: 11px; overflow: hidden; box-shadow: var(--shadow); }
.table-head, .project-row { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(130px, .8fr) minmax(110px, .7fr); gap: 12px; align-items: center; }
.table-head { padding: 9px 12px; background: var(--surface); color: var(--muted); font-size: 11px; font-weight: 650; text-transform: uppercase; letter-spacing: .04em; }
.project-row { width: 100%; border: 0; border-top: 1px solid var(--border); padding: 12px; background: var(--bg); text-align: left; cursor: pointer; }
.project-row:hover { background: var(--surface-hover); }
.project-name { min-width: 0; }
.project-title { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 620; }
.project-code { display: block; margin-top: 3px; color: var(--muted); font-size: 12px; }
.deadline { color: var(--text); font-size: 13px; }
.deadline.missing { color: var(--muted); }
.deadline.overdue { color: var(--danger); font-weight: 620; }
.status { display: inline-flex; width: fit-content; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border-radius: 999px; padding: 5px 9px; font-size: 12px; font-weight: 620; }
.status-neutral { background: var(--surface); color: var(--muted); }
.status-progress { background: var(--accent-soft); color: var(--accent); }
.status-done { background: var(--success-soft); color: var(--success); }
.status-warning { background: var(--warning-soft); color: var(--warning); }
.status-danger { background: var(--danger-soft); color: var(--danger); }
.empty, .error { padding: 22px 14px; color: var(--muted); text-align: center; }
.error { color: var(--danger); }
.details { margin-top: 14px; padding: 14px; border: 1px solid var(--border); border-radius: 11px; background: var(--surface); }
.details[hidden], .error[hidden] { display: none; }
.details-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.details-title { margin: 0; font-size: 16px; }
.details-code { margin: 3px 0 0; color: var(--muted); font-size: 12px; }
.details-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin: 14px 0 0; }
.detail-label { display: block; margin-bottom: 3px; color: var(--muted); font-size: 11px; }
.detail-value { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
.loading { color: var(--muted); font-size: 13px; }
@media (max-width: 600px) {
.app { padding: 12px; }
.header { display: block; }
.header .button { margin-top: 10px; }
.table-head, .project-row { grid-template-columns: minmax(0, 1.4fr) minmax(100px, .8fr) minmax(90px, .7fr); gap: 8px; }
.table-head, .project-row { padding-left: 9px; padding-right: 9px; }
.details-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
</style>
</head>
<body>
<main class="app" aria-live="polite">
<header class="header">
<div>
<h1 data-i18n="title">Активные проекты</h1>
<p class="subtitle" data-i18n="subtitle">Сроки завершения и текущие статусы</p>
</div>
<button class="button" id="refresh" type="button" data-i18n="refresh">Обновить</button>
</header>
<div class="toolbar">
<input class="search" id="search" type="search" autocomplete="off" data-i18n-placeholder="search" placeholder="Поиск по проекту или коду">
</div>
<div class="summary" id="summary"></div>
<section class="table" aria-label="Проекты">
<div class="table-head">
<span data-i18n="project">Проект</span>
<span data-i18n="deadline">Дата завершения</span>
<span data-i18n="status">Статус</span>
</div>
<div id="rows"></div>
<div class="empty" id="empty" hidden></div>
<div class="error" id="error" hidden></div>
</section>
<section class="details" id="details" hidden>
<div class="details-header">
<div>
<h2 class="details-title" id="details-title"></h2>
<p class="details-code" id="details-code"></p>
</div>
<button class="button" id="close-details" type="button" data-i18n="close">Закрыть</button>
</div>
<div class="details-grid" id="details-grid"></div>
<p class="loading" id="details-loading" hidden></p>
</section>
</main>
<script>
(function () {
'use strict';
var messages = {
ru: {
title: 'Активные проекты',
subtitle: 'Сроки завершения и текущие статусы',
refresh: 'Обновить',
search: 'Поиск по проекту или коду',
project: 'Проект',
deadline: 'Дата завершения',
status: 'Статус',
close: 'Закрыть',
missingDate: 'Не указана',
noProjects: 'Проекты не найдены.',
noMatch: 'По фильтру ничего не найдено.',
loading: 'Загрузка…',
loadingProject: 'Загрузка данных проекта…',
refreshError: 'Не удалось обновить список проектов.',
projectError: 'Не удалось получить данные проекта.',
total: 'Всего',
withoutDate: 'Без даты',
archived: 'Архивных',
phase: 'Этап',
timezone: 'Часовой пояс',
updated: 'Обновлён',
code: 'Код'
},
en: {
title: 'Active projects',
subtitle: 'Completion dates and current statuses',
refresh: 'Refresh',
search: 'Search by project or code',
project: 'Project',
deadline: 'Completion date',
status: 'Status',
close: 'Close',
missingDate: 'Not set',
noProjects: 'No projects found.',
noMatch: 'No projects match the filter.',
loading: 'Loading…',
loadingProject: 'Loading project data…',
refreshError: 'Could not refresh the project list.',
projectError: 'Could not load the project data.',
total: 'Total',
withoutDate: 'No date',
archived: 'Archived',
phase: 'Phase',
timezone: 'Timezone',
updated: 'Updated',
code: 'Code'
}
};
var locale = (document.documentElement.lang || 'ru').toLowerCase().indexOf('ru') === 0 ? 'ru' : 'en';
var t = messages[locale];
var currentSnapshot = null;
var currentProjects = [];
var latestToolInput = null;
var pendingRequests = new Map();
var nextRequestId = 1;
var mcpAppsProtocolVersion = '2026-01-26';
var busy = false;
var elements = {
refresh: document.getElementById('refresh'),
search: document.getElementById('search'),
summary: document.getElementById('summary'),
rows: document.getElementById('rows'),
empty: document.getElementById('empty'),
error: document.getElementById('error'),
details: document.getElementById('details'),
detailsTitle: document.getElementById('details-title'),
detailsCode: document.getElementById('details-code'),
detailsGrid: document.getElementById('details-grid'),
detailsLoading: document.getElementById('details-loading'),
closeDetails: document.getElementById('close-details')
};
function applyTranslations() {
document.querySelectorAll('[data-i18n]').forEach(function (node) {
var key = node.getAttribute('data-i18n');
if (t[key]) node.textContent = t[key];
});
document.querySelectorAll('[data-i18n-placeholder]').forEach(function (node) {
var key = node.getAttribute('data-i18n-placeholder');
if (t[key]) node.setAttribute('placeholder', t[key]);
});
}
function request(method, params) {
var openai = typeof window !== 'undefined' ? window.openai : undefined;
if (method === 'tools/call' && openai && typeof openai.callTool === 'function') {
return openai.callTool(params.name, params.arguments || {});
}
var id = nextRequestId++;
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, '*');
return new Promise(function (resolve, reject) {
var timeout = window.setTimeout(function () {
pendingRequests.delete(id);
reject(new Error('Host request timed out'));
}, 10000);
pendingRequests.set(id, { resolve: resolve, reject: reject, timeout: timeout });
});
}
function notify(method, params) {
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params || {} }, '*');
}
function initializeHost() {
return request('ui/initialize', {
protocolVersion: mcpAppsProtocolVersion,
appInfo: {
name: 'SHD Active Projects',
title: 'SHD Active Projects',
version: '1.0.0',
websiteUrl: 'https://shd.xyz.su'
},
appCapabilities: {}
}).then(function () {
notify('ui/notifications/initialized', {});
}).catch(function () {
// The window.openai compatibility bridge can work without the raw postMessage handshake.
});
}
function normalizePayload(value) {
if (!value) return null;
if (value.structuredContent) return value.structuredContent;
if (value.toolOutput) return value.toolOutput;
return value;
}
function getProjects(value) {
var payload = normalizePayload(value);
if (!payload || payload.error) return [];
if (Array.isArray(payload.projects)) return payload.projects.filter(function (item) { return item && typeof item === 'object'; });
var data = payload.data;
if (data && !Array.isArray(data) && Array.isArray(data.data)) data = data.data;
return Array.isArray(data) ? data.filter(function (item) { return item && typeof item === 'object'; }) : [];
}
function getProjectCode(project) {
return String(project.code || project.project_code || project.contract_project_id || project.id || '');
}
function getProjectName(project) {
return String(project.display_name || project.title || project.name || getProjectCode(project) || '—');
}
function getStatus(project) {
return String(project.status || project.phase || '—');
}
function getTone(status) {
var value = String(status).toLowerCase();
if (/done|complete|completed|closed|resolved|finished|готов|заверш/.test(value)) return 'done';
if (/cancel|reject|blocked|error|отмен|отклон|блок/.test(value)) return 'danger';
if (/progress|active|work|processing|в работе|актив/.test(value)) return 'progress';
if (/pending|review|pause|ожид|провер|пауза/.test(value)) return 'warning';
return 'neutral';
}
function getDeadlineTime(project) {
var value = project.deadline;
if (!value) return Number.POSITIVE_INFINITY;
var timestamp = Date.parse(String(value));
return Number.isNaN(timestamp) ? Number.POSITIVE_INFINITY : timestamp;
}
function formatDate(value) {
if (!value) return t.missingDate;
var timestamp = Date.parse(String(value));
if (Number.isNaN(timestamp)) return String(value);
try {
return new Intl.DateTimeFormat(locale, { day: '2-digit', month: '2-digit', year: 'numeric' }).format(new Date(timestamp));
} catch (error) {
return String(value);
}
}
function isOverdue(project) {
var timestamp = getDeadlineTime(project);
var status = getStatus(project).toLowerCase();
return Number.isFinite(timestamp) && timestamp < Date.now() && !/done|complete|closed|resolved|заверш/.test(status);
}
function makeElement(tag, text, className) {
var node = document.createElement(tag);
if (className) node.className = className;
if (text !== undefined) node.textContent = text;
return node;
}
function renderSummary(projects) {
elements.summary.replaceChildren();
var noDate = projects.filter(function (project) { return !project.deadline; }).length;
var archived = projects.filter(function (project) { return project.is_archived === true; }).length;
var total = makeElement('span', '', 'summary-item');
total.append(makeElement('strong', String(projects.length)), ' ' + t.total);
elements.summary.append(total);
if (noDate) {
var missing = makeElement('span', '', 'summary-item');
missing.append(makeElement('strong', String(noDate)), ' ' + t.withoutDate);
elements.summary.append(missing);
}
if (archived) {
var archivedNode = makeElement('span', '', 'summary-item');
archivedNode.append(makeElement('strong', String(archived)), ' ' + t.archived);
elements.summary.append(archivedNode);
}
}
function renderRows() {
var query = elements.search.value.trim().toLowerCase();
var projects = currentProjects.filter(function (project) {
if (!query) return true;
return (getProjectName(project) + ' ' + getProjectCode(project) + ' ' + getStatus(project)).toLowerCase().indexOf(query) !== -1;
});
elements.rows.replaceChildren();
elements.error.hidden = true;
elements.empty.hidden = projects.length !== 0;
elements.empty.textContent = currentProjects.length === 0 ? t.noProjects : t.noMatch;
projects.forEach(function (project) {
var row = makeElement('button', '', 'project-row');
row.type = 'button';
row.setAttribute('aria-label', getProjectName(project));
var name = makeElement('span', '', 'project-name');
name.append(makeElement('span', getProjectName(project), 'project-title'));
name.append(makeElement('span', getProjectCode(project), 'project-code'));
var deadline = makeElement('span', formatDate(project.deadline), 'deadline' + (project.deadline ? '' : ' missing') + (isOverdue(project) ? ' overdue' : ''));
var status = makeElement('span', getStatus(project), 'status status-' + getTone(getStatus(project)));
row.append(name, deadline, status);
row.addEventListener('click', function () { showProject(project); });
elements.rows.append(row);
});
}
function render(value) {
var payload = normalizePayload(value);
currentSnapshot = payload;
currentProjects = getProjects(payload).slice().sort(function (left, right) {
return getDeadlineTime(left) - getDeadlineTime(right) || getProjectName(left).localeCompare(getProjectName(right), locale);
});
renderSummary(currentProjects);
renderRows();
}
function setBusy(nextBusy, label) {
busy = nextBusy;
elements.refresh.disabled = nextBusy;
if (nextBusy) elements.refresh.textContent = label || t.loading;
else elements.refresh.textContent = t.refresh;
}
function setError(message) {
elements.error.hidden = false;
elements.error.textContent = message;
}
function unwrapResult(value) {
var payload = normalizePayload(value);
if (value && value.isError) throw new Error(payload && payload.error ? payload.error : t.refreshError);
if (payload && payload.error) throw new Error(String(payload.error));
return payload || value;
}
async function refresh() {
if (busy) return;
setBusy(true);
elements.error.hidden = true;
try {
var result = await request('tools/call', { name: 'shd_list_projects', arguments: { archived: false, limit: 100 } });
render(unwrapResult(result));
} catch (error) {
setError(error && error.message ? error.message : t.refreshError);
} finally {
setBusy(false);
}
}
function addDetail(label, value) {
var item = makeElement('div');
item.append(makeElement('span', label, 'detail-label'), makeElement('span', value || '—', 'detail-value'));
elements.detailsGrid.append(item);
}
function showDetailSnapshot(project) {
elements.detailsTitle.textContent = getProjectName(project);
elements.detailsCode.textContent = getProjectCode(project) ? t.code + ': ' + getProjectCode(project) : '';
elements.detailsGrid.replaceChildren();
addDetail(t.deadline, formatDate(project.deadline));
addDetail(t.status, getStatus(project));
addDetail(t.phase, String(project.phase || '—'));
addDetail(t.timezone, String(project.timezone || '—'));
addDetail(t.updated, formatDate(project.updated_at));
elements.details.hidden = false;
}
async function showProject(project) {
showDetailSnapshot(project);
var code = getProjectCode(project);
if (!code) return;
elements.detailsLoading.hidden = false;
elements.detailsLoading.textContent = t.loadingProject;
try {
var result = await request('tools/call', { name: 'shd_get_project', arguments: { project_code: code } });
var payload = unwrapResult(result);
var data = payload && payload.data;
if (Array.isArray(data)) data = data[0];
if (data && typeof data === 'object') showDetailSnapshot(data);
} catch (error) {
setError(error && error.message ? error.message : t.projectError);
} finally {
elements.detailsLoading.hidden = true;
}
}
window.addEventListener('message', function (event) {
if (event.source !== window.parent) return;
var message = event.data;
if (!message || message.jsonrpc !== '2.0') return;
if (message.id !== undefined && pendingRequests.has(message.id)) {
var pending = pendingRequests.get(message.id);
pendingRequests.delete(message.id);
window.clearTimeout(pending.timeout);
if (message.error) pending.reject(new Error(message.error.message || t.refreshError));
else pending.resolve(message.result);
return;
}
if (message.method === 'ui/notifications/tool-input') {
latestToolInput = message.params || null;
if (!currentProjects.length) render(latestToolInput);
}
if (message.method === 'ui/notifications/tool-result') render(message.params && message.params.structuredContent);
}, { passive: true });
elements.search.addEventListener('input', renderRows);
elements.refresh.addEventListener('click', refresh);
elements.closeDetails.addEventListener('click', function () { elements.details.hidden = true; });
applyTranslations();
void initializeHost();
var initialOutput = typeof window !== 'undefined' && window.openai ? window.openai.toolOutput : null;
if (initialOutput) render(initialOutput);
else {
latestToolInput = typeof window !== 'undefined' && window.openai ? window.openai.toolInput : null;
render(latestToolInput || currentSnapshot);
}
}());
</script>
</body>
</html>
@@ -0,0 +1,20 @@
{
"schemaVersion": 1,
"name": "shd-active-projects",
"version": "1.0.0",
"resources": [
{
"file": "index.html",
"bytes": 22732,
"gzipBytes": 6133,
"mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/active-projects/v1.html",
"sha256": "49b3ab8629540f2c1075c3e096e2990055ec56181e6dd02dcd998bc98d9c7069"
}
],
"license": "../../../../../LICENSE",
"source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/active-projects/v1/index.html"
}
}