Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,906 changes: 2,006 additions & 1,900 deletions package-lock.json

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions src/mcp/server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createMcpHandler } from "agents/mcp";

Check warning on line 1 in src/mcp/server.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 6 meaningful terms.

Check notice on line 1 in src/mcp/server.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.
import type { Context } from "hono";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
Expand Down Expand Up @@ -57,6 +57,7 @@
import { loadOrComputeIssueQualityResponse } from "../services/issue-quality";
import { loadOrComputeBurdenForecastResponse } from "../services/burden-forecast";
import { buildMcpClientTelemetry } from "../services/client-telemetry";
import { buildRelatedToolsHint } from "../services/related-mcp";
import { loadOrComputeRepoOutcomePatternsResponse } from "../services/repo-outcome-patterns";
import { buildUnavailableQueueTrendReport } from "../services/queue-trends";
import {
Expand Down Expand Up @@ -510,6 +511,12 @@
supportedTools: z.unknown().optional(),
};

const relatedToolsOutputSchema = {
self: z.unknown().optional(),
related: z.unknown().optional(),
note: z.string().optional(),
};

const validateLinkedIssueOutputSchema = {
status: z.string().optional(),
repoFullName: z.string().optional(),
Expand Down Expand Up @@ -1187,6 +1194,17 @@
async (input) => this.toolResult(await this.agentPreparePrPacket(input)),
);

server.registerTool(
"gittensory_related_tools",
{
description:
"Point the agent to the sibling MCP for adjacent intents outside Gittensory's scope. Gittensory handles gittensor (SN74) code-contribution workflow; for subnet discovery, validation, or invocation methods (does this subnet exist, what does it do, how do I call it) it links to metagraphed. Distinct scopes — link, don't merge. No auth, no input.",
inputSchema: {},
outputSchema: relatedToolsOutputSchema,
},
async () => this.toolResult(this.relatedTools()),
);

// ── Miner planning prompts ───────────────────────────────────────────
server.registerPrompt(
"gittensory_select_contribution_issue",
Expand Down Expand Up @@ -2065,6 +2083,15 @@
);
}

// Cross-MCP related-tools hint (#696): static product metadata; no env/auth/IO needed.
private relatedTools(): ToolPayload {
const hint = buildRelatedToolsHint();
return {
summary: `For subnet discovery/validation/invocation, use ${hint.related.map((sibling) => sibling.name).join(", ")} — Gittensory stays scoped to gittensor (SN74) contribution work.`,
data: hint as unknown as Record<string, unknown>,
};
}

private toolResult(payload: ToolPayload) {
const data = redactSensitiveForMcp(payload.data) as Record<string, unknown>;
return {
Expand Down
90 changes: 90 additions & 0 deletions src/services/related-mcp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { GITTENSORY_SITE_URL } from "../github/footer";

Check warning on line 1 in src/services/related-mcp.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 6 meaningful terms.

Check notice on line 1 in src/services/related-mcp.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.
import { GITTENSORY_MCP_PACKAGE_NAME } from "./mcp-compatibility";
import { GITTENSOR_NETUID } from "./subnet-interface";

// Public catalog MCP for Bittensor subnet discovery/validation/invocation (the sibling of Gittensory).
export const METAGRAPHED_NAME = "metagraphed";
export const METAGRAPHED_SITE_URL = "https://metagraph.sh";

/**
* One direction of a cross-MCP related-tools hint (#696): "for this adjacent intent, use that sibling MCP".
* Pure product metadata (names, URLs, intents) — never private/reward/score wording, so it never needs
* sanitization and is safe on public + unauthenticated surfaces.
*/
export type RelatedMcpHint = {
name: string;
role: "subnet_discovery" | "contribution_interface";
site: string;
package?: string;
summary: string;
// Adjacent intents the agent should hand off to the sibling for (not served here).
useFor: ReadonlyArray<string>;
// Representative sibling tool/query names to reach for once handed off.
handoffTools: ReadonlyArray<string>;
// The scope line that keeps the two MCPs linked, not merged.
boundary: string;
};

const SCOPE_BOUNDARY =
"Gittensory stays scoped to gittensor (SN74) code-contribution workflow; metagraphed covers cross-subnet discovery, validation, and invocation. Link, don't merge.";

/**
* gittensory → metagraphed: an agent inside the Gittensory MCP that needs the *adjacent* intent
* (does this subnet exist, what does it do, how do I call it) is pointed at metagraphed (#696).
*/
export const METAGRAPHED_RELATED_HINT: RelatedMcpHint = {
name: METAGRAPHED_NAME,
role: "subnet_discovery",
site: METAGRAPHED_SITE_URL,
summary: "Bittensor subnet discovery, validation, and invocation catalog across all subnets.",
useFor: [
`Validate that a subnet (e.g. gittensor / SN${GITTENSOR_NETUID}) exists and confirm what it does.`,
"Discover how to invoke a subnet's APIs or agents (invocation methods).",
"Browse the cross-subnet agent catalog beyond code contribution.",
],
handoffTools: ["get_subnet", "list_subnet_apis", "get_agent_catalog", "how_do_i_call"],
boundary: SCOPE_BOUNDARY,
};

/**
* metagraphed → gittensory: the reverse hint, for metagraphed (and any agent that discovered SN74 there)
* to route the code-contribution intent back to Gittensory. Surfaced in the public subnet-interface
* descriptor so the link is declared from both sides without merging scopes (#696, builds on #695).
*/
export const GITTENSORY_RELATED_HINT: RelatedMcpHint = {
name: "gittensory",
role: "contribution_interface",
site: GITTENSORY_SITE_URL,
package: GITTENSORY_MCP_PACKAGE_NAME,
summary: `Gittensor (SN${GITTENSOR_NETUID}) code-contribution quality & planning layer for miners and maintainers.`,
useFor: [
`Plan and prep an actual code contribution to a gittensor (SN${GITTENSOR_NETUID}) repo.`,
"Find high-fit, low-duplicate issues and check an issue before starting work.",
"Preflight a planned PR for lane fit, duplicate risk, and review burden.",
],
handoffTools: ["gittensory_get_decision_pack", "gittensory_check_before_start", "gittensory_preflight_pr"],
boundary: SCOPE_BOUNDARY,
};

export type RelatedToolsHint = {
self: { name: string; role: "contribution_interface"; site: string; summary: string };
related: ReadonlyArray<RelatedMcpHint>;
note: string;
};

/**
* The cross-MCP related-tools payload returned by the gittensory_related_tools MCP tool (#696): declares
* Gittensory's own scope and points the agent at metagraphed for the adjacent subnet-discovery intent.
*/
export function buildRelatedToolsHint(): RelatedToolsHint {
return {
self: {
name: "gittensory",
role: "contribution_interface",
site: GITTENSORY_SITE_URL,
summary: GITTENSORY_RELATED_HINT.summary,
},
related: [METAGRAPHED_RELATED_HINT],
note: SCOPE_BOUNDARY,
};
}
5 changes: 5 additions & 0 deletions src/services/subnet-interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { GITTENSOR_HOME_URL, GITTENSORY_SITE_URL } from "../github/footer";

Check warning on line 1 in src/services/subnet-interface.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 6 meaningful terms.

Check notice on line 1 in src/services/subnet-interface.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.
import { GITTENSORY_MCP_PACKAGE_NAME, LATEST_RECOMMENDED_MCP_VERSION, MINIMUM_SUPPORTED_MCP_VERSION } from "./mcp-compatibility";
import { METAGRAPHED_RELATED_HINT, type RelatedMcpHint } from "./related-mcp";

// Gittensor is Bittensor subnet 74 (the code subnet). Gittensory is its contribution interface.
export const GITTENSOR_NETUID = 74;
Expand Down Expand Up @@ -42,6 +43,9 @@
};
githubApp: { kind: "github_app"; slug: string; installUrl: string };
};
// Sibling MCPs for adjacent intents — agents are pointed here (link, don't merge) instead of Gittensory
// growing out-of-scope tools (#696). Currently: metagraphed for subnet discovery/validation/invocation.
related: RelatedMcpHint[];
onboarding: { docs: string; steps: string[] };
};

Expand Down Expand Up @@ -83,6 +87,7 @@
installUrl: `https://github.com/apps/${args.appSlug}`,
},
},
related: [METAGRAPHED_RELATED_HINT],
onboarding: {
docs: GITTENSORY_SITE_URL,
steps: [...ONBOARDING_STEPS],
Expand Down
2 changes: 2 additions & 0 deletions test/integration/subnet-interface.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";

Check warning on line 1 in test/integration/subnet-interface.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 6 meaningful terms.

Check notice on line 1 in test/integration/subnet-interface.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.
import { createApp } from "../../src/api/routes";
import { createTestEnv } from "../helpers/d1";

Expand All @@ -18,6 +18,8 @@
mcp: { endpoint: "https://gittensory-api.aethereal.dev/mcp", transport: "http" },
githubApp: { slug: "gittensory", installUrl: "https://github.com/apps/gittensory" },
},
// Cross-MCP related-tools hint (#696): links out to metagraphed for subnet discovery/invocation.
related: [{ name: "metagraphed", role: "subnet_discovery", site: "https://metagraph.sh" }],
});
});

Expand Down
17 changes: 17 additions & 0 deletions test/unit/mcp-output-schemas.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Client } from "@modelcontextprotocol/sdk/client/index.js";

Check warning on line 1 in test/unit/mcp-output-schemas.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 6 meaningful terms.

Check notice on line 1 in test/unit/mcp-output-schemas.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { describe, expect, it } from "vitest";
import { persistSignalSnapshot, upsertBounty, upsertIssueFromGitHub, upsertRepositoryFromGitHub } from "../../src/db/repositories";
Expand Down Expand Up @@ -94,6 +94,23 @@
expect(names.has("gittensory_agent_plan_next_work")).toBe(true);
expect(names.has("gittensory_compare_pr_variants")).toBe(true);
});

// Cross-MCP related-tools hint (#696): an agent in the Gittensory MCP is pointed to the metagraphed
// sibling for the adjacent subnet-discovery/validation/invocation intent — no auth, no input.
it("gittensory_related_tools points the agent to the metagraphed sibling", async () => {
const { client } = await connectTestClient();
const result = await client.callTool({ name: "gittensory_related_tools", arguments: {} });
expect(result.isError).toBeFalsy();

const data = result.structuredContent as { self?: { name?: string }; related?: Array<{ name?: string; site?: string; role?: string }>; note?: string };
expect(data.self?.name).toBe("gittensory");
const metagraphed = (data.related ?? []).find((sibling) => sibling.name === "metagraphed");
expect(metagraphed, "metagraphed sibling is surfaced").toBeDefined();
expect(metagraphed?.site).toBe("https://metagraph.sh");
expect(metagraphed?.role).toBe("subnet_discovery");
expect((data.note ?? "").toLowerCase()).toContain("link, don't merge");
expect(JSON.stringify(data)).not.toMatch(/hotkey|coldkey|wallet|payout|reward/i);
});
});

// ── Structured content validates against the declared schema ─────────────────────
Expand Down
35 changes: 35 additions & 0 deletions test/unit/related-mcp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { describe, expect, it } from "vitest";

Check warning on line 1 in test/unit/related-mcp.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 6 meaningful terms.

Check notice on line 1 in test/unit/related-mcp.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.
import {
buildRelatedToolsHint,
METAGRAPHED_NAME,
METAGRAPHED_RELATED_HINT,
METAGRAPHED_SITE_URL,
} from "../../src/services/related-mcp";

describe("cross-MCP related-tools hint (#696)", () => {
it("points a Gittensory agent at metagraphed for the adjacent subnet-discovery intent", () => {
const hint = buildRelatedToolsHint();

expect(hint.self.name).toBe("gittensory");
expect(hint.self.role).toBe("contribution_interface");

const metagraphed = hint.related.find((sibling) => sibling.name === METAGRAPHED_NAME);
expect(metagraphed, "metagraphed sibling hint is present").toBeDefined();
expect(metagraphed?.site).toBe(METAGRAPHED_SITE_URL);
expect(metagraphed?.role).toBe("subnet_discovery");
// The sibling exposes its own discovery/validation/invocation tools to hand off to.
expect(metagraphed?.handoffTools).toEqual(expect.arrayContaining(["get_subnet", "how_do_i_call"]));
expect(metagraphed?.useFor.length).toBeGreaterThan(0);
});

it("keeps the two scopes linked, not merged", () => {
const hint = buildRelatedToolsHint();
expect(hint.note.toLowerCase()).toContain("link, don't merge");
expect(METAGRAPHED_RELATED_HINT.boundary.toLowerCase()).toContain("link, don't merge");
});

it("is pure product metadata with no private/reward/score wording", () => {
const serialized = JSON.stringify(buildRelatedToolsHint());
expect(serialized).not.toMatch(/wallet|hotkey|coldkey|reward|payout|trust score|ranking/i);
});
});
8 changes: 8 additions & 0 deletions test/unit/subnet-interface.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";

Check warning on line 1 in test/unit/subnet-interface.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 6 meaningful terms.

Check notice on line 1 in test/unit/subnet-interface.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.
import { buildSubnetInterfaceDescriptor, GITTENSOR_NETUID } from "../../src/services/subnet-interface";

describe("buildSubnetInterfaceDescriptor", () => {
Expand All @@ -23,6 +23,14 @@
expect(toolNames).toContain("gittensory_list_notifications");
expect(descriptor.interfaces.mcp.tools.every((tool) => tool.summary.length > 0)).toBe(true);
expect(descriptor.onboarding.steps.length).toBeGreaterThan(0);

// Cross-MCP related-tools hint (#696): the descriptor links out to metagraphed for the adjacent
// subnet-discovery/validation/invocation intent — distinct scopes, link not merge.
const metagraphed = descriptor.related.find((sibling) => sibling.name === "metagraphed");
expect(metagraphed, "metagraphed sibling hint is present in the descriptor").toBeDefined();
expect(metagraphed?.site).toBe("https://metagraph.sh");
expect(metagraphed?.role).toBe("subnet_discovery");
expect(metagraphed?.boundary.toLowerCase()).toContain("link, don't merge");
});

it("defaults the upstream repo when not provided and contains no private/reward wording", () => {
Expand Down
Loading